mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-29 18:31:33 +00:00
97 lines
2.9 KiB
TypeScript
97 lines
2.9 KiB
TypeScript
import type { Terminal } from "@xterm/xterm";
|
|
import type { KeybindingAction } from "@/types/keybindings";
|
|
import {
|
|
hasSnippetInputs,
|
|
resolveSnippetContent,
|
|
type SnippetHostContext,
|
|
} from "@/lib/snippet-variables";
|
|
|
|
export interface KeybindingDispatchContext {
|
|
terminal: Terminal;
|
|
webSocketRef: React.MutableRefObject<WebSocket | null>;
|
|
writeTextToClipboard: (text: string) => Promise<boolean>;
|
|
readTextFromClipboard: () => Promise<string>;
|
|
getSnippetById: (id: string) => Promise<{ content: string } | undefined>;
|
|
/** Host context used to silently resolve $HOST/$USER/$PORT/$NAME in runSnippet actions. */
|
|
hostContext?: SnippetHostContext | null;
|
|
/**
|
|
* Called instead of sending the snippet directly when its content still has
|
|
* unresolved $INPUT_n placeholders after host-variable substitution -- the
|
|
* caller is expected to collect values (e.g. via a dialog) and send itself.
|
|
*/
|
|
onSnippetNeedsInputs?: (snippet: { id: string; content: string }) => void;
|
|
}
|
|
|
|
export function sendRawToSocket(
|
|
webSocketRef: React.MutableRefObject<WebSocket | null>,
|
|
data: string,
|
|
): void {
|
|
if (webSocketRef.current?.readyState === 1) {
|
|
webSocketRef.current.send(JSON.stringify({ type: "input", data }));
|
|
}
|
|
}
|
|
|
|
export function dispatchKeybindingAction(
|
|
action: KeybindingAction,
|
|
ctx: KeybindingDispatchContext,
|
|
): void {
|
|
const sendRaw = (data: string) => sendRawToSocket(ctx.webSocketRef, data);
|
|
|
|
switch (action.type) {
|
|
case "copy": {
|
|
const selection = ctx.terminal.getSelection();
|
|
if (selection) {
|
|
ctx.writeTextToClipboard(selection);
|
|
ctx.terminal.clearSelection();
|
|
}
|
|
return;
|
|
}
|
|
case "paste": {
|
|
ctx.readTextFromClipboard().then((text) => {
|
|
if (text) ctx.terminal.paste(text);
|
|
});
|
|
return;
|
|
}
|
|
case "sendControlCode": {
|
|
if (!action.controlCode) return;
|
|
const code = action.controlCode.toLowerCase().charCodeAt(0) - 96;
|
|
if (code >= 1 && code <= 26) sendRaw(String.fromCharCode(code));
|
|
return;
|
|
}
|
|
case "sendText": {
|
|
sendRaw((action.text ?? "") + (action.appendEnter ? "\r" : ""));
|
|
return;
|
|
}
|
|
case "runSnippet": {
|
|
if (!action.snippetId) return;
|
|
ctx.getSnippetById(action.snippetId).then((snippet) => {
|
|
if (!snippet) return;
|
|
if (hasSnippetInputs(snippet.content)) {
|
|
ctx.onSnippetNeedsInputs?.({
|
|
id: action.snippetId!,
|
|
content: snippet.content,
|
|
});
|
|
return;
|
|
}
|
|
const resolved = resolveSnippetContent(
|
|
snippet.content,
|
|
ctx.hostContext ?? null,
|
|
{},
|
|
);
|
|
sendRaw(resolved + (action.appendEnter !== false ? "\r" : ""));
|
|
});
|
|
return;
|
|
}
|
|
case "nextTab":
|
|
case "previousTab":
|
|
case "openCommandPalette": {
|
|
window.dispatchEvent(
|
|
new CustomEvent("termix:global-keybinding", {
|
|
detail: { type: action.type },
|
|
}),
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|