refactor: 终端操作抽象.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<!-- 终端右键菜单 -->
|
||||
<a-dropdown class="terminal-context-menu"
|
||||
trigger="contextMenu"
|
||||
:popup-max-height="false"
|
||||
trigger="contextMenu"
|
||||
position="bl"
|
||||
alignPoint>
|
||||
<!-- 终端插槽 -->
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
// 打开 新
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
// 发送关闭消息
|
||||
|
||||
@@ -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
|
||||
// 打开 新
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user