Improvements to playground editor

This commit is contained in:
2025-06-27 21:31:35 -07:00
parent 5743eb39ea
commit cee1519b8e
7 changed files with 74 additions and 19 deletions

View File

@@ -33,7 +33,7 @@
], ],
"onEnterRules": [ "onEnterRules": [
{ {
"beforeText": "\\b(where|of|case)$", "beforeText": "\\b(where|of|do)\\s*$",
"action": { "indent": "indent" } "action": { "indent": "indent" }
}, },
{ {

View File

@@ -7,4 +7,9 @@ export const ABBREV: Record<string, string> = {
"\\circ": "∘", "\\circ": "∘",
"\\1": "₁", "\\1": "₁",
"\\2": "₂", "\\2": "₂",
"\\<": "⟨",
"\\>": "⟩",
"\\_0": "₀",
"\\_1": "₁",
"\\bN": ""
}; };

View File

@@ -1,10 +1,12 @@
import { AbstractEditor, EditorDelegate, Marker } from "./types"; import { AbstractEditor, EditorDelegate, Marker } from "./types";
import { basicSetup } from "codemirror"; import { basicSetup } from "codemirror";
import { EditorView, hoverTooltip, Tooltip } from "@codemirror/view"; import { defaultKeymap, indentMore, indentLess, toggleLineComment } from "@codemirror/commands";
import { Compartment } from "@codemirror/state"; import { EditorView, hoverTooltip, keymap, Tooltip } from "@codemirror/view";
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 {
indentService,
LanguageSupport, LanguageSupport,
StreamLanguage, StreamLanguage,
StringStream, StringStream,
@@ -61,7 +63,7 @@ interface State {
} }
function tokenizer(stream: StringStream, state: State): string | null { function tokenizer(stream: StringStream, state: State): string | null {
stream.eatSpace(); if (stream.eatSpace()) return null
if (stream.match("--")) { if (stream.match("--")) {
stream.skipToEnd(); stream.skipToEnd();
return "comment"; return "comment";
@@ -70,17 +72,19 @@ function tokenizer(stream: StringStream, state: State): string | null {
state.tokenizer = commentTokenizer; state.tokenizer = commentTokenizer;
return state.tokenizer(stream, state); return state.tokenizer(stream, state);
} }
if (stream.match(/^[\w_][\w\d_']*/)) {
// TODO match tokenizer better..
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";
console.log('IDENT', )
return "identifier"; return "identifier";
} }
// unhandled // unhandled
stream.next(); stream.next()
return null; return null;
} }
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;
@@ -100,6 +104,12 @@ const newtLanguage2 = StreamLanguage.define({
token(stream, st) { token(stream, st) {
return st.tokenizer(stream, st); return st.tokenizer(stream, st);
}, },
languageData: {
commentTokens: {
line: "--"
},
wordChars: "!#$%^&*_+-=<>|",
}
}); });
function newt() { function newt() {
@@ -120,12 +130,46 @@ export class CMEditor implements AbstractEditor {
extensions: [ extensions: [
basicSetup, basicSetup,
linter((view) => this.delegate.lint(view)), linter((view) => this.delegate.lint(view)),
this.theme.of(EditorView.baseTheme({})), // For indent on return
indentService.of((ctx, pos) => {
let line = ctx.lineAt(pos)
if (!line) return null
let prevLine = ctx.lineAt(line.from - 1);
if (prevLine.text.trimEnd().match(/\b(of|where|do)\s*$/)) {
let pindent = prevLine.text.match(/^\s*/)?.[0].length ?? 0
return pindent + 2
}
return null
}),
Prec.highest(keymap.of([
{
key: "Tab",
preventDefault: true,
run: indentMore,
},
{
key: "Cmd-/",
run: toggleLineComment,
},
// ok, we can do multiple keys (we'll want this for Idris)
{
key: "c-c c-s",
run: () => {
console.log("C-c C-s")
return false
}
},
{
key: "Shift-Tab",
preventDefault: true,
run: indentLess,
},
])),
EditorView.updateListener.of((update) => { EditorView.updateListener.of((update) => {
let doc = update.state.doc; let doc = update.state.doc;
update.changes.iterChanges((fromA, toA, fromB, toB, inserted) => { update.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
if (" ')\\".includes(inserted.toString())) { if (" ')\\_".includes(inserted.toString())) {
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);
let e = fromA - line.from; let e = fromA - line.from;
@@ -146,6 +190,7 @@ export class CMEditor implements AbstractEditor {
} }
}); });
}), }),
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;
@@ -155,7 +200,8 @@ export class CMEditor implements AbstractEditor {
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);
console.log("entry", entry); if (!entry) entry = this.delegate.getEntry('_'+word+'_', line, col);
console.log("entry for", word, "is", entry);
if (entry) { if (entry) {
let rval: Tooltip = { let rval: Tooltip = {
pos: range.head, pos: range.head,

View File

@@ -185,6 +185,7 @@ const language: EditorDelegate = {
return topData?.context.find((entry) => entry.name === word); return topData?.context.find((entry) => entry.name === word);
}, },
onChange(value) { onChange(value) {
// run via the linter now
// clearTimeout(timeout); // clearTimeout(timeout);
// timeout = setTimeout(() => { // timeout = setTimeout(() => {
// run(value); // run(value);
@@ -199,15 +200,17 @@ const language: EditorDelegate = {
async lint(view) { async lint(view) {
console.log("LINT"); console.log("LINT");
let src = view.state.doc.toString(); let src = view.state.doc.toString();
localStorage.code = src
// we'll want to pull it from the file.
const fileName = state.currentFile.value; const fileName = state.currentFile.value;
console.log("FN", fileName); console.log("FN", fileName);
// console.log("SRC", src);
try { try {
let out = await runCommand({ let out = await runCommand({
id: nextID(), id: nextID(),
type: "compileRequest", type: "compileRequest",
fileName, fileName,
src, src,
compile: false,
}); });
console.log("OUT", out); console.log("OUT", out);
let markers = processOutput(out); let markers = processOutput(out);

View File

@@ -6,6 +6,7 @@ export interface CompileReq {
type: "compileRequest"; type: "compileRequest";
fileName: string; fileName: string;
src: string; src: string;
compile: boolean;
} }
export interface CompileRes { export interface CompileRes {

View File

@@ -3,8 +3,8 @@ import { archive, preload } from "./preload";
import { CompileReq, CompileRes } from "./types"; import { CompileReq, CompileRes } from "./types";
console.log = (m) => { console.log = (m) => {
shim.stdout += '\n' + m shim.stdout += "\n" + m;
} };
const handleMessage = async function (ev: { data: CompileReq }) { const handleMessage = async function (ev: { data: CompileReq }) {
console.log("message", ev.data); console.log("message", ev.data);
@@ -12,7 +12,10 @@ const handleMessage = async function (ev: { data: CompileReq }) {
shim.archive = archive; shim.archive = archive;
let { id, src, fileName } = ev.data; let { id, src, fileName } = ev.data;
const outfile = "out.js"; const outfile = "out.js";
if (ev.data.compile)
shim.process.argv = ["browser", "newt", fileName, "-o", outfile, "--top"]; shim.process.argv = ["browser", "newt", fileName, "-o", outfile, "--top"];
else
shim.process.argv = ["browser", "newt", fileName, "--top"];
shim.files[fileName] = new TextEncoder().encode(src); shim.files[fileName] = new TextEncoder().encode(src);
shim.files[outfile] = new TextEncoder().encode("No JS output"); shim.files[outfile] = new TextEncoder().encode("No JS output");
shim.stdout = ""; shim.stdout = "";
@@ -29,7 +32,7 @@ const handleMessage = async function (ev: { data: CompileReq }) {
console.log(`process ${fileName} in ${duration} ms`); console.log(`process ${fileName} in ${duration} ms`);
let javascript = new TextDecoder().decode(shim.files[outfile]); let javascript = new TextDecoder().decode(shim.files[outfile]);
let output = shim.stdout; let output = shim.stdout;
sendResponse({ id, type: 'compileResult', javascript, output, duration }); sendResponse({ id, type: "compileResult", javascript, output, duration });
}; };
// hooks for worker.html to override // hooks for worker.html to override

View File

@@ -286,10 +286,6 @@ stmtToDoc (JError str) = text "throw new Error(" ++ text (quoteString str) ++ te
stmtToDoc (JCase sc alts) = stmtToDoc (JCase sc alts) =
text "switch (" ++ expToDoc sc ++ text ")" <+> bracket "{" (stack $ map altToDoc alts) "}" text "switch (" ++ expToDoc sc ++ text ")" <+> bracket "{" (stack $ map altToDoc alts) "}"
mkArgs : Nat -> List String -> List String
mkArgs Z acc = acc
mkArgs (S k) acc = mkArgs k ("h\{show k}" :: acc)
-- use iife to turn stmts into expr -- use iife to turn stmts into expr
maybeWrap : JSStmt Return -> JSExp maybeWrap : JSStmt Return -> JSExp
maybeWrap (JReturn exp) = exp maybeWrap (JReturn exp) = exp
@@ -399,6 +395,7 @@ process name = do
eraseEntries eraseEntries
liftWhere liftWhere
entries <- readIORef ref entries <- readIORef ref
-- Now working with defs
exprs <- mapM defToCExp $ toList entries exprs <- mapM defToCExp $ toList entries
let cexpMap = foldMap const EmptyMap exprs let cexpMap = foldMap const EmptyMap exprs
cexpMap <- tailCallOpt cexpMap cexpMap <- tailCallOpt cexpMap