diff --git a/tabby-electron/src/pathDrop.ts b/tabby-electron/src/pathDrop.ts index 9b39a696..4718a333 100644 --- a/tabby-electron/src/pathDrop.ts +++ b/tabby-electron/src/pathDrop.ts @@ -1,11 +1,12 @@ import { Injectable } from '@angular/core' -import { TerminalDecorator, BaseTerminalTabComponent } from 'tabby-terminal' +import { TerminalDecorator, BaseTerminalTabComponent, BaseTerminalProfile } from 'tabby-terminal' import { webUtils } from 'electron' +import { ShellType, TerminalTabComponent } from 'tabby-local' /** @hidden */ @Injectable() export class PathDropDecorator extends TerminalDecorator { - attach (terminal: BaseTerminalTabComponent): void { + attach (terminal: BaseTerminalTabComponent): void { setTimeout(() => { this.subscribeUntilDetached(terminal, terminal.frontend?.dragOver$.subscribe(event => { event.preventDefault() @@ -19,12 +20,55 @@ export class PathDropDecorator extends TerminalDecorator { }) } - private injectPath (terminal: BaseTerminalTabComponent, path: string) { - path = path.replace(/[\x00-\x1F\x7F]/g, '') - if (path.includes(' ')) { - path = `"${path}"` + private injectPath (terminal: BaseTerminalTabComponent, path: string) { + const shellType = this.getShellType(terminal) + let data = this.quotePath(path, shellType) + ' ' + + if (terminal.config.store.terminal.bracketedPaste && terminal.frontend?.supportsBracketedPaste()) { + data = `\x1b[200~${data}\x1b[201~` } - path = path.replaceAll('\\', '\\\\') - terminal.sendInput(path + ' ') + + terminal.sendInput(data) + } + + private getShellType (terminal: BaseTerminalTabComponent): ShellType { + const profileShellType = terminal instanceof TerminalTabComponent ? terminal.profile.options.shellType : null + + return profileShellType ?? 'unix' + } + + private quotePath (path: string, shellType: ShellType): string { + path = path.replace(/[\x00-\x1F\x7F]/g, '') + + if (shellType === 'powershell') { + return this.quoteForPowerShell(path) + } + + if (shellType === 'cmd') { + return this.quoteForCmd(path) + } + + return this.quoteForUnix(path) + } + + private quoteForUnix (path: string): string { + return `'${path.replace(/'/g, `'\\''`)}'` + } + + private quoteForPowerShell (path: string): string { + // double any single-quote-class chars already present + return `'${path.replace(/['\u2018\u2019\u201A\u201B]/g, m => m + m)}'` + } + + private quoteForCmd (path: string): string { + if (!path) { + return '""' + } + const escaped = path + .replace(/\^/g, '^^') + .replace(/!/g, '^!') + .replace(/"/g, '""') + .replace(/%/g, '%%') + return `"${escaped}"` } } diff --git a/tabby-electron/src/shells/cmder.ts b/tabby-electron/src/shells/cmder.ts index 0c97164d..cf2e3927 100644 --- a/tabby-electron/src/shells/cmder.ts +++ b/tabby-electron/src/shells/cmder.ts @@ -35,6 +35,7 @@ export class CmderShellProvider extends ShellProvider { env: { TERM: 'cygwin', }, + shellType: 'cmd', }, { id: 'cmderps', @@ -51,6 +52,7 @@ export class CmderShellProvider extends ShellProvider { ], icon: require('../icons/cmder-powershell.svg'), env: {}, + shellType: 'powershell', }, ] } diff --git a/tabby-electron/src/shells/cygwin32.ts b/tabby-electron/src/shells/cygwin32.ts index f77b0003..50cfadfb 100644 --- a/tabby-electron/src/shells/cygwin32.ts +++ b/tabby-electron/src/shells/cygwin32.ts @@ -39,6 +39,7 @@ export class Cygwin32ShellProvider extends ShellProvider { env: { TERM: 'cygwin', }, + shellType: 'unix', }] } } diff --git a/tabby-electron/src/shells/cygwin64.ts b/tabby-electron/src/shells/cygwin64.ts index 4e3118fc..41ef47a6 100644 --- a/tabby-electron/src/shells/cygwin64.ts +++ b/tabby-electron/src/shells/cygwin64.ts @@ -39,6 +39,7 @@ export class Cygwin64ShellProvider extends ShellProvider { env: { TERM: 'cygwin', }, + shellType: 'unix', }] } } diff --git a/tabby-electron/src/shells/gitBash.ts b/tabby-electron/src/shells/gitBash.ts index 19b480f9..92482d8d 100644 --- a/tabby-electron/src/shells/gitBash.ts +++ b/tabby-electron/src/shells/gitBash.ts @@ -44,6 +44,7 @@ export class GitBashShellProvider extends WindowsBaseShellProvider { args: ['--login', '-i'], icon: require('../icons/git-bash.svg'), env: this.getEnvironment(), + shellType: 'unix', }] } } diff --git a/tabby-electron/src/shells/linuxDefault.ts b/tabby-electron/src/shells/linuxDefault.ts index 8c8a419a..08cb6c50 100644 --- a/tabby-electron/src/shells/linuxDefault.ts +++ b/tabby-electron/src/shells/linuxDefault.ts @@ -31,6 +31,7 @@ export class LinuxDefaultShellProvider extends ShellProvider { name: this.translate.instant('User default'), command: '/bin/sh', env: {}, + shellType: 'unix', }] } else { return [{ @@ -40,6 +41,7 @@ export class LinuxDefaultShellProvider extends ShellProvider { args: ['--login'], hidden: true, env: {}, + shellType: 'unix', }] } } diff --git a/tabby-electron/src/shells/macDefault.ts b/tabby-electron/src/shells/macDefault.ts index 32dcc462..949dca9e 100644 --- a/tabby-electron/src/shells/macDefault.ts +++ b/tabby-electron/src/shells/macDefault.ts @@ -27,6 +27,7 @@ export class MacOSDefaultShellProvider extends ShellProvider { args: ['--login'], hidden: true, env: {}, + shellType: 'unix', }] } diff --git a/tabby-electron/src/shells/msys2.ts b/tabby-electron/src/shells/msys2.ts index 3475b377..be194520 100644 --- a/tabby-electron/src/shells/msys2.ts +++ b/tabby-electron/src/shells/msys2.ts @@ -43,6 +43,7 @@ export class MSYS2ShellProvider extends ShellProvider { icon: require('../icons/msys2.svg'), env: {}, cwd: homePath, + shellType: 'unix', })) } } diff --git a/tabby-electron/src/shells/posix.ts b/tabby-electron/src/shells/posix.ts index 6707ac88..3bb044c2 100644 --- a/tabby-electron/src/shells/posix.ts +++ b/tabby-electron/src/shells/posix.ts @@ -36,6 +36,7 @@ export class POSIXShellsProvider extends ShellProvider { command: x, args: ['-l'], env: {}, + shellType: 'unix', })) } } diff --git a/tabby-electron/src/shells/powershellCore.ts b/tabby-electron/src/shells/powershellCore.ts index 2792fd10..b9352555 100644 --- a/tabby-electron/src/shells/powershellCore.ts +++ b/tabby-electron/src/shells/powershellCore.ts @@ -39,6 +39,7 @@ export class PowerShellCoreShellProvider extends WindowsBaseShellProvider { args: ['-nologo'], icon: require('../icons/powershell-core.svg'), env: this.getEnvironment(), + shellType: 'powershell', }] } } diff --git a/tabby-electron/src/shells/vs.ts b/tabby-electron/src/shells/vs.ts index 045c61d1..f6c50cbd 100644 --- a/tabby-electron/src/shells/vs.ts +++ b/tabby-electron/src/shells/vs.ts @@ -48,6 +48,7 @@ export class VSDevToolsProvider extends ShellProvider { args: ['/k', bat], icon: vsIconMap[version], env: {}, + shellType: 'cmd', }) } } catch (_) { diff --git a/tabby-electron/src/shells/windowsStock.ts b/tabby-electron/src/shells/windowsStock.ts index d1094a97..c6ab9648 100644 --- a/tabby-electron/src/shells/windowsStock.ts +++ b/tabby-electron/src/shells/windowsStock.ts @@ -52,6 +52,7 @@ export class WindowsStockShellsProvider extends WindowsBaseShellProvider { WT_SESSION: '0', }, icon: require('../icons/clink.svg'), + shellType: 'cmd', }, { id: 'cmd', @@ -59,6 +60,7 @@ export class WindowsStockShellsProvider extends WindowsBaseShellProvider { command: 'cmd.exe', env: {}, icon: require('../icons/cmd.svg'), + shellType: 'cmd', }, { id: 'powershell', @@ -67,6 +69,7 @@ export class WindowsStockShellsProvider extends WindowsBaseShellProvider { args: ['-nologo'], icon: require('../icons/powershell.svg'), env: this.getEnvironment(), + shellType: 'powershell', }, ] } diff --git a/tabby-electron/src/shells/wsl.ts b/tabby-electron/src/shells/wsl.ts index 67811a4d..fd327100 100644 --- a/tabby-electron/src/shells/wsl.ts +++ b/tabby-electron/src/shells/wsl.ts @@ -67,6 +67,7 @@ export class WSLShellProvider extends ShellProvider { TERM: 'xterm-color', COLORTERM: 'truecolor', }, + shellType: 'unix', // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition icon: wslIconMap[defaultDistKey.DistributionName.value] ?? wslIconMap.Linux, } @@ -85,6 +86,7 @@ export class WSLShellProvider extends ShellProvider { TERM: 'xterm-color', COLORTERM: 'truecolor', }, + shellType: 'unix', }] } else { return [] @@ -109,6 +111,7 @@ export class WSLShellProvider extends ShellProvider { TERM: 'xterm-color', COLORTERM: 'truecolor', }, + shellType: 'unix', // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition icon: wslIconMap[name] ?? wslIconMap.Linux, } diff --git a/tabby-local/src/api.ts b/tabby-local/src/api.ts index ffdc6eb9..c23ded76 100644 --- a/tabby-local/src/api.ts +++ b/tabby-local/src/api.ts @@ -1,5 +1,7 @@ import { BaseTerminalProfile } from 'tabby-terminal' +export type ShellType = 'unix' | 'powershell' | 'cmd' + export interface Shell { id: string name: string @@ -20,6 +22,8 @@ export interface Shell { */ icon?: string + shellType?: ShellType + hidden?: boolean } @@ -39,6 +43,7 @@ export interface SessionOptions { env: Record width: number | null height: number | null + shellType: ShellType | null pauseAfterExit: boolean runAsAdministrator: boolean } diff --git a/tabby-local/src/profiles.ts b/tabby-local/src/profiles.ts index e055c7ac..d4738517 100644 --- a/tabby-local/src/profiles.ts +++ b/tabby-local/src/profiles.ts @@ -22,6 +22,7 @@ export class LocalProfilesService extends ProfileProvider { }, width: null, height: null, + shellType: null, pauseAfterExit: false, runAsAdministrator: false, }, @@ -82,6 +83,7 @@ export class LocalProfilesService extends ProfileProvider { args: shell.args ?? [], env: shell.env, cwd: shell.cwd ?? null, + shellType: shell.shellType ?? null, } }