feat: 补全终端交互逻辑.

This commit is contained in:
lijiahangmax
2024-01-12 02:05:27 +08:00
parent 6c0ad50b4e
commit c1a046f30e
14 changed files with 150 additions and 52 deletions

View File

@@ -1,11 +1,7 @@
import {
ITerminalChannel,
ITerminalOutputProcessor,
ITerminalSessionManager,
OutputPayload
} from '../types/terminal.type';
import { ITerminalChannel, ITerminalOutputProcessor, ITerminalSessionManager, OutputPayload } from '../types/terminal.type';
import { InputProtocol } from '../types/terminal.protocol';
import { TerminalStatus } from '../types/terminal.const';
import { useTerminalStore } from '@/store';
// 终端输出消息体处理器实现
export default class TerminalOutputProcessor implements ITerminalOutputProcessor {
@@ -29,8 +25,14 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
session.status = TerminalStatus.CLOSED;
return;
}
const { preference } = useTerminalStore();
// 发送 connect 命令
this.channel.send(InputProtocol.CONNECT, { sessionId, cols: session.inst.cols, rows: session.inst.rows });
this.channel.send(InputProtocol.CONNECT, {
sessionId,
terminalType: preference.sessionSetting.terminalEmulationType || 'xterm',
cols: session.inst.cols,
rows: session.inst.rows
});
}
// 处理连接消息

View File

@@ -1,14 +1,20 @@
import type { UnwrapRef } from 'vue';
import type { TerminalPreference } from '@/store/modules/terminal/types';
import type { ITerminalChannel, ITerminalSession, TerminalAddons } from '../types/terminal.type';
import { useTerminalStore } from '@/store';
import { fontFamilySuffix, TerminalStatus } from '../types/terminal.const';
import { InputProtocol } from '../types/terminal.protocol';
import { ITerminalOptions, Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { WebglAddon } from 'xterm-addon-webgl';
import { WebLinksAddon } from 'xterm-addon-web-links';
import { SearchAddon } from 'xterm-addon-search';
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';
const copy = useCopy();
// 终端会话实现
export default class TerminalSession implements ITerminalSession {
@@ -49,33 +55,29 @@ export default class TerminalSession implements ITerminalSession {
this.inst = new Terminal({
...(preference.displaySetting as any),
theme: preference.theme.schema,
fastScrollModifier: 'alt',
fastScrollModifier: !!preference.interactSetting.fastScrollModifier ? 'alt' : 'none',
altClickMovesCursor: !!preference.interactSetting.altClickMovesCursor,
rightClickSelectsWord: !!preference.interactSetting.rightClickSelectsWord,
fontFamily: preference.displaySetting.fontFamily + fontFamilySuffix,
wordSeparator: preference.interactSetting.wordSeparator,
scrollback: preference.sessionSetting.scrollBackLine,
});
// 注册快捷键
// 注册事件
this.registerEvent(dom, preference);
// 注册插件
this.addons.fit = new FitAddon();
// this.addons.webgl = new WebglAddon();
this.addons.canvas = new CanvasAddon();
this.addons.link = new WebLinksAddon();
this.addons.search = new SearchAddon();
this.addons.image = new ImageAddon();
for (const addon of Object.values(this.addons)) {
this.inst.loadAddon(addon);
}
this.registerAddions(preference);
// 打开终端
this.inst.open(dom);
// 自适应
this.addons.fit.fit();
}
// 设置已连接
connect(): void {
this.status = TerminalStatus.CONNECTED;
this.connected = true;
this.inst.focus();
// 注册事件
private registerEvent(dom: HTMLElement, preference: UnwrapRef<TerminalPreference>) {
// 注册输入事件
this.inst.onData(s => {
if (!this.canWrite) {
if (!this.canWrite || !this.connected) {
return;
}
// 输入
@@ -84,14 +86,81 @@ export default class TerminalSession implements ITerminalSession {
command: s
});
});
// 启用响铃
if (preference.interactSetting.enableBell) {
this.inst.onBell(() => {
// 播放蜂鸣
playBell();
});
}
// 选中复制
if (preference.interactSetting.selectionChangeCopy) {
this.inst.onSelectionChange(() => {
// 复制选中内容
this.copySelection();
});
}
// 注册 resize 事件
this.inst.onResize(({ cols, rows }) => {
if (!this.connected) {
return;
}
this.channel.send(InputProtocol.RESIZE, {
sessionId: this.sessionId,
cols,
rows
});
});
// 设置右键选项
dom.addEventListener('contextmenu', async (event) => {
// 如果开启了右键粘贴 右键选中 右键菜单 则关闭默认右键菜单
if (preference.interactSetting.rightClickSelectsWord
|| preference.interactSetting.rightClickPaste
|| preference.interactSetting.enableRightClickMenu) {
event.preventDefault();
}
// 右键粘贴逻辑
if (preference.interactSetting.rightClickPaste) {
if (!this.canWrite || !this.connected) {
return;
}
// 未开启右键选中 || 开启并无选中的内容则粘贴
if (!preference.interactSetting.rightClickSelectsWord || !this.inst.hasSelection()) {
this.pasteTrimEnd(await copy.readText());
}
}
});
}
// 注册插件
private registerAddions(preference: UnwrapRef<TerminalPreference>) {
this.addons.fit = new FitAddon();
this.addons.search = new SearchAddon();
// 超链接插件
if (preference.pluginsSetting.enableWeblinkPlugin) {
this.addons.weblink = new WebLinksAddon();
}
if (preference.pluginsSetting.enableWebglPlugin) {
// WebGL 渲染插件
this.addons.webgl = new WebglAddon();
} else {
// canvas 渲染插件
this.addons.canvas = new CanvasAddon();
}
// 图片渲染插件
if (preference.pluginsSetting.enableImagePlugin) {
this.addons.image = new ImageAddon();
}
for (const addon of Object.values(this.addons)) {
this.inst.loadAddon(addon);
}
}
// 设置已连接
connect(): void {
this.status = TerminalStatus.CONNECTED;
this.connected = true;
this.inst.focus();
}
// 设置是否可写
@@ -132,15 +201,36 @@ export default class TerminalSession implements ITerminalSession {
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();
}
// 获取选中
getSelection(): string {
const selection = this.inst.getSelection();
// 复制选中
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;
}