Microsoft VSCode MonacoEditor开发指南

MonacoEditor开发指南

想要把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)
本文会不定期更新。

基本操作

  • 创建editor:
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')])
    

进阶操作

  • 支持文件使用restful api加载:
   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()
});
  • 支持手机端minimap的scroll:
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
}
  • 给peek definition widget加一个action按钮 (例子:在新窗口中打开reference)
_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 });
       }
    };
}

更多内容,尽请期待…

发布了51 篇原创文章 · 获赞 37 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/prog_6103/article/details/89452217