refactor: 终端操作抽象.

This commit is contained in:
lijiahang
2024-01-16 11:42:12 +08:00
parent 419b364c9e
commit 18962c5e99
6 changed files with 132 additions and 309 deletions

View File

@@ -1,8 +1,8 @@
<template>
<!-- 终端右键菜单 -->
<a-dropdown class="terminal-context-menu"
trigger="contextMenu"
:popup-max-height="false"
trigger="contextMenu"
position="bl"
alignPoint>
<!-- 终端插槽 -->

View File

@@ -109,7 +109,7 @@
// 发送命令
const writeCommand = (value: string) => {
if (session.value?.canWrite) {
session.value.pasteTrimEnd(value);
session.value?.handler.pasteTrimEnd(value);
}
};
@@ -125,8 +125,7 @@
// 执行终端操作
const doTerminalHandle = (handle: string) => {
const handler = session.value?.handler[handle as keyof ITerminalSessionHandler] as () => void;
handler && handler.call(session.value?.handler);
session.value?.handler.invokeHandle.call(session.value?.handler, handle);
};
// 右侧操作
@@ -152,7 +151,7 @@
});
});
// 会话
// 关闭会话
onUnmounted(() => {
sessionManager.closeSession(props.tab.key);
});

View File

@@ -1,4 +1,4 @@
import type { TerminalPreference } from '@/store/modules/terminal/types';
import type { TerminalInteractSetting, TerminalShortcutKey } from '@/store/modules/terminal/types';
import type { ITerminalSession, ITerminalSessionHandler, ITerminalTabManager, TerminalDomRef } from '../types/terminal.type';
import type { Terminal } from 'xterm';
import { useTerminalStore } from '@/store';
@@ -15,7 +15,9 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
private readonly session: ITerminalSession;
private readonly preference: TerminalPreference;
private readonly interactSetting: TerminalInteractSetting;
private readonly shortcutKeys: Array<TerminalShortcutKey>;
private readonly tabManager: ITerminalTabManager;
@@ -25,7 +27,71 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
this.inst = session.inst;
this.domRef = domRef;
const { preference, tabManager } = useTerminalStore();
this.preference = preference;
this.interactSetting = preference.interactSetting;
// this.shortcutKeys = preference.shortcutSetting.keys;
this.shortcutKeys = [
{
option: 'copy',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'C'
}, {
option: 'paste',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'V'
}, {
option: 'toTop',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'ArrowUp'
}, {
option: 'toBottom',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'ArrowDown'
}, {
option: 'selectAll',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'A'
}, {
option: 'search',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'F'
}, {
option: 'fontSizePlus',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: '+'
}, {
option: 'fontSizeSubtract',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: '_'
}, {
option: 'commandEditor',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'O'
}, {
option: 'close',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'W'
}
];
this.tabManager = tabManager;
}
@@ -33,6 +99,7 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
enabledStatus(option: string): boolean {
switch (option) {
case 'paste':
case 'pasteTrimEnd':
case 'interrupt':
case 'enter':
case 'commandEditor':
@@ -44,12 +111,41 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
}
}
// 调用处理方法
invokeHandle(option: string) {
// 检测启用状态
if (!this.enabledStatus(option)) {
return;
}
// 调用实际处理方法
const handler = this[option as keyof this] as () => void;
handler && handler.call(this);
}
// 触发快捷键
triggerShortcutKey(e: KeyboardEvent): boolean {
// 检测触发的快捷键
const key = this.shortcutKeys.find(key => {
return key.key === e.key
&& key.altKey === e.altKey
&& key.shiftKey === e.shiftKey
&& key.ctrlKey === e.ctrlKey;
});
if (key) {
// 调用处理方法
this.invokeHandle.call(this, key.option);
return false;
} else {
return true;
}
}
// 复制选中
copy() {
let selection = this.inst.getSelection();
if (selection) {
// 去除尾部空格
if (this.preference.interactSetting.copyAutoTrim) {
if (this.interactSetting.copyAutoTrim) {
selection = selection.trimEnd();
}
// 复制
@@ -61,22 +157,18 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
// 粘贴
paste() {
if (this.enabledStatus('paste')) {
readText().then(s => this.pasteTrimEnd(s));
}
readText().then(s => this.pasteTrimEnd(s));
}
// 粘贴并且去除尾部空格 (如果配置)
pasteTrimEnd(value: string) {
if (this.enabledStatus('paste')) {
if (this.preference.interactSetting.pasteAutoTrim) {
// 粘贴前去除尾部空格
this.inst.paste(value.trimEnd());
} else {
this.inst.paste(value);
}
this.inst.focus();
if (this.interactSetting.pasteAutoTrim) {
// 粘贴前去除尾部空格
this.inst.paste(value.trimEnd());
} else {
this.inst.paste(value);
}
this.inst.focus();
}
// 选中全部
@@ -123,35 +215,29 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
// 打开命令编辑器
commandEditor() {
if (this.enabledStatus('commandEditor')) {
this.domRef.editorModal?.open('', '');
}
this.domRef.editorModal?.open('', '');
}
// ctrl + c
interrupt() {
if (this.enabledStatus('interrupt')) {
this.inst.paste(String.fromCharCode(3));
}
this.inst.paste(String.fromCharCode(3));
}
// 回车
enter() {
if (this.enabledStatus('enter')) {
this.inst.paste(String.fromCharCode(13));
}
this.inst.paste(String.fromCharCode(13));
}
// 清空
clear() {
this.inst.clear();
this.inst.clearSelection();
this.inst.focus();
}
// 断开连接
disconnect() {
if (this.enabledStatus('disconnect')) {
this.session.disconnect();
}
this.session.disconnect();
}
// 关闭
@@ -159,9 +245,8 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
this.tabManager.deleteTab(this.session.sessionId);
}
// 聚焦
focus() {
this.inst.focus();
}
// todo
// 切换 tab
// 打开 新
}

View File

@@ -4,7 +4,7 @@ import type { ITerminalChannel, ITerminalSession, ITerminalSessionHandler, Termi
import { useTerminalStore } from '@/store';
import { fontFamilySuffix, TerminalStatus } from '../types/terminal.const';
import { InputProtocol } from '../types/terminal.protocol';
import { ITerminalOptions, Terminal } from 'xterm';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { WebLinksAddon } from 'xterm-addon-web-links';
import { ISearchOptions, SearchAddon } from 'xterm-addon-search';
@@ -12,12 +12,8 @@ import { ImageAddon } from 'xterm-addon-image';
import { CanvasAddon } from 'xterm-addon-canvas';
import { WebglAddon } from 'xterm-addon-webgl';
import { playBell } from '@/utils/bell';
import useCopy from '@/hooks/copy';
import TerminalShortcutDispatcher from './terminal-shortcut-dispatch';
import TerminalSessionHandler from './terminal-session-handler';
const copy = useCopy();
// 终端会话实现
export default class TerminalSession implements ITerminalSession {
@@ -83,7 +79,6 @@ export default class TerminalSession implements ITerminalSession {
// 注册快捷键
private registerShortcut(preference: UnwrapRef<TerminalPreference>) {
const dispatcher = new TerminalShortcutDispatcher(this, preference.shortcutSetting.keys);
// 处理自定义按键
this.inst.attachCustomKeyEventHandler((e: KeyboardEvent) => {
e.preventDefault();
@@ -95,8 +90,8 @@ export default class TerminalSession implements ITerminalSession {
if (e.type !== 'keydown') {
return true;
}
// 调度快捷键
return dispatcher.dispatch(e);
// 触发快捷键
return this.handler.triggerShortcutKey(e);
});
}
@@ -124,7 +119,7 @@ export default class TerminalSession implements ITerminalSession {
if (preference.interactSetting.selectionChangeCopy) {
this.inst.onSelectionChange(() => {
// 复制选中内容
this.copySelection();
this.handler.copy();
});
}
// 注册 resize 事件
@@ -139,7 +134,7 @@ export default class TerminalSession implements ITerminalSession {
});
});
// 设置右键选项
dom.addEventListener('contextmenu', async (event) => {
dom.addEventListener('contextmenu', async () => {
// 右键粘贴逻辑
if (preference.interactSetting.rightClickPaste) {
if (!this.canWrite || !this.connected) {
@@ -147,7 +142,7 @@ export default class TerminalSession implements ITerminalSession {
}
// 未开启右键选中 || 开启并无选中的内容则粘贴
if (!preference.interactSetting.rightClickSelectsWord || !this.inst.hasSelection()) {
this.pasteTrimEnd(await copy.readText());
this.handler.paste();
}
}
});
@@ -172,6 +167,7 @@ export default class TerminalSession implements ITerminalSession {
if (preference.pluginsSetting.enableImagePlugin) {
this.addons.image = new ImageAddon();
}
// 加载插件
for (const addon of Object.values(this.addons)) {
this.inst.loadAddon(addon);
}
@@ -209,53 +205,6 @@ export default class TerminalSession implements ITerminalSession {
this.inst.focus();
}
// 清空
clear(): void {
this.inst.clear();
this.inst.clearSelection();
this.inst.focus();
}
// 粘贴
paste(value: string): void {
this.inst.paste(value);
this.inst.focus();
}
// 粘贴并且去除尾部空格 (如果配置)
pasteTrimEnd(value: string): void {
if (useTerminalStore().preference.interactSetting.pasteAutoTrim) {
// 粘贴前去除尾部空格
this.inst.paste(value.trimEnd());
} else {
this.inst.paste(value);
}
this.inst.focus();
}
// 选中全部
selectAll(): void {
this.inst.selectAll();
this.inst.focus();
}
// 复制选中
copySelection(): string {
let selection = this.inst.getSelection();
if (selection) {
// 去除尾部空格
const { preference } = useTerminalStore();
if (preference.interactSetting.copyAutoTrim) {
selection = selection.trimEnd();
}
// 复制
copy.copy(selection, false);
}
// 聚焦
this.inst.focus();
return selection;
}
// 查找
find(word: string, next: boolean, options: ISearchOptions): void {
if (next) {
@@ -265,28 +214,6 @@ export default class TerminalSession implements ITerminalSession {
}
}
// 去顶部
toTop(): void {
this.inst.scrollToTop();
this.inst.focus();
}
// 去底部
toBottom(): void {
this.inst.scrollToBottom();
this.inst.focus();
}
// 获取配置
getOption(option: string): any {
return this.inst.options[option as keyof ITerminalOptions] as any;
}
// 设置配置
setOption(option: string, value: any): void {
this.inst.options[option as keyof ITerminalOptions] = value;
}
// 断开连接
disconnect(): void {
// 发送关闭消息

View File

@@ -1,167 +0,0 @@
import type { TerminalShortcutKey } from '@/store/modules/terminal/types';
import type { ITerminalSession, ITerminalShortcutDispatcher } from '../types/terminal.type';
import useCopy from '@/hooks/copy';
const { readText } = useCopy();
// 终端快捷键调度实现
export default class TerminalShortcutDispatch implements ITerminalShortcutDispatcher {
private readonly session: ITerminalSession;
private readonly keys: Array<TerminalShortcutKey>;
constructor(session: ITerminalSession, keys: Array<TerminalShortcutKey>) {
this.session = session;
// this.keys = keys;
this.keys = [
{
option: 'copy',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'C'
}, {
option: 'paste',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'V'
}, {
option: 'toTop',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'ArrowUp'
}, {
option: 'toBottom',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'ArrowDown'
}, {
option: 'selectAll',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'A'
}, {
option: 'search',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'F'
}, {
option: 'fontSizePlus',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: '+'
}, {
option: 'fontSizeSubtract',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: '_'
}, {
option: 'commandEditor',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'O'
}, {
option: 'close',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'W'
}
];
}
// 调度快捷键
dispatch(e: KeyboardEvent): boolean {
console.log(e);
for (const key of this.keys) {
if (key.altKey === e.altKey
&& key.shiftKey === e.shiftKey
&& key.ctrlKey === e.ctrlKey
&& key.key === e.key) {
const runner = this[key.option as keyof this] as () => void;
runner && runner.apply(this);
return false;
}
}
return true;
}
// 复制
private copy(): void {
this.session.copySelection();
}
// 粘贴
private paste(): void {
// FIXME status
readText().then((e) => {
this.session.pasteTrimEnd(e);
});
}
// 去顶部
private toTop(): void {
this.session.toTop();
}
// 去底部
private toBottom(): void {
this.session.toBottom();
}
// 全选
private selectAll(): void {
this.session.selectAll();
}
// 搜索
private search(): void {
// fixme
}
// 增大字号
private fontSizePlus(): void {
this.fontSizeAdd(1);
}
// 减小字号
private fontSizeSubtract(): void {
this.fontSizeAdd(-1);
}
// 字号增加
private fontSizeAdd(addSize: number) {
this.session.setOption('fontSize', this.session.getOption('fontSize') + addSize);
if (this.session.connected) {
this.session.fit();
this.session.focus();
}
}
// 命令编辑器
private commandEditor(): void {
// fixme
}
// 关闭终端
private close(): void {
}
private to(): void {
}
// 切换 tab
// 打开 新
}

View File

@@ -176,26 +176,8 @@ export interface ITerminalSession {
fit: () => void;
// 聚焦
focus: () => void;
// 清空
clear: () => void;
// 粘贴
paste: (value: string) => void;
// 粘贴并且去除尾部空格 (如果配置)
pasteTrimEnd: (value: string) => void;
// 选中全部
selectAll: () => void;
// 复制选中
copySelection: () => string;
// 查找
find: (word: string, next: boolean, options: ISearchOptions) => void;
// 去顶部
toTop: () => void;
// 去底部
toBottom: () => void;
// 获取配置
getOption: (option: string) => any;
// 设置配置
setOption: (option: string, value: any) => void;
// 断开连接
disconnect: () => void;
// 关闭
@@ -206,6 +188,11 @@ export interface ITerminalSession {
export interface ITerminalSessionHandler {
// 启用状态
enabledStatus: (option: string) => boolean;
// 调用处理方法
invokeHandle: (option: string) => void;
// 触发快捷键
triggerShortcutKey: (e: KeyboardEvent) => boolean;
// 复制选中
copy: () => void;
// 粘贴
@@ -236,12 +223,4 @@ export interface ITerminalSessionHandler {
disconnect: () => void;
// 关闭
close: () => void;
// 聚焦
focus: () => void;
}
// 终端快捷键调度定义
export interface ITerminalShortcutDispatcher {
// 调度快捷键
dispatch(e: KeyboardEvent): boolean;
}