remove monaco, add input method to codemirror

This commit is contained in:
2025-06-17 17:32:12 -07:00
parent e167d7e629
commit 64652edf4b
5 changed files with 23 additions and 348 deletions

View File

@@ -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;

View File

@@ -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);
};

View File

@@ -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"],
],
},
};