Add basic multi-view support
This commit is contained in:
parent
1d31225dad
commit
001d215b0e
|
|
@ -1,7 +1,10 @@
|
||||||
import { basicSetup } from "codemirror";
|
import { Transaction } from "@codemirror/state";
|
||||||
import { EditorView } from "@codemirror/view";
|
import { EditorView, keymap } from "@codemirror/view";
|
||||||
|
import { defaultKeymap, undo, redo } from "@codemirror/commands";
|
||||||
import { oneDark } from "@codemirror/theme-one-dark";
|
import { oneDark } from "@codemirror/theme-one-dark";
|
||||||
|
|
||||||
|
import { OpenFile } from "./filestate";
|
||||||
|
|
||||||
const fixedHeightEditor = EditorView.theme({
|
const fixedHeightEditor = EditorView.theme({
|
||||||
"&": {
|
"&": {
|
||||||
height: "100%",
|
height: "100%",
|
||||||
|
|
@ -11,17 +14,33 @@ const fixedHeightEditor = EditorView.theme({
|
||||||
width: "600px",
|
width: "600px",
|
||||||
minWidth: "8em",
|
minWidth: "8em",
|
||||||
flex: "none",
|
flex: "none",
|
||||||
|
fontSize: "16px",
|
||||||
},
|
},
|
||||||
".cm-scroller": { overflow: "auto scroll" },
|
".cm-scroller": { overflow: "auto scroll" },
|
||||||
});
|
});
|
||||||
|
|
||||||
export class Editor {
|
export class Editor {
|
||||||
view: EditorView;
|
view: EditorView;
|
||||||
|
file: OpenFile;
|
||||||
|
|
||||||
constructor() {
|
dispatch(tr: Transaction, inhibitSync = false) {
|
||||||
|
this.view.update([tr]);
|
||||||
|
if (!inhibitSync) {
|
||||||
|
this.file.dispatch({ changes: tr.changes }, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(file: OpenFile) {
|
||||||
|
this.file = file;
|
||||||
|
const kmap = keymap.of([
|
||||||
|
...defaultKeymap,
|
||||||
|
{ key: "Mod-z", run: () => undo(file.target) },
|
||||||
|
{ key: "Mod-shift-z", run: () => redo(file.target) },
|
||||||
|
]);
|
||||||
this.view = new EditorView({
|
this.view = new EditorView({
|
||||||
doc: "Start document",
|
doc: file.rootState.doc,
|
||||||
extensions: [basicSetup, oneDark, fixedHeightEditor],
|
dispatch: (trs) => this.dispatch(trs),
|
||||||
|
extensions: [oneDark, fixedHeightEditor, kmap],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import van from "vanjs-core";
|
||||||
import * as vanX from "vanjs-ext";
|
import * as vanX from "vanjs-ext";
|
||||||
const v = van.tags;
|
const v = van.tags;
|
||||||
|
|
||||||
import { Editor } from "./editor";
|
import { OpenFile } from "./filestate";
|
||||||
import * as u from "./utils";
|
import * as u from "./utils";
|
||||||
|
|
||||||
const EditorWrapper = (editor: any, del: any, k: any) =>
|
const EditorWrapper = (editor: any, del: any, k: any) =>
|
||||||
|
|
@ -20,9 +20,10 @@ const editors = vanX.reactive([[]]);
|
||||||
const currentTab = van.state(0);
|
const currentTab = van.state(0);
|
||||||
van.derive(() => console.log("Setting tab to", currentTab.val));
|
van.derive(() => console.log("Setting tab to", currentTab.val));
|
||||||
|
|
||||||
export function addEditor() {
|
export function addEditor(file: OpenFile) {
|
||||||
console.log("Adding editor to tab ", currentTab.val, editors);
|
console.log("Adding editor to tab ", currentTab.val, editors);
|
||||||
editors[currentTab.val].push(vanX.noreactive(new Editor()));
|
const editor = file.createEditor();
|
||||||
|
editors[currentTab.val].push(vanX.noreactive(editor));
|
||||||
}
|
}
|
||||||
export function addTab() {
|
export function addTab() {
|
||||||
editors.push([]);
|
editors.push([]);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import {
|
||||||
|
EditorState,
|
||||||
|
EditorStateConfig,
|
||||||
|
TransactionSpec,
|
||||||
|
StateEffect,
|
||||||
|
} from "@codemirror/state";
|
||||||
|
import { history } from "@codemirror/commands";
|
||||||
|
import { Editor } from "./editor";
|
||||||
|
|
||||||
|
const openFiles: { [path: string]: OpenFile } = {};
|
||||||
|
|
||||||
|
export class OpenFile {
|
||||||
|
filePath: string;
|
||||||
|
editors: Editor[];
|
||||||
|
rootState: EditorState;
|
||||||
|
|
||||||
|
constructor(cfg: EditorStateConfig) {
|
||||||
|
this.filePath = null;
|
||||||
|
this.editors = [];
|
||||||
|
this.rootState = EditorState.create(cfg).update({
|
||||||
|
effects: [StateEffect.appendConfig.of([history()])],
|
||||||
|
}).state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async openFile(filePath?: string) {
|
||||||
|
const { content, path } = await window.electronAPI.readFile(filePath);
|
||||||
|
const file = new OpenFile({ doc: content });
|
||||||
|
file.setPath(path);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setPath(path: string) {
|
||||||
|
delete openFiles[this.filePath];
|
||||||
|
this.filePath = path;
|
||||||
|
openFiles[path] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveFile() {
|
||||||
|
if (this.filePath) {
|
||||||
|
await window.electronAPI.saveFile(
|
||||||
|
this.rootState.doc.toString(),
|
||||||
|
this.filePath,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await this.saveAs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveAs(filePath?: string) {
|
||||||
|
const { path } = await window.electronAPI.saveFile(
|
||||||
|
this.rootState.doc.toString(),
|
||||||
|
filePath,
|
||||||
|
);
|
||||||
|
this.setPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to create and return a new EditorView for this file
|
||||||
|
createEditor(): Editor {
|
||||||
|
const editor = new Editor(this);
|
||||||
|
this.editors.push(editor);
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(trs: TransactionSpec, origin?: Editor) {
|
||||||
|
console.log("Dispatching trs", trs, "to", this.editors, "from", origin);
|
||||||
|
console.log(this.rootState);
|
||||||
|
this.rootState = this.rootState.update(trs).state;
|
||||||
|
if (origin) {
|
||||||
|
const es = this.editors.filter((e) => e !== origin);
|
||||||
|
es.forEach((e) => e.dispatch(e.view.state.update(trs), true));
|
||||||
|
} else {
|
||||||
|
this.editors.forEach((e) =>
|
||||||
|
e.dispatch(e.view.state.update(trs), true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get target() {
|
||||||
|
console.log("Getting target");
|
||||||
|
return {
|
||||||
|
state: this.rootState,
|
||||||
|
dispatch: (tr: TransactionSpec) => this.dispatch(tr),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,13 +2,17 @@
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
import van from "vanjs-core";
|
import van from "vanjs-core";
|
||||||
import * as vanX from "vanjs-ext";
|
|
||||||
const v = van.tags;
|
const v = van.tags;
|
||||||
|
|
||||||
import { Editor } from "./editor";
|
|
||||||
import { FolderTreeView } from "./foldernav";
|
import { FolderTreeView } from "./foldernav";
|
||||||
import { EditorTabs, addTab, addEditor } from "./editorgrid";
|
import { EditorTabs, addTab, addEditor } from "./editorgrid";
|
||||||
import * as u from "./utils";
|
import * as u from "./utils";
|
||||||
|
import { OpenFile } from "./filestate";
|
||||||
|
|
||||||
|
function newFile() {
|
||||||
|
const file = new OpenFile({});
|
||||||
|
addEditor(file);
|
||||||
|
}
|
||||||
|
|
||||||
const app = v.div(
|
const app = v.div(
|
||||||
{ class: "h-screen max-h-screen w-screen max-w-screen flex" },
|
{ class: "h-screen max-h-screen w-screen max-w-screen flex" },
|
||||||
|
|
@ -17,7 +21,7 @@ const app = v.div(
|
||||||
class: "flex-none resize-x overflow-x-hidden overflow-y-scroll w-3xs min-w-32",
|
class: "flex-none resize-x overflow-x-hidden overflow-y-scroll w-3xs min-w-32",
|
||||||
},
|
},
|
||||||
u.InlineButton(addTab, "Add Tab", "+Tab"),
|
u.InlineButton(addTab, "Add Tab", "+Tab"),
|
||||||
u.InlineButton(addEditor, "Add Editor", "+File"),
|
u.InlineButton(newFile, "Add Editor", "+File"),
|
||||||
FolderTreeView,
|
FolderTreeView,
|
||||||
),
|
),
|
||||||
EditorTabs,
|
EditorTabs,
|
||||||
|
|
@ -25,4 +29,6 @@ const app = v.div(
|
||||||
|
|
||||||
van.add(document.body, app);
|
van.add(document.body, app);
|
||||||
|
|
||||||
addEditor();
|
const file = new OpenFile({});
|
||||||
|
addEditor(file);
|
||||||
|
addEditor(file);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue