Skip to main content

ADR-005: Use Monaco editor for Code Editing

Date: 2025-10-06 Status: Accepted Deciders: Development Team Tags: editor, ui, components

Context​

The IDE requires a powerful code editor with:

  • Syntax highlighting for 100+ languages
  • IntelliSense/autocomplete
  • Multi-cursor editing
  • Find/replace with regex
  • Diff viewer
  • Minimap navigation
  • Bracket matching
  • Code folding
  • Theme support

Decision​

We will use Monaco editor 0.45 (the editor that powers VS Code) via @monaco-editor/react.

Rationale​

Why Monaco editor​

  1. Industry Standard: Powers VS Code, trusted by millions
  2. Feature Complete: Everything VS Code has (same engine)
  3. Language Support: 100+ languages out of the box
  4. IntelliSense: Built-in autocomplete and type checking
  5. Performance: Virtual scrolling, optimized for large files
  6. React Integration: Official React wrapper available
  7. Themes: VS Code themes work directly
  8. Diff editor: Built-in side-by-side diff viewer

Key Features​

  • Multi-language: TypeScript, Python, Rust, Go, JSON, Markdown, etc.
  • Keyboard Shortcuts: VS Code shortcuts (Cmd+P, Cmd+Shift+P, etc.)
  • editor Actions: Format, refactor, organize imports
  • Accessibility: Screen reader support, keyboard navigation
  • Extensibility: Custom languages, themes, commands

Alternatives Considered​

CodeMirror 6​

  • Pros: Lightweight, modular, good for simple editors
  • Cons: Less feature-complete, smaller ecosystem
  • Rejected: Monaco more familiar to developers (VS Code UX)

Ace editor​

  • Pros: Mature, good language support
  • Cons: Older architecture, less active development
  • Rejected: Monaco more modern, better maintained

Custom editor​

  • Pros: Full control, minimal bundle
  • Cons: Years of development, no IntelliSense
  • Rejected: Not feasible for IDE-grade editor

ProseMirror / Slate​

  • Pros: Excellent for rich text
  • Cons: Not designed for code editing
  • Rejected: Wrong use case (rich text vs code)

Consequences​

Positive​

  • Familiar VS Code experience for users
  • Full IDE features out of the box
  • Excellent TypeScript/JavaScript support
  • Multi-cursor, IntelliSense, refactoring
  • Active development by Microsoft
  • Large community, many examples

Negative​

  • Large bundle size (~2-3MB min+gzip)
  • Initial load time (~500ms-1s)
  • Memory intensive for many tabs
  • Some features require web workers

Neutral​

  • Need to configure languages manually
  • Theme customization requires CSS
  • Web worker setup for TypeScript

Implementation​

Basic Setup​

// editor-panel.tsx
import editor from '@monaco-editor/react';
import { useColorMode } from '@chakra-ui/react';

export default function editorPanel() {
const { colorMode } = useColorMode();
const activeTab = useeditorStore(state =>
state.tabs.find(t => t.id === state.activeTabId)
);

return (
<editor
height="100%"
language={activeTab?.language || 'plaintext'}
value={activeTab?.content || ''}
theme={colorMode === 'dark' ? 'vs-dark' : 'vs-light'}
onChange={(value) => {
if (activeTab) {
useeditorStore.getState().updateTabContent(activeTab.id, value || '');
}
}}
options={{
fontSize: 14,
fontFamily: 'Fira Code, Monaco, Consolas, monospace',
minimap: { enabled: true },
lineNumbers: 'on',
wordWrap: 'on',
autoClosingBrackets: 'always',
autoClosingQuotes: 'always',
formatOnPaste: true,
formatOnType: true,
suggestOnTriggerCharacters: true,
quickSuggestions: true,
tabSize: 2,
insertSpaces: true,
}}
/>
);
}

TypeScript Configuration​

// Configure TypeScript compiler
import * as monaco from 'monaco-editor';

monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2020,
allowNonTsExtensions: true,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
module: monaco.languages.typescript.ModuleKind.CommonJS,
noEmit: true,
esModuleInterop: true,
jsx: monaco.languages.typescript.JsxEmit.React,
reactNamespace: 'React',
allowJs: true,
typeRoots: ['node_modules/@types'],
});

Custom Theme​

// VS Code-like dark theme
monaco.editor.defineTheme('az1ai-dark', {
base: 'vs-dark',
inherit: true,
rules: [
{ token: 'comment', foreground: '6A9955' },
{ token: 'keyword', foreground: 'C586C0' },
{ token: 'string', foreground: 'CE9178' },
{ token: 'number', foreground: 'B5CEA8' },
],
colors: {
'editor.background': '#1e1e1e',
'editor.foreground': '#cccccc',
'editorLineNumber.foreground': '#858585',
'editor.selectionBackground': '#264f78',
'editor.inactiveSelectionBackground': '#3a3d41',
},
});

Diff Viewer Integration​

import { Diffeditor } from '@monaco-editor/react';

export function DiffPanel({ original, modified }: DiffPanelProps) {
return (
<Diffeditor
height="100%"
original={original}
modified={modified}
language="typescript"
theme="vs-dark"
options={{
renderSideBySide: true,
readOnly: false,
}}
/>
);
}

Language Detection​

// Auto-detect language from file extension
function detectLanguage(filename: string): string {
const ext = filename.split('.').pop()?.toLowerCase();

const languageMap: Record<string, string> = {
ts: 'typescript',
tsx: 'typescript',
js: 'javascript',
jsx: 'javascript',
py: 'python',
rs: 'rust',
go: 'go',
json: 'json',
md: 'markdown',
css: 'css',
scss: 'scss',
html: 'html',
yaml: 'yaml',
yml: 'yaml',
};

return languageMap[ext || ''] || 'plaintext';
}

Performance Optimization​

Code Splitting​

// Lazy load Monaco to reduce initial bundle
import { lazy, Suspense } from 'react';

const Monacoeditor = lazy(() => import('@monaco-editor/react'));

export function editorPanel() {
return (
<Suspense fallback={<Spinner />}>
<Monacoeditor {...props} />
</Suspense>
);
}

Web Worker Configuration​

// vite.config.ts
import { defineConfig } from 'vite';
import monacoeditorPlugin from 'vite-plugin-monaco-editor';

export default defineConfig({
plugins: [
monacoeditorPlugin({
languageWorkers: ['typescript', 'json', 'css', 'html']
})
]
});

Memory Management​

// Dispose editor instances when tabs close
function disposeeditor(tabId: string) {
const editor = editorInstances.get(tabId);
if (editor) {
editor.dispose();
editorInstances.delete(tabId);
}
}

Integration Points​

  • File System: Load/save files via OPFS/FoundationDB
  • llm: Send code context to llm for analysis
  • terminal: Run commands on current file
  • Git: Show diff in editor
  • Themes: Sync with Chakra UI dark/light mode

References​