Updates to highlighting in playground

This commit is contained in:
2025-11-05 08:04:23 -08:00
parent 1b0aeb1eac
commit e8b00ad680
3 changed files with 128 additions and 82 deletions

View File

@@ -1,11 +1,16 @@
import { AbstractEditor, EditorDelegate, Marker } from "./types"; import { AbstractEditor, EditorDelegate, Marker } from "./types";
import { basicSetup } from "codemirror"; import { basicSetup } from "codemirror";
import { defaultKeymap, indentMore, indentLess, toggleLineComment } from "@codemirror/commands"; import {
indentMore,
indentLess,
toggleLineComment,
} from "@codemirror/commands";
import { EditorView, hoverTooltip, keymap, Tooltip } from "@codemirror/view"; import { EditorView, hoverTooltip, keymap, Tooltip } from "@codemirror/view";
import { Compartment, Prec } from "@codemirror/state"; import { Compartment, Prec } from "@codemirror/state";
import { oneDark } from "@codemirror/theme-one-dark"; import { oneDark } from "@codemirror/theme-one-dark";
import { linter } from "@codemirror/lint"; import { linter } from "@codemirror/lint";
import { import {
bracketMatching,
indentService, indentService,
LanguageSupport, LanguageSupport,
StreamLanguage, StreamLanguage,
@@ -13,9 +18,6 @@ import {
} from "@codemirror/language"; } from "@codemirror/language";
import { ABBREV } from "./abbrev.js"; import { ABBREV } from "./abbrev.js";
// maybe use https://github.com/codemirror/legacy-modes/blob/main/mode/simple-mode.js instead.
// @codemirror/legacy-modes/mode/simple-mode.js
// prettier flattened this... // prettier flattened this...
const keywords = [ const keywords = [
"let", "let",
@@ -61,39 +63,75 @@ const keywords = [
"|", "|",
]; ];
// a stack of tokenizers, current is first
// we need to push / pop {} so we can parse strings correctly
interface State { interface State {
tokenizer(stream: StringStream, state: State): string | null; tokenizers: Tokenizer[];
} }
type Tokenizer = (stream: StringStream, state: State) => string | null;
function tokenizer(stream: StringStream, state: State): string | null { function tokenizer(stream: StringStream, state: State): string | null {
if (stream.eatSpace()) return null if (stream.eatSpace()) return null;
if (stream.match("--")) { if (stream.match("--")) {
stream.skipToEnd(); stream.skipToEnd();
return "comment"; return "comment";
} }
if (stream.match(/^[/]-/)) { // maybe keyword?
state.tokenizer = commentTokenizer; if (stream.match(/{/)) {
return state.tokenizer(stream, state); state.tokenizers.unshift(tokenizer);
return null;
}
if (stream.match(/}/) && state.tokenizers.length > 1) {
state.tokenizers.shift();
return state.tokenizers[0] === stringTokenizer ? "keyword" : null;
}
if (stream.match(/^[/]-/)) {
state.tokenizers.unshift(commentTokenizer);
return state.tokenizers[0](stream, state);
}
if (stream.match(/"/)) {
state.tokenizers.unshift(stringTokenizer);
return stringTokenizer(stream, state);
} }
// TODO match tokenizer better.. // TODO match tokenizer better..
if (stream.match(/[^\\(){}[\],.@;\s][^()\\{}\[\],.@;\s]*/)) { if (stream.match(/[^\\(){}[\],.@;\s][^()\\{}\[\],.@;\s]*/)) {
let word = stream.current(); let word = stream.current();
if (keywords.includes(word)) return "keyword"; if (keywords.includes(word)) return "keyword";
if (word[0] >= "A" && word[0] <= "Z") return "typename"; if (word[0] >= "A" && word[0] <= "Z") return "typeName";
return "identifier"; return "variableName";
} }
// unhandled // unhandled
stream.next() stream.next();
return null; return null;
} }
function stringTokenizer(stream: StringStream, state: State) {
while (true) {
if (stream.current() && stream.match(/^\\{/, false)) {
return "string";
}
if (stream.match(/^\\{/)) {
state.tokenizers.unshift(tokenizer);
return "keyword";
}
let ch = stream.next();
if (!ch) return "string";
if (ch === '"') {
state.tokenizers.shift();
return "string";
}
}
}
// We have a tokenizer for this because codemirror processes a line at a time.
// So we may need to end the line in `comment` state and see the -/ later
function commentTokenizer(stream: StringStream, state: State): string | null { function commentTokenizer(stream: StringStream, state: State): string | null {
console.log("ctok"); console.log("ctok");
let dash = false; let dash = false;
let ch; let ch;
while ((ch = stream.next())) { while ((ch = stream.next())) {
if (dash && ch === "/") { if (dash && ch === "/") {
state.tokenizer = tokenizer; state.tokenizers.shift();
return "comment"; return "comment";
} }
dash = ch === "-"; dash = ch === "-";
@@ -101,17 +139,18 @@ function commentTokenizer(stream: StringStream, state: State): string | null {
return "comment"; return "comment";
} }
const newtLanguage2 = StreamLanguage.define({ const newtLanguage2: StreamLanguage<State> = StreamLanguage.define({
startState: () => ({ tokenizer }), startState: () => ({ tokenizers: [tokenizer] }),
token(stream, st) { token(stream, st) {
return st.tokenizer(stream, st); return st.tokenizers[0](stream, st);
}, },
languageData: { languageData: {
commentTokens: { commentTokens: {
line: "--" line: "--",
}, },
// The real list would include almost every character.
wordChars: "!#$%^&*_+-=<>|", wordChars: "!#$%^&*_+-=<>|",
} },
}); });
function newt() { function newt() {
@@ -134,44 +173,44 @@ export class CMEditor implements AbstractEditor {
linter((view) => this.delegate.lint(view)), linter((view) => this.delegate.lint(view)),
// For indent on return // For indent on return
indentService.of((ctx, pos) => { indentService.of((ctx, pos) => {
let line = ctx.lineAt(pos) let line = ctx.lineAt(pos);
if (!line || !line.from) return null if (!line || !line.from) return null;
let prevLine = ctx.lineAt(line.from - 1); let prevLine = ctx.lineAt(line.from - 1);
if (prevLine.text.trimEnd().match(/\b(of|where|do)\s*$/)) { if (prevLine.text.trimEnd().match(/\b(of|where|do)\s*$/)) {
let pindent = prevLine.text.match(/^\s*/)?.[0].length ?? 0 let pindent = prevLine.text.match(/^\s*/)?.[0].length ?? 0;
return pindent + 2 return pindent + 2;
} }
return null return null;
}), }),
Prec.highest(keymap.of([ Prec.highest(
{ keymap.of([
key: "Tab", {
preventDefault: true, key: "Tab",
run: indentMore, preventDefault: true,
}, run: indentMore,
{ },
key: "Cmd-/", {
run: toggleLineComment, key: "Cmd-/",
}, run: toggleLineComment,
// ok, we can do multiple keys (we'll want this for Idris) },
{ // ok, we can do multiple keys (we'll want this for Idris)
key: "c-c c-s", {
run: () => { key: "c-c c-s",
console.log("C-c C-s") run: () => {
return false console.log("C-c C-s");
} return false;
}, },
{ },
key: "Shift-Tab", {
preventDefault: true, key: "Shift-Tab",
run: indentLess, preventDefault: true,
}, run: indentLess,
])), },
])
),
EditorView.updateListener.of((update) => { EditorView.updateListener.of((update) => {
let doc = update.state.doc; let doc = update.state.doc;
console.log('update', update)
update.changes.iterChanges((fromA, toA, fromB, toB, inserted) => { update.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
console.log('inserted', inserted)
if (" ')\\_".includes(inserted.toString()) || inserted.lines > 1) { if (" ')\\_".includes(inserted.toString()) || inserted.lines > 1) {
console.log("changes", update.changes, update.changes.desc); console.log("changes", update.changes, update.changes.desc);
let line = doc.lineAt(fromA); let line = doc.lineAt(fromA);
@@ -193,36 +232,37 @@ export class CMEditor implements AbstractEditor {
} }
}); });
}), }),
this.theme.of(EditorView.baseTheme({})), this.theme.of(EditorView.baseTheme({})),
hoverTooltip((view, pos) => { hoverTooltip((view, pos) => {
let cursor = this.view.state.doc.lineAt(pos); let cursor = this.view.state.doc.lineAt(pos);
let line = cursor.number; let line = cursor.number;
let range = this.view.state.wordAt(pos); let range = this.view.state.wordAt(pos);
console.log(range); console.log(range);
if (range) { if (range) {
let col = range.from - cursor.from; let col = range.from - cursor.from;
let word = this.view.state.doc.sliceString(range.from, range.to); let word = this.view.state.doc.sliceString(range.from, range.to);
let entry = this.delegate.getEntry(word, line, col); let entry = this.delegate.getEntry(word, line, col);
if (!entry) entry = this.delegate.getEntry('_'+word+'_', line, col); if (!entry)
console.log("entry for", word, "is", entry); entry = this.delegate.getEntry("_" + word + "_", line, col);
if (entry) { console.log("entry for", word, "is", entry);
let rval: Tooltip = { if (entry) {
pos: range.head, let rval: Tooltip = {
above: true, pos: range.head,
create: () => { above: true,
let dom = document.createElement("div"); create: () => {
dom.className = "tooltip"; let dom = document.createElement("div");
dom.textContent = entry.type; dom.className = "tooltip";
return { dom }; dom.textContent = entry.type;
}, return { dom };
}; },
return rval; };
return rval;
}
} }
} // we'll iterate the syntax tree for word.
// we'll iterate the syntax tree for word. // let entry = delegate.getEntry(word, line, col)
// let entry = delegate.getEntry(word, line, col) return null;
return null; }),
}),
newt(), newt(),
], ],
}); });

View File

@@ -122,3 +122,6 @@ svg.icon path {
.cm-editor .cm-content { .cm-editor .cm-content {
font-family: 'Comic Code', monospace; font-family: 'Comic Code', monospace;
} }
.cm-editor {
height: 100%;
}

View File

@@ -75,6 +75,9 @@ insert : {l u h} -> Intv l u -> T23 l u h -> TooBig l u h + T23 l u h
insert (intv x lx xu) (leaf lu) = inl (x , (leaf lx , leaf xu)) insert (intv x lx xu) (leaf lu) = inl (x , (leaf lx , leaf xu))
insert (intv x lx xu) (node2 y tly tyu) = case cmp x y of insert (intv x lx xu) (node2 y tly tyu) = case cmp x y of
-- u := N y is not solved at this time -- u := N y is not solved at this time
-- The problem looks like
-- %v13 <= %v8 =?= N %v13 <<= (?m104 ...)
-- This might work if ?m104 is solved another way, so perhaps it could be postponed
inl xy => case insert (intv {_} {N y} x lx xy) tly of inl xy => case insert (intv {_} {N y} x lx xy) tly of
inl (z , (tlz , tzy)) => inr (node3 z y tlz tzy tyu) inl (z , (tlz , tzy)) => inr (node3 z y tlz tzy tyu)
inr tly' => inr (node2 y tly' tyu) inr tly' => inr (node2 y tly' tyu)