@@ -8,7 +8,7 @@ import { Interceptor } from '..';
8
8
import { HtkConfig } from '../../config' ;
9
9
import { logError , addBreadcrumb } from '../../error-tracking' ;
10
10
import { isErrorLike } from '../../util/error' ;
11
- import { canAccess , commandExists } from '../../util/fs' ;
11
+ import { canAccess , commandExists , getRealPath , resolveCommandPath } from '../../util/fs' ;
12
12
import { spawnToResult } from '../../util/process-management' ;
13
13
14
14
import { getTerminalEnvVars } from './terminal-env-overrides' ;
@@ -108,38 +108,6 @@ const getLinuxTerminalCommand = async (): Promise<SpawnArgs | null> => {
108
108
return null ;
109
109
} ;
110
110
111
- const getXTerminalCommand = async ( command = 'x-terminal-emulator' ) : Promise < SpawnArgs > => {
112
- // x-terminal-emulator is a wrapper/symlink to the terminal of choice.
113
- // Unfortunately, we need to pass specific args that aren't supported by all terminals (to ensure
114
- // terminals run in the foreground), and the Debian XTE wrapper at least doesn't pass through
115
- // any of the args we want to use. To fix this, we parse --help to try and detect the underlying
116
- // terminal, and run it directly with the args we need.
117
- try {
118
- // Run the command with -h to get some output we can use to infer the terminal itself.
119
- // --version would be nice, but the debian wrapper ignores it. --help isn't supported by xterm.
120
- const { stdout } = await spawnToResult ( command , [ '-h' ] ) ;
121
- const helpOutput = stdout . toLowerCase ( ) . replace ( / [ ^ \w \d ] + / g, ' ' ) ;
122
-
123
- if ( helpOutput . includes ( 'gnome terminal' ) && await commandExists ( 'gnome-terminal' ) ) {
124
- return getGnomeTerminalCommand ( ) ;
125
- } else if ( helpOutput . includes ( 'xfce4 terminal' ) && await commandExists ( 'xfce4-terminal' ) ) {
126
- return getXfceTerminalCommand ( ) ;
127
- } else if ( helpOutput . includes ( 'konsole' ) && await commandExists ( 'konsole' ) ) {
128
- return getKonsoleTerminalCommand ( ) ;
129
- }
130
- } catch ( e ) {
131
- if ( isErrorLike ( e ) && e . message ?. includes ( 'rxvt' ) ) {
132
- // Bizarrely, rxvt -h prints help but always returns a non-zero exit code.
133
- // Doesn't need any special arguments anyway though, so just ignore it
134
- } else {
135
- logError ( e ) ;
136
- }
137
- }
138
-
139
- // If there's an error, or we just don't recognize the console, give up & run it directly
140
- return { command } ;
141
- } ;
142
-
143
111
const getKgxCommand = async ( command = 'kgx' ) : Promise < SpawnArgs > => {
144
112
return { command, envVars : { DBUS_SESSION_BUS_ADDRESS : '' } } ;
145
113
} ;
@@ -194,6 +162,59 @@ const getXfceTerminalCommand = async (command = 'xfce4-terminal'): Promise<Spawn
194
162
return { command, args : extraArgs } ;
195
163
} ;
196
164
165
+ const X_TERMINAL_MATCHERS = [
166
+ { helpString : 'gnome terminal' , command : 'gnome-terminal' , commandBuilder : getGnomeTerminalCommand } ,
167
+ { helpString : 'xfce4 terminal' , command : 'xfce4-terminal' , commandBuilder : getXfceTerminalCommand } ,
168
+ { helpString : 'konsole' , command : 'konsole' , commandBuilder : getKonsoleTerminalCommand } ,
169
+ { helpString : 'kgx' , command : 'kgx' , commandBuilder : getKgxCommand }
170
+ ] as const ;
171
+
172
+ const getXTerminalCommand = async ( command = 'x-terminal-emulator' ) : Promise < SpawnArgs > => {
173
+ // x-terminal-emulator is a wrapper/symlink to the terminal of choice.
174
+ // Unfortunately, we need to pass specific args that aren't supported by all terminals (to ensure
175
+ // terminals run in the foreground), and the Debian XTE wrapper at least doesn't pass through
176
+ // any of the args we want to use. To fix this, we parse --help to try and detect the underlying
177
+ // terminal, and run it directly with the args we need.
178
+ try {
179
+ // In parallel, get the command's help output, and its real resolved path on disk:
180
+ const [ helpOutput , commandPath ] = await Promise . all ( [
181
+ // Run the command with -h to get some output we can use to infer the terminal itself.
182
+ // --version would be nice, but the debian wrapper ignores it. --help isn't supported by xterm.
183
+ spawnToResult ( command , [ '-h' ] )
184
+ . then ( ( { stdout } ) => stdout . toLowerCase ( ) . replace ( / [ ^ \w \d ] + / g, ' ' ) ) ,
185
+ // We find the command in PATH, and then follow any symbolic links, and see what the real
186
+ // underlying path is:
187
+ resolveCommandPath ( command )
188
+ . then ( ( path ) => { if ( path ) return getRealPath ( path ) } )
189
+ ] ) ;
190
+
191
+ for ( let terminalPattern of X_TERMINAL_MATCHERS ) {
192
+ // We match the help output or command path for known strings:
193
+ if (
194
+ ! helpOutput . includes ( terminalPattern . helpString ) &&
195
+ ! commandPath ?. includes ( command )
196
+ ) continue ;
197
+
198
+ // If found, we use the terminal _if_ it exists globally with our expected command.
199
+ // We can't use 'command' directly, because some terms (gnome-terminal, Debian generally)
200
+ // use weird wrappers that don't behave properly.
201
+ if ( await commandExists ( terminalPattern . command ) ) {
202
+ return terminalPattern . commandBuilder ( ) ;
203
+ }
204
+ }
205
+ } catch ( e ) {
206
+ if ( isErrorLike ( e ) && e . message ?. includes ( 'rxvt' ) ) {
207
+ // Bizarrely, rxvt -h prints help but always returns a non-zero exit code.
208
+ // Doesn't need any special arguments anyway though, so just ignore it
209
+ } else {
210
+ logError ( e ) ;
211
+ }
212
+ }
213
+
214
+ // If there's an error, or we just don't recognize the console, give up & run it directly:
215
+ return { command } ;
216
+ } ;
217
+
197
218
const terminals : _ . Dictionary < ChildProcess [ ] | undefined > = { }
198
219
199
220
export class FreshTerminalInterceptor implements Interceptor {
0 commit comments