From 64652edf4b4a1ff602f9d702f90325c4e06d33dc Mon Sep 17 00:00:00 2001 From: Steve Dunham Date: Tue, 17 Jun 2025 17:32:12 -0700 Subject: [PATCH] remove monaco, add input method to codemirror --- playground/build | 2 - playground/package.json | 1 - playground/src/cmeditor.ts | 25 ++++- playground/src/monaco.ts | 210 ------------------------------------- playground/src/monarch.ts | 133 ----------------------- 5 files changed, 23 insertions(+), 348 deletions(-) delete mode 100644 playground/src/monaco.ts delete mode 100644 playground/src/monarch.ts diff --git a/playground/build b/playground/build index 0c4f791..de7b26e 100755 --- a/playground/build +++ b/playground/build @@ -1,7 +1,5 @@ #!/bin/sh mkdir -p public -echo build monaco worker -esbuild --bundle node_modules/monaco-editor/esm/vs/editor/editor.worker.js > public/workerMain.js echo build newt worker esbuild src/worker.ts --bundle --format=esm > public/worker.js esbuild src/frame.ts --bundle --format=esm > public/frame.js diff --git a/playground/package.json b/playground/package.json index c0a6ae4..8e5b9ee 100644 --- a/playground/package.json +++ b/playground/package.json @@ -19,7 +19,6 @@ "dependencies": { "@preact/signals": "^1.3.0", "codemirror": "^6.0.1", - "monaco-editor": "^0.52.0", "preact": "^10.24.3" } } diff --git a/playground/src/cmeditor.ts b/playground/src/cmeditor.ts index bc51ce1..10eb74e 100644 --- a/playground/src/cmeditor.ts +++ b/playground/src/cmeditor.ts @@ -12,6 +12,7 @@ import { StreamLanguage, StringStream, } from "@codemirror/language"; +import { ABBREV } from "./abbrev.js"; let parserWithMetadata = parser.configure({ props: [ @@ -32,7 +33,7 @@ const newtLanguage = LRLanguage.define({ }, }, }); -// prettier did this... +// prettier flattened this... const keywords = [ "let", "in", @@ -113,7 +114,6 @@ function commentTokenizer(stream: StringStream, state: State): string | null { } dash = ch === "-"; } - console.log("XX", stream.current()); return "comment"; } @@ -143,6 +143,27 @@ export class CMEditor implements AbstractEditor { basicSetup, linter((view) => this.delegate.lint(view)), this.theme.of(EditorView.baseTheme({})), + EditorView.updateListener.of((update) => { + let doc = update.state.doc + + update.changes.iterChanges((fromA, toA, fromB, toB, inserted)=> { + if (" ')\\".includes(inserted.toString())) { + console.log('changes', update.changes, update.changes.desc) + let line = doc.lineAt(fromA) + let e = fromA - line.from + const m = line.text.slice(0, e).match(/(\\[^ ]+)$/); + if (m) { + let s = e - m[0].length + let key = line.text.slice(s, e) + if (ABBREV[key]) { + this.view.dispatch({ + changes: { from: line.from + s, to: fromA, insert: ABBREV[key] }, + }); + } + } + } + }) + }), hoverTooltip((view, pos) => { let cursor = this.view.state.doc.lineAt(pos); let line = cursor.number; diff --git a/playground/src/monaco.ts b/playground/src/monaco.ts deleted file mode 100644 index d103023..0000000 --- a/playground/src/monaco.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { ABBREV } from "./abbrev.ts"; -import { newtConfig, newtTokens } from "./monarch.ts"; -import * as monaco from "monaco-editor"; -import { AbstractEditor, EditorDelegate, Marker } from "./types.ts"; - -// we need to fix the definition of word -monaco.languages.register({ id: "newt" }); -monaco.languages.setMonarchTokensProvider("newt", newtTokens); -monaco.languages.setLanguageConfiguration("newt", newtConfig); - -self.MonacoEnvironment = { - getWorkerUrl(moduleId, _label) { - console.log("Get worker", moduleId); - return moduleId; - }, -}; - -export class MonacoEditor implements AbstractEditor { - editor: monaco.editor.IStandaloneCodeEditor; - delegate: EditorDelegate; - - constructor(container: HTMLElement, value: string, language: EditorDelegate) { - this.delegate = language; - let editor = (this.editor = monaco.editor.create(container, { - value, - language: "newt", - fontFamily: "Comic Code, Menlo, Monaco, Courier New, sans", - automaticLayout: true, - acceptSuggestionOnEnter: "off", - unicodeHighlight: { ambiguousCharacters: false }, - minimap: { enabled: false }, - })); - monaco.languages.registerDefinitionProvider("newt", { - provideDefinition(model, position, token) { - const info = model.getWordAtPosition(position); - if (!info) return; - let entry = language.getEntry( - info.word, - position.lineNumber, - info.startColumn - ); - if (!entry) return; - - // HACK - we don't know our filename which was generated from `module` decl, so - // assume the last context entry is in our file. - - let file = language.getFileName(); - if (!entry || entry.fc.file !== file) return; - let lineNumber = entry.fc.line + 1; - let column = entry.fc.col + 1; - let word = model.getWordAtPosition({ lineNumber, column }); - let range = new monaco.Range(lineNumber, column, lineNumber, column); - if (word) { - range = new monaco.Range( - lineNumber, - word.startColumn, - lineNumber, - word.endColumn - ); - } - return { uri: model.uri, range }; - }, - }); - monaco.languages.registerHoverProvider("newt", { - provideHover(model, position, token, context) { - const info = model.getWordAtPosition(position); - if (!info) return; - let entry = language.getEntry( - info.word, - position.lineNumber, - info.startColumn - ); - if (!entry) return; - return { - range: new monaco.Range( - position.lineNumber, - info.startColumn, - position.lineNumber, - info.endColumn - ), - contents: [{ value: `${entry.name} : ${entry.type}` }], - }; - }, - }); - editor.onDidChangeModelContent((ev) => { - this.delegate.onChange(editor.getValue()); - - let last = ev.changes[ev.changes.length - 1]; - const model = editor.getModel(); - // figure out heuristics here, what chars do we want to trigger? - // the lean one will only be active if it sees you type \ - // and bail if it decides you've gone elsewhere - // it maintains an underline annotation, too. - if (model && last.text && " ')\\".includes(last.text)) { - console.log("LAST", last); - let { startLineNumber, startColumn } = last.range; - const text = model.getValueInRange( - new monaco.Range( - startLineNumber, - Math.max(1, startColumn - 10), - startLineNumber, - startColumn - ) - ); - const m = text.match(/(\\[^ ]+)$/); - if (m) { - let cand = m[0]; - console.log("GOT", cand); - let text = ABBREV[cand]; - if (text) { - const range = new monaco.Range( - startLineNumber, - startColumn - cand.length, - startLineNumber, - startColumn - ); - editor.executeEdits("replaceSequence", [{ range, text: text }]); - } - } - } - }); - } - setValue(value: string) { - this.editor.setValue(value); - } - getValue() { - return this.editor.getValue(); - } - setMarkers(markers: Marker[]) { - let model = this.editor.getModel()!; - const mapMarker = (marker: Marker): monaco.editor.IMarkerData => { - let severity = - marker.severity === "ERROR" - ? monaco.MarkerSeverity.Error - : monaco.MarkerSeverity.Info; - // translate to surrounding word - // FIXME - we have `getWord` in monaco, but probably belongs to the delegate - // and eventually we have better FC - let { message, startColumn, endColumn, startLineNumber, endLineNumber } = - marker; - let word = model.getWordAtPosition({ - column: startColumn, - lineNumber: startLineNumber, - }); - if (word) endColumn = word.endColumn; - return { - message, - startColumn, - endColumn, - startLineNumber, - endLineNumber, - severity, - }; - }; - monaco.editor.setModelMarkers(model, "newt", markers.map(mapMarker)); - } -} - -// scratch -const processOutput = ( - editor: monaco.editor.IStandaloneCodeEditor, - output: string -) => { - let model = editor.getModel()!; - let markers: monaco.editor.IMarkerData[] = []; - let lines = output.split("\n"); - let m = lines[0].match(/.*Process (.*)/); - let fn = ""; - if (m) fn = m[1]; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const match = line.match(/(INFO|ERROR) at (.*):\((\d+), (\d+)\):\s*(.*)/); - if (match) { - let [_full, kind, file, line, col, message] = match; - let lineNumber = +line + 1; - let column = +col + 1; - // FIXME - pass the real path in - if (fn && fn !== file) { - lineNumber = column = 0; - } - let start = { column, lineNumber }; - // we don't have the full range, so grab the surrounding word - let endColumn = column + 1; - let word = model.getWordAtPosition(start); - if (word) endColumn = word.endColumn; - - // heuristics to grab the entire message: - // anything indented - // Context:, or Goal: are part of PRINTME - // unexpected / expecting appear in parse errors - while (lines[i + 1]?.match(/^( )/)) { - message += "\n" + lines[++i]; - } - const severity = - kind === "ERROR" - ? monaco.MarkerSeverity.Error - : monaco.MarkerSeverity.Info; - if (kind === "ERROR" || lineNumber) - markers.push({ - severity, - message, - startLineNumber: lineNumber, - endLineNumber: lineNumber, - startColumn: column, - endColumn, - }); - } - } - monaco.editor.setModelMarkers(model, "newt", markers); -}; diff --git a/playground/src/monarch.ts b/playground/src/monarch.ts deleted file mode 100644 index 12b83e5..0000000 --- a/playground/src/monarch.ts +++ /dev/null @@ -1,133 +0,0 @@ -import * as monaco from "monaco-editor"; - -export let newtConfig: monaco.languages.LanguageConfiguration = { - // see singleton in Tokenizer.idr - wordPattern: /[^()\\{}\[\],.@\s]+/, - comments: { - // symbol used for single line comment. Remove this entry if your language does not support line comments - lineComment: "--", - // symbols used for start and end a block comment. Remove this entry if your language does not support block comments - blockComment: ["/-", "-/"], - }, - // symbols used as brackets - brackets: [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ], - // symbols that are auto closed when typing - autoClosingPairs: [ - { open: "{", close: "}" }, - { open: "[", close: "]" }, - { open: "(", close: ")" }, - { open: '"', close: '"' }, - // { open: "'", close: "'" }, causes problems with foo'' - { open: "/-", close: "-/" }, - ], - // symbols that can be used to surround a selection - surroundingPairs: [ - { open: "{", close: "}" }, - { open: "[", close: "]" }, - { open: "(", close: ")" }, - { open: '"', close: '"' }, - { open: "'", close: "'" }, - ], - onEnterRules: [ - { - beforeText: /^\s+$/, - action: { - indentAction: monaco.languages.IndentAction.Outdent, - }, - }, - { - beforeText: /\b(where|of|case)$/, - action: { - indentAction: monaco.languages.IndentAction.Indent, - }, - }, - { - beforeText: /\/-/, - afterText: /-\//, - action: { - indentAction: monaco.languages.IndentAction.IndentOutdent, - }, - }, - ], -}; - -export let newtTokens: monaco.languages.IMonarchLanguage = { - // Set defaultToken to invalid to see what you do not tokenize yet - // defaultToken: "invalid", - - keywords: [ - "let", - "in", - "where", - "case", - "record", - "of", - "data", - "forall", - "∀", - "U", - "module", - "ptype", - "pfunc", - "if", - "uses", - "then", - "else", - "class", - "instance", - "module", - "infixl", - "infixr", - "infix", - ], - specialOps: ["=>", "->", ":", "=", ":=", "<-"], - tokenizer: { - root: [ - // char literal, but I don't think there is a class for that. - [/'\\?.'/, "string"], - [ - /[a-z_$][\w$']*/, - { cases: { "@keywords": "keyword", "@default": "identifier" } }, - ], - [/[A-Z][\w\$]*/, "type.identifier"], - [/\\|λ/, "keyword"], - { include: "@whitespace" }, - [/[{}()\[\]]/, "@brackets"], - [ - /[:!#$%&*+.<=>?@\\^|~\/-]+/, - { - cases: { - "@specialOps": "keyword", - "@default": "operator", - }, - }, - ], - - [/\d+/, "number"], - - // strings - [/"([^"\\]|\\.)*$/, "string.invalid"], // non-teminated string - [/"/, { token: "string.quote", bracket: "@open", next: "@string" }], - ], - comment: [ - [/[^-]+/, "comment"], - ["-/", "comment", "@pop"], - ["-", "comment"], - ], - string: [ - [/[^\\"]+/, "string"], - // [/@escapes/, "string.escape"], - // [/\\./, "string.escape.invalid"], - [/"/, { token: "string.quote", bracket: "@close", next: "@pop" }], - ], - whitespace: [ - [/[ \t\r\n]+/, "white"], - ["/-", "comment", "@comment"], - [/--.*$/, "comment"], - ], - }, -};