feat(core): mix模式下使用diff去修改代码防止触发整个文件代码高亮的重新构建
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
goog.loadJs('common', () => {
|
||||
|
||||
goog.require('ace');
|
||||
goog.require('ace.ExtLanguageTools');
|
||||
goog.require('Mixly.Config');
|
||||
goog.require('Mixly.XML');
|
||||
goog.require('Mixly.Env');
|
||||
@@ -11,6 +9,8 @@ goog.require('Mixly.Menu');
|
||||
goog.require('Mixly.ContextMenu');
|
||||
goog.require('Mixly.IdGenerator');
|
||||
goog.require('Mixly.CodeFormatter');
|
||||
goog.require('Mixly.MonacoTheme');
|
||||
goog.require('Mixly.MonacoTreeSitter');
|
||||
goog.require('Mixly.EditorMonaco');
|
||||
goog.provide('Mixly.EditorCode');
|
||||
|
||||
@@ -24,12 +24,16 @@ const {
|
||||
ContextMenu,
|
||||
IdGenerator,
|
||||
CodeFormatter,
|
||||
MonacoTheme,
|
||||
MonacoTreeSitter,
|
||||
EditorMonaco
|
||||
} = Mixly;
|
||||
const { USER } = Config;
|
||||
|
||||
|
||||
class EditorCode extends EditorMonaco {
|
||||
#contextMenu_ = null;
|
||||
#monacoTreeSitter_ = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -41,6 +45,7 @@ class EditorCode extends EditorMonaco {
|
||||
this.setTabSize(4);
|
||||
this.#addContextMenu_();
|
||||
this.setTheme(USER.theme);
|
||||
this.#monacoTreeSitter_ = new MonacoTreeSitter(this);
|
||||
}
|
||||
|
||||
onMounted() {
|
||||
@@ -113,40 +118,65 @@ class EditorCode extends EditorMonaco {
|
||||
|
||||
setValue(data, ext) {
|
||||
this.disableChangeEvent();
|
||||
super.setValue(data, ext);
|
||||
super.setValue(data);
|
||||
const language = this.getLanguageByExt(ext);
|
||||
if (MonacoTheme.supportThemes.includes(language)) {
|
||||
this.setTheme(`${USER.theme}-${language}`);
|
||||
} else {
|
||||
this.setTheme(USER.theme);
|
||||
}
|
||||
this.setLanguage(language);
|
||||
this.enableChangeEvent();
|
||||
CodeFormatter.activateFormatter(language)
|
||||
.then((formatter) => {
|
||||
const menu = this.#contextMenu_.getItem('code');
|
||||
if (!formatter) {
|
||||
menu.remove('sep-format');
|
||||
menu.remove('format');
|
||||
return;
|
||||
this.setCodeFormatter(language, ext).catch(Debug.error);
|
||||
}
|
||||
|
||||
diffEdits(data, ext) {
|
||||
this.disableChangeEvent();
|
||||
super.diffEdits(data);
|
||||
const language = this.getLanguageByExt(ext);
|
||||
this.setLanguage(language);
|
||||
if (MonacoTheme.supportThemes.includes(language)) {
|
||||
this.setTheme(`${USER.theme}-${language}`);
|
||||
} else {
|
||||
this.setTheme(USER.theme);
|
||||
}
|
||||
this.#monacoTreeSitter_.setValue(language, USER.theme, data);
|
||||
this.enableChangeEvent();
|
||||
this.setCodeFormatter(language, ext).catch(Debug.error);
|
||||
}
|
||||
|
||||
async setCodeFormatter(language, ext) {
|
||||
const formatter = await CodeFormatter.activateFormatter(language);
|
||||
const menu = this.#contextMenu_.getItem('code');
|
||||
if (!formatter) {
|
||||
menu.remove('sep-format');
|
||||
menu.remove('format');
|
||||
return;
|
||||
}
|
||||
menu.add({
|
||||
weight: 6,
|
||||
id: 'sep-format',
|
||||
data: '---------'
|
||||
});
|
||||
menu.add({
|
||||
weight: 6,
|
||||
id: 'format',
|
||||
data: {
|
||||
isHtmlName: true,
|
||||
name: Menu.getItem(Msg.Lang['editor.contextMenu.formatDocument'], ''),
|
||||
callback: () => {
|
||||
CodeFormatter.format(language, this.getValue())
|
||||
.then((data) => {
|
||||
super.setValue(data, ext);
|
||||
})
|
||||
.catch(Debug.error);
|
||||
}
|
||||
menu.add({
|
||||
weight: 6,
|
||||
id: 'sep-format',
|
||||
data: '---------'
|
||||
});
|
||||
menu.add({
|
||||
weight: 6,
|
||||
id: 'format',
|
||||
data: {
|
||||
isHtmlName: true,
|
||||
name: Menu.getItem(Msg.Lang['editor.contextMenu.formatDocument'], ''),
|
||||
callback: (key, opt) => {
|
||||
CodeFormatter.format(language, this.getValue())
|
||||
.then((data) => {
|
||||
super.setValue(data, ext);
|
||||
})
|
||||
.catch(Debug.error);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(Debug.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getTreeSitter() {
|
||||
return this.#monacoTreeSitter_;
|
||||
}
|
||||
|
||||
getLanguageByExt(ext) {
|
||||
@@ -183,7 +213,10 @@ class EditorCode extends EditorMonaco {
|
||||
|
||||
#addChangeEventListenerExt_() {
|
||||
this.offEvent('change');
|
||||
this.bind('change', () => this.addDirty());
|
||||
this.bind('change', () => {
|
||||
this.addDirty();
|
||||
this.#monacoTreeSitter_.setValue(this.getLanguage(), USER.theme, this.getValue());
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
@@ -39,7 +39,7 @@ const {
|
||||
HTMLTemplate,
|
||||
LayerExt
|
||||
} = Mixly;
|
||||
const { BOARD, SOFTWARE } = Config;
|
||||
const { BOARD, SOFTWARE, USER } = Config;
|
||||
|
||||
const { form } = layui;
|
||||
|
||||
@@ -173,6 +173,7 @@ class EditorMix extends EditorBase {
|
||||
codePage.offEvent('change');
|
||||
codePage.bind('change', () => {
|
||||
this.addDirty();
|
||||
codePage.getTreeSitter().setValue(codePage.getLanguage(), USER.theme, codePage.getValue());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -217,12 +218,12 @@ class EditorMix extends EditorBase {
|
||||
&& typeof this.py2BlockEditor.updateBlock === 'function') {
|
||||
this.py2BlockEditor.updateBlock();
|
||||
} else {
|
||||
codePage.setValue(blockPage.getValue(), this.#language_);
|
||||
codePage.diffEdits(blockPage.getValue(), this.#language_);
|
||||
}
|
||||
break;
|
||||
case Drag.Extend.POSITIVE:
|
||||
this.#temp_ = blockPage.getValue();
|
||||
codePage.setValue(this.#temp_, this.#language_);
|
||||
codePage.diffEdits(this.#temp_, this.#language_);
|
||||
break;
|
||||
}
|
||||
blockPage.resize();
|
||||
@@ -363,7 +364,7 @@ class EditorMix extends EditorBase {
|
||||
return;
|
||||
}
|
||||
this.#temp_ = code;
|
||||
codePage.setValue(code, this.#language_);
|
||||
codePage.diffEdits(code, this.#language_);
|
||||
});
|
||||
this.#addCodeChangeEventListener_();
|
||||
}
|
||||
@@ -549,7 +550,7 @@ class EditorMix extends EditorBase {
|
||||
return;
|
||||
}
|
||||
this.drag.full(Drag.Extend.NEGATIVE); // 完全显示代码编辑器
|
||||
codePage.setValue(code, this.#language_);
|
||||
codePage.diffEdits(code, this.#language_);
|
||||
endFunc('USE_CODE');
|
||||
return;
|
||||
}
|
||||
@@ -585,7 +586,7 @@ class EditorMix extends EditorBase {
|
||||
Blockly.hideChaff();
|
||||
if (!useIncompleteBlocks && codeDom && xmlDom.attr('shown') === 'code') {
|
||||
this.drag.full(Drag.Extend.NEGATIVE); // 完全显示代码编辑器
|
||||
codePage.setValue(code, this.#language_);
|
||||
codePage.diffEdits(code, this.#language_);
|
||||
endFunc();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
goog.loadJs('common', () => {
|
||||
|
||||
goog.require('Diff');
|
||||
goog.require('monaco');
|
||||
goog.require('Mixly.XML');
|
||||
goog.require('Mixly.Env');
|
||||
@@ -70,8 +71,8 @@ class EditorMonaco extends EditorBase {
|
||||
}
|
||||
});
|
||||
|
||||
editor.onDidChangeModelContent(() => {
|
||||
this.events.run('change');
|
||||
editor.onDidChangeModelContent((...args) => {
|
||||
this.events.run('change', ...args);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -121,6 +122,7 @@ class EditorMonaco extends EditorBase {
|
||||
#state_ = null;
|
||||
#tabSize_ = null;
|
||||
#language_ = null;
|
||||
#mode_ = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -132,6 +134,7 @@ class EditorMonaco extends EditorBase {
|
||||
init() {
|
||||
super.init();
|
||||
this.#editor_ = monaco.editor.createModel('');
|
||||
this.#editor_.setEOL(monaco.editor.EndOfLineSequence.LF);
|
||||
}
|
||||
|
||||
onMounted() {
|
||||
@@ -187,13 +190,21 @@ class EditorMonaco extends EditorBase {
|
||||
}
|
||||
|
||||
setTheme(mode) {
|
||||
if (this.#mode_ === mode) {
|
||||
return;
|
||||
}
|
||||
const editor = EditorMonaco.getEditor();
|
||||
editor.updateOptions({
|
||||
theme: `vs-${mode}`
|
||||
});
|
||||
this.#mode_ = mode;
|
||||
}
|
||||
|
||||
setValue(data, ext) {
|
||||
getTheme() {
|
||||
return this.#mode_;
|
||||
}
|
||||
|
||||
setValue(data) {
|
||||
if (this.getValue() === data) {
|
||||
return;
|
||||
}
|
||||
@@ -209,6 +220,72 @@ class EditorMonaco extends EditorBase {
|
||||
return this.#editor_.getValue();
|
||||
}
|
||||
|
||||
diffEdits(data) {
|
||||
const prevData = this.getValue();
|
||||
if (prevData === data) {
|
||||
return;
|
||||
}
|
||||
const edits = this.buildDiffEdits(prevData, data);
|
||||
if (!edits.length) {
|
||||
return;
|
||||
}
|
||||
this.#editor_.pushEditOperations([], edits, () => null);
|
||||
}
|
||||
|
||||
buildDiffEdits(prevData, nextData) {
|
||||
if (prevData === nextData) {
|
||||
return [];
|
||||
}
|
||||
const diffs = Diff.diffChars(prevData, nextData);
|
||||
const edits = [];
|
||||
const state = {
|
||||
index: 0,
|
||||
pendingStart: null,
|
||||
pendingText: ''
|
||||
};
|
||||
for (const d of diffs) {
|
||||
if (d.added) {
|
||||
if (state.pendingStart == null) {
|
||||
state.pendingStart = state.index;
|
||||
}
|
||||
state.pendingText += d.value;
|
||||
}
|
||||
else if (d.removed) {
|
||||
if (state.pendingStart == null) {
|
||||
state.pendingStart = state.index;
|
||||
}
|
||||
state.index += d.value.length;
|
||||
}
|
||||
else {
|
||||
this.flushPendingEdit(state, edits);
|
||||
state.index += d.value.length;
|
||||
}
|
||||
}
|
||||
this.flushPendingEdit(state, edits);
|
||||
return edits;
|
||||
}
|
||||
|
||||
flushPendingEdit(state, edits) {
|
||||
const { pendingStart, index, pendingText } = state;
|
||||
if (pendingStart == null) {
|
||||
return;
|
||||
}
|
||||
const startPos = this.#editor_.getPositionAt(pendingStart);
|
||||
const endPos = this.#editor_.getPositionAt(index);
|
||||
edits.push({
|
||||
range: new monaco.Range(
|
||||
startPos.lineNumber,
|
||||
startPos.column,
|
||||
endPos.lineNumber,
|
||||
endPos.column
|
||||
),
|
||||
text: pendingText,
|
||||
forceMoveMarkers: true
|
||||
});
|
||||
state.pendingStart = null;
|
||||
state.pendingText = '';
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.setValue('', true);
|
||||
}
|
||||
@@ -242,7 +319,7 @@ class EditorMonaco extends EditorBase {
|
||||
let selection = editor.getSelection();
|
||||
let selectedText = this.#editor_.getValueInRange(selection);
|
||||
if (selection) {
|
||||
editor.executeEdits("cut", [{ range: selection, text: '' }]);
|
||||
editor.executeEdits('cut', [{ range: selection, text: '' }]);
|
||||
navigator.clipboard.writeText(selectedText);
|
||||
}
|
||||
this.focus();
|
||||
@@ -281,6 +358,10 @@ class EditorMonaco extends EditorBase {
|
||||
monaco.editor.setModelLanguage(this.#editor_, language);
|
||||
}
|
||||
|
||||
getLanguage() {
|
||||
return this.#language_;
|
||||
}
|
||||
|
||||
setTabSize(tabSize) {
|
||||
if (this.#tabSize_ === tabSize) {
|
||||
return;
|
||||
|
||||
119
common/modules/mixly-modules/common/monaco-theme.js
Normal file
119
common/modules/mixly-modules/common/monaco-theme.js
Normal file
@@ -0,0 +1,119 @@
|
||||
goog.loadJs('common', () => {
|
||||
|
||||
goog.require('monaco');
|
||||
goog.require('Mixly.Registry');
|
||||
goog.provide('Mixly.MonacoTheme');
|
||||
|
||||
const { Registry } = Mixly;
|
||||
|
||||
|
||||
class MonacoTheme {
|
||||
static {
|
||||
this.cssClassNamePrefix = 'mts';
|
||||
this.themesRegistry = new Registry();
|
||||
this.supportThemes = [];
|
||||
// this.supportThemes = ['cpp', 'python'];
|
||||
|
||||
this.getClassNameOfTerm = function (type, theme, term) {
|
||||
return `${this.cssClassNamePrefix}-${type}-${theme}-${term}`;
|
||||
}
|
||||
|
||||
/*this.themesRegistry.register('vs-dark-cpp', new MonacoTheme(
|
||||
'vs-dark-cpp',
|
||||
'cpp',
|
||||
'dark',
|
||||
goog.readJsonSync('../common/templates/json/tree-sitter/themes/dark-cpp.json')
|
||||
));
|
||||
|
||||
this.themesRegistry.register('vs-light-cpp', new MonacoTheme(
|
||||
'vs-light-cpp',
|
||||
'cpp',
|
||||
'light',
|
||||
goog.readJsonSync('../common/templates/json/tree-sitter/themes/light-cpp.json')
|
||||
));
|
||||
|
||||
this.themesRegistry.register('vs-dark-python', new MonacoTheme(
|
||||
'vs-dark-python',
|
||||
'python',
|
||||
'dark',
|
||||
goog.readJsonSync('../common/templates/json/tree-sitter/themes/dark-python.json')
|
||||
));
|
||||
|
||||
this.themesRegistry.register('vs-light-python', new MonacoTheme(
|
||||
'vs-light-python',
|
||||
'python',
|
||||
'light',
|
||||
goog.readJsonSync('../common/templates/json/tree-sitter/themes/light-python.json')
|
||||
));*/
|
||||
}
|
||||
|
||||
#id_ = null;
|
||||
#type_ = null;
|
||||
#theme_ = null;
|
||||
#$tag_ = null;
|
||||
#config_ = null;
|
||||
|
||||
constructor(id, type, theme, config) {
|
||||
this.#id_ = id;
|
||||
this.#type_ = type;
|
||||
this.#theme_ = theme;
|
||||
this.load(config);
|
||||
}
|
||||
|
||||
load(config) {
|
||||
monaco.editor.defineTheme(this.#id_, config.base);
|
||||
this.#config_ = config;
|
||||
|
||||
if (!this.#$tag_) {
|
||||
let hasStyleNode = $('head').find(`style[style-id='${this.id_}']`).length;
|
||||
if (hasStyleNode) {
|
||||
return;
|
||||
}
|
||||
this.#$tag_ = $('<style></style>');
|
||||
this.#$tag_.attr('style-id', this.id);
|
||||
this.#$tag_.attr('type', 'text/css')
|
||||
$('head').append(this.#$tag_);
|
||||
}
|
||||
|
||||
this.#$tag_.html(this.generateCss());
|
||||
}
|
||||
|
||||
generateCss() {
|
||||
return Object.keys(this.#config_.monacoTreeSitter)
|
||||
.map(term =>
|
||||
`span.${MonacoTheme.getClassNameOfTerm(this.#type_, this.#theme_, term)}{${this.generateStyleOfTerm(term)}}`
|
||||
)
|
||||
.join('');
|
||||
}
|
||||
|
||||
generateStyleOfTerm(term) {
|
||||
const style = this.#config_.monacoTreeSitter[term];
|
||||
if (!style) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof style === 'string') {
|
||||
return `color:${style}`;
|
||||
}
|
||||
|
||||
return `color:${style.color};${style.extraCssStyles || ''}`;
|
||||
}
|
||||
|
||||
getColorOfTerm(term) {
|
||||
const style = this.#config_.monacoTreeSitter[term];
|
||||
if (!style) {
|
||||
return undefined;
|
||||
}
|
||||
return typeof style === 'object' ? style.color : style;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.#$tag_ && this.#$tag_.remove();
|
||||
this.#$tag_ = null;
|
||||
this.#config_ = null;
|
||||
}
|
||||
}
|
||||
|
||||
Mixly.MonacoTheme = MonacoTheme;
|
||||
|
||||
});
|
||||
127
common/modules/mixly-modules/common/monaco-tree-sitter.js
Normal file
127
common/modules/mixly-modules/common/monaco-tree-sitter.js
Normal file
@@ -0,0 +1,127 @@
|
||||
goog.loadJs('common', () => {
|
||||
|
||||
goog.require('_');
|
||||
goog.require('monaco');
|
||||
goog.require('Mixly.Registry');
|
||||
goog.require('Mixly.MonacoTheme');
|
||||
goog.provide('Mixly.MonacoTreeSitter');
|
||||
|
||||
const { Registry, MonacoTheme } = Mixly;
|
||||
|
||||
|
||||
class MonacoTreeSitter {
|
||||
static {
|
||||
this.workerPath = '../common/modules/mixly-modules/workers/common/tree-sitter/index.js';
|
||||
this.supportTreeSitters_ = new Registry();
|
||||
this.activeTreeSitters_ = new Registry();
|
||||
|
||||
/*this.supportTreeSitters_.register('python', {
|
||||
workerName: 'pythonTreeSitterService',
|
||||
wasm: 'tree-sitter-python.wasm'
|
||||
});
|
||||
|
||||
this.supportTreeSitters_.register('cpp', {
|
||||
workerName: 'cppTreeSitterService',
|
||||
wasm: 'tree-sitter-cpp.wasm'
|
||||
});*/
|
||||
|
||||
this.activateTreeSitter = async function (type) {
|
||||
if (!this.supportTreeSitters_.hasKey(type)) return null;
|
||||
|
||||
const info = this.supportTreeSitters_.getItem(type);
|
||||
if (this.activeTreeSitters_.hasKey(type)) {
|
||||
const ts = this.activeTreeSitters_.getItem(type);
|
||||
if (ts.loading) await ts.loading;
|
||||
return ts;
|
||||
}
|
||||
|
||||
const treeSitter = workerpool.pool(this.workerPath, {
|
||||
workerOpts: { name: info.workerName },
|
||||
workerType: 'web'
|
||||
});
|
||||
|
||||
const grammar = await goog.readJson(
|
||||
`../common/templates/json/tree-sitter/grammars/${type}.json`
|
||||
);
|
||||
|
||||
treeSitter.loading = treeSitter.exec(
|
||||
'init',
|
||||
[info.wasm, grammar]
|
||||
);
|
||||
|
||||
this.activeTreeSitters_.register(type, treeSitter);
|
||||
await treeSitter.loading;
|
||||
treeSitter.loading = null;
|
||||
return treeSitter;
|
||||
};
|
||||
|
||||
this.treeSitterPostion = function (pos) {
|
||||
return {
|
||||
row: pos.lineNumber - 1,
|
||||
column: pos.column - 1
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
constructor(editor, opts) {
|
||||
this.editor = editor;
|
||||
|
||||
this.seq = 0;
|
||||
this.decorations = [];
|
||||
|
||||
this.refresh = _.debounce(
|
||||
this.refresh.bind(this),
|
||||
opts?.debounceUpdate ?? 15
|
||||
);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.pool.terminate(true);
|
||||
this.decorations = [];
|
||||
}
|
||||
|
||||
async updateWorker(type, theme, text) {
|
||||
const treeSitter = await MonacoTreeSitter.activateTreeSitter(type);
|
||||
if (!treeSitter) {
|
||||
return;
|
||||
}
|
||||
const id = ++this.seq;
|
||||
const dto = await treeSitter.exec('update', [text]);
|
||||
if (id !== this.seq) return;
|
||||
this.applyDecorations(type, theme, dto);
|
||||
}
|
||||
|
||||
applyDecorations(type, theme, dto) {
|
||||
const decos = [];
|
||||
|
||||
for (const [term, ranges] of Object.entries(dto)) {
|
||||
const className = MonacoTheme.getClassNameOfTerm(type, theme, term);
|
||||
for (const r of ranges) {
|
||||
decos.push({
|
||||
range: new monaco.Range(
|
||||
r.startLineNumber,
|
||||
r.startColumn,
|
||||
r.endLineNumber,
|
||||
r.endColumn
|
||||
),
|
||||
options: {
|
||||
inlineClassName: className
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
this.decorations = this.editor.getEditor().deltaDecorations(this.decorations, decos);
|
||||
}
|
||||
|
||||
refresh(type, theme, newText) {
|
||||
this.updateWorker(type, theme, newText);
|
||||
}
|
||||
|
||||
setValue(type, theme, newText) {
|
||||
this.refresh(type, theme, newText);
|
||||
}
|
||||
}
|
||||
|
||||
Mixly.MonacoTreeSitter = MonacoTreeSitter;
|
||||
|
||||
});
|
||||
@@ -9,6 +9,7 @@ goog.require('Mixly.Debug');
|
||||
goog.require('Mixly.Config');
|
||||
goog.require('Mixly.StatusBar');
|
||||
goog.require('Mixly.SideBarsManager');
|
||||
goog.require('Mixly.RightSideBarsManager');
|
||||
goog.require('Mixly.HTMLTemplate');
|
||||
goog.require('Mixly.PageBase');
|
||||
goog.require('Mixly.Menu');
|
||||
|
||||
@@ -60,7 +60,7 @@ class StatusBar extends EditorAce {
|
||||
} else {
|
||||
editor.setOption('theme', 'ace/theme/xcode');
|
||||
}
|
||||
editor.getSession().setMode("ace/mode/python");
|
||||
editor.getSession().setMode('ace/mode/python');
|
||||
editor.setReadOnly(true);
|
||||
// editor.setScrollSpeed(0.3);
|
||||
editor.setShowPrintMargin(false);
|
||||
|
||||
@@ -286,8 +286,6 @@
|
||||
{
|
||||
"path": "/common/editor-code.js",
|
||||
"require": [
|
||||
"ace",
|
||||
"ace.ExtLanguageTools",
|
||||
"Mixly.Config",
|
||||
"Mixly.XML",
|
||||
"Mixly.Env",
|
||||
@@ -297,6 +295,8 @@
|
||||
"Mixly.ContextMenu",
|
||||
"Mixly.IdGenerator",
|
||||
"Mixly.CodeFormatter",
|
||||
"Mixly.MonacoTheme",
|
||||
"Mixly.MonacoTreeSitter",
|
||||
"Mixly.EditorMonaco"
|
||||
],
|
||||
"provide": [
|
||||
@@ -351,6 +351,7 @@
|
||||
{
|
||||
"path": "/common/editor-monaco.js",
|
||||
"require": [
|
||||
"Diff",
|
||||
"monaco",
|
||||
"Mixly.XML",
|
||||
"Mixly.Env",
|
||||
@@ -778,6 +779,28 @@
|
||||
"Mixly.MJson"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/common/monaco-theme.js",
|
||||
"require": [
|
||||
"monaco",
|
||||
"Mixly.Registry"
|
||||
],
|
||||
"provide": [
|
||||
"Mixly.MonacoTheme"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/common/monaco-tree-sitter.js",
|
||||
"require": [
|
||||
"_",
|
||||
"monaco",
|
||||
"Mixly.Registry",
|
||||
"Mixly.MonacoTheme"
|
||||
],
|
||||
"provide": [
|
||||
"Mixly.MonacoTreeSitter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/common/msg.js",
|
||||
"require": [
|
||||
@@ -1115,6 +1138,7 @@
|
||||
"Mixly.Config",
|
||||
"Mixly.StatusBar",
|
||||
"Mixly.SideBarsManager",
|
||||
"Mixly.RightSideBarsManager",
|
||||
"Mixly.HTMLTemplate",
|
||||
"Mixly.PageBase",
|
||||
"Mixly.Menu",
|
||||
|
||||
141
common/modules/mixly-modules/workers/common/tree-sitter/index.js
Normal file
141
common/modules/mixly-modules/workers/common/tree-sitter/index.js
Normal file
@@ -0,0 +1,141 @@
|
||||
importScripts('./tree-sitter.js');
|
||||
importScripts('../../../../web-modules/workerpool.min.js');
|
||||
|
||||
|
||||
const terms = [
|
||||
'type',
|
||||
'scope',
|
||||
'function',
|
||||
'variable',
|
||||
'number',
|
||||
'string',
|
||||
'comment',
|
||||
'constant',
|
||||
'directive',
|
||||
'control',
|
||||
'operator',
|
||||
'modifier',
|
||||
'punctuation'
|
||||
];
|
||||
|
||||
let parser;
|
||||
let tree = null;
|
||||
let language = null;
|
||||
|
||||
|
||||
class Language {
|
||||
constructor(grammarJson) {
|
||||
this.simpleTerms = {};
|
||||
this.complexTerms = [];
|
||||
this.complexScopes = {};
|
||||
for (const t in grammarJson.simpleTerms)
|
||||
this.simpleTerms[t] = grammarJson.simpleTerms[t];
|
||||
for (const t in grammarJson.complexTerms)
|
||||
this.complexTerms[t] = grammarJson.complexTerms[t];
|
||||
for (const t in grammarJson.complexScopes)
|
||||
this.complexScopes[t] = grammarJson.complexScopes[t];
|
||||
this.complexDepth = 0;
|
||||
this.complexOrder = false;
|
||||
for (const s in this.complexScopes) {
|
||||
const depth = s.split('>').length;
|
||||
if (depth > this.complexDepth)
|
||||
this.complexDepth = depth;
|
||||
if (s.indexOf('[') >= 0)
|
||||
this.complexOrder = true;
|
||||
}
|
||||
this.complexDepth--;
|
||||
}
|
||||
}
|
||||
|
||||
async function init(languageWasmPath, grammarJson) {
|
||||
await TreeSitter.init();
|
||||
|
||||
parser = new TreeSitter();
|
||||
const lang = await TreeSitter.Language.load(languageWasmPath);
|
||||
console.log(lang.version)
|
||||
parser.setLanguage(lang);
|
||||
language = new Language(grammarJson);
|
||||
|
||||
tree = null;
|
||||
}
|
||||
|
||||
function buildDecorations(tree) {
|
||||
const result = [];
|
||||
const stack = [];
|
||||
let node = tree.rootNode.firstChild;
|
||||
while (stack.length > 0 || node) {
|
||||
if (node) {
|
||||
stack.push(node);
|
||||
node = node.firstChild;
|
||||
}
|
||||
else {
|
||||
node = stack.pop();
|
||||
let type = node.type;
|
||||
if (!node.isNamed())
|
||||
type = '"' + type + '"';
|
||||
let term = null;
|
||||
if (!language.complexTerms.includes(type)) {
|
||||
term = language.simpleTerms[type];
|
||||
}
|
||||
else {
|
||||
let desc = type;
|
||||
let scopes = [desc];
|
||||
let parent = node.parent;
|
||||
for (let i = 0; i < language.complexDepth && parent; i++) {
|
||||
let parentType = parent.type;
|
||||
if (!parent.isNamed())
|
||||
parentType = '"' + parentType + '"';
|
||||
desc = parentType + ' > ' + desc;
|
||||
scopes.push(desc);
|
||||
parent = parent.parent;
|
||||
}
|
||||
if (language.complexOrder) {
|
||||
let index = 0;
|
||||
let sibling = node.previousSibling;
|
||||
while (sibling) {
|
||||
if (sibling.type === node.type)
|
||||
index++;
|
||||
sibling = sibling.previousSibling;
|
||||
}
|
||||
let rindex = -1;
|
||||
sibling = node.nextSibling;
|
||||
while (sibling) {
|
||||
if (sibling.type === node.type)
|
||||
rindex--;
|
||||
sibling = sibling.nextSibling;
|
||||
}
|
||||
const orderScopes = [];
|
||||
for (let i = 0; i < scopes.length; i++)
|
||||
orderScopes.push(scopes[i], scopes[i] + '[' + index + ']', scopes[i] + '[' + rindex + ']');
|
||||
scopes = orderScopes;
|
||||
}
|
||||
for (const d of scopes)
|
||||
if (d in language.complexScopes)
|
||||
term = language.complexScopes[d];
|
||||
}
|
||||
if (terms.includes(term)) {
|
||||
if (!result[term]) {
|
||||
result[term] = [];
|
||||
}
|
||||
result[term].push({
|
||||
startLineNumber: node.startPosition.row + 1,
|
||||
startColumn: node.startPosition.column + 1,
|
||||
endLineNumber: node.endPosition.row + 1,
|
||||
endColumn: node.endPosition.column + 1
|
||||
});
|
||||
}
|
||||
node = node.nextSibling;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function update(text) {
|
||||
const tree = parser.parse(text);
|
||||
return buildDecorations(tree);
|
||||
}
|
||||
|
||||
workerpool.worker({
|
||||
init,
|
||||
update
|
||||
});
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
1
common/modules/web-modules/diff.min.js
vendored
Normal file
1
common/modules/web-modules/diff.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user