想要把vscode搬到浏览器端,还是得花点功夫。
monaco editor就是vscode的核心部件:https://microsoft.github.io/monaco-editor/index.html
首先share的是monaco的官方API doc:https://microsoft.github.io/monaco-editor/api/index.html
虽然感觉这个doc在深度定制monaco时并没有什么用,不过倒是可以快速查阅一些type的字段;深度定制monaco的时候,建议还是边看vscode的代码,边尝试使用Chrome的console打印出创建出来的editor api对象。
当然,还有一些既成solution:
https://github.com/codercom/code-server (在后台启动vscode然后用一个proxy链入browser,开始load的大小大概50mb,如果server是跨国慎用,慢的要死…)
https://github.com/theia-ide/theia (深度fork后的vscode)
本文会不定期更新。
require(
[
'vs/editor/editor.main'
], function () {
var editor_api = api = monaco.editor.create(dom_container, editor_options, service_overrides);
}
);
销毁editor: editor_api.dispose()
改变大小: /* after update container size */ editor_api.layout()
定义新语言:
monaco.languages.register({ id: 'new_lang', extensions: [ '.lang' ] });
// 语言解析语法请参考:https://microsoft.github.io/monaco-editor/monarch.html
monaco.languages.setMonarchTokensProvider('new_lang', {
tokenizer: {
root: [ /0-9+/, 'lang-number' ]
}
});
monaco.editor.defineTheme('new_lang', {
base: 'vs',
inherit: false,
rules: [
{ token: 'lang-number', foreground: '008888' }
]
});
editor_api.setValue(text)
var model = editor_api.getModel();
// 默认设置javascript作为语言
monaco.editor.setModelLanguage(model, lang_id || 'javascript');
editor_api.setTheme(theme)
monaco.editor.setTheme(theme_id || 'vs-dark');
显示指定行列: editor_api.revealPositionInCenter({ lineNumber: 1, column: 1 })
选中指定行列: editor_api.setSelection({ startLineNumber:1, startColumn:1, endLineNumber: 1, endColumn: 1 })
更新文本框内容:
editor_api.setValue(text)
var model = editor_api.getModel();
var editOperation = require('vs/editor/common/core/editOperation').EditOperation;
// ref: https://github.com/microsoft/vscode/blob/master/src/vs/editor/common/core/editOperation.ts
// 操作类型 insert, delete, replace, replaceMove
// 操作生效后不可撤销(操作不记入历史)
model.applyEdits([editOperation.insert({lineNumber:1, column: 1}, 'hello')])
FlameTextModelService.prototype = {
createModelReference: function (uri) {
return this.getModel(uri);
},
registerTextModelContentProvider: function () {
return { dispose: function () {} };
},
hasTextModelContentProvider: function (schema) {
return true;
},
_buildReference: function (model) {
var lifecycle = require('vs/base/common/lifecycle');
var ref = new lifecycle.ImmortalReference({ textEditorModel: model });
return {
object: ref.object,
dispose: function () { ref.dispose(); }
};
},
getModel: function (uri) {
var _this = this;
return new Promise(function (r) {
var model = monaco.editor.getModel(uri);
if (!model) {
// 从ajax读取文件
// ajax.get('http://host/to_file_name').then((contents) => {
// r(monaco.editor.createModel(contents, 'javascript', uri);)
// });
// return;
}
r(_this._buildReference(model));
});
}
};
var editor_api = monaco.editor.create(dom_container, editor_options, {
textModelService: new FlameTextModelService()
});
function patch_minimap_touch(editor_api) {
// 找到 minimap 的 ViewPart
var minimap = editor_api._modelData.view.viewParts.filter((x) => x._slider)[0];
if (!minimap) return;
var vscode_dom = require('vs/base/browser/dom');
minimap._sliderTouchStartListener = vscode_dom.addStandardDisposableListener(
minimap._slider.domNode, 'touchstart', function(evt) {
evt.preventDefault();
var touch = evt.touches[0];
if (!touch) return;
if (!minimap._lastRenderData) return;
minimap._slider.toggleClassName('active', true);
var initialMousePosition = touch.clientY;
var initialSliderState = minimap._lastRenderData.renderedLayout;
var monitor_move = vscode_dom.addStandardDisposableListener(document.body, 'touchmove', function (e) {
var touch = e.touches[0];
if (!touch) return;
var mouseDelta = touch.clientY - initialMousePosition;
minimap._context.viewLayout.setScrollPositionNow({
scrollTop: initialSliderState.getDesiredScrollTopFromDelta(mouseDelta)
});
});
var monitor_stop = vscode_dom.addStandardDisposableListener(document.body, 'touchend', function (e) {
minimap._slider.toggleClassName('active', false);
monitor_move.dispose();
monitor_stop.dispose();
});
}
);
}
if (!window.monaco_patched) {
window.monaco_patched = true;
var hoverProvider = {
provideHover: function (model, position, token) {
// 可以使用 ajax 去取数据,然后 return new Promise(function (resolve, reject) { ... })
return Promise.resolve({
contents: [ { value: 'hello world' } ],
range: { startLineNumber:1, startColumn:1, endLineNumber: 1, endColumn: 1 }
});
}
};
var definitionProvider = {
provideDefinition: function (model, position, token) {
// 可以使用 ajax 去取数据,然后 return new Promise(function (resolve, reject) { ... })
return Promise.resolve([{
uri: monaco.Uri.parse('http://host/to_file_name'),
range: { startLineNumber:1, startColumn:1, endLineNumber: 1, endColumn: 1 }
}]);
}
};
lang.forEach(function (lang) {
monaco.languages.onLanguage(lang, function () {
monaco.languages.registerHoverProvider(lang, hoverProvider);
monaco.languages.registerDefinitionProvider(lang, definitionProvider);
});
}); // foreach; register worker to language
}
_patchReferencesController() {
let rc = this.editor.getEditorApi().getContribution('editor.contrib.referencesController');
this._backup.rc_toggleWidget = rc.toggleWidget;
rc.toggleWidget = (range, modelPromise, options) => {
let _widget = this._backup.rc_widget;
this._backup.rc_toggleWidget.call(rc, range, modelPromise, options);
if (_widget !== rc._widget) {
_widget = rc._widget;
this._backup.rc_widget = _widget;
let lib = window['require']('vs/base/common/actions')
let bar = _widget._actionbarWidget;
bar.push(new lib.Action('flame.openInNewTab', '⬒', 'class', true, () => {
if (!_widget) return;
if (!_widget._previewModelReference) return;
if (!_widget._previewModelReference.object) return;
if (!_widget._previewModelReference.object.textEditorModel) return;
let model = _widget._previewModelReference.object.textEditorModel;
let uri = model.uri;
let loc = '';
let range = _widget && _widget._revealedReference && _widget._revealedReference._range;
if (range) {
if (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) {
loc = `#L${range.startLineNumber}.${range.startColumn}`;
} else {
loc = `#L${range.startLineNumber}.${range.startColumn}-${range.endLineNumber}.${range.endColumn}`;
}
}
window.open('/url/to/browse/' + uri.authority + uri.path + loc);
}), { index: 0 });
}
};
}
更多内容,尽请期待…