Some progress.

This commit is contained in:
Henning Dieterichs
2019-08-07 17:23:34 +02:00
parent 85692ac720
commit ec664bad91
21 changed files with 4943 additions and 286 deletions

View File

@@ -24,11 +24,11 @@ export type CommonDataType =
| CommonDataTypes.GraphData;
export module CommonDataTypes {
export interface Text extends ExtractedData {
export interface Text {
kind: { text: true };
text: string;
// what should be used here? File type (.ts, .txt) or mime type?
fileType?: string;
mimeType?: string;
fileName?: string;
}
export interface GraphData {
@@ -52,7 +52,7 @@ export module CommonDataTypes {
text: string;
}
export interface TreeNodeData extends ExtractedData {
export interface TreeNodeData {
kind: { tree: true };
root: TreeNode<{}>;
}

View File

@@ -51,6 +51,7 @@ export function selfContainedInitDataExtractorApi(): boolean {
.filter(([key, value]) => key.startsWith(prefix))
.map(([key, value]) => value as DataExtractor<ExtractedData>);
}
obj["$asData"] = (x: unknown) => x;
obj[key] = api = {
registerExtractor(extractor) {
obj[prefix + extractor.id] = extractor;

View File

@@ -18,7 +18,7 @@ export class AsIsDataExtractor implements DataExtractor<ExtractedData> {
extractionCollector.addExtraction({
id: "as-is",
name: "As Is",
priority: 100,
priority: 500,
extractData() {
return obj;
},

View File

@@ -2,6 +2,7 @@ import * as ts from "typescript";
import { DataExtractor, ExtractionCollector } from "../DataExtractor";
import { CommonDataTypes } from "../../CommonDataTypes";
// This class is self contained and can be injected into both nodejs and browser environments.
export class TypeScriptAstDataExtractor
implements DataExtractor<CommonDataTypes.AstData> {
readonly id = "TypeScriptAst";
@@ -83,14 +84,12 @@ export class TypeScriptAstDataExtractor
id: memberName,
children: children,
data: {
length: 0,
position: 0,
length: node.end - node.pos,
position: node.pos,
},
emphasizedValue: emphasizedValueFn(node),
isMarked: marked.has(node),
value,
// startPos: node.pos,
// endPos: node.end
};
}
@@ -139,7 +138,7 @@ export class TypeScriptAstDataExtractor
kind: { text: true, tree: true, ast: true },
root: toTreeNode(root, "root", marked, fn),
text: root.text,
fileType: "ts",
fileName: "index.ts",
};
},
});

View File

@@ -10,18 +10,20 @@ import {
} from "@hediet/debug-visualizer-data-extraction";
enableHotReload();
import * as ts from "typescript";
import { liveLogId, liveLog } from "@hediet/live-debug";
//import { liveLogId, liveLog } from "@hediet/live-debug";
registerUpdateReconciler(module);
registerAll();
/*
let i = 0;
setInterval(() => {
i++;
liveLog("bla" + i * 2);
liveLog("test" + i);
}, 1000);
*/
@hotClass(module)
class Main {
@@ -29,18 +31,10 @@ class Main {
const sf = ts.createSourceFile(
"test",
`
class Test {
public test() {
console.log("aaa");
const x = e;
}
}
class Test {
public test() {
console.log("aaa");
const x = e;
}
class uiae {
}
`,
ts.ScriptTarget.Latest,
true

View File

@@ -44,16 +44,24 @@ export class ConnectionHandler {
},
openInBrowser: async ({}) => {
try {
await launchChrome(server.indexUrl);
} catch (e) {
//await launchChrome(server.indexUrl);
open(server.indexUrl);
}
} catch (e) {}
},
setPreferredDataExtractor: async ({ dataExtractorId }) => {
if (this.watcher) {
this.watcher.setPreferredDataExtractor(dataExtractorId);
}
},
getCompletions: async ({ text, column }) => {
const completions = await sources.jsSource.getCompletions(
text,
column
);
return {
completions,
};
},
}
);

View File

@@ -1,5 +1,8 @@
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { DataExtractionState } from "@hediet/debug-visualizer-vscode-shared";
import {
DataExtractionState,
CompletionItem,
} from "@hediet/debug-visualizer-vscode-shared";
export interface EvaluationWatcherOptions {
preferredDataExtractor?: DataExtractorId | undefined;
@@ -10,6 +13,7 @@ export interface DataSource {
expression: string,
options: EvaluationWatcherOptions
): EvaluationWatcher;
getCompletions(text: string, column: number): Promise<CompletionItem[]>;
}
export interface EvaluationWatcher {

View File

@@ -13,7 +13,10 @@ import {
ApiHasNotBeenInitializedCode,
selfContainedInitDataExtractorApi,
} from "@hediet/debug-visualizer-data-extraction";
import { DataExtractionState } from "@hediet/debug-visualizer-vscode-shared";
import {
DataExtractionState,
CompletionItem,
} from "@hediet/debug-visualizer-vscode-shared";
export function createJsDebuggerSource(): JsDataSource {
return new JsDebuggerSourceImplementation();
@@ -50,6 +53,28 @@ class JsDebuggerSourceImplementation implements JsDataSource {
registerDataExtractor(classExpression: JsCode): void {}
async getCompletions(
text: string,
column: number
): Promise<CompletionItem[]> {
const session = vscode.debug.activeDebugSession;
if (!session) {
return [];
}
try {
const reply = await session.customRequest("completions", {
text,
frameId: this.lastFrameId,
column,
});
return reply.targets;
} catch (error) {
console.error(error);
return [];
}
}
public createEvaluationWatcher(
expression: string,
options: EvaluationWatcherOptions
@@ -79,10 +104,10 @@ class JsDebuggerSourceImplementation implements JsDataSource {
? `"${w.preferredDataExtractor}"`
: "undefined";
const expression = `(${fnSrc})().getData(${
w.expression
}, expr => eval(expr), ${preferredExtractor})`;
// uia
const expression =
`(${fnSrc})().getData(${w.expression},` +
`expr => eval(expr), ${preferredExtractor})`;
const reply = await session.customRequest("evaluate", {
expression,
frameId: this.lastFrameId,

View File

@@ -0,0 +1,84 @@
import React = require("react");
import { Model } from "../Model/Model";
import { observer, disposeOnUnmount } from "mobx-react";
import { observable, action, autorun } from "mobx";
import * as monaco from "monaco-editor";
export function getModel(): monaco.editor.ITextModel {
return monaco.editor.createModel(
"",
"javascript",
monaco.Uri.parse(`file:///main.ts`)
);
}
@observer
export class ExpressionInput extends React.Component<{ model: Model }> {
@action.bound
submit() {
const val = this.model.getValue();
console.log(val);
this.props.model.setExpression(val);
}
@observable private editor: monaco.editor.IStandaloneCodeEditor | undefined;
private model = getModel();
componentWillUnmount() {
if (this.editor) {
this.editor.dispose();
}
this.model.dispose();
}
@disposeOnUnmount
private _updateText = autorun(() => {
const val = this.model.getValue();
const newVal = this.props.model.expression;
if (val !== newVal) {
this.model.setValue(newVal);
}
});
private readonly setEditorDiv = (editorDiv: HTMLDivElement) => {
if (!editorDiv) {
return;
}
this.editor = monaco.editor.create(editorDiv, {
model: this.model,
scrollBeyondLastLine: false,
minimap: { enabled: false },
fixedOverflowWidgets: true,
lineNumbers: "off",
glyphMargin: false,
folding: false,
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
automaticLayout: true,
scrollbar: {
horizontal: "hidden",
vertical: "hidden",
horizontalScrollbarSize: 0,
verticalScrollbarSize: 0,
},
});
this.editor.onKeyDown(e => {
if (e.keyCode == monaco.KeyCode.Enter) {
e.preventDefault();
e.stopPropagation();
this.submit();
}
});
};
render() {
return (
<div className="component-monaco-editor2">
<div className="part-editor" ref={this.setEditorDiv} />
</div>
);
}
}

View File

@@ -0,0 +1,47 @@
import React = require("react");
import { observer } from "mobx-react";
import { observable } from "mobx";
import Measure from "react-measure";
@observer
export class NoData extends React.Component<{ label: string }> {
@observable height = 0;
@observable width = 0;
render() {
const { width, height } = this;
return (
<Measure
client={true}
onResize={e => {
if (e.client) {
this.height = e.client.height;
this.width = e.client.width;
}
}}
>
{({ measureRef }) => (
<div className="component-NoData" ref={measureRef}>
<svg>
<line x1={0} y1={0} x2={width} y2={height} />
<line x1={width} y1={0} x2={0} y2={height} />
<rect
x={width / 2 - 50}
y={height / 2 - 20}
width={100}
height={40}
/>
<text
x={width / 2}
y={height / 2}
textAnchor="middle"
alignmentBaseline="central"
children={this.props.label}
/>
</svg>
</div>
)}
</Measure>
);
}
}

View File

@@ -88,15 +88,3 @@ export function SvgRect(
/>
);
}
/*
export function Svg(props: { position: Point } & SvgAttributes<SVGSVGElement>) {
return (
<svg
x={props.position.x}
y={props.position.y}
{...omit(props, ["position"])}
/>
);
}
*/

View File

@@ -0,0 +1,36 @@
import React = require("react");
import { Model } from "../Model/Model";
import { observer } from "mobx-react";
import { NoData } from "./NoData";
@observer
export class Visualizer extends React.Component<{ model: Model }> {
render() {
return (
<div className="component-Visualizer">{this.renderContent()}</div>
);
}
renderContent(): JSX.Element {
const s = this.props.model.state;
if (s.kind === "loading") {
return <NoData label="Loading" />;
} else if (s.kind === "error") {
return <NoData label={"Error: " + s.message} />;
} else if (s.kind === "noExpression") {
return <NoData label="No Expression" />;
} else if (s.kind === "noDebugSession") {
return <NoData label="No Debug Session" />;
} else if (s.kind === "data") {
const vis = this.props.model.visualizations;
if (!vis || !vis.visualization) {
return <NoData label="No Visualization" />;
}
return vis.visualization.render();
} else {
const nvr: never = s;
return <div />;
}
}
}

View File

@@ -1,12 +1,13 @@
import React = require("react");
import { Model } from "../Model";
import { Model } from "../Model/Model";
import { observer } from "mobx-react";
import { observable, action, computed } from "mobx";
import { Popover, Icon, Button, Spinner, InputGroup } from "@blueprintjs/core";
import { observable, computed } from "mobx";
import { Popover, Button, Spinner } from "@blueprintjs/core";
import classnames = require("classnames");
import Measure from "react-measure";
import { DataExtractorInfo } from "@hediet/debug-visualizer-data-extraction";
import { knownVisualizations } from "../Visualizers";
import { Visualizer } from "./Visualizer";
import { ExpressionInput } from "./ExpressionInput";
@observer
export class GUI extends React.Component<{ model: Model }> {
@observable text: string = "node";
@@ -24,15 +25,23 @@ export class GUI extends React.Component<{ model: Model }> {
/>
<div className="part-header-content">
<div className="part-header-main">
<ExpressionInput model={m} />
<Button
minimal
small
className="part-icon"
icon="refresh"
onClick={() => m.refresh()}
/>
<div className="part-expression-input ">
<ExpressionInput model={m} />
</div>
<div style={{ width: 4 }} />
{m.loading ? (
<div style={{ padding: "0 4px" }}>
<Spinner size={Spinner.SIZE_SMALL} />
</div>
) : (
<Button
minimal
small
className="part-icon"
icon="refresh"
onClick={() => m.refresh()}
/>
)}
<Button
minimal
small
@@ -122,134 +131,6 @@ export class ExpandedOptions extends React.Component<{ model: Model }> {
}
}
@observer
export class ExpressionInput extends React.Component<{ model: Model }> {
@observable editedExpression: string | undefined;
@computed get currentExpression(): string {
return this.editedExpression !== undefined
? this.editedExpression
: this.props.model.expression;
}
@action.bound
updateCurrentExpression(newValue: string) {
this.editedExpression = newValue;
}
@action.bound
submit() {
if (!this.editedExpression) {
return;
}
this.props.model.setExpression(this.editedExpression);
this.editedExpression = undefined;
}
render() {
const model = this.props.model;
return (
<InputGroup
small
fill
rightElement={
model.loading ? (
<Spinner size={Icon.SIZE_STANDARD} />
) : (
undefined
)
}
value={this.currentExpression}
onChange={
((e: { target: { value: string } }) =>
this.updateCurrentExpression(e.target.value)) as any
}
onBlur={this.submit}
onKeyPress={e => {
if (e.charCode === 13) {
// enter
this.submit();
}
}}
/>
);
}
}
@observer
export class Visualizer extends React.Component<{ model: Model }> {
render() {
return (
<div className="component-Visualizer">{this.renderContent()}</div>
);
}
renderContent(): JSX.Element {
const s = this.props.model.state;
if (s.kind === "loading") {
return <NoData label="Loading" />;
} else if (s.kind === "error") {
return <NoData label={"Error: " + s.message} />;
} else if (s.kind === "noExpression") {
return <NoData label="No Expression" />;
} else if (s.kind === "noDebugSession") {
return <NoData label="No Debug Session" />;
} else if (s.kind === "data") {
const vis = this.props.model.visualizations;
if (!vis || !vis.visualization) {
return <NoData label="No Visualization" />;
}
return vis.visualization.render();
} else {
const nvr: never = s;
return <div />;
}
}
}
@observer
class NoData extends React.Component<{ label: string }> {
@observable height = 0;
@observable width = 0;
render() {
const { width, height } = this;
return (
<Measure
client={true}
onResize={e => {
if (e.client) {
this.height = e.client.height;
this.width = e.client.width;
}
}}
>
{({ measureRef }) => (
<div className="component-NoData" ref={measureRef}>
<svg>
<line x1={0} y1={0} x2={width} y2={height} />
<line x1={width} y1={0} x2={0} y2={height} />
<rect
x={width / 2 - 50}
y={height / 2 - 20}
width={100}
height={40}
/>
<text
x={width / 2}
y={height / 2}
textAnchor="middle"
alignmentBaseline="central"
children={this.props.label}
/>
</svg>
</div>
)}
</Measure>
);
}
}
export class ExpandButton extends React.Component<{
expanded: boolean;
setExpanded: (val: boolean) => void;

View File

@@ -4,81 +4,17 @@ import {
} from "@hediet/debug-visualizer-vscode-shared";
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
import { observable, action, computed } from "mobx";
import { Barrier } from "@hediet/std/synchronization";
import { observable, action, computed, when } from "mobx";
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { Visualization, VisualizationId } from "./Visualizers/Visualizer";
import { knownVisualizations } from "./Visualizers";
interface VsCodeApi {
getState<TState>(): TState | undefined;
setState(state: unknown): void;
}
import { Visualization, VisualizationId } from "../Visualizers/Visualizer";
import { knownVisualizations } from "../Visualizers";
import { getApi as getVsCodeApi } from "./VsCodeApi";
import { MonacoBridge } from "./MonacoBridge";
declare const window: Window & {
serverPort?: number;
VsCodeApi?: VsCodeApi;
};
interface VSCodeApi<TState> {
getState(): Promise<TState | undefined>;
setState(state: TState): Promise<void>;
}
type IncomingCommand = {
command: "getStateResult";
state: unknown;
};
type OutgoingCommand =
| {
command: "setState";
state: unknown;
}
| { command: "getState" };
class IFrameVSCodeApi<TState> implements VSCodeApi<TState> {
private currentGetStatePromise: Barrier<TState> | undefined = undefined;
constructor() {
window.addEventListener("message", event => {
const data = event.data as IncomingCommand;
if (data.command === "getStateResult") {
this.currentGetStatePromise!.unlock(data.state as any);
}
});
}
private sendCommand(command: OutgoingCommand) {
window.parent.postMessage(command, "*");
}
getState(): Promise<TState | undefined> {
this.sendCommand({ command: "getState" });
if (this.currentGetStatePromise) {
throw new Error("Get state already in progress.");
}
this.currentGetStatePromise = new Barrier();
return this.currentGetStatePromise.onUnlocked;
}
async setState(state: TState): Promise<void> {
this.sendCommand({ command: "setState", state });
}
}
class DirectVSCodeApi<TState> implements VSCodeApi<TState> {
constructor(private readonly vsCodeApi: VsCodeApi) {}
async getState(): Promise<TState | undefined> {
return this.vsCodeApi.getState<TState | undefined>();
}
async setState(state: TState): Promise<void> {
this.vsCodeApi.setState(state);
}
}
export class Model {
port: number;
@@ -113,13 +49,12 @@ export class Model {
}
}
private server:
@observable.ref
public server:
| typeof debugVisualizerUIContract.TServerInterface
| undefined = undefined;
private readonly vsCodeApi = window.VsCodeApi
? new DirectVSCodeApi<{ expression: string }>(window.VsCodeApi)
: new IFrameVSCodeApi<{ expression: string }>();
private readonly vsCodeApi = getVsCodeApi<{ expression: string }>();
@observable private _loading = false;
@@ -127,6 +62,8 @@ export class Model {
return this._loading;
}
private readonly bridge = new MonacoBridge(this);
constructor() {
if (window.serverPort) {
this.port = window.serverPort;
@@ -137,6 +74,11 @@ export class Model {
throw new Error("No port given.");
}
this.port = parseInt(portStr);
const expr = url.searchParams.get("expression");
if (expr) {
this.setExpression(expr);
}
}
this.stayConnected();
@@ -151,13 +93,17 @@ export class Model {
@action
setExpression(newExpression: string) {
this.expression = newExpression;
if (this.server) {
this.server.setExpression({ newExpression });
}
when(() => !!this.server).then(() => {
this.server!.setExpression({ newExpression });
});
this.vsCodeApi.setState({
expression: newExpression,
});
const url = new URL(window.location.href);
url.searchParams.set("expression", newExpression);
history.replaceState(null, document.title, url.toString());
}
setPreferredExtractorId(id: DataExtractorId) {
@@ -187,7 +133,7 @@ export class Model {
stream,
new ConsoleRpcLogger(),
{
updateState: ({ newState }) => {
updateState: async ({ newState }) => {
this._loading = newState.kind === "loading";
if (!this._loading) {
this.state = newState;

View File

@@ -0,0 +1,104 @@
import * as monaco from "monaco-editor";
import { Model } from "./Model";
export class MonacoBridge {
constructor(private readonly model: Model) {
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false,
noSyntaxValidation: false,
});
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES5,
allowNonTsExtensions: true,
moduleResolution:
monaco.languages.typescript.ModuleResolutionKind.NodeJs,
module: monaco.languages.typescript.ModuleKind.CommonJS,
strict: true,
});
const text = require("!!raw-loader!./lib.es5.d.ts.txt")
.default as string;
monaco.languages.typescript.javascriptDefaults.addExtraLib(text);
const commonTypes = require("!!raw-loader!@hediet/debug-visualizer-data-extraction/src/CommonDataTypes.ts")
.default as string;
monaco.languages.typescript.javascriptDefaults.addExtraLib(
commonTypes,
`file:///node_modules/CommonDataTypes/index.ts`
);
monaco.languages.typescript.javascriptDefaults.addExtraLib(
`declare function $asData(data: import("CommonDataTypes").CommonDataType): import("CommonDataTypes").CommonDataType;`,
`file:///types.d.ts`
);
monaco.languages.registerCompletionItemProvider("javascript", {
triggerCharacters: ["."],
provideCompletionItems: async (
textModel,
position
): Promise<monaco.languages.CompletionList> => {
const expression = textModel.getValue();
if (!this.model.server) {
return { suggestions: [] };
}
const completions = (await this.model.server.getCompletions({
text: expression,
column: position.column,
})).completions;
const map = {
method: monaco.languages.CompletionItemKind.Method,
function: monaco.languages.CompletionItemKind.Function,
constructor:
monaco.languages.CompletionItemKind.Constructor,
field: monaco.languages.CompletionItemKind.Field,
variable: monaco.languages.CompletionItemKind.Variable,
class: monaco.languages.CompletionItemKind.Class,
interface: monaco.languages.CompletionItemKind.Interface,
module: monaco.languages.CompletionItemKind.Module,
property: monaco.languages.CompletionItemKind.Property,
unit: monaco.languages.CompletionItemKind.Unit,
value: monaco.languages.CompletionItemKind.Value,
enum: monaco.languages.CompletionItemKind.Enum,
keyword: monaco.languages.CompletionItemKind.Keyword,
snippet: monaco.languages.CompletionItemKind.Snippet,
text: monaco.languages.CompletionItemKind.Text,
color: monaco.languages.CompletionItemKind.Color,
file: monaco.languages.CompletionItemKind.File,
reference: monaco.languages.CompletionItemKind.Reference,
customcolor:
monaco.languages.CompletionItemKind.Customcolor,
};
const p = textModel.getWordAtPosition(position);
return {
suggestions: completions.map<
monaco.languages.CompletionItem
>(c => {
const startColumn =
c.start || (p && p.startColumn) || position.column;
return {
insertText: c.text || c.label,
label: c.label,
kind: map[c.type || "text"],
range: {
startColumn,
endColumn: startColumn + (c.length || 0),
startLineNumber: 0,
endLineNumber: 0,
},
sortText:
c.type === "property" ? "zzzzzzz" : c.label,
};
}),
};
},
});
}
}

View File

@@ -0,0 +1,75 @@
import { Barrier } from "@hediet/std/synchronization";
export interface VSCodeApi<TState> {
getState(): Promise<TState | undefined>;
setState(state: TState): Promise<void>;
}
type IncomingCommand = {
command: "getStateResult";
state: unknown;
};
type OutgoingCommand =
| {
command: "setState";
state: unknown;
}
| { command: "getState" };
interface VsCodeApi {
getState<TState>(): TState | undefined;
setState(state: unknown): void;
}
export function getApi<TState>(): VSCodeApi<TState> {
return window.VsCodeApi
? new DirectVSCodeApi<TState>(window.VsCodeApi)
: new IFrameVSCodeApi<TState>();
}
declare const window: Window & {
VsCodeApi?: VsCodeApi;
};
export class IFrameVSCodeApi<TState> implements VSCodeApi<TState> {
private currentGetStatePromise: Barrier<TState> | undefined = undefined;
constructor() {
window.addEventListener("message", event => {
const data = event.data as IncomingCommand;
if (data.command === "getStateResult") {
this.currentGetStatePromise!.unlock(data.state as any);
}
});
}
private sendCommand(command: OutgoingCommand) {
window.parent.postMessage(command, "*");
}
getState(): Promise<TState | undefined> {
this.sendCommand({ command: "getState" });
if (this.currentGetStatePromise) {
throw new Error("Get state already in progress.");
}
this.currentGetStatePromise = new Barrier();
return this.currentGetStatePromise.onUnlocked;
}
async setState(state: TState): Promise<void> {
this.sendCommand({ command: "setState", state });
}
}
export class DirectVSCodeApi<TState> implements VSCodeApi<TState> {
constructor(private readonly vsCodeApi: VsCodeApi) {}
async getState(): Promise<TState | undefined> {
return this.vsCodeApi.getState<TState | undefined>();
}
async setState(state: TState): Promise<void> {
this.vsCodeApi.setState(state);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,8 +12,61 @@ import React = require("react");
import { observer, disposeOnUnmount } from "mobx-react";
import { autorun, observable } from "mobx";
export class MonacoTextVisualizer extends VisualizationProvider {
getVisualizations(
data: ExtractedData,
collector: VisualizationCollector
): void {
if (isCommonDataType(data, { text: true })) {
collector.addVisualization({
id: asVisualizationId("monaco-text"),
name: "Monaco Editor",
priority: 95,
render() {
let id = "text";
if (data.fileName) {
id = getLanguageId(data.fileName);
}
return <MonacoEditor text={data.text} languageId={id} />;
},
});
}
}
}
function getLanguageId(fileName: string): string {
const l = monaco.languages.getLanguages();
const result = l.find(l => {
if (l.filenamePatterns) {
for (const p of l.filenamePatterns) {
if (new RegExp(p).test(fileName)) {
return true;
}
}
}
if (l.extensions) {
for (const p of l.extensions) {
if (fileName.endsWith(p)) {
return true;
}
}
}
return false;
});
if (result) {
return result.id;
}
return "text";
}
@observer
export class MonacoEditor extends React.Component<{ text: string }> {
export class MonacoEditor extends React.Component<{
text: string;
languageId: string;
}> {
@observable private editor: monaco.editor.IStandaloneCodeEditor | undefined;
componentWillUnmount() {
@@ -22,10 +75,34 @@ export class MonacoEditor extends React.Component<{ text: string }> {
}
}
private model: monaco.editor.ITextModel | undefined = undefined;
@disposeOnUnmount
private _updateText = autorun(() => {
if (this.editor) {
this.editor.setValue(this.props.text);
const model = monaco.editor.createModel(
this.props.text,
this.props.languageId,
undefined
);
this.editor.setModel(model);
/* this.editor!.updateOptions({ readOnly: false });
setTimeout(() => {
const a = this.editor!.getAction(
"editor.action.formatDocument"
);
a.run().then(() => {
this.editor!.updateOptions({ readOnly: true });
});
}, 200);*/
if (this.model) {
this.model.dispose();
}
this.model = model;
}
});
@@ -35,7 +112,7 @@ export class MonacoEditor extends React.Component<{ text: string }> {
}
this.editor = monaco.editor.create(editorDiv, {
value: this.props.text,
model: null,
automaticLayout: true,
scrollBeyondLastLine: false,
minimap: { enabled: false },
@@ -52,21 +129,3 @@ export class MonacoEditor extends React.Component<{ text: string }> {
);
}
}
export class MonacoTextVisualizer extends VisualizationProvider {
getVisualizations(
data: ExtractedData,
collector: VisualizationCollector
): void {
if (isCommonDataType(data, { text: true })) {
collector.addVisualization({
id: asVisualizationId("monaco-text"),
name: "Monaco Editor",
priority: 95,
render() {
return <MonacoEditor text={data.text} />;
},
});
}
}
}

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import "./style.scss";
import { Model } from "./Model";
import { Model } from "./Model/Model";
import Components = require("./Components");
const model = new Model();

View File

@@ -80,6 +80,7 @@ body,
.part-header-content {
flex: 1;
min-width: 0;
transition: flex 0.3s ease-out;
@@ -89,8 +90,9 @@ body,
align-items: center;
.part-expression-input {
min-width: 0;
//color: rgb(155, 70, 176);
font-family: var(--vscode-editor-font-family);
//font-family: var(--vscode-editor-font-family);
flex: 1;
}
}
@@ -118,6 +120,31 @@ body,
}
}
.component-monaco-editor2 {
height: 23px;
width: 100%;
min-width: 0;
box-sizing: border-box;
display: flex;
padding: 2px;
background-color: white;
// overflow: hidden;
.part-editor {
min-width: 0;
min-height: 0;
flex: 1;
.current-line {
border: none !important;
}
.selectionHighlight {
background: transparent !important;
}
}
}
.decorator-collapse-icon {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23646465' d='M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z'/%3E%3C/svg%3E");

View File

@@ -1,4 +1,10 @@
import { contract, notificationContract, types } from "@hediet/typed-json-rpc";
import {
contract,
notificationContract,
types,
requestContract,
JSONObject,
} from "@hediet/typed-json-rpc";
import {
DataExtractionResult,
DataExtractorId,
@@ -22,6 +28,57 @@ export type DataExtractionState =
result: DataExtractionResult;
};
// https://microsoft.github.io/debug-adapter-protocol/specification#CompletionItem
type CompletionItemType =
| "method"
| "function"
| "constructor"
| "field"
| "variable"
| "class"
| "interface"
| "module"
| "property"
| "unit"
| "value"
| "enum"
| "keyword"
| "snippet"
| "text"
| "color"
| "file"
| "reference"
| "customcolor";
export interface CompletionItem {
/**
* The label of this completion item. By default this is also the text that is inserted when selecting this completion.
*/
label: string;
/**
* If text is not falsy then it is inserted instead of the label.
*/
text?: string;
/**
* The item's type. Typically the client uses this information to render the item in the UI with an icon.
*/
type?: CompletionItemType;
/**
* This value determines the location (in the CompletionsRequest's 'text' attribute) where the completion text is added.
* If missing the text is added at the location specified by the CompletionsRequest's 'column' attribute.
*/
start?: number;
/**
* This value determines how many characters are overwritten by the completion text.
* If missing the value 0 is assumed which results in the completion text being inserted.
*/
length?: number;
}
export const debugVisualizerUIContract = contract({
client: {
updateState: notificationContract({
@@ -43,5 +100,15 @@ export const debugVisualizerUIContract = contract({
}),
refresh: notificationContract({}),
openInBrowser: notificationContract({}),
getCompletions: requestContract({
params: types.type({
text: types.string,
column: types.number,
}),
result: types.type({
completions: types.array(unchecked<CompletionItem>()),
}),
}),
},
});