From ec40c759d39761ca1a320dfd0390dd55a2288098 Mon Sep 17 00:00:00 2001 From: Quinten Kock Date: Mon, 10 Nov 2025 01:49:50 +0100 Subject: [PATCH] add file saving and dirty tracking --- src/app/editor.ts | 11 +++++++++-- src/app/editorgrid.ts | 7 ++++++- src/app/filestate.ts | 40 ++++++++++++++++++++++------------------ 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/app/editor.ts b/src/app/editor.ts index 6fdcb73..5762659 100644 --- a/src/app/editor.ts +++ b/src/app/editor.ts @@ -63,9 +63,16 @@ export class Editor { ...searchKeymap, { key: "Mod-z", run: () => undo(file.target) }, { key: "Mod-shift-z", run: () => redo(file.target) }, + { + key: "Ctrl-s", + run: () => { + file.saveFile(); + return true; + }, + }, ]); this.view = new EditorView({ - doc: file.rootState.doc, + doc: file.rootState.val.doc, dispatch: (trs) => this.dispatch(trs), extensions: [ oneDark, @@ -92,7 +99,7 @@ export class Editor { }); const language = LanguageDescription.matchFilename( languages, - file.filePath, + file.filePath.val, ) ?.load() .then((Lang) => { diff --git a/src/app/editorgrid.ts b/src/app/editorgrid.ts index 6e910d1..5d51a6b 100644 --- a/src/app/editorgrid.ts +++ b/src/app/editorgrid.ts @@ -10,7 +10,12 @@ const EditorWrapper = (editor: any, del: any, k: any) => { class: "flex flex-col" }, v.div( { class: "flex" }, - v.span({ class: "mx-1 flex-1" }, "Editor " + k), + v.span( + { class: "mx-1 flex-1" }, + () => + editor.val.file.filePath.val + + (editor.val.file.isDirty() ? "*" : ""), + ), u.InlineButton(del, "Close", "❌"), ), v.div({ class: "flex-auto h-4" }, editor.val.dom), diff --git a/src/app/filestate.ts b/src/app/filestate.ts index 5178c2c..6ad5b01 100644 --- a/src/app/filestate.ts +++ b/src/app/filestate.ts @@ -7,21 +7,25 @@ import { } from "@codemirror/state"; import { history } from "@codemirror/commands"; import { Editor } from "./editor"; +import { State } from "vanjs-core"; +import van from "vanjs-core"; const openFiles: { [path: string]: OpenFile } = {}; export class OpenFile { - filePath: string; + filePath: State; editors: Editor[]; - rootState: EditorState; - lastSaved?: Text; + rootState: State; + lastSaved?: State; constructor(cfg: EditorStateConfig) { - this.filePath = null; + this.filePath = van.state(null); this.editors = []; - this.rootState = EditorState.create(cfg).update({ - effects: [StateEffect.appendConfig.of([history()])], - }).state; + this.rootState = van.state( + EditorState.create(cfg).update({ + effects: [StateEffect.appendConfig.of([history()])], + }).state, + ); } static async openFile(filePath?: string): Promise { @@ -30,24 +34,24 @@ export class OpenFile { } const { content, path } = await window.electronAPI.readFile(filePath); const file = new OpenFile({ doc: content }); - file.lastSaved = file.rootState.doc; + file.lastSaved = van.state(file.rootState.val.doc); file.setPath(path); return file; } private setPath(path: string) { - delete openFiles[this.filePath]; - this.filePath = path; + delete openFiles[this.filePath?.val]; + this.filePath.val = path; openFiles[path] = this; } async saveFile() { if (this.filePath) { await window.electronAPI.saveFile( - this.rootState.doc.toString(), - this.filePath, + this.rootState.val.doc.toString(), + this.filePath.val, ); - this.lastSaved = this.rootState.doc; + this.lastSaved.val = this.rootState.val.doc; } else { await this.saveAs(); } @@ -55,11 +59,11 @@ export class OpenFile { async saveAs(filePath?: string) { const { path } = await window.electronAPI.saveFile( - this.rootState.doc.toString(), + this.rootState.val.doc.toString(), filePath, ); this.setPath(path); - this.lastSaved = this.rootState.doc; + this.lastSaved.val = this.rootState.val.doc; } // Function to create and return a new EditorView for this file @@ -70,7 +74,7 @@ export class OpenFile { } dispatch(trs: TransactionSpec, origin?: Editor) { - this.rootState = this.rootState.update(trs).state; + this.rootState.val = this.rootState.val.update(trs).state; if (origin) { const es = this.editors.filter((e) => e !== origin); es.forEach((e) => e.dispatch(e.view.state.update(trs), true)); @@ -84,12 +88,12 @@ export class OpenFile { get target() { console.log("Getting target"); return { - state: this.rootState, + state: this.rootState.val, dispatch: (tr: TransactionSpec) => this.dispatch(tr), }; } isDirty(): boolean { - return this.lastSaved !== this.rootState.doc; + return !this.lastSaved.val.eq(this.rootState.val.doc); } }