1import * as monaco from 'monaco-editor/editor/editor.api.js'2import 'monaco-editor/features/find/register.js'3import 'monaco-editor/editor/contrib/bracketMatching/browser/bracketMatching.js'4import 'monaco-editor/editor/contrib/clipboard/browser/clipboard.js'5import 'monaco-editor/editor/contrib/contextmenu/browser/contextmenu.js'6import 'monaco-editor/editor/contrib/folding/browser/folding.js'7import { editorLanguageFor } from '../editor-languages.js'8import { registerMonacoLanguages } from './monaco-languages.js'9import { configureMonacoWorkers } from './monaco-workers.js'1011const THEME_NAME = 'diagramzip'1213export function createEditor({14 element,15 source,16 diagramType,17 language,18 modelUri,19 ariaLabel = 'Diagram source editor',20 onChange,21 onSave,22}) {23 configureMonacoWorkers()24 registerMonacoLanguages(monaco)25 defineTheme()2627 let suppressChanges = 028 const model = monaco.editor.createModel(29 source,30 language ?? editorLanguageFor(diagramType),31 monaco.Uri.parse(modelUri ?? 'inmemory://diagram.zip/source'),32 )33 const rootStyle = getComputedStyle(document.documentElement)34 const editor = monaco.editor.create(element, {35 model,36 ariaLabel,37 automaticLayout: true,38 contextmenu: true,39 fixedOverflowWidgets: true,40 folding: true,41 fontFamily: cssValue(rootStyle, '--font-mono'),42 fontSize: 14,43 glyphMargin: false,44 lineHeight: 23,45 lineNumbers: 'on',46 minimap: { enabled: false },47 overviewRulerBorder: false,48 padding: { top: 28, bottom: 80 },49 renderLineHighlight: 'all',50 scrollBeyondLastLine: false,51 stickyScroll: { enabled: false },52 theme: THEME_NAME,53 wordWrap: 'off',54 })5556 const contentSubscription = model.onDidChangeContent(() => {57 if (suppressChanges === 0) onChange()58 })59 editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => onSave())6061 return {62 backend: 'monaco',63 getValue: () => model.getValue(),64 setDocument({ source: nextSource, diagramType: nextType = diagramType, language: nextLanguage = language }) {65 suppressChanges += 166 try {67 const previousPosition = editor.getPosition()68 model.setValue(nextSource)69 monaco.editor.setModelLanguage(model, nextLanguage ?? editorLanguageFor(nextType))70 if (previousPosition) {71 const lineNumber = Math.min(previousPosition.lineNumber, model.getLineCount())72 const column = Math.min(previousPosition.column, model.getLineMaxColumn(lineNumber))73 editor.setPosition({ lineNumber, column })74 editor.revealPositionInCenterIfOutsideViewport({ lineNumber, column })75 }76 } finally {77 suppressChanges -= 178 }79 },80 focus: () => editor.focus(),81 layout: () => editor.layout(),82 setTheme() {83 defineTheme()84 monaco.editor.setTheme(THEME_NAME)85 },86 dispose() {87 contentSubscription.dispose()88 editor.dispose()89 model.dispose()90 },91 }92}9394function defineTheme() {95 const style = getComputedStyle(document.documentElement)96 monaco.editor.defineTheme(THEME_NAME, {97 base: document.documentElement.dataset.theme === 'dark' ? 'vs-dark' : 'vs',98 inherit: true,99 rules: [100 { token: 'keyword', foreground: tokenColor(style, '--syntax-keyword'), fontStyle: 'bold' },101 { token: 'string', foreground: tokenColor(style, '--syntax-string') },102 { token: 'string.escape', foreground: tokenColor(style, '--syntax-string') },103 { token: 'comment', foreground: tokenColor(style, '--syntax-comment'), fontStyle: 'italic' },104 { token: 'number', foreground: tokenColor(style, '--syntax-number') },105 { token: 'number.hex', foreground: tokenColor(style, '--syntax-number') },106 { token: 'operator', foreground: tokenColor(style, '--syntax-operator') },107 { token: 'delimiter', foreground: tokenColor(style, '--syntax-operator') },108 { token: 'identifier', foreground: tokenColor(style, '--syntax-variable') },109 { token: 'key', foreground: tokenColor(style, '--syntax-variable') },110 ],111 colors: {112 'editor.background': themeColor(style, '--surface'),113 'editor.foreground': themeColor(style, '--ink'),114 'editorCursor.foreground': themeColor(style, '--accent'),115 'editorGutter.background': themeColor(style, '--surface'),116 'editorLineNumber.foreground': themeColor(style, '--muted'),117 'editor.lineHighlightBackground': themeColor(style, '--editor-active-line'),118 'editor.selectionBackground': themeColor(style, '--editor-selection'),119 },120 })121}122123function cssValue(style, property) {124 return style.getPropertyValue(property).trim()125}126127function tokenColor(style, property) {128 return themeColor(style, property).replace(/^#/, '')129}130131function themeColor(style, property) {132 const color = cssValue(style, property)133 const shorthand = color.match(/^#([0-9a-f]{3,4})$/i)134 if (shorthand) {135 return `#${[...shorthand[1]].map(character => character.repeat(2)).join('')}`136 }137 return color138}