Renders all whitespace for text visualization & introduces decoration support.

This commit is contained in:
Henning Dieterichs
2021-06-07 00:43:03 +02:00
parent ec221b506e
commit 814b33720d
5 changed files with 1258 additions and 1207 deletions

View File

@@ -105,6 +105,7 @@ export class MonacoDiffEditor extends React.Component<{
fixedOverflowWidgets: true,
readOnly: true,
theme: this.props.theme.kind === "dark" ? "vs-dark" : "vs",
renderWhitespace: "all",
});
if (this.originalModel && this.modifiedModel) {
this.editor.setModel({

View File

@@ -6,11 +6,17 @@ import { Theme } from "@hediet/visualization-core";
import { getLoadedMonaco } from "@hediet/monaco-editor-react";
import { getLanguageId } from "./getLanguageId";
export interface Decoration {
label?: string;
range: monacoTypes.IRange;
}
@observer
export class MonacoEditor extends React.Component<{
text: string;
theme: Theme;
fileName?: string;
decorations?: Decoration[];
}> {
@observable private editor:
| monacoTypes.editor.IStandaloneCodeEditor
@@ -31,18 +37,82 @@ export class MonacoEditor extends React.Component<{
private model: monacoTypes.editor.ITextModel | undefined = undefined;
private prevDecorations: string[] = [];
@disposeOnUnmount
private _updateText = autorun(() => {
if (this.editor) {
const decorations = new Array<
monacoTypes.editor.IModelDeltaDecoration
>();
const propDecorations = this.props.decorations || [];
const lines = this.props.text.split("\n");
const mappedLines = lines.map((l, idx) => {
if (idx === lines.length - 1) {
return l;
}
if (
!propDecorations.some(
d =>
((d.range.endLineNumber === idx + 2 &&
d.range.endColumn === 1) ||
(d.range.startLineNumber === idx + 1 &&
d.range.startColumn >= l.length + 1)) &&
(d.range.startColumn !== d.range.endColumn ||
d.range.startLineNumber !==
d.range.endLineNumber)
)
) {
return l;
}
decorations.push({
range: {
startLineNumber: idx + 1,
endLineNumber: idx + 1,
startColumn: l.length + 1,
endColumn: l.length + 2,
},
options: {
inlineClassName: "decoration-whitespace",
},
});
return l + "␊"; // ␍␊
});
const text = mappedLines.join("\n");
const model = getLoadedMonaco().editor.createModel(
this.props.text,
text,
this.languageId,
undefined
);
this.editor.setModel(model);
/* this.editor!.updateOptions({ readOnly: false });
this.prevDecorations = this.editor.deltaDecorations(
[],
[
...decorations,
...propDecorations.map(d => {
const r = getLoadedMonaco().Range.lift(d.range);
return {
range: r,
options: {
hoverMessage: d.label
? { value: d.label, isTrusted: false }
: undefined,
className: r.isEmpty()
? "decoration-empty"
: "decoration",
},
};
}),
]
);
/*
this.editor!.updateOptions({ readOnly: false });
setTimeout(() => {
const a = this.editor!.getAction(
@@ -52,6 +122,7 @@ export class MonacoEditor extends React.Component<{
this.editor!.updateOptions({ readOnly: true });
});
}, 200);*/
if (this.model) {
this.model.dispose();
}
@@ -71,6 +142,7 @@ export class MonacoEditor extends React.Component<{
fixedOverflowWidgets: true,
readOnly: true,
theme: this.props.theme.kind === "dark" ? "vs-dark" : "vs",
renderWhitespace: "all",
});
};

View File

@@ -4,6 +4,9 @@ import {
sString,
sOptionalProp,
sProp,
sArrayOf,
sNumber,
sUnion,
} from "@hediet/semantic-json";
import * as React from "react";
import {
@@ -13,11 +16,31 @@ import {
} from "@hediet/visualization-core";
import { visualizationNs } from "../../consts";
import { makeLazyLoadable } from "../../utils/LazyLoadable";
import { Decoration } from "./MonacoEditor";
export const MonacoEditorLazyLoadable = makeLazyLoadable(
async () => (await import("./MonacoEditor")).MonacoEditor
);
export const LineColumnPosition = sOpenObject({
line: sProp(sNumber(), { description: "The 0-based line number" }),
column: sProp(sNumber(), { description: "The 0-based column number" }),
}).defineAs(visualizationNs("LineColumnPosition"));
export const LineColumnRange = sOpenObject({
start: sProp(LineColumnPosition, { description: "The start position" }),
end: sProp(LineColumnPosition, { description: "The end position" }),
}).defineAs(visualizationNs("LineColumnRange"));
export const OffsetPosition = sNumber().defineAs(
visualizationNs("OffsetPosition")
);
export const OffsetRange = sOpenObject({
start: sProp(OffsetPosition, { description: "The start position" }),
end: sProp(OffsetPosition, { description: "The end position" }),
}).defineAs(visualizationNs("OffsetRange"));
export const monacoTextVisualizer = createVisualizer({
id: "monaco-text",
name: "Text",
@@ -26,6 +49,14 @@ export const monacoTextVisualizer = createVisualizer({
text: sLiteral(true),
}),
text: sProp(sString(), { description: "The text to show" }),
decorations: sOptionalProp(
sArrayOf(
sOpenObject({
range: sUnion([LineColumnRange]),
label: sOptionalProp(sString()),
})
)
),
fileName: sOptionalProp(sString(), {
description:
"An optional filename that might be used for chosing a syntax highlighter",
@@ -35,13 +66,28 @@ export const monacoTextVisualizer = createVisualizer({
createReactVisualization(
self,
{ priority: 500, preload: MonacoEditorLazyLoadable.preload },
({ theme }) => (
<MonacoEditorLazyLoadable
text={data.text}
fileName={data.fileName}
theme={theme}
/>
)
function({ theme }) {
const decorations = data.decorations
? data.decorations.map<Decoration>(d => ({
label: d.label,
range: {
startLineNumber: d.range.start.line + 1,
startColumn: d.range.start.column + 1,
endLineNumber: d.range.end.line + 1,
endColumn: d.range.end.column + 1,
},
}))
: [];
return (
<MonacoEditorLazyLoadable
text={data.text}
fileName={data.fileName}
theme={theme}
decorations={decorations}
/>
);
}
),
});

View File

@@ -25,3 +25,17 @@
}
}
}
.decoration-whitespace {
color: black !important;
opacity: 0.4;
}
.decoration-empty {
margin-left: -1px;
border-left: solid rgba(255, 0, 0, 0.815) 3px;
}
.decoration {
background-color: rgba(250, 36, 36, 0.459);
}

2314
yarn.lock

File diff suppressed because it is too large Load Diff