396 lines
11 KiB
JavaScript
396 lines
11 KiB
JavaScript
goog.loadJs('common', () => {
|
|
|
|
goog.require('Diff');
|
|
goog.require('monaco');
|
|
goog.require('Mixly.XML');
|
|
goog.require('Mixly.Env');
|
|
goog.require('Mixly.Msg');
|
|
goog.require('Mixly.Debug');
|
|
goog.require('Mixly.Events');
|
|
goog.require('Mixly.HTMLTemplate');
|
|
goog.require('Mixly.EditorBase');
|
|
goog.provide('Mixly.EditorMonaco');
|
|
|
|
const {
|
|
XML,
|
|
Env,
|
|
Msg,
|
|
Debug,
|
|
Events,
|
|
HTMLTemplate,
|
|
EditorBase
|
|
} = Mixly;
|
|
|
|
|
|
class EditorMonaco extends EditorBase {
|
|
static {
|
|
HTMLTemplate.add(
|
|
'html/editor/editor-code.html',
|
|
new HTMLTemplate(goog.readFileSync(path.join(Env.templatePath, 'html/editor/editor-code.html')))
|
|
);
|
|
|
|
this.$monaco = $('<div class="page-item"></div>');
|
|
this.editor = null;
|
|
this.events = new Events(['change']);
|
|
|
|
this.addEventsListener = () => {
|
|
const { editor } = this;
|
|
$('#mixly-footer-cursor').hide();
|
|
|
|
editor.onDidBlurEditorText(() => {
|
|
$('#mixly-footer-cursor').hide();
|
|
});
|
|
|
|
editor.onDidFocusEditorText(() => {
|
|
const position = editor.getPosition();
|
|
$('#mixly-footer-row').html(position.lineNumber);
|
|
$('#mixly-footer-column').html(position.column);
|
|
const selection = editor.getSelection();
|
|
if (selection.isEmpty()) {
|
|
$('#mixly-footer-selected').parent().hide();
|
|
} else {
|
|
const text = editor.getModel().getValueInRange(selection);
|
|
$('#mixly-footer-selected').parent().css('display', 'inline-flex');
|
|
$('#mixly-footer-selected').html(text.length);
|
|
}
|
|
$('#mixly-footer-cursor').show();
|
|
});
|
|
|
|
editor.onDidChangeCursorPosition((e) => {
|
|
$('#mixly-footer-row').html(e.position.lineNumber);
|
|
$('#mixly-footer-column').html(e.position.column);
|
|
});
|
|
|
|
editor.onDidChangeCursorSelection((e) => {
|
|
if (e.selection.isEmpty()) {
|
|
$('#mixly-footer-selected').parent().hide();
|
|
} else {
|
|
const text = editor.getModel().getValueInRange(e.selection);
|
|
$('#mixly-footer-selected').parent().css('display', 'inline-flex');
|
|
$('#mixly-footer-selected').html(text.length);
|
|
}
|
|
});
|
|
|
|
editor.onDidChangeModelContent((...args) => {
|
|
this.events.run('change', ...args);
|
|
});
|
|
}
|
|
|
|
this.getEditor = () => {
|
|
return this.editor;
|
|
}
|
|
|
|
this.getContent = () => {
|
|
return this.$monaco;
|
|
}
|
|
|
|
this.initMonaco = () => {
|
|
this.editor = monaco.editor.create(this.$monaco[0], {
|
|
theme: 'vs-dark',
|
|
disableLayerHinting: true, // 等宽优化
|
|
emptySelectionClipboard: false, // 空选择剪切板
|
|
selectionClipboard: false, // 选择剪切板
|
|
codeLens: true, // 代码镜头
|
|
scrollBeyondLastLine: true, // 滚动完最后一行后再滚动一屏幕
|
|
colorDecorators: true, // 颜色装饰器
|
|
accessibilitySupport: 'off', // 辅助功能支持 "auto" | "off" | "on"
|
|
lineNumbers: 'on', // 行号 取值: "on" | "off" | "relative" | "interval" | function
|
|
lineNumbersMinChars: 5, // 行号最小字符 number
|
|
enableSplitViewResizing: false,
|
|
contextmenu: false,
|
|
fontSize: 17,
|
|
automaticLayout: false,
|
|
wordWrap: 'wordWrapColumn',
|
|
wordWrapColumn: 300,
|
|
scrollbar: {
|
|
vertical: 'visible',
|
|
horizontal: 'visible',
|
|
verticalScrollbarSize: 10,
|
|
horizontalScrollbarSize: 10
|
|
}
|
|
});
|
|
this.addEventsListener();
|
|
}
|
|
|
|
this.initMonaco();
|
|
}
|
|
|
|
#readOnly_ = false;
|
|
#changeListener_ = null;
|
|
#enableChangeEvent_ = true;
|
|
#editor_ = null;
|
|
#state_ = null;
|
|
#tabSize_ = null;
|
|
#language_ = null;
|
|
#mode_ = null;
|
|
|
|
constructor() {
|
|
super();
|
|
const editorHTMLTemplate = HTMLTemplate.get('html/editor/editor-code.html');
|
|
this.setContent($(editorHTMLTemplate.render()));
|
|
this.addEventsType(['change']);
|
|
}
|
|
|
|
init() {
|
|
super.init();
|
|
this.#editor_ = monaco.editor.createModel('');
|
|
this.#editor_.setEOL(monaco.editor.EndOfLineSequence.LF);
|
|
}
|
|
|
|
onMounted() {
|
|
super.onMounted();
|
|
const editor = EditorMonaco.getEditor();
|
|
editor.setModel(this.#editor_);
|
|
if (this.#state_) {
|
|
editor.restoreViewState(this.#state_);
|
|
}
|
|
this.setReadOnly(this.#readOnly_);
|
|
this.getContent().append(EditorMonaco.getContent());
|
|
if (!this.#readOnly_) {
|
|
this.focus();
|
|
}
|
|
this.#addChangeEventListener_();
|
|
}
|
|
|
|
disableChangeEvent() {
|
|
this.#enableChangeEvent_ = false;
|
|
}
|
|
|
|
enableChangeEvent() {
|
|
this.#enableChangeEvent_ = true;
|
|
}
|
|
|
|
#addChangeEventListener_() {
|
|
const $content = EditorMonaco.getContent();
|
|
this.#changeListener_ = EditorMonaco.events.bind('change', () => {
|
|
this.#enableChangeEvent_ && this.runEvent('change');
|
|
});
|
|
}
|
|
|
|
#removeChangeEventListener_() {
|
|
this.#changeListener_ && EditorMonaco.events.unbind(this.#changeListener_);
|
|
this.offEvent('change');
|
|
}
|
|
|
|
onUnmounted() {
|
|
super.onUnmounted();
|
|
const editor = EditorMonaco.getEditor();
|
|
const $content = EditorMonaco.getContent();
|
|
this.#state_ = editor.saveViewState();
|
|
$content.detach();
|
|
this.getContent().empty();
|
|
this.#removeChangeEventListener_();
|
|
}
|
|
|
|
dispose() {
|
|
this.#removeChangeEventListener_();
|
|
this.#editor_.dispose();
|
|
super.dispose();
|
|
this.#editor_ = null;
|
|
}
|
|
|
|
setTheme(mode) {
|
|
if (this.#mode_ === mode) {
|
|
return;
|
|
}
|
|
const editor = EditorMonaco.getEditor();
|
|
editor.updateOptions({
|
|
theme: `vs-${mode}`
|
|
});
|
|
this.#mode_ = mode;
|
|
}
|
|
|
|
getTheme() {
|
|
return this.#mode_;
|
|
}
|
|
|
|
setValue(data) {
|
|
if (this.getValue() === data) {
|
|
return;
|
|
}
|
|
this.#editor_.setValue(data);
|
|
}
|
|
|
|
addValue(data) {
|
|
const prevData = this.getValue();
|
|
this.setValue(prevData + data);
|
|
}
|
|
|
|
getValue() {
|
|
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);
|
|
}
|
|
|
|
scrollToBottom() {
|
|
const editor = EditorMonaco.getEditor();
|
|
editor.setScrollTop(editor.getScrollHeight());
|
|
}
|
|
|
|
scrollToTop() {
|
|
const editor = EditorMonaco.getEditor();
|
|
editor.setScrollTop(0);
|
|
}
|
|
|
|
resize() {
|
|
super.resize();
|
|
const editor = EditorMonaco.getEditor();
|
|
editor.layout(null, true);
|
|
}
|
|
|
|
undo() {
|
|
this.#editor_.undo();
|
|
}
|
|
|
|
redo() {
|
|
this.#editor_.redo();
|
|
}
|
|
|
|
cut() {
|
|
const editor = EditorMonaco.getEditor();
|
|
let selection = editor.getSelection();
|
|
let selectedText = this.#editor_.getValueInRange(selection);
|
|
if (selection) {
|
|
editor.executeEdits('cut', [{ range: selection, text: '' }]);
|
|
navigator.clipboard.writeText(selectedText);
|
|
}
|
|
this.focus();
|
|
}
|
|
|
|
copy() {
|
|
const editor = EditorMonaco.getEditor();
|
|
editor.trigger('source', 'editor.action.clipboardCopyWithSyntaxHighlightingAction');
|
|
}
|
|
|
|
paste() {
|
|
const editor = EditorMonaco.getEditor();
|
|
navigator.clipboard.readText()
|
|
.then((clipboardText) => {
|
|
editor.trigger('source', 'type', { text: clipboardText });
|
|
this.focus();
|
|
})
|
|
.catch(Debug.error);
|
|
}
|
|
|
|
getEditor() {
|
|
return this.#editor_;
|
|
}
|
|
|
|
setReadOnly(readOnly) {
|
|
const editor = EditorMonaco.getEditor();
|
|
editor.updateOptions({ readOnly });
|
|
this.#readOnly_ = readOnly;
|
|
}
|
|
|
|
setLanguage(language) {
|
|
if (this.#language_ === language) {
|
|
return;
|
|
}
|
|
this.#language_ = language;
|
|
monaco.editor.setModelLanguage(this.#editor_, language);
|
|
}
|
|
|
|
getLanguage() {
|
|
return this.#language_;
|
|
}
|
|
|
|
setTabSize(tabSize) {
|
|
if (this.#tabSize_ === tabSize) {
|
|
return;
|
|
}
|
|
this.#tabSize_ = tabSize;
|
|
this.#editor_.updateOptions({ tabSize });
|
|
}
|
|
|
|
setFontSize(fontSize) {
|
|
const editor = EditorMonaco.getEditor();
|
|
editor.updateOptions({ fontSize });
|
|
}
|
|
|
|
focus() {
|
|
const editor = EditorMonaco.getEditor();
|
|
editor.focus();
|
|
}
|
|
|
|
commentLine() {
|
|
const editor = EditorMonaco.getEditor();
|
|
EditorMonaco.getEditor().trigger('source', 'editor.action.commentLine');
|
|
}
|
|
|
|
blockComment() {
|
|
const editor = EditorMonaco.getEditor();
|
|
editor.trigger('source', 'editor.action.blockComment');
|
|
}
|
|
}
|
|
|
|
Mixly.EditorMonaco = EditorMonaco;
|
|
|
|
}); |