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 { autocompletion, closeBrackets } from "@codemirror/autocomplete";
|
||||||
import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
|
import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
|
||||||
import van from "vanjs-core";
|
import van from "vanjs-core";
|
||||||
|
import { Displayable } from "./displayable";
|
||||||
|
|
||||||
import { OpenFile } from "./filestate";
|
import { OpenFile } from "./filestate";
|
||||||
import { Displayable } from "./editorgrid";
|
|
||||||
|
|
||||||
const fixedHeightEditor = EditorView.theme({
|
const fixedHeightEditor = EditorView.theme({
|
||||||
"&": {
|
"&": {
|
||||||
|
|
@ -74,11 +74,9 @@ function fileStatusPanel(view: EditorView) {
|
||||||
"padding: 2px 10px; font-size: 12px; color: white; background: #900;";
|
"padding: 2px 10px; font-size: 12px; color: white; background: #900;";
|
||||||
return { top: true, dom };
|
return { top: true, dom };
|
||||||
}
|
}
|
||||||
|
export class Editor extends Displayable {
|
||||||
export class Editor implements Displayable {
|
|
||||||
view: EditorView;
|
view: EditorView;
|
||||||
file: OpenFile;
|
file: OpenFile;
|
||||||
deleteFn?: () => void;
|
|
||||||
|
|
||||||
private wordWrapCompartment = new Compartment();
|
private wordWrapCompartment = new Compartment();
|
||||||
private languageCompartment = new Compartment();
|
private languageCompartment = new Compartment();
|
||||||
|
|
@ -91,6 +89,7 @@ export class Editor implements Displayable {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(file: OpenFile) {
|
constructor(file: OpenFile) {
|
||||||
|
super();
|
||||||
this.file = file;
|
this.file = file;
|
||||||
const kmap = keymap.of([
|
const kmap = keymap.of([
|
||||||
...defaultKeymap,
|
...defaultKeymap,
|
||||||
|
|
@ -105,7 +104,6 @@ export class Editor implements Displayable {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ key: "Mod-w", run: () => this.close() },
|
|
||||||
{
|
{
|
||||||
key: "Alt-z",
|
key: "Alt-z",
|
||||||
run: () => {
|
run: () => {
|
||||||
|
|
@ -116,8 +114,6 @@ export class Editor implements Displayable {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ key: "Alt--", run: () => this.changeWidth(-100) },
|
|
||||||
{ key: "Alt-=", run: () => this.changeWidth(100) },
|
|
||||||
]);
|
]);
|
||||||
this.view = new EditorView({
|
this.view = new EditorView({
|
||||||
doc: file.rootState.val.doc,
|
doc: file.rootState.val.doc,
|
||||||
|
|
@ -149,9 +145,6 @@ export class Editor implements Displayable {
|
||||||
// lintKeymap,
|
// lintKeymap,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
this.view.dom.addEventListener("focusin", () =>
|
|
||||||
this.view.dom.scrollIntoView({ behavior: "smooth" }),
|
|
||||||
);
|
|
||||||
|
|
||||||
van.derive(() => {
|
van.derive(() => {
|
||||||
LanguageDescription.matchFilename(languages, file.filePath.val)
|
LanguageDescription.matchFilename(languages, file.filePath.val)
|
||||||
|
|
@ -185,12 +178,6 @@ export class Editor implements Displayable {
|
||||||
return this.file.filePath.val + (this.file.isDirty() ? "*" : "");
|
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() {
|
close() {
|
||||||
if (this.deleteFn) {
|
if (this.deleteFn) {
|
||||||
this.file.removeEditor(this, this.deleteFn);
|
this.file.removeEditor(this, this.deleteFn);
|
||||||
|
|
@ -205,8 +192,4 @@ export class Editor implements Displayable {
|
||||||
effects: compartment.reconfigure(on ? [] : extension),
|
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 * as u from "./utils";
|
||||||
import { Editor } from "./editor";
|
import { Editor } from "./editor";
|
||||||
import { Terminal } from "./terminal";
|
import { Terminal } from "./terminal";
|
||||||
|
import { Displayable } from "./displayable";
|
||||||
export interface Displayable {
|
|
||||||
setDeleteFunction(del: () => void): void;
|
|
||||||
title(): string;
|
|
||||||
close(): void;
|
|
||||||
focus(): void;
|
|
||||||
dom: HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditorWrapper = (
|
const EditorWrapper = (
|
||||||
editor: State<Displayable>,
|
editor: State<Displayable>,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { Displayable } from "./editorgrid";
|
import { Displayable } from "./displayable";
|
||||||
import * as xterm from "@xterm/xterm";
|
import * as xterm from "@xterm/xterm";
|
||||||
import { FitAddon } from "@xterm/addon-fit";
|
import { FitAddon } from "@xterm/addon-fit";
|
||||||
import van, { State } from "vanjs-core";
|
import van, { State } from "vanjs-core";
|
||||||
const v = van.tags;
|
const v = van.tags;
|
||||||
|
|
||||||
export class Terminal implements Displayable {
|
export class Terminal extends Displayable {
|
||||||
term: xterm.Terminal;
|
term: xterm.Terminal;
|
||||||
currentTitle: State<string> = van.state("Terminal");
|
currentTitle: State<string> = van.state("Terminal");
|
||||||
del: () => void;
|
|
||||||
dom: HTMLElement;
|
dom: HTMLElement;
|
||||||
private terminalId: string | null = null;
|
private terminalId: string | null = null;
|
||||||
private fitAddon: FitAddon;
|
private fitAddon: FitAddon;
|
||||||
|
|
@ -15,15 +14,12 @@ export class Terminal implements Displayable {
|
||||||
private unsubTerminalData?: () => void;
|
private unsubTerminalData?: () => void;
|
||||||
private unsubTerminalExit?: () => void;
|
private unsubTerminalExit?: () => void;
|
||||||
|
|
||||||
setDeleteFunction(del: () => void): void {
|
|
||||||
this.del = del;
|
|
||||||
}
|
|
||||||
|
|
||||||
title(): string {
|
title(): string {
|
||||||
return this.currentTitle.val;
|
return this.currentTitle.val;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super();
|
||||||
this.term = new xterm.Terminal({
|
this.term = new xterm.Terminal({
|
||||||
// cursorBlink: true,
|
// cursorBlink: true,
|
||||||
// fontSize: 14,
|
// fontSize: 14,
|
||||||
|
|
@ -33,8 +29,9 @@ export class Terminal implements Displayable {
|
||||||
this.fitAddon = new FitAddon();
|
this.fitAddon = new FitAddon();
|
||||||
this.term.loadAddon(this.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 = v.div({
|
||||||
this.dom.addEventListener("focusin", () => this.focus());
|
class: "h-full w-2xl resize-x overflow-x-hidden scroll-m-[100px]",
|
||||||
|
});
|
||||||
|
|
||||||
const loaded = van.state(false);
|
const loaded = van.state(false);
|
||||||
|
|
||||||
|
|
@ -124,6 +121,7 @@ export class Terminal implements Displayable {
|
||||||
if (this.unsubTerminalData) this.unsubTerminalData();
|
if (this.unsubTerminalData) this.unsubTerminalData();
|
||||||
if (this.unsubTerminalExit) this.unsubTerminalExit();
|
if (this.unsubTerminalExit) this.unsubTerminalExit();
|
||||||
this.term.dispose();
|
this.term.dispose();
|
||||||
this.del();
|
if (this.deleteFn) this.deleteFn();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue