Add Displayable abstract class
This commit is contained in:
parent
d9584e7543
commit
2f3d640ffb
|
|
@ -0,0 +1,78 @@
|
|||
export type KeyHandler = (e: KeyboardEvent) => void;
|
||||
|
||||
function canonicalizeEventKey(e: KeyboardEvent) {
|
||||
const mods = [] as string[];
|
||||
if (e.ctrlKey) mods.push("Ctrl");
|
||||
if (e.altKey) mods.push("Alt");
|
||||
if (e.shiftKey) mods.push("Shift");
|
||||
if (e.metaKey) mods.push("Meta");
|
||||
let k = e.key;
|
||||
if (k.length === 1) k = k.toLowerCase();
|
||||
mods.push(k);
|
||||
return mods.join("-");
|
||||
}
|
||||
|
||||
export abstract class Displayable {
|
||||
protected deleteFn?: () => void;
|
||||
private shortcuts = new Map<string, KeyHandler>();
|
||||
|
||||
constructor() {
|
||||
// Attempt to install handlers shortly after construction. If `dom` is not
|
||||
// available yet, retry a few times.
|
||||
setTimeout(() => this.installHandlers(0), 0);
|
||||
|
||||
// Add general shortcuts
|
||||
this.addShortcut("Ctrl-w", () => this.close());
|
||||
this.addShortcut("Alt--", () => this.changeWidth(-100));
|
||||
this.addShortcut("Alt-=", () => this.changeWidth(100));
|
||||
}
|
||||
|
||||
setDeleteFunction(fn: () => void) {
|
||||
this.deleteFn = fn;
|
||||
}
|
||||
|
||||
addShortcut(k: string, handler: KeyHandler) {
|
||||
this.shortcuts.set(k, handler);
|
||||
}
|
||||
|
||||
private handleKeyEvent(e: KeyboardEvent) {
|
||||
const k = canonicalizeEventKey(e);
|
||||
const h = this.shortcuts.get(k);
|
||||
if (h) {
|
||||
if (e.type == "keydown") h(e);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
changeWidth(increment: number) {
|
||||
const w = parseInt(window.getComputedStyle(this.dom).width, 10);
|
||||
this.dom.style.width = w + increment + "px";
|
||||
return true;
|
||||
}
|
||||
|
||||
private installHandlers(attempt: number) {
|
||||
try {
|
||||
const root = this.dom;
|
||||
if (!root) throw new Error("no dom");
|
||||
|
||||
const keyHandler = (e: KeyboardEvent) => this.handleKeyEvent(e);
|
||||
root.addEventListener("keydown", keyHandler, { capture: true });
|
||||
root.addEventListener("keyup", keyHandler, { capture: true });
|
||||
|
||||
root.addEventListener("focusin", () => {
|
||||
this.dom.scrollIntoView({ behavior: "smooth" });
|
||||
});
|
||||
} catch (err) {
|
||||
if (attempt < 5) {
|
||||
setTimeout(() => this.installHandlers(attempt + 1), 50);
|
||||
} else {
|
||||
console.error("Failed to install key handlers:", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract focus(): void;
|
||||
abstract title(): string;
|
||||
abstract close(): boolean;
|
||||
abstract get dom(): HTMLElement;
|
||||
}
|
||||
|
|
@ -33,9 +33,9 @@ import { languages } from "@codemirror/language-data";
|
|||
import { autocompletion, closeBrackets } from "@codemirror/autocomplete";
|
||||
import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
|
||||
import van from "vanjs-core";
|
||||
import { Displayable } from "./displayable";
|
||||
|
||||
import { OpenFile } from "./filestate";
|
||||
import { Displayable } from "./editorgrid";
|
||||
|
||||
const fixedHeightEditor = EditorView.theme({
|
||||
"&": {
|
||||
|
|
@ -74,11 +74,9 @@ function fileStatusPanel(view: EditorView) {
|
|||
"padding: 2px 10px; font-size: 12px; color: white; background: #900;";
|
||||
return { top: true, dom };
|
||||
}
|
||||
|
||||
export class Editor implements Displayable {
|
||||
export class Editor extends Displayable {
|
||||
view: EditorView;
|
||||
file: OpenFile;
|
||||
deleteFn?: () => void;
|
||||
|
||||
private wordWrapCompartment = new Compartment();
|
||||
private languageCompartment = new Compartment();
|
||||
|
|
@ -91,6 +89,7 @@ export class Editor implements Displayable {
|
|||
}
|
||||
|
||||
constructor(file: OpenFile) {
|
||||
super();
|
||||
this.file = file;
|
||||
const kmap = keymap.of([
|
||||
...defaultKeymap,
|
||||
|
|
@ -105,7 +104,6 @@ export class Editor implements Displayable {
|
|||
return true;
|
||||
},
|
||||
},
|
||||
{ key: "Mod-w", run: () => this.close() },
|
||||
{
|
||||
key: "Alt-z",
|
||||
run: () => {
|
||||
|
|
@ -116,8 +114,6 @@ export class Editor implements Displayable {
|
|||
return true;
|
||||
},
|
||||
},
|
||||
{ key: "Alt--", run: () => this.changeWidth(-100) },
|
||||
{ key: "Alt-=", run: () => this.changeWidth(100) },
|
||||
]);
|
||||
this.view = new EditorView({
|
||||
doc: file.rootState.val.doc,
|
||||
|
|
@ -149,9 +145,6 @@ export class Editor implements Displayable {
|
|||
// lintKeymap,
|
||||
],
|
||||
});
|
||||
this.view.dom.addEventListener("focusin", () =>
|
||||
this.view.dom.scrollIntoView({ behavior: "smooth" }),
|
||||
);
|
||||
|
||||
van.derive(() => {
|
||||
LanguageDescription.matchFilename(languages, file.filePath.val)
|
||||
|
|
@ -185,12 +178,6 @@ export class Editor implements Displayable {
|
|||
return this.file.filePath.val + (this.file.isDirty() ? "*" : "");
|
||||
}
|
||||
|
||||
changeWidth(increment: number) {
|
||||
const w = parseInt(window.getComputedStyle(this.view.dom).width, 10);
|
||||
this.view.dom.style.width = w + increment + "px";
|
||||
return true;
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.deleteFn) {
|
||||
this.file.removeEditor(this, this.deleteFn);
|
||||
|
|
@ -205,8 +192,4 @@ export class Editor implements Displayable {
|
|||
effects: compartment.reconfigure(on ? [] : extension),
|
||||
});
|
||||
}
|
||||
|
||||
setDeleteFunction(fn: () => void) {
|
||||
this.deleteFn = fn;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,7 @@ import { OpenFile } from "./filestate";
|
|||
import * as u from "./utils";
|
||||
import { Editor } from "./editor";
|
||||
import { Terminal } from "./terminal";
|
||||
|
||||
export interface Displayable {
|
||||
setDeleteFunction(del: () => void): void;
|
||||
title(): string;
|
||||
close(): void;
|
||||
focus(): void;
|
||||
dom: HTMLElement;
|
||||
}
|
||||
import { Displayable } from "./displayable";
|
||||
|
||||
const EditorWrapper = (
|
||||
editor: State<Displayable>,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { Displayable } from "./editorgrid";
|
||||
import { Displayable } from "./displayable";
|
||||
import * as xterm from "@xterm/xterm";
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
import van, { State } from "vanjs-core";
|
||||
const v = van.tags;
|
||||
|
||||
export class Terminal implements Displayable {
|
||||
export class Terminal extends Displayable {
|
||||
term: xterm.Terminal;
|
||||
currentTitle: State<string> = van.state("Terminal");
|
||||
del: () => void;
|
||||
dom: HTMLElement;
|
||||
private terminalId: string | null = null;
|
||||
private fitAddon: FitAddon;
|
||||
|
|
@ -15,15 +14,12 @@ export class Terminal implements Displayable {
|
|||
private unsubTerminalData?: () => void;
|
||||
private unsubTerminalExit?: () => void;
|
||||
|
||||
setDeleteFunction(del: () => void): void {
|
||||
this.del = del;
|
||||
}
|
||||
|
||||
title(): string {
|
||||
return this.currentTitle.val;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.term = new xterm.Terminal({
|
||||
// cursorBlink: true,
|
||||
// fontSize: 14,
|
||||
|
|
@ -33,8 +29,9 @@ export class Terminal implements Displayable {
|
|||
this.fitAddon = new FitAddon();
|
||||
this.term.loadAddon(this.fitAddon);
|
||||
|
||||
this.dom = v.div({ class: "h-full w-2xl resize-x overflow-x-hidden scroll-m-[100px]" });
|
||||
this.dom.addEventListener("focusin", () => this.focus());
|
||||
this.dom = v.div({
|
||||
class: "h-full w-2xl resize-x overflow-x-hidden scroll-m-[100px]",
|
||||
});
|
||||
|
||||
const loaded = van.state(false);
|
||||
|
||||
|
|
@ -124,6 +121,7 @@ export class Terminal implements Displayable {
|
|||
if (this.unsubTerminalData) this.unsubTerminalData();
|
||||
if (this.unsubTerminalExit) this.unsubTerminalExit();
|
||||
this.term.dispose();
|
||||
this.del();
|
||||
if (this.deleteFn) this.deleteFn();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue