You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在VSCode CustomTextEditor扩展中集成Monaco Editor

Great question! Integrating Monaco Editor into a VS Code Custom Text Editor extension is the perfect way to build that enhanced editor you’re aiming for—complete with overview rulers, line numbers, and all of Monaco’s powerful features. Let’s walk through the process step by step, from setup to implementing core functionality:

1. Start with a Custom Text Editor Extension Base

First, initialize your extension using the official Yeoman generator. Run this in your terminal:

yo code

When prompted, select the Custom Text Editor template. This sets up the basic structure you’ll need, including the extension activation logic and package.json configuration.

In your package.json, make sure you have the necessary contributions and activation events:

{
  "activationEvents": ["onCustomEditor:your.editor.id"],
  "contributes": {
    "customEditors": [
      {
        "viewType": "your.editor.id",
        "displayName": "Enhanced Monaco Editor",
        "selector": [
          {
            "filenamePattern": "*.your-extension"
          }
        ],
        "priority": "default"
      }
    ]
  }
}
2. Install Monaco Editor Dependencies

Next, install the required packages. You’ll need the Monaco Editor itself, its TypeScript types, and a webpack plugin to handle bundling efficiently:

npm install monaco-editor @types/monaco-editor --save
npm install monaco-editor-webpack-plugin --save-dev
3. Configure Webpack for Monaco

VS Code extensions use webpack under the hood, so you’ll need to modify your webpack.config.js to include the Monaco Webpack Plugin. This ensures Monaco is bundled correctly and lets you trim down the bundle size by only including the languages/features you need:

const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');

module.exports = {
  // ... existing config
  plugins: [
    new MonacoWebpackPlugin({
      // Specify languages you want to support (e.g., 'javascript', 'typescript', 'markdown')
      languages: ['javascript'],
      // Optional: include only specific features to reduce bundle size
      features: ['coreCommands', 'find', 'lineNumbers', 'overviewRuler']
    })
  ]
};
4. Implement the Custom Editor Provider with Monaco

Now, open your extension.ts file and modify the custom editor provider to initialize a Monaco Editor instance instead of the default simple text editor. Here’s the core logic:

First, import Monaco at the top:

import * as monaco from 'monaco-editor';

Then, in your resolveCustomTextEditor method, set up the Monaco Editor:

export class EnhancedMonacoEditorProvider implements vscode.CustomTextEditorProvider {
  public static register(context: vscode.ExtensionContext): vscode.Disposable {
    const provider = new EnhancedMonacoEditorProvider(context);
    const providerRegistration = vscode.window.registerCustomEditorProvider(EnhancedMonacoEditorProvider.viewType, provider);
    return providerRegistration;
  }

  private static readonly viewType = 'your.editor.id';

  constructor(
    private readonly context: vscode.ExtensionContext
  ) { }

  public async resolveCustomTextEditor(
    document: vscode.TextDocument,
    webviewPanel: vscode.WebviewPanel,
    _token: vscode.CancellationToken
  ): Promise<void> {
    // Enable script in the webview
    webviewPanel.webview.options = {
      enableScripts: true,
      localResourceRoots: [this.context.extensionUri]
    };

    // Load Monaco scripts into the webview
    const monacoUri = webviewPanel.webview.asWebviewUri(
      vscode.Uri.joinPath(this.context.extensionUri, 'node_modules', 'monaco-editor', 'min', 'vs')
    );

    // Set webview HTML with Monaco container
    webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview, monacoUri);

    // Wait for the webview to be ready
    const editorPromise = new Promise<monaco.editor.IStandaloneCodeEditor>((resolve) => {
      webviewPanel.webview.onDidReceiveMessage((message) => {
        if (message.type === 'monacoReady') {
          // Initialize Monaco Editor
          const editor = monaco.editor.create(document.getElementById('monaco-container')!, {
            value: document.getText(),
            language: 'javascript', // Match your file type or detect dynamically
            lineNumbers: 'on', // Enable line numbers
            overviewRulerLanes: 3, // Show the overview ruler with 3 lanes
            overviewRulerBorder: false,
            minimap: { enabled: true }, // Optional: enable minimap
            automaticLayout: true // Auto-resize with the webview
          });

          // Sync document changes to Monaco
          const documentChangeDisposable = vscode.workspace.onDidChangeTextDocument((e) => {
            if (e.document.uri.toString() === document.uri.toString()) {
              editor.setValue(e.document.getText());
            }
          });

          // Sync Monaco changes back to the VS Code document
          editor.onDidChangeModelContent(async () => {
            const value = editor.getValue();
            const edit = new vscode.WorkspaceEdit();
            edit.replace(
              document.uri,
              new vscode.Range(0, 0, document.lineCount, 0),
              value
            );
            await vscode.workspace.applyEdit(edit);
          });

          // Clean up when the panel is disposed
          webviewPanel.onDidDispose(() => {
            editor.dispose();
            documentChangeDisposable.dispose();
          });

          resolve(editor);
        }
      });
    });
  }

  private getHtmlForWebview(webview: vscode.Webview, monacoUri: vscode.Uri): string {
    return `
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="${monacoUri}/loader.js"></script>
      </head>
      <body style="margin:0;">
        <div id="monaco-container" style="width:100%;height:100vh;"></div>
        <script>
          require.config({ paths: { 'vs': '${monacoUri}/vs' } });
          require(['vs/editor/editor.main'], () => {
            window.postMessage({ type: 'monacoReady' }, '*');
          });
        </script>
      </body>
      </html>
    `;
  }
}
5. Enable Core Monaco Features

The example above already includes line numbers (lineNumbers: 'on') and the overview ruler (overviewRulerLanes: 3). You can tweak more options to match your needs:

  • Minimap: Add minimap: { enabled: true } to show the code minimap.
  • Line Highlighting: Enable lineHighlight: 'all' to highlight all lines containing the current selection.
  • Folding: Turn on folding: true to allow code folding.

All these options are part of Monaco’s IEditorConstructionOptions interface—you can explore more granular settings to customize the editor further.

6. Handle Lifecycle and Cleanup

It’s crucial to dispose of the Monaco Editor instance and any event listeners when the webview panel is closed. The code above already includes this in the onDidDispose handler, which prevents memory leaks in your extension.

Quick Tips for Optimization
  • Bundle Size: Use the monaco-editor-webpack-plugin to only include the languages and features you need—this can drastically reduce your extension’s bundle size.
  • Performance: Enable automaticLayout: true so Monaco resizes with the webview, and avoid unnecessary sync operations between the VS Code document and Monaco model.
  • Theme Sync: To match VS Code’s theme, listen for VS Code’s theme change events and update Monaco’s theme accordingly using monaco.editor.setTheme().

内容的提问来源于stack exchange,提问作者FireFuro99

火山引擎 最新活动