Skip to content

Commit

Permalink
feat: interactive command marks
Browse files Browse the repository at this point in the history
  • Loading branch information
CyanSalt committed Mar 20, 2024
1 parent 353af00 commit 1d366ca
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 31 deletions.
7 changes: 6 additions & 1 deletion resources/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@
"Confirm#!terminal.3": "确认",
"Cancel#!terminal.4": "取消",
"Find#!terminal.5": "查找",
"Addon [${name}] only supports ${engine} ${range}, not ${version}#!terminal.6": "插件 [${name}] 仅支持 ${engine} ${range},而非 ${version}"
"Addon [${name}] only supports ${engine} ${range}, not ${version}#!terminal.6": "插件 [${name}] 仅支持 ${engine} ${range},而非 ${version}",
"Exit Code: ${code}#!terminal.7": "退出代码: ${code}",
"Started At: ${time}#!terminal.8": "开始运行于: ${time}",
"Time taken: ${duration}#!terminal.9": "耗时: ${duration}",
"Rerun Command#!terminal.10": "重新运行命令",
"Copy Command#!terminal.11": "复制命令"
}
14 changes: 7 additions & 7 deletions src/renderer/components/TerminalTeletype.vue
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,13 @@ function mountCompletion(el: HTMLElement, item: CommandCompletion) {
margin-left: calc(var(--command-mark-padding) - var(--integration-width));
border-radius: 4px;
cursor: default;
// &.is-interactive {
// transition: background 0.2s;
// cursor: pointer;
// &:hover {
// background: var(--design-highlight-background);
// }
// }
&.is-interactive {
transition: background 0.2s;
cursor: pointer;
&:hover {
background: var(--design-highlight-background);
}
}
&::before {
content: '';
display: block;
Expand Down
9 changes: 8 additions & 1 deletion src/renderer/compositions/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { WebLinksAddon } from '@xterm/addon-web-links'
import { WebglAddon } from '@xterm/addon-webgl'
import type { IMarker, ITerminalOptions } from '@xterm/xterm'
import { Terminal } from '@xterm/xterm'
import { ipcRenderer, shell } from 'electron'
import { clipboard, ipcRenderer, shell } from 'electron'
import { isMatch, trim } from 'lodash'
import { effectScope, markRaw, nextTick, reactive, shallowReactive, toRaw, watch, watchEffect } from 'vue'
import * as commas from '../../../api/core-renderer'
Expand Down Expand Up @@ -470,6 +470,10 @@ export function handleTerminalMessages() {
if (!currentTerminal) return
closeTerminalTab(currentTerminal)
})
ipcRenderer.on('execute-terminal', (event, command: string, restart?: boolean) => {
if (!currentTerminal) return
executeTerminalTab(currentTerminal, command, restart)
})
ipcRenderer.on('select-tab', (event, index: number) => {
if (!tabs.length) return
let targetIndex = index % tabs.length
Expand Down Expand Up @@ -515,6 +519,9 @@ export function handleTerminalMessages() {
if (!currentTerminal) return
currentTerminal.pane?.instance?.save?.()
})
ipcRenderer.on('copy', (event, text: string) => {
clipboard.writeText(text)
})
handleRenderer('get-history', (event, count?: number) => {
if (!currentTerminal) return []
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
Expand Down
103 changes: 81 additions & 22 deletions src/renderer/utils/shell-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import fuzzaldrin from 'fuzzaldrin-plus'
import { isEqual } from 'lodash'
import { nextTick, reactive, toRaw } from 'vue'
import { toCSSHEX, toRGBA } from '../../shared/color'
import type { MenuItem } from '../../typings/menu'
import type { CommandCompletion, TerminalTab } from '../../typings/terminal'
import { useSettings } from '../compositions/settings'
import { scrollToMarker, writeTerminalTab } from '../compositions/terminal'
import { useTheme } from '../compositions/theme'
import { openContextMenu } from './frame'
import { translate } from './i18n'
import { getReadableSignal } from './terminal'

interface IntegratedShellCommandAction {
command: string,
Expand All @@ -19,7 +23,7 @@ interface IntegratedShellCommand {
marker: IMarker,
decoration: IDecoration,
cursorX: number,
startedAt: Date,
startedAt?: Date,
endedAt?: Date,
actions?: IntegratedShellCommandAction[],
}
Expand Down Expand Up @@ -167,9 +171,8 @@ export class ShellIntegrationAddon implements ITerminalAddon {
const decoration = this._createCommandDecoration(
xterm,
marker,
() => currentCommand!,
actions ? theme.yellow : theme.foreground,
Boolean(actions),
actions ? 'strong' : undefined,
)
if (this.currentCommand) {
this.currentCommand.marker.dispose()
Expand All @@ -182,7 +185,6 @@ export class ShellIntegrationAddon implements ITerminalAddon {
decoration,
cursorX: xterm.buffer.active.cursorX,
actions,
startedAt: new Date(),
}
this.commands.push(currentCommand)
this.currentCommand = currentCommand
Expand All @@ -193,6 +195,9 @@ export class ShellIntegrationAddon implements ITerminalAddon {
case 'C':
// OutputStart
this.tab.idle = false
if (this.currentCommand) {
this.currentCommand.startedAt = new Date()
}
return true
case 'D':
// CommandComplete
Expand All @@ -205,23 +210,23 @@ export class ShellIntegrationAddon implements ITerminalAddon {
if (!this.currentCommand.marker.isDisposed) {
const theme = useTheme()
this.currentCommand.decoration.dispose()
if (exitCode > 0 && exitCode < 128 && settings['terminal.shell.highlightErrors']) {
const shouldHighlight = exitCode > 0 && exitCode < 128 && settings['terminal.shell.highlightErrors']
if (shouldHighlight) {
this._createHighlightDecoration(
xterm,
this.currentCommand.marker.line,
xterm.buffer.active.baseY + xterm.buffer.active.cursorY - 1,
theme.red,
)
} else {
const currentCommand = this.currentCommand
this.currentCommand.decoration = this._createCommandDecoration(
xterm,
currentCommand.marker,
() => currentCommand,
exitCode > 0 ? theme.red : theme.green,
true,
)
}
const currentCommand = this.currentCommand
this.currentCommand.decoration = this._createCommandDecoration(
xterm,
currentCommand.marker,
exitCode > 0 ? theme.red : theme.green,
shouldHighlight ? 'transparent' : 'strong',
currentCommand,
)
}
}
this.tab.command = ''
Expand Down Expand Up @@ -290,31 +295,85 @@ export class ShellIntegrationAddon implements ITerminalAddon {
this.recentCompletionAppliedPosition = undefined
}

_createCommandMenu(command: IntegratedShellCommand) {
const menu: MenuItem[] = []
if (command.exitCode) {
const signal = getReadableSignal(command.exitCode)
menu.push({
label: translate('Exit Code: ${code}#!terminal.7', {
code: `${command.exitCode}${signal ? ` (${signal})` : ''}`,
}),
enabled: false,
})
}
const timeFormat = new Intl.DateTimeFormat(undefined, {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
if (command.startedAt) {
const startedAt = timeFormat.format(command.startedAt)
menu.push({
label: translate('Started At: ${time}#!terminal.8', {
time: startedAt,
}),
enabled: false,
})
}
if (command.startedAt && command.endedAt) {
const duration = command.endedAt.getTime() - command.startedAt.getTime()
menu.push({
label: translate('Time taken: ${duration}#!terminal.9', {
duration: duration > 1000 ? `${duration / 1000}s` : `${duration}ms`,
}),
enabled: false,
})
}
if (command.command) {
if (menu.length) {
menu.push({ type: 'separator' })
}
menu.push(
{
label: translate('Rerun Command#!terminal.10'),
command: 'execute-terminal',
args: [command.command, true],
},
{
label: translate('Copy Command#!terminal.11'),
command: 'copy',
args: [command.command],
},
)
}
return menu
}

_createCommandDecoration(
xterm: Terminal,
marker: IMarker,
factory: () => IntegratedShellCommand,
color: string,
interactive?: boolean,
style?: 'strong' | 'transparent' | undefined,
command?: IntegratedShellCommand,
) {
const rgba = toRGBA(color)
const decoration = xterm.registerDecoration({
marker,
overviewRulerOptions: interactive ? {
overviewRulerOptions: style === 'strong' ? {
color: toCSSHEX({ ...rgba, a: 0.5 }),
position: 'right',
} : undefined,
})!
updateDecorationElement(decoration, el => {
el.style.setProperty('--color', `${rgba.r} ${rgba.g} ${rgba.b}`)
el.style.setProperty('--opacity', interactive ? '1' : '0.25')
el.style.setProperty('--opacity', style === 'strong' ? '1' : (style === 'transparent' ? '0' : '0.25'))
el.classList.add('terminal-command-mark')
if (interactive) {
if (command) {
el.classList.add('is-interactive')
el.addEventListener('click', event => {
openContextMenu(this._createCommandMenu(command), event)
})
}
// el.addEventListener('click', event => {
// const command = factory()
// })
})
return decoration
}
Expand Down
8 changes: 8 additions & 0 deletions src/renderer/utils/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,11 @@ export function getTerminalTabID(tab: TerminalTab) {
return `process@${tab.pid}`
}
}

export function getReadableSignal(code: number) {
if (code > 128 && code < 256) {
return Object.entries(os.constants.signals)
.find(([name, value]) => 128 + value === code)
?.[0]
}
}

0 comments on commit 1d366ca

Please sign in to comment.