@kerebron/extension-codejar 0.4.27 → 0.4.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/CodeJar.d.ts +51 -0
- package/esm/CodeJar.d.ts.map +1 -0
- package/esm/CodeJar.js +549 -0
- package/esm/CodeJar.js.map +1 -0
- package/esm/Decorator.d.ts +11 -0
- package/esm/Decorator.d.ts.map +1 -0
- package/esm/Decorator.js +46 -0
- package/esm/Decorator.js.map +1 -0
- package/esm/ExtensionCodeJar.d.ts +15 -0
- package/esm/ExtensionCodeJar.d.ts.map +1 -0
- package/esm/ExtensionCodeJar.js +62 -0
- package/esm/ExtensionCodeJar.js.map +1 -0
- package/esm/NodeCodeJar.d.ts +18 -0
- package/esm/NodeCodeJar.d.ts.map +1 -0
- package/esm/NodeCodeJar.js +70 -0
- package/esm/NodeCodeJar.js.map +1 -0
- package/esm/TreeSitterHighlighter.d.ts +11 -0
- package/esm/TreeSitterHighlighter.d.ts.map +1 -0
- package/esm/TreeSitterHighlighter.js +63 -0
- package/esm/TreeSitterHighlighter.js.map +1 -0
- package/esm/_dnt.shims.d.ts +2 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +58 -0
- package/esm/_dnt.shims.js.map +1 -0
- package/esm/codeJarBlockNodeView.d.ts +6 -0
- package/esm/codeJarBlockNodeView.d.ts.map +1 -0
- package/esm/codeJarBlockNodeView.js +254 -0
- package/esm/codeJarBlockNodeView.js.map +1 -0
- package/esm/codeJarLineNumbers.d.ts +13 -0
- package/esm/codeJarLineNumbers.d.ts.map +1 -0
- package/esm/codeJarLineNumbers.js +86 -0
- package/esm/codeJarLineNumbers.js.map +1 -0
- package/esm/mod.d.ts +2 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +2 -0
- package/esm/mod.js.map +1 -0
- package/esm/package.json +3 -0
- package/esm/utils.d.ts +13 -0
- package/esm/utils.d.ts.map +1 -0
- package/esm/utils.js +49 -0
- package/esm/utils.js.map +1 -0
- package/package.json +11 -8
- package/src/CodeJar.ts +636 -0
- package/src/Decorator.ts +67 -0
- package/src/ExtensionCodeJar.ts +83 -0
- package/src/NodeCodeJar.ts +107 -0
- package/src/TreeSitterHighlighter.ts +85 -0
- package/src/_dnt.shims.ts +60 -0
- package/src/codeJarBlockNodeView.ts +372 -0
- package/src/codeJarLineNumbers.ts +129 -0
- package/src/mod.ts +1 -0
- package/src/utils.ts +77 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Node, Schema } from 'prosemirror-model';
|
|
2
|
+
import {
|
|
3
|
+
AnyExtensionOrReq,
|
|
4
|
+
type Converter,
|
|
5
|
+
type CoreEditor,
|
|
6
|
+
Extension,
|
|
7
|
+
} from '@kerebron/editor';
|
|
8
|
+
import { createNodeFromObject } from '@kerebron/editor/utilities';
|
|
9
|
+
import { NodeCodeJar, NodeCodeJarConfig } from './NodeCodeJar.js';
|
|
10
|
+
|
|
11
|
+
export * from './NodeCodeJar.js';
|
|
12
|
+
|
|
13
|
+
export interface ExtensionCodeJarConfig {
|
|
14
|
+
readOnly?: boolean;
|
|
15
|
+
languageWhitelist?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class ExtensionCodeJar extends Extension {
|
|
19
|
+
override name = 'code-jar';
|
|
20
|
+
requires: AnyExtensionOrReq[];
|
|
21
|
+
|
|
22
|
+
constructor(protected override config: ExtensionCodeJarConfig = {}) {
|
|
23
|
+
super(config);
|
|
24
|
+
|
|
25
|
+
this.requires = [
|
|
26
|
+
new NodeCodeJar({
|
|
27
|
+
languageWhitelist: config.languageWhitelist,
|
|
28
|
+
// theme: config.theme,
|
|
29
|
+
}),
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override getConverters(
|
|
34
|
+
editor: CoreEditor,
|
|
35
|
+
schema: Schema,
|
|
36
|
+
): Record<string, Converter> {
|
|
37
|
+
return {
|
|
38
|
+
'text/code-only': {
|
|
39
|
+
fromDoc: async (document: Node): Promise<Uint8Array> => {
|
|
40
|
+
const retVal = [];
|
|
41
|
+
if (document.content) {
|
|
42
|
+
for (const node of document.content.toJSON()) {
|
|
43
|
+
if ('code_block' === node.type && Array.isArray(node.content)) {
|
|
44
|
+
for (const content of node.content) {
|
|
45
|
+
retVal.push(content.text);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return new TextEncoder().encode(retVal.join(''));
|
|
51
|
+
},
|
|
52
|
+
toDoc: async (buffer: Uint8Array): Promise<Node> => {
|
|
53
|
+
const code = new TextDecoder().decode(buffer);
|
|
54
|
+
const content = {
|
|
55
|
+
'type': 'doc_code',
|
|
56
|
+
'content': [
|
|
57
|
+
{
|
|
58
|
+
'type': 'code_block',
|
|
59
|
+
'attrs': {
|
|
60
|
+
'lang': schema.topNodeType.spec.defaultAttrs?.lang,
|
|
61
|
+
},
|
|
62
|
+
'content': [
|
|
63
|
+
{
|
|
64
|
+
'type': 'text',
|
|
65
|
+
'text': code,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return createNodeFromObject(
|
|
73
|
+
content,
|
|
74
|
+
schema,
|
|
75
|
+
{
|
|
76
|
+
errorOnInvalidContent: false,
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { EditorView } from 'prosemirror-view';
|
|
2
|
+
import {
|
|
3
|
+
EditorState,
|
|
4
|
+
Plugin,
|
|
5
|
+
PluginKey,
|
|
6
|
+
Selection,
|
|
7
|
+
Transaction,
|
|
8
|
+
} from 'prosemirror-state';
|
|
9
|
+
import type { NodeType } from 'prosemirror-model';
|
|
10
|
+
|
|
11
|
+
import { type CoreEditor } from '@kerebron/editor';
|
|
12
|
+
import { getShadowRoot } from '@kerebron/editor/utilities';
|
|
13
|
+
import {
|
|
14
|
+
type CommandFactories,
|
|
15
|
+
type CommandShortcuts,
|
|
16
|
+
} from '@kerebron/editor/commands';
|
|
17
|
+
|
|
18
|
+
import { getLangsList } from '@kerebron/wasm';
|
|
19
|
+
import { NodeCodeBlock } from '@kerebron/extension-basic-editor/NodeCodeBlock';
|
|
20
|
+
|
|
21
|
+
import { codeJarBlockNodeView } from './codeJarBlockNodeView.js';
|
|
22
|
+
|
|
23
|
+
export const codeJarBlockKey = new PluginKey('code-jar-block');
|
|
24
|
+
|
|
25
|
+
function arrowHandler(dir: 'left' | 'right' | 'up' | 'down') {
|
|
26
|
+
return (
|
|
27
|
+
state: EditorState,
|
|
28
|
+
dispatch: (tr: Transaction) => void,
|
|
29
|
+
view: EditorView,
|
|
30
|
+
) => {
|
|
31
|
+
if (state.selection.empty && view.endOfTextblock(dir)) {
|
|
32
|
+
let side = dir == 'left' || dir == 'up' ? -1 : 1;
|
|
33
|
+
let $head = state.selection.$head;
|
|
34
|
+
let nextPos = Selection.near(
|
|
35
|
+
state.doc.resolve(side > 0 ? $head.after() : $head.before()),
|
|
36
|
+
side,
|
|
37
|
+
);
|
|
38
|
+
if (nextPos.$head && nextPos.$head.parent.type.name == 'code_block') {
|
|
39
|
+
dispatch(state.tr.setSelection(nextPos));
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface NodeCodeJarConfig {
|
|
48
|
+
readOnly?: boolean;
|
|
49
|
+
languageWhitelist?: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class NodeCodeJar extends NodeCodeBlock {
|
|
53
|
+
constructor(override config: NodeCodeJarConfig) {
|
|
54
|
+
super(config);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override getCommandFactories(
|
|
58
|
+
editor: CoreEditor,
|
|
59
|
+
type: NodeType,
|
|
60
|
+
): Partial<CommandFactories> {
|
|
61
|
+
return {
|
|
62
|
+
'setCodeBlock': (lang?: string) =>
|
|
63
|
+
editor.commandFactories.setBlockType(type, { lang }),
|
|
64
|
+
// ArrowLeft: () => arrowHandler('left'),
|
|
65
|
+
// ArrowRight: () => arrowHandler('right'),
|
|
66
|
+
// ArrowUp: () => arrowHandler('up'),
|
|
67
|
+
// ArrowDown: () => arrowHandler('down'),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override getKeyboardShortcuts(): Partial<CommandShortcuts> {
|
|
72
|
+
return {
|
|
73
|
+
'Shift-Ctrl-"': 'setCodeBlock',
|
|
74
|
+
'ArrowLeft': 'ArrowLeft',
|
|
75
|
+
'ArrowRight': 'ArrowRight',
|
|
76
|
+
'ArrowUp': 'ArrowUp',
|
|
77
|
+
'ArrowDown': 'ArrowDown',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override getProseMirrorPlugins(): Plugin[] {
|
|
82
|
+
const shadowRoot = getShadowRoot(this.editor.config.element);
|
|
83
|
+
|
|
84
|
+
const settings = {
|
|
85
|
+
languageWhitelist: this.config.languageWhitelist || getLangsList(),
|
|
86
|
+
shadowRoot,
|
|
87
|
+
readOnly: this.editor.config.readOnly || this.config.readOnly,
|
|
88
|
+
undo: () => {
|
|
89
|
+
this.editor.chain().undo().run();
|
|
90
|
+
},
|
|
91
|
+
redo: () => {
|
|
92
|
+
this.editor.chain().redo().run();
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return [
|
|
97
|
+
new Plugin({
|
|
98
|
+
key: codeJarBlockKey,
|
|
99
|
+
props: {
|
|
100
|
+
nodeViews: {
|
|
101
|
+
[this.name]: codeJarBlockNodeView(settings, this.editor),
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createParser, type Parser } from '@kerebron/tree-sitter';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
fetchTextResource,
|
|
5
|
+
fetchWasm,
|
|
6
|
+
getLangTreeSitter,
|
|
7
|
+
} from '@kerebron/wasm';
|
|
8
|
+
|
|
9
|
+
import { DecorationInline, Decorator } from './Decorator.js';
|
|
10
|
+
|
|
11
|
+
export class TreeSitterHighlighter {
|
|
12
|
+
parser: Parser | undefined;
|
|
13
|
+
hightligtScm: string | undefined;
|
|
14
|
+
cdnUrl? = 'http://localhost:8000/wasm/';
|
|
15
|
+
lang?: string;
|
|
16
|
+
|
|
17
|
+
async init(lang: string): Promise<boolean> {
|
|
18
|
+
this.lang = lang;
|
|
19
|
+
if (!lang) {
|
|
20
|
+
this.parser = undefined;
|
|
21
|
+
this.hightligtScm = undefined;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const treeSitterConfig = getLangTreeSitter(lang, this.cdnUrl);
|
|
25
|
+
const wasmUrl = treeSitterConfig.files[0]; // TODO add support for split parsers like markdown
|
|
26
|
+
const highlightUrl = treeSitterConfig.queries['highlights.scm'];
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const wasm = await fetchWasm(wasmUrl);
|
|
30
|
+
this.parser = await createParser(wasm);
|
|
31
|
+
|
|
32
|
+
this.hightligtScm = await fetchTextResource(highlightUrl);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error('Error init highlight for: ' + lang, err);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!this.parser) {
|
|
38
|
+
console.warn('Parser not inited');
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (!this.hightligtScm) {
|
|
42
|
+
console.warn('hightligtScm not inited');
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
highlight(code: string, decorator: Decorator) {
|
|
50
|
+
if (!this.lang || !this.parser || !this.hightligtScm) {
|
|
51
|
+
decorator.decorationGroups['highlight'] = [];
|
|
52
|
+
return decorator.highlight(code);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const tree = this.parser.parse(code)!;
|
|
56
|
+
const root = tree.rootNode;
|
|
57
|
+
|
|
58
|
+
const highlightsQuery = root.query(this.hightligtScm);
|
|
59
|
+
|
|
60
|
+
const captures = [];
|
|
61
|
+
|
|
62
|
+
for (const item of highlightsQuery) {
|
|
63
|
+
captures.push(...item.captures);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const decorations: DecorationInline[] = [];
|
|
67
|
+
|
|
68
|
+
for (const capture of captures) {
|
|
69
|
+
const { node, name } = capture; // name is the capture like "@string"
|
|
70
|
+
|
|
71
|
+
const startIndex = node.startIndex;
|
|
72
|
+
const endIndex = node.endIndex;
|
|
73
|
+
|
|
74
|
+
decorations.push({
|
|
75
|
+
startIndex,
|
|
76
|
+
endIndex,
|
|
77
|
+
className: 'ts-' + name.replaceAll('.', '_'),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
decorator.decorationGroups['highlight'] = decorations;
|
|
82
|
+
|
|
83
|
+
return decorator.highlight(code);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const dntGlobals = {
|
|
2
|
+
};
|
|
3
|
+
export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
|
|
4
|
+
|
|
5
|
+
function createMergeProxy<T extends object, U extends object>(
|
|
6
|
+
baseObj: T,
|
|
7
|
+
extObj: U,
|
|
8
|
+
): Omit<T, keyof U> & U {
|
|
9
|
+
return new Proxy(baseObj, {
|
|
10
|
+
get(_target, prop, _receiver) {
|
|
11
|
+
if (prop in extObj) {
|
|
12
|
+
return (extObj as any)[prop];
|
|
13
|
+
} else {
|
|
14
|
+
return (baseObj as any)[prop];
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
set(_target, prop, value) {
|
|
18
|
+
if (prop in extObj) {
|
|
19
|
+
delete (extObj as any)[prop];
|
|
20
|
+
}
|
|
21
|
+
(baseObj as any)[prop] = value;
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
deleteProperty(_target, prop) {
|
|
25
|
+
let success = false;
|
|
26
|
+
if (prop in extObj) {
|
|
27
|
+
delete (extObj as any)[prop];
|
|
28
|
+
success = true;
|
|
29
|
+
}
|
|
30
|
+
if (prop in baseObj) {
|
|
31
|
+
delete (baseObj as any)[prop];
|
|
32
|
+
success = true;
|
|
33
|
+
}
|
|
34
|
+
return success;
|
|
35
|
+
},
|
|
36
|
+
ownKeys(_target) {
|
|
37
|
+
const baseKeys = Reflect.ownKeys(baseObj);
|
|
38
|
+
const extKeys = Reflect.ownKeys(extObj);
|
|
39
|
+
const extKeysSet = new Set(extKeys);
|
|
40
|
+
return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
|
|
41
|
+
},
|
|
42
|
+
defineProperty(_target, prop, desc) {
|
|
43
|
+
if (prop in extObj) {
|
|
44
|
+
delete (extObj as any)[prop];
|
|
45
|
+
}
|
|
46
|
+
Reflect.defineProperty(baseObj, prop, desc);
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
50
|
+
if (prop in extObj) {
|
|
51
|
+
return Reflect.getOwnPropertyDescriptor(extObj, prop);
|
|
52
|
+
} else {
|
|
53
|
+
return Reflect.getOwnPropertyDescriptor(baseObj, prop);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
has(_target, prop) {
|
|
57
|
+
return prop in extObj || prop in baseObj;
|
|
58
|
+
},
|
|
59
|
+
}) as any;
|
|
60
|
+
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { Node } from 'prosemirror-model';
|
|
2
|
+
import {
|
|
3
|
+
Decoration,
|
|
4
|
+
DecorationSource,
|
|
5
|
+
EditorView,
|
|
6
|
+
EditorView as PMEditorView,
|
|
7
|
+
NodeView,
|
|
8
|
+
} from 'prosemirror-view';
|
|
9
|
+
import { Diagnostic } from 'vscode-languageserver-protocol';
|
|
10
|
+
|
|
11
|
+
import { CoreEditor, RawTextResult } from '@kerebron/editor';
|
|
12
|
+
import { PositionMapper } from '@kerebron/extension-markdown/PositionMapper';
|
|
13
|
+
import { ExtensionLsp, LspSource } from '@kerebron/extension-lsp';
|
|
14
|
+
import { toRawTextResult } from '@kerebron/editor/utilities';
|
|
15
|
+
|
|
16
|
+
import { CodeJar, Position } from './CodeJar.js';
|
|
17
|
+
import { computeChange, forwardSelection, valueChanged } from './utils.js';
|
|
18
|
+
import { TreeSitterHighlighter } from './TreeSitterHighlighter.js';
|
|
19
|
+
import { DecorationInline, Decorator } from './Decorator.js';
|
|
20
|
+
import {
|
|
21
|
+
initLineNumbers,
|
|
22
|
+
lineNumberOptions,
|
|
23
|
+
refreshNumbers,
|
|
24
|
+
} from './codeJarLineNumbers.js';
|
|
25
|
+
import { NodeCodeJarConfig } from './NodeCodeJar.js';
|
|
26
|
+
|
|
27
|
+
class CodeJarBlockNodeView implements NodeView {
|
|
28
|
+
dom: HTMLDivElement;
|
|
29
|
+
codeJar: CodeJar;
|
|
30
|
+
updating: boolean;
|
|
31
|
+
element: HTMLDivElement;
|
|
32
|
+
highlighter: TreeSitterHighlighter;
|
|
33
|
+
decorator: Decorator;
|
|
34
|
+
languageDropDown: HTMLSelectElement;
|
|
35
|
+
lineNumbers: HTMLElement;
|
|
36
|
+
|
|
37
|
+
source: LspSource;
|
|
38
|
+
extensionLsp: ExtensionLsp | undefined;
|
|
39
|
+
lang: string = 'plaintext';
|
|
40
|
+
uri: string = 'file:///' + Math.random() + '.ts';
|
|
41
|
+
diagListener: (event: Event) => void;
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
private node: Node,
|
|
45
|
+
private view: EditorView,
|
|
46
|
+
private getPos: () => number | undefined,
|
|
47
|
+
private config: NodeCodeJarConfig,
|
|
48
|
+
private editor: CoreEditor,
|
|
49
|
+
) {
|
|
50
|
+
this.updating = false;
|
|
51
|
+
const dom = document.createElement('div');
|
|
52
|
+
this.dom = dom;
|
|
53
|
+
dom.className = 'codejar-root';
|
|
54
|
+
|
|
55
|
+
this.languageDropDown = this.addLanguageDropDown();
|
|
56
|
+
|
|
57
|
+
const root = (editor.view && 'root' in editor.view)
|
|
58
|
+
? editor.view.root
|
|
59
|
+
: document || document;
|
|
60
|
+
|
|
61
|
+
this.element = document.createElement('div');
|
|
62
|
+
this.element.classList.add('codejar');
|
|
63
|
+
|
|
64
|
+
this.codeJar = new CodeJar(
|
|
65
|
+
this.element,
|
|
66
|
+
(element) => this.highlight(element),
|
|
67
|
+
{
|
|
68
|
+
tab: ' ',
|
|
69
|
+
indentOn: new RegExp('^(?!)'),
|
|
70
|
+
moveToNewLine: new RegExp('^(?!)'),
|
|
71
|
+
history: false,
|
|
72
|
+
readOnly: this.config.readOnly,
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
this.codeJar.onUpdate(() => {
|
|
77
|
+
if (!this.updating) {
|
|
78
|
+
const textUpdate = this.codeJar.toString();
|
|
79
|
+
valueChanged(textUpdate, this.node, getPos, view);
|
|
80
|
+
if (document.activeElement === this.element) {
|
|
81
|
+
forwardSelection(this.codeJar, view, getPos);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const client = this.extensionLsp?.getClient(this.lang);
|
|
86
|
+
if (client) {
|
|
87
|
+
client.workspace.changedFile(
|
|
88
|
+
this.uri,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
this.highlighter = new TreeSitterHighlighter();
|
|
94
|
+
this.highlighter.cdnUrl = this.editor.config.cdnUrl;
|
|
95
|
+
this.decorator = new Decorator();
|
|
96
|
+
|
|
97
|
+
dom.append(this.element);
|
|
98
|
+
|
|
99
|
+
this.lineNumbers = initLineNumbers(this.element, lineNumberOptions);
|
|
100
|
+
|
|
101
|
+
this.source = {
|
|
102
|
+
ui: this.editor.ui,
|
|
103
|
+
getMappedContent: async () => {
|
|
104
|
+
const editor = this.editor;
|
|
105
|
+
const result: RawTextResult = toRawTextResult(
|
|
106
|
+
this.codeJar.toString(),
|
|
107
|
+
0,
|
|
108
|
+
);
|
|
109
|
+
const mapper = new PositionMapper(editor, result.rawTextMap);
|
|
110
|
+
return {
|
|
111
|
+
...result,
|
|
112
|
+
mapper,
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
this.extensionLsp = editor.getExtension('lsp');
|
|
118
|
+
|
|
119
|
+
let lastDiag = 0;
|
|
120
|
+
this.diagListener = (event: Event) => {
|
|
121
|
+
const detail = (event as CustomEvent).detail;
|
|
122
|
+
if (detail.params.uri !== this.uri) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
|
|
128
|
+
lastDiag = +Date();
|
|
129
|
+
|
|
130
|
+
const client = this.extensionLsp?.getClient(this.lang);
|
|
131
|
+
if (client) {
|
|
132
|
+
const file = client.workspace.getFile(this.uri);
|
|
133
|
+
if (file) {
|
|
134
|
+
const { mapper } = file;
|
|
135
|
+
console.debug({
|
|
136
|
+
diagnostics: detail.params.diagnostics,
|
|
137
|
+
mapper,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const diagnostics: Diagnostic[] = detail.params.diagnostics;
|
|
141
|
+
|
|
142
|
+
const decors: DecorationInline[] = [];
|
|
143
|
+
|
|
144
|
+
for (const diag of diagnostics) {
|
|
145
|
+
const startIndex = mapper.fromLineChar(
|
|
146
|
+
diag.range.start.line,
|
|
147
|
+
diag.range.start.character,
|
|
148
|
+
);
|
|
149
|
+
const endIndex = mapper.fromLineChar(
|
|
150
|
+
diag.range.end.line,
|
|
151
|
+
diag.range.end.character,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
decors.push({
|
|
155
|
+
startIndex,
|
|
156
|
+
endIndex,
|
|
157
|
+
className: 'kb-lsp__error',
|
|
158
|
+
title: diag.message || '',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.decorator.decorationGroups.innerDiag = decors;
|
|
163
|
+
this.highlight(this.element);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
addLanguageDropDown() {
|
|
170
|
+
const select = document.createElement('select');
|
|
171
|
+
select.classList.add('codejar-select');
|
|
172
|
+
for (const lang of [''].concat(this.config.languageWhitelist || [])) {
|
|
173
|
+
const option = document.createElement('option');
|
|
174
|
+
option.value = lang;
|
|
175
|
+
option.innerText = lang;
|
|
176
|
+
select.appendChild(option);
|
|
177
|
+
}
|
|
178
|
+
this.dom.appendChild(select);
|
|
179
|
+
select.addEventListener('change', async () => {
|
|
180
|
+
const lang = select.value;
|
|
181
|
+
const pos = this.getPos();
|
|
182
|
+
if (pos) {
|
|
183
|
+
this.view.dispatch(
|
|
184
|
+
this.view.state.tr.setNodeMarkup(pos, undefined, {
|
|
185
|
+
...this.node.attrs,
|
|
186
|
+
lang,
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
await this.setLang(lang);
|
|
191
|
+
});
|
|
192
|
+
return select;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async setLang(lang: string) {
|
|
196
|
+
this.languageDropDown.value = lang || '';
|
|
197
|
+
await this.highlighter.init(lang);
|
|
198
|
+
this.highlight(this.element);
|
|
199
|
+
this.lang = lang;
|
|
200
|
+
|
|
201
|
+
const client = this.extensionLsp?.getClient(this.lang);
|
|
202
|
+
if (client) {
|
|
203
|
+
client.addEventListener(
|
|
204
|
+
'textDocument/publishDiagnostics',
|
|
205
|
+
this.diagListener,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
client.connect(this.uri, this.source);
|
|
209
|
+
client.workspace.openFile(
|
|
210
|
+
this.uri,
|
|
211
|
+
lang,
|
|
212
|
+
this.source,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async init() {
|
|
218
|
+
this.codeJar.updateCode(this.node.textContent, false);
|
|
219
|
+
if (this.node.attrs.lang) {
|
|
220
|
+
await this.setLang(this.node.attrs.lang);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
setSelection(anchor: number, head: number) {
|
|
225
|
+
this.element.focus();
|
|
226
|
+
this.updating = true;
|
|
227
|
+
|
|
228
|
+
const pos = this.getPos();
|
|
229
|
+
if (pos) {
|
|
230
|
+
anchor -= pos;
|
|
231
|
+
head -= pos;
|
|
232
|
+
|
|
233
|
+
const posJar: Position = {
|
|
234
|
+
start: Math.min(anchor, head),
|
|
235
|
+
end: Math.max(anchor, head),
|
|
236
|
+
dir: (anchor <= head) ? '->' : '<-',
|
|
237
|
+
};
|
|
238
|
+
this.codeJar.restore(posJar);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.updating = false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
highlight(editor: HTMLElement) {
|
|
245
|
+
// const highlight = withLineNumbers((element) => this.highlight(element));
|
|
246
|
+
if (!this.highlighter) {
|
|
247
|
+
editor.innerHTML = editor.textContent;
|
|
248
|
+
} else {
|
|
249
|
+
const content = editor.textContent;
|
|
250
|
+
editor.innerHTML = this.highlighter.highlight(content, this.decorator) ||
|
|
251
|
+
content;
|
|
252
|
+
}
|
|
253
|
+
refreshNumbers(this.lineNumbers, editor);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
update(
|
|
257
|
+
updateNode: Node,
|
|
258
|
+
_decorations: readonly Decoration[],
|
|
259
|
+
innerDecorations: DecorationSource,
|
|
260
|
+
) {
|
|
261
|
+
const codeDecorations: Decoration[] = [];
|
|
262
|
+
|
|
263
|
+
innerDecorations
|
|
264
|
+
.forEachSet((set) =>
|
|
265
|
+
set.find()
|
|
266
|
+
.map((d) => {
|
|
267
|
+
codeDecorations.push(d);
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const decors: DecorationInline[] = [];
|
|
272
|
+
|
|
273
|
+
for (const cd of codeDecorations) {
|
|
274
|
+
if ('type' in cd) {
|
|
275
|
+
const type = cd.type;
|
|
276
|
+
decors.push({
|
|
277
|
+
startIndex: cd.from,
|
|
278
|
+
endIndex: cd.to,
|
|
279
|
+
className: type?.attrs?.class || '',
|
|
280
|
+
title: type?.attrs?.title || '',
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this.decorator.decorationGroups.diag = decors;
|
|
286
|
+
|
|
287
|
+
const oldNode = this.node;
|
|
288
|
+
|
|
289
|
+
const content = this.codeJar.toString();
|
|
290
|
+
|
|
291
|
+
const change = computeChange(
|
|
292
|
+
content,
|
|
293
|
+
updateNode.textContent,
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
if (change) {
|
|
297
|
+
const pos = this.codeJar.save();
|
|
298
|
+
|
|
299
|
+
this.updating = true;
|
|
300
|
+
this.codeJar.updateCode(updateNode.textContent, true);
|
|
301
|
+
this.updating = false;
|
|
302
|
+
|
|
303
|
+
// TODO fix for yjs collab
|
|
304
|
+
// change.from, change.to, change.text.length
|
|
305
|
+
if (pos) {
|
|
306
|
+
this.codeJar.restore(pos);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
this.node = updateNode;
|
|
311
|
+
if (updateNode.attrs.lang !== oldNode.attrs.lang) {
|
|
312
|
+
this.setLang(updateNode.attrs.lang);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
selectNode() {
|
|
319
|
+
this.element.focus();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
stopEvent(_e: Event) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
ignoreMutation() {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
destroy() {
|
|
331
|
+
const client = this.extensionLsp?.getClient(this.lang);
|
|
332
|
+
if (client) {
|
|
333
|
+
if (this.uri) {
|
|
334
|
+
client.disconnect(this.uri);
|
|
335
|
+
}
|
|
336
|
+
if (this.diagListener) {
|
|
337
|
+
client.removeEventListener(
|
|
338
|
+
'textDocument/publishDiagnostics',
|
|
339
|
+
this.diagListener,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
this.codeJar.destroy();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export const codeJarBlockNodeView: (
|
|
348
|
+
settings: NodeCodeJarConfig,
|
|
349
|
+
editor: CoreEditor,
|
|
350
|
+
) => (
|
|
351
|
+
node: Node,
|
|
352
|
+
view: EditorView,
|
|
353
|
+
getPos: () => number | undefined,
|
|
354
|
+
decorations: readonly Decoration[],
|
|
355
|
+
innerDecorations: DecorationSource,
|
|
356
|
+
) => NodeView = (settings, editor) => {
|
|
357
|
+
return (
|
|
358
|
+
pmNode: Node,
|
|
359
|
+
view: PMEditorView,
|
|
360
|
+
getPos: () => number | undefined,
|
|
361
|
+
) => {
|
|
362
|
+
const plugin = new CodeJarBlockNodeView(
|
|
363
|
+
pmNode,
|
|
364
|
+
view,
|
|
365
|
+
getPos,
|
|
366
|
+
settings,
|
|
367
|
+
editor,
|
|
368
|
+
);
|
|
369
|
+
plugin.init();
|
|
370
|
+
return plugin;
|
|
371
|
+
};
|
|
372
|
+
};
|