feat: 命令右键菜单.

This commit is contained in:
lijiahang
2024-01-25 17:43:44 +08:00
parent 3011ecee9e
commit 6ac99b692d
10 changed files with 203 additions and 62 deletions

View File

@@ -75,15 +75,19 @@ export interface TerminalShortcutSetting {
} }
// 终端快捷键 // 终端快捷键
export interface TerminalShortcutKey { export interface ShortcutKey {
item: string;
enabled: boolean;
ctrlKey: boolean; ctrlKey: boolean;
shiftKey: boolean; shiftKey: boolean;
altKey: boolean; altKey: boolean;
code: string; code: string;
} }
// 终端快捷键
export interface TerminalShortcutKey extends ShortcutKey {
item: string;
enabled: boolean;
}
// 终端快捷键编辑 // 终端快捷键编辑
export interface TerminalShortcutKeyEditable extends TerminalShortcutKey { export interface TerminalShortcutKeyEditable extends TerminalShortcutKey {
editable: boolean; editable: boolean;

View File

@@ -294,4 +294,10 @@ body[terminal-theme='dark'] .arco-modal-container {
align-items: center; align-items: center;
} }
} }
&-icon {
font-size: 16px;
margin: 0 8px 0 4px;
}
} }

View File

@@ -5,7 +5,7 @@
@close="onClose"> @close="onClose">
<!-- 表头 --> <!-- 表头 -->
<template #title> <template #title>
<span class="snippet-drawer-title"> <span class="snippet-drawer-title usn">
<icon-code /> <icon-code />
命令片段 命令片段
</span> </span>

View File

@@ -45,20 +45,24 @@
:deep(.arco-collapse-item) { :deep(.arco-collapse-item) {
border: none; border: none;
.arco-collapse-item-header-title { &-header {
user-select: none;
}
.arco-collapse-item-header {
border: none; border: none;
&-title {
user-select: none;
}
&-extra {
user-select: none;
}
} }
.arco-collapse-item-content { &-content {
background-color: unset; background-color: unset;
padding: 0; padding: 0;
} }
.arco-collapse-item-content-box { &-content-box {
padding: 0; padding: 0;
} }
} }

View File

@@ -1,47 +1,108 @@
<template> <template>
<div class="snippet-item-wrapper" <a-dropdown class="terminal-context-menu"
:class="[!!item.expand ? 'snippet-item-wrapper-expand' : '']" :popup-max-height="false"
@click="expandItem"> trigger="contextMenu"
<div class="snippet-item"> position="bl"
<div class="snippet-item-title"> alignPoint>
<!-- 名称 --> <!-- 命令 -->
<span class="snippet-item-title-name"> <div class="snippet-item-wrapper"
:class="[!!item.expand ? 'snippet-item-wrapper-expand' : '']"
@click="clickItem">
<div class="snippet-item">
<div class="snippet-item-title">
<!-- 名称 -->
<span class="snippet-item-title-name">
{{ item.name }} {{ item.name }}
</span> </span>
<!-- 操作 --> <!-- 操作 -->
<div class="snippet-item-title-actions"> <div class="snippet-item-title-actions">
<a-space> <a-space>
<!-- 粘贴 --> <!-- 粘贴 -->
<a-tag class="pointer usn" <a-tag class="pointer usn"
size="small" size="small"
:checkable="true" :checkable="true"
:checked="true" :checked="true"
@click.stop="paste"> @click.stop.prevent="paste">
<template #icon> <template #icon>
<icon-paste /> <icon-paste />
</template> </template>
粘贴 粘贴
</a-tag> </a-tag>
<!-- 执行 --> <!-- 执行 -->
<a-tag class="pointer usn" <a-tag class="pointer usn"
size="small" size="small"
:checkable="true" :checkable="true"
:checked="true" :checked="true"
@click.stop="exec"> @click.stop="exec">
<template #icon> <template #icon>
<icon-thunderbolt /> <icon-thunderbolt />
</template> </template>
执行 执行
</a-tag> </a-tag>
</a-space> </a-space>
</div>
</div> </div>
</div> <!-- 命令 -->
<!-- 命令 --> <span class="snippet-item-command">
<span class="snippet-item-command">
{{ item.command }} {{ item.command }}
</span> </span>
</div>
</div> </div>
</div> <!-- 右键菜单 -->
<template #content>
<!-- 复制 -->
<a-doption @click="copyCommand">
<div class="terminal-context-menu-icon">
<icon-copy />
</div>
<div>复制</div>
</a-doption>
<!-- 粘贴 -->
<a-doption @click="paste">
<div class="terminal-context-menu-icon">
<icon-paste />
</div>
<div>粘贴</div>
</a-doption>
<!-- 执行 -->
<a-doption @click="exec">
<div class="terminal-context-menu-icon">
<icon-thunderbolt />
</div>
<div>执行</div>
</a-doption>
<!-- 修改 -->
<a-doption @click="exec">
<div class="terminal-context-menu-icon">
<icon-edit />
</div>
<div>修改</div>
</a-doption>
<!-- 删除 -->
<a-doption @click="exec">
<div class="terminal-context-menu-icon">
<icon-delete />
</div>
<div>删除</div>
</a-doption>
<!-- 展开 -->
<a-doption v-if="!item.expand"
@click="() => item.expand = true">
<div class="terminal-context-menu-icon">
<icon-expand />
</div>
<div>展开</div>
</a-doption>
<!-- 收起 -->
<a-doption v-else
@click="() => item.expand = false">
<div class="terminal-context-menu-icon">
<icon-shrink />
</div>
<div>收起</div>
</a-doption>
</template>
</a-dropdown>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -53,18 +114,44 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { CommandSnippetQueryResponse } from '@/api/asset/command-snippet'; import type { CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import { useDebounceFn } from '@vueuse/core';
import useCopy from '@/hooks/copy';
const props = defineProps<{ const props = defineProps<{
item: CommandSnippetQueryResponse item: CommandSnippetQueryResponse
}>(); }>();
const { copy } = useCopy();
const { getCurrentTerminalSession } = useTerminalStore(); const { getCurrentTerminalSession } = useTerminalStore();
// TODO 右键菜单 复制 粘贴 删除 执行 修改 // TODO 修改 删除 拼接有bug
// 展开命令 let clickCount = 0;
const expandItem = () => {
props.item.expand = !props.item.expand; // 点击命令
const clickItem = () => {
if (++clickCount == 2) {
clickCount = 0;
exec();
} else {
expandItem();
}
};
// 展开
const expandItem = useDebounceFn(() => {
setTimeout(() => {
// 为 0 则代表为双击
if (clickCount !== 0) {
props.item.expand = !props.item.expand;
clickCount = 0;
}
}, 50);
});
// 复制命令
const copyCommand = () => {
copy(props.item.command, false);
}; };
// 粘贴 // 粘贴
@@ -116,6 +203,7 @@
text-overflow: unset; text-overflow: unset;
word-break: break-all; word-break: break-all;
white-space: unset; white-space: unset;
user-select: unset;
} }
} }
} }
@@ -153,6 +241,7 @@
height: 24px; height: 24px;
display: flex; display: flex;
align-items: center; align-items: center;
user-select: none;
&-name { &-name {
width: @item-inline-width; width: @item-inline-width;
@@ -173,7 +262,9 @@
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: pre; white-space: pre;
width: @item-inline-width; width: @item-inline-width;
user-select: none;
} }
} }
} }
</style> </style>

View File

@@ -14,7 +14,7 @@
:disabled="!session.handler.enabledStatus(action.item)" :disabled="!session.handler.enabledStatus(action.item)"
@click="emits('click', action.item)"> @click="emits('click', action.item)">
<!-- 图标 --> <!-- 图标 -->
<div class="action-icon"> <div class="terminal-context-menu-icon">
<component :is="action.icon" /> <component :is="action.icon" />
</div> </div>
<!-- 文本 --> <!-- 文本 -->
@@ -53,9 +53,4 @@
<style lang="less" scoped> <style lang="less" scoped>
.action-icon {
font-size: 16px;
margin: 0 8px 0 4px;
}
</style> </style>

View File

@@ -68,7 +68,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
// 处理 pong 消息 // 处理 pong 消息
processPong(payload: OutputPayload): void { processPong(payload: OutputPayload): void {
console.log('pong'); // console.log('pong');
} }
// 处理输出消息 // 处理输出消息

View File

@@ -1,4 +1,4 @@
import type { TerminalInteractSetting, TerminalShortcutKey } from '@/store/modules/terminal/types'; import type { ShortcutKey, TerminalInteractSetting, TerminalShortcutKey } from '@/store/modules/terminal/types';
import type { ITerminalSession, ITerminalSessionHandler, ITerminalTabManager, TerminalDomRef } from '../types/terminal.type'; import type { ITerminalSession, ITerminalSessionHandler, ITerminalTabManager, TerminalDomRef } from '../types/terminal.type';
import type { Terminal } from 'xterm'; import type { Terminal } from 'xterm';
import useCopy from '@/hooks/copy'; import useCopy from '@/hooks/copy';
@@ -9,6 +9,21 @@ import { saveAs } from 'file-saver';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { dateFormat } from '@/utils'; import { dateFormat } from '@/utils';
// 组织默认行为的快捷键
const preventKeys: Array<ShortcutKey> = [
{
ctrlKey: true,
altKey: false,
shiftKey: true,
code: 'KeyV'
}, {
ctrlKey: false,
altKey: false,
shiftKey: true,
code: 'Insert'
},
];
const { copy: copyValue, readText } = useCopy(); const { copy: copyValue, readText } = useCopy();
// 终端会话处理器实现 // 终端会话处理器实现
@@ -37,6 +52,19 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
this.tabManager = tabManager; this.tabManager = tabManager;
} }
// 检测是否忽略默认行为
checkPreventDefault(e: KeyboardEvent): boolean {
if (e.type !== 'keydown') {
return false;
}
return !!preventKeys.find(key => {
return key.code === e.code
&& key.altKey === e.altKey
&& key.shiftKey === e.shiftKey
&& key.ctrlKey === e.ctrlKey;
});
}
// 启用状态 // 启用状态
enabledStatus(option: string): boolean { enabledStatus(option: string): boolean {
switch (option) { switch (option) {
@@ -187,7 +215,15 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
checkAppendMissing(value: string): void { checkAppendMissing(value: string): void {
// 获取最后一行数据 // 获取最后一行数据
const buffer = this.inst.buffer?.active; const buffer = this.inst.buffer?.active;
let lastLine = (buffer?.getLine(buffer?.viewportY + buffer?.cursorY)?.translateToString() || '').trimEnd(); let lastLine = '';
if (buffer) {
for (let i = buffer.viewportY + buffer.cursorY; i >= 0; i--) {
lastLine = (buffer.getLine(i)?.translateToString() || '').trimEnd() + lastLine;
if (lastLine.length > value.length) {
break;
}
}
}
// 边界检查 // 边界检查
const lastLineLen = lastLine.length; const lastLineLen = lastLine.length;
const spinPartLen = value.length; const spinPartLen = value.length;

View File

@@ -82,7 +82,10 @@ export default class TerminalSession implements ITerminalSession {
private registerShortcut(preference: UnwrapRef<TerminalPreference>) { private registerShortcut(preference: UnwrapRef<TerminalPreference>) {
// 处理自定义按键 // 处理自定义按键
this.inst.attachCustomKeyEventHandler((e: KeyboardEvent) => { this.inst.attachCustomKeyEventHandler((e: KeyboardEvent) => {
e.preventDefault(); // 检测是否忽略默认行为
if (this.handler.checkPreventDefault(e)) {
e.preventDefault();
}
// 触发快捷键检测 // 触发快捷键检测
if (e.type === 'keydown' if (e.type === 'keydown'
&& preference.shortcutSetting.enabled && preference.shortcutSetting.enabled

View File

@@ -203,6 +203,8 @@ export interface ITerminalSession {
// 终端会话处理器定义 // 终端会话处理器定义
export interface ITerminalSessionHandler { export interface ITerminalSessionHandler {
// 检测是否忽略默认行为
checkPreventDefault: (e: KeyboardEvent) => boolean;
// 启用状态 // 启用状态
enabledStatus: (option: string) => boolean; enabledStatus: (option: string) => boolean;
// 调用处理方法 // 调用处理方法