diff --git a/playground/src/cmeditor.ts b/playground/src/cmeditor.ts index d22934f..b74279c 100644 --- a/playground/src/cmeditor.ts +++ b/playground/src/cmeditor.ts @@ -154,6 +154,36 @@ const newtLanguage2: StreamLanguage = StreamLanguage.define({ }, }); +export function scheme() { + return new LanguageSupport(schemeLanguage); +} + +const schemeLanguage: StreamLanguage = StreamLanguage.define({ + startState: () => null, + token(stream, st) { + const keywords = ["define", "let", "case", "cond", "import", "include", "lambda", "else"]; + if (stream.eatSpace()) return null; + if (stream.match("--")) { + stream.skipToEnd(); + return "comment"; + } + if (stream.match(/[0-9A-Za-z!%&*+./:<=>?@^_~-]+/)) { + let word = stream.current(); + if (keywords.includes(word)) return "keyword"; + return null; + } + // unhandled + stream.next(); + return null + }, + languageData: { + commentTokens: { + line: ";;", + }, + wordChars: "!%&*+-./:<=>?@^_~", + }, +}); + function newt() { return new LanguageSupport(newtLanguage2); } diff --git a/playground/src/ipc.ts b/playground/src/ipc.ts index 719bac5..b414ac7 100644 --- a/playground/src/ipc.ts +++ b/playground/src/ipc.ts @@ -49,7 +49,7 @@ export interface API { hoverInfo(fileName: string, row: number, col: number): HoverResult | boolean | null; codeActionInfo(fileName: string, row: number, col: number): CodeAction[] | null; // we need to add this to the LSP build - compile(fileName: string): string; + compile(fileName: string, language: 'javascript'|'scheme'): string; } export interface Message { @@ -116,4 +116,3 @@ export class IPC { } } -class IPCClient {} diff --git a/playground/src/main.ts b/playground/src/main.ts index 3bf7232..7c9158e 100644 --- a/playground/src/main.ts +++ b/playground/src/main.ts @@ -11,12 +11,12 @@ import { TopData, Marker, } from "./types.ts"; -import { CMEditor } from "./cmeditor.ts"; +import { CMEditor, scheme } from "./cmeditor.ts"; import { deflate } from "./deflate.ts"; import { inflate } from "./inflate.ts"; import { IPC, Position } from "./ipc.ts"; import helpText from "./help.md?raw"; -import { basicSetup, EditorView } from "codemirror"; +import { basicSetup, EditorView} from "codemirror"; import {Compartment, EditorState} from "@codemirror/state"; import { javascript } from "@codemirror/lang-javascript"; import { oneDark } from "@codemirror/theme-one-dark"; @@ -82,11 +82,23 @@ if (!state.javascript.value) { const fileName = state.currentFile.value; // maybe send fileName, src? await ipc.sendMessage("updateFile", [fileName, src]); - let js = await ipc.sendMessage("compile", [fileName]); + let js = await ipc.sendMessage("compile", [fileName, "javascript"]); state.javascript.value = bundle(js); } } +async function refreshScheme() { +if (!state.scheme.value) { + let src = state.editor.value!.getValue(); + console.log("SEND TO", iframe.contentWindow); + const fileName = state.currentFile.value; + // maybe send fileName, src? + await ipc.sendMessage("updateFile", [fileName, src]); + let scheme = await ipc.sendMessage("compile", [fileName, "scheme"]); + state.scheme.value = bundle(scheme); + } +} + async function runOutput() { await refreshJS() const src = state.javascript.value; @@ -153,15 +165,23 @@ function getSavedCode() { return value; } +const RESULTS = "Output"; +const JAVASCRIPT = "JS"; +const SCHEME = "Scheme"; +const CONSOLE = "Console"; +const HELP = "Help"; + const state = { output: signal(""), toast: signal(""), javascript: signal(""), + scheme: signal(""), messages: signal([]), editor: signal(null), dark: signal(false), files: signal(["Tour.newt"]), currentFile: signal(localStorage.currentFile ?? "Tour.newt"), + selected: signal(localStorage.tab ?? RESULTS), }; // Monitor dark mode state (TODO - let user override system setting) @@ -264,7 +284,12 @@ const language: EditorDelegate = { } setOutput(res.output) // less flashy version - ipc.sendMessage("compile", [fileName]).then(js => state.javascript.value = bundle(js)); + if (state.selected.value === JAVASCRIPT) + ipc.sendMessage("compile", [fileName, "javascript"]).then(js => state.javascript.value = bundle(js)); + if (state.selected.value === SCHEME) + ipc.sendMessage("compile", [fileName, "scheme"]).then(scheme=> state.scheme.value = scheme); + // UI will update + state.scheme.value = ""; return diags; } catch (e) { console.log("ERR", e); @@ -287,26 +312,26 @@ function Editor({ initialValue }: EditorProps) { return h("div", { id: "editor", ref }); } -// for extra credit, we could have a read-only monaco -function JavaScript() { - const text = state.javascript.value; +interface ViewerProps { + language: 'javascript' | 'scheme' +} +function SourceViewer({language}: ViewerProps) { + const text = state[language].value; // return h("div", { id: "javascript" }, text); const ref = useRef(null); const editorView = useRef(null); const themeRef = useRef(null); useEffect(() => { - console.log('JSEFFECT') const container = ref.current!; themeRef.current = new Compartment(); - const editor = new EditorView({ doc: text, parent: container, extensions: [ basicSetup, themeRef.current.of(state.dark.value ? oneDark : EditorView.baseTheme({})), - javascript(), + language == 'javascript' ? javascript() : scheme(), EditorState.readOnly.of(true), EditorView.editable.of(false), ], @@ -347,16 +372,11 @@ function Console() { ); } -const RESULTS = "Output"; -const JAVASCRIPT = "JS"; -const CONSOLE = "Console"; -const HELP = "Help"; - function Tabs() { - const [selected, setSelected] = useState(localStorage.tab ?? RESULTS); + const selected = state.selected.value const Tab = (label: string) => { let onClick = () => { - setSelected(label); + state.selected.value = label; localStorage.tab = label; }; let className = "tab"; @@ -365,20 +385,24 @@ function Tabs() { }; useEffect(() => { - if (state.messages.value.length) setSelected(CONSOLE); + if (state.messages.value.length) state.selected.value = CONSOLE; }, [state.messages.value]); useEffect(() => { if (selected === JAVASCRIPT && !state.javascript.value) refreshJS(); - }, [selected, state.javascript.value]); + if (selected === SCHEME && !state.scheme.value) refreshScheme(); + }, [selected, state.javascript.value, state.scheme.value]); let body; switch (selected) { case RESULTS: body = h(Result, {}); break; + case SCHEME: + body = h(SourceViewer, {language: 'scheme'}); + break; case JAVASCRIPT: - body = h(JavaScript, {}); + body = h(SourceViewer, {language:'javascript'}); break; case CONSOLE: body = h(Console, {}); @@ -398,6 +422,7 @@ function Tabs() { { className: "tabBar" }, Tab(RESULTS), Tab(JAVASCRIPT), + Tab(SCHEME), Tab(CONSOLE), Tab(HELP), ), diff --git a/playground/src/newt.d.ts b/playground/src/newt.d.ts index a2166db..40c838b 100644 --- a/playground/src/newt.d.ts +++ b/playground/src/newt.d.ts @@ -5,3 +5,4 @@ export function LSP_checkFile(name: string): Diagnostic[]; export function LSP_hoverInfo(name: string, row: number, col: number): HoverResult | boolean | null; export function LSP_codeActionInfo(name: string, row: number, col: number): CodeAction[] | null; export function LSP_compileJS(name: string): string; +export function LSP_compileToScheme(name: string): string; diff --git a/playground/src/worker.ts b/playground/src/worker.ts index e951dcf..1fb01f0 100644 --- a/playground/src/worker.ts +++ b/playground/src/worker.ts @@ -1,7 +1,7 @@ import { shim } from "./emul"; import { API, Message, ResponseMSG } from "./ipc"; import { archive, preload } from "./preload"; -import { LSP_checkFile, LSP_codeActionInfo, LSP_compileJS, LSP_hoverInfo, LSP_updateFile } from './newt'; +import { LSP_checkFile, LSP_codeActionInfo, LSP_compileJS, LSP_compileToScheme, LSP_hoverInfo, LSP_updateFile } from './newt'; const LOG = console.log @@ -25,7 +25,10 @@ const api: API = { }, hoverInfo: LSP_hoverInfo, codeActionInfo: LSP_codeActionInfo, - compile: LSP_compileJS, + compile: (fn, lang) => { + if (lang == 'scheme') return LSP_compileToScheme(fn); + return LSP_compileJS(fn); + } } const handleMessage = async function (ev: { data: Message }) { diff --git a/src/LSP.newt b/src/LSP.newt index 6ee9a29..a7fd4c9 100644 --- a/src/LSP.newt +++ b/src/LSP.newt @@ -17,6 +17,7 @@ import Lib.ProcessDecl import Lib.Prettier import Lib.Error import Lib.CompileJS +import Lib.CompileScheme pfunc js_castArray : Array JSObject → JSObject := `x => x` pfunc js_castInt : Int → JSObject := `x => x` @@ -278,7 +279,6 @@ compileJS fn = unsafePerformIO $ do when (st.baseDir /= base) $ \ _ => resetState base repo <- lspFileSource (Right (top, src)) <- (do - putStrLn "woo" mod <- processModule emptyFC repo Nil modName docs <- compile let src = unlines $ @@ -290,4 +290,19 @@ compileJS fn = unsafePerformIO $ do modifyIORef state [ topContext := top ] pure $ js_castStr src -#export updateFile checkFile hoverInfo codeActionInfo compileJS docSymbols +compileToScheme : String → JSObject +compileToScheme fn = unsafePerformIO $ do + let (base, modName) = decomposeName fn + st <- readIORef state + when (st.baseDir /= base) $ \ _ => resetState base + repo <- lspFileSource + (Right (top, src)) <- (do + mod <- processModule emptyFC repo Nil modName + docs <- compileScheme + let src = unlines docs + pure src).runM st.topContext + | Left err => pure $ js_castStr ";; \{errorMsg err}" + modifyIORef state [ topContext := top ] + pure $ js_castStr src + +#export updateFile checkFile hoverInfo codeActionInfo compileJS docSymbols compileToScheme