add file saving and dirty tracking

This commit is contained in:
Quinten Kock 2025-11-10 01:49:50 +01:00
parent 49cbd4ac35
commit ec40c759d3
3 changed files with 37 additions and 21 deletions

View File

@ -63,9 +63,16 @@ export class Editor {
...searchKeymap, ...searchKeymap,
{ key: "Mod-z", run: () => undo(file.target) }, { key: "Mod-z", run: () => undo(file.target) },
{ key: "Mod-shift-z", run: () => redo(file.target) }, { key: "Mod-shift-z", run: () => redo(file.target) },
{
key: "Ctrl-s",
run: () => {
file.saveFile();
return true;
},
},
]); ]);
this.view = new EditorView({ this.view = new EditorView({
doc: file.rootState.doc, doc: file.rootState.val.doc,
dispatch: (trs) => this.dispatch(trs), dispatch: (trs) => this.dispatch(trs),
extensions: [ extensions: [
oneDark, oneDark,
@ -92,7 +99,7 @@ export class Editor {
}); });
const language = LanguageDescription.matchFilename( const language = LanguageDescription.matchFilename(
languages, languages,
file.filePath, file.filePath.val,
) )
?.load() ?.load()
.then((Lang) => { .then((Lang) => {

View File

@ -10,7 +10,12 @@ const EditorWrapper = (editor: any, del: any, k: any) =>
{ class: "flex flex-col" }, { class: "flex flex-col" },
v.div( v.div(
{ class: "flex" }, { 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", "❌"), u.InlineButton(del, "Close", "❌"),
), ),
v.div({ class: "flex-auto h-4" }, editor.val.dom), v.div({ class: "flex-auto h-4" }, editor.val.dom),

View File

@ -7,21 +7,25 @@ import {
} from "@codemirror/state"; } from "@codemirror/state";
import { history } from "@codemirror/commands"; import { history } from "@codemirror/commands";
import { Editor } from "./editor"; import { Editor } from "./editor";
import { State } from "vanjs-core";
import van from "vanjs-core";
const openFiles: { [path: string]: OpenFile } = {}; const openFiles: { [path: string]: OpenFile } = {};
export class OpenFile { export class OpenFile {
filePath: string; filePath: State<string>;
editors: Editor[]; editors: Editor[];
rootState: EditorState; rootState: State<EditorState>;
lastSaved?: Text; lastSaved?: State<Text>;
constructor(cfg: EditorStateConfig) { constructor(cfg: EditorStateConfig) {
this.filePath = null; this.filePath = van.state(null);
this.editors = []; this.editors = [];
this.rootState = EditorState.create(cfg).update({ this.rootState = van.state(
effects: [StateEffect.appendConfig.of([history()])], EditorState.create(cfg).update({
}).state; effects: [StateEffect.appendConfig.of([history()])],
}).state,
);
} }
static async openFile(filePath?: string): Promise<OpenFile> { static async openFile(filePath?: string): Promise<OpenFile> {
@ -30,24 +34,24 @@ export class OpenFile {
} }
const { content, path } = await window.electronAPI.readFile(filePath); const { content, path } = await window.electronAPI.readFile(filePath);
const file = new OpenFile({ doc: content }); const file = new OpenFile({ doc: content });
file.lastSaved = file.rootState.doc; file.lastSaved = van.state(file.rootState.val.doc);
file.setPath(path); file.setPath(path);
return file; return file;
} }
private setPath(path: string) { private setPath(path: string) {
delete openFiles[this.filePath]; delete openFiles[this.filePath?.val];
this.filePath = path; this.filePath.val = path;
openFiles[path] = this; openFiles[path] = this;
} }
async saveFile() { async saveFile() {
if (this.filePath) { if (this.filePath) {
await window.electronAPI.saveFile( await window.electronAPI.saveFile(
this.rootState.doc.toString(), this.rootState.val.doc.toString(),
this.filePath, this.filePath.val,
); );
this.lastSaved = this.rootState.doc; this.lastSaved.val = this.rootState.val.doc;
} else { } else {
await this.saveAs(); await this.saveAs();
} }
@ -55,11 +59,11 @@ export class OpenFile {
async saveAs(filePath?: string) { async saveAs(filePath?: string) {
const { path } = await window.electronAPI.saveFile( const { path } = await window.electronAPI.saveFile(
this.rootState.doc.toString(), this.rootState.val.doc.toString(),
filePath, filePath,
); );
this.setPath(path); 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 // Function to create and return a new EditorView for this file
@ -70,7 +74,7 @@ export class OpenFile {
} }
dispatch(trs: TransactionSpec, origin?: Editor) { dispatch(trs: TransactionSpec, origin?: Editor) {
this.rootState = this.rootState.update(trs).state; this.rootState.val = this.rootState.val.update(trs).state;
if (origin) { if (origin) {
const es = this.editors.filter((e) => e !== origin); const es = this.editors.filter((e) => e !== origin);
es.forEach((e) => e.dispatch(e.view.state.update(trs), true)); es.forEach((e) => e.dispatch(e.view.state.update(trs), true));
@ -84,12 +88,12 @@ export class OpenFile {
get target() { get target() {
console.log("Getting target"); console.log("Getting target");
return { return {
state: this.rootState, state: this.rootState.val,
dispatch: (tr: TransactionSpec) => this.dispatch(tr), dispatch: (tr: TransactionSpec) => this.dispatch(tr),
}; };
} }
isDirty(): boolean { isDirty(): boolean {
return this.lastSaved !== this.rootState.doc; return !this.lastSaved.val.eq(this.rootState.val.doc);
} }
} }