2020-04-22 19:07:51 +05:30
|
|
|
import { debounce } from 'lodash';
|
2020-06-23 00:09:42 +05:30
|
|
|
import { editor as monacoEditor, KeyCode, KeyMod, Range } from 'monaco-editor';
|
2018-05-09 12:01:36 +05:30
|
|
|
import DecorationsController from './decorations/controller';
|
|
|
|
import DirtyDiffController from './diff/controller';
|
|
|
|
import Disposable from './common/disposable';
|
|
|
|
import ModelManager from './common/model_manager';
|
2020-06-23 00:09:42 +05:30
|
|
|
import { editorOptions, defaultEditorOptions, defaultDiffEditorOptions } from './editor_options';
|
2020-03-13 15:44:24 +05:30
|
|
|
import { themes } from './themes';
|
2020-05-24 23:13:21 +05:30
|
|
|
import languages from './languages';
|
2020-07-28 23:09:34 +05:30
|
|
|
import schemas from './schemas';
|
2018-10-15 14:42:47 +05:30
|
|
|
import keymap from './keymap.json';
|
2020-03-13 15:44:24 +05:30
|
|
|
import { clearDomElement } from '~/editor/utils';
|
2020-07-28 23:09:34 +05:30
|
|
|
import { registerLanguages, registerSchemas } from '../utils';
|
2018-05-09 12:01:36 +05:30
|
|
|
|
2020-03-13 15:44:24 +05:30
|
|
|
function setupThemes() {
|
|
|
|
themes.forEach(theme => {
|
|
|
|
monacoEditor.defineTheme(theme.name, theme.data);
|
|
|
|
});
|
2018-11-08 19:23:39 +05:30
|
|
|
}
|
|
|
|
|
2018-05-09 12:01:36 +05:30
|
|
|
export default class Editor {
|
2020-10-24 23:57:45 +05:30
|
|
|
static create(...args) {
|
2018-11-08 19:23:39 +05:30
|
|
|
if (!this.editorInstance) {
|
2020-10-24 23:57:45 +05:30
|
|
|
this.editorInstance = new Editor(...args);
|
2018-11-08 19:23:39 +05:30
|
|
|
}
|
2018-05-09 12:01:36 +05:30
|
|
|
return this.editorInstance;
|
|
|
|
}
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
constructor(store, options = {}) {
|
2018-05-09 12:01:36 +05:30
|
|
|
this.currentModel = null;
|
|
|
|
this.instance = null;
|
|
|
|
this.dirtyDiffController = null;
|
|
|
|
this.disposable = new Disposable();
|
2018-11-08 19:23:39 +05:30
|
|
|
this.modelManager = new ModelManager();
|
2018-05-09 12:01:36 +05:30
|
|
|
this.decorationsController = new DecorationsController(this);
|
2020-03-13 15:44:24 +05:30
|
|
|
this.options = {
|
|
|
|
...defaultEditorOptions,
|
|
|
|
...options,
|
|
|
|
};
|
2020-06-23 00:09:42 +05:30
|
|
|
this.diffOptions = {
|
|
|
|
...defaultDiffEditorOptions,
|
|
|
|
...options,
|
|
|
|
};
|
2020-10-24 23:57:45 +05:30
|
|
|
this.store = store;
|
2018-05-09 12:01:36 +05:30
|
|
|
|
2020-03-13 15:44:24 +05:30
|
|
|
setupThemes();
|
2020-05-24 23:13:21 +05:30
|
|
|
registerLanguages(...languages);
|
2018-05-09 12:01:36 +05:30
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
if (gon.features?.schemaLinting) {
|
|
|
|
registerSchemas(...schemas);
|
|
|
|
}
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
this.debouncedUpdate = debounce(() => {
|
2018-05-09 12:01:36 +05:30
|
|
|
this.updateDimensions();
|
|
|
|
}, 200);
|
|
|
|
}
|
|
|
|
|
|
|
|
createInstance(domElement) {
|
|
|
|
if (!this.instance) {
|
|
|
|
clearDomElement(domElement);
|
|
|
|
|
|
|
|
this.disposable.add(
|
2018-11-08 19:23:39 +05:30
|
|
|
(this.instance = monacoEditor.create(domElement, {
|
2020-03-13 15:44:24 +05:30
|
|
|
...this.options,
|
2018-05-09 12:01:36 +05:30
|
|
|
})),
|
|
|
|
(this.dirtyDiffController = new DirtyDiffController(
|
|
|
|
this.modelManager,
|
|
|
|
this.decorationsController,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
|
2018-10-15 14:42:47 +05:30
|
|
|
this.addCommands();
|
|
|
|
|
2018-05-09 12:01:36 +05:30
|
|
|
window.addEventListener('resize', this.debouncedUpdate, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
createDiffInstance(domElement) {
|
2018-05-09 12:01:36 +05:30
|
|
|
if (!this.instance) {
|
|
|
|
clearDomElement(domElement);
|
|
|
|
|
|
|
|
this.disposable.add(
|
2018-11-08 19:23:39 +05:30
|
|
|
(this.instance = monacoEditor.createDiffEditor(domElement, {
|
2020-06-23 00:09:42 +05:30
|
|
|
...this.diffOptions,
|
2018-05-09 12:01:36 +05:30
|
|
|
renderSideBySide: Editor.renderSideBySide(domElement),
|
|
|
|
})),
|
|
|
|
);
|
|
|
|
|
2018-10-15 14:42:47 +05:30
|
|
|
this.addCommands();
|
|
|
|
|
2018-05-09 12:01:36 +05:30
|
|
|
window.addEventListener('resize', this.debouncedUpdate, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-15 14:42:47 +05:30
|
|
|
createModel(file, head = null) {
|
|
|
|
return this.modelManager.addModel(file, head);
|
2018-05-09 12:01:36 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
attachModel(model) {
|
|
|
|
if (this.isDiffEditorType) {
|
|
|
|
this.instance.setModel({
|
|
|
|
original: model.getOriginalModel(),
|
|
|
|
modified: model.getModel(),
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.instance.setModel(model.getModel());
|
|
|
|
if (this.dirtyDiffController) this.dirtyDiffController.attachModel(model);
|
|
|
|
|
|
|
|
this.currentModel = model;
|
|
|
|
|
|
|
|
this.instance.updateOptions(
|
|
|
|
editorOptions.reduce((acc, obj) => {
|
|
|
|
Object.keys(obj).forEach(key => {
|
|
|
|
Object.assign(acc, {
|
|
|
|
[key]: obj[key](model),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return acc;
|
|
|
|
}, {}),
|
|
|
|
);
|
|
|
|
|
|
|
|
if (this.dirtyDiffController) this.dirtyDiffController.reDecorate(model);
|
|
|
|
}
|
|
|
|
|
|
|
|
attachMergeRequestModel(model) {
|
|
|
|
this.instance.setModel({
|
|
|
|
original: model.getBaseModel(),
|
|
|
|
modified: model.getModel(),
|
|
|
|
});
|
|
|
|
|
2018-11-08 19:23:39 +05:30
|
|
|
monacoEditor.createDiffNavigator(this.instance, {
|
2018-05-09 12:01:36 +05:30
|
|
|
alwaysRevealFirst: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
clearEditor() {
|
|
|
|
if (this.instance) {
|
|
|
|
this.instance.setModel(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dispose() {
|
|
|
|
window.removeEventListener('resize', this.debouncedUpdate);
|
|
|
|
|
|
|
|
// catch any potential errors with disposing the error
|
|
|
|
// this is mainly for tests caused by elements not existing
|
|
|
|
try {
|
|
|
|
this.disposable.dispose();
|
|
|
|
|
|
|
|
this.instance = null;
|
|
|
|
} catch (e) {
|
|
|
|
this.instance = null;
|
|
|
|
|
|
|
|
if (process.env.NODE_ENV !== 'test') {
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
console.error(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateDimensions() {
|
|
|
|
this.instance.layout();
|
|
|
|
this.updateDiffView();
|
|
|
|
}
|
|
|
|
|
|
|
|
setPosition({ lineNumber, column }) {
|
|
|
|
this.instance.revealPositionInCenter({
|
|
|
|
lineNumber,
|
|
|
|
column,
|
|
|
|
});
|
|
|
|
this.instance.setPosition({
|
|
|
|
lineNumber,
|
|
|
|
column,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
onPositionChange(cb) {
|
|
|
|
if (!this.instance.onDidChangeCursorPosition) return;
|
|
|
|
|
|
|
|
this.disposable.add(this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)));
|
|
|
|
}
|
|
|
|
|
|
|
|
updateDiffView() {
|
|
|
|
if (!this.isDiffEditorType) return;
|
|
|
|
|
|
|
|
this.instance.updateOptions({
|
|
|
|
renderSideBySide: Editor.renderSideBySide(this.instance.getDomNode()),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
replaceSelectedText(text) {
|
|
|
|
let selection = this.instance.getSelection();
|
|
|
|
const range = new Range(
|
|
|
|
selection.startLineNumber,
|
|
|
|
selection.startColumn,
|
|
|
|
selection.endLineNumber,
|
|
|
|
selection.endColumn,
|
|
|
|
);
|
|
|
|
|
|
|
|
this.instance.executeEdits('', [{ range, text }]);
|
|
|
|
|
|
|
|
selection = this.instance.getSelection();
|
|
|
|
this.instance.setPosition({ lineNumber: selection.endLineNumber, column: selection.endColumn });
|
|
|
|
}
|
|
|
|
|
2018-05-09 12:01:36 +05:30
|
|
|
get isDiffEditorType() {
|
|
|
|
return this.instance.getEditorType() === 'vs.editor.IDiffEditor';
|
|
|
|
}
|
|
|
|
|
|
|
|
static renderSideBySide(domElement) {
|
|
|
|
return domElement.offsetWidth >= 700;
|
|
|
|
}
|
2018-10-15 14:42:47 +05:30
|
|
|
|
|
|
|
addCommands() {
|
2020-10-24 23:57:45 +05:30
|
|
|
const { store } = this;
|
2018-10-15 14:42:47 +05:30
|
|
|
const getKeyCode = key => {
|
|
|
|
const monacoKeyMod = key.indexOf('KEY_') === 0;
|
|
|
|
|
2018-11-08 19:23:39 +05:30
|
|
|
return monacoKeyMod ? KeyCode[key] : KeyMod[key];
|
2018-10-15 14:42:47 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
keymap.forEach(command => {
|
|
|
|
const keybindings = command.bindings.map(binding => {
|
|
|
|
const keys = binding.split('+');
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-bitwise
|
|
|
|
return keys.length > 1 ? getKeyCode(keys[0]) | getKeyCode(keys[1]) : getKeyCode(keys[0]);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.instance.addAction({
|
|
|
|
id: command.id,
|
|
|
|
label: command.label,
|
|
|
|
keybindings,
|
|
|
|
run() {
|
|
|
|
store.dispatch(command.action.name, command.action.params);
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2018-05-09 12:01:36 +05:30
|
|
|
}
|