@theia/terminal 1.53.0-next.4 → 1.53.0-next.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -30
- package/lib/browser/terminal-link-provider.d.ts +1 -1
- package/lib/browser/terminal-link-provider.d.ts.map +1 -1
- package/lib/browser/terminal-widget-impl.js +4 -4
- package/package.json +10 -10
- package/src/browser/base/terminal-service.ts +60 -60
- package/src/browser/base/terminal-widget.ts +268 -268
- package/src/browser/index.ts +17 -17
- package/src/browser/search/terminal-search-container.ts +28 -28
- package/src/browser/search/terminal-search-widget.tsx +161 -161
- package/src/browser/shell-terminal-profile.ts +45 -45
- package/src/browser/style/terminal-search.css +99 -99
- package/src/browser/style/terminal.css +32 -32
- package/src/browser/terminal-contribution.ts +19 -19
- package/src/browser/terminal-copy-on-selection-handler.ts +92 -92
- package/src/browser/terminal-file-link-provider.ts +289 -289
- package/src/browser/terminal-frontend-contribution.ts +1134 -1134
- package/src/browser/terminal-frontend-module.ts +138 -138
- package/src/browser/terminal-link-helpers.ts +187 -187
- package/src/browser/terminal-link-provider.ts +203 -203
- package/src/browser/terminal-preferences.ts +428 -428
- package/src/browser/terminal-profile-service.ts +180 -180
- package/src/browser/terminal-quick-open-service.ts +132 -132
- package/src/browser/terminal-theme-service.ts +213 -213
- package/src/browser/terminal-url-link-provider.ts +66 -66
- package/src/browser/terminal-widget-impl.ts +996 -996
- package/src/common/base-terminal-protocol.ts +125 -125
- package/src/common/shell-terminal-protocol.ts +103 -103
- package/src/common/terminal-common-module.ts +30 -30
- package/src/common/terminal-protocol.ts +32 -32
- package/src/common/terminal-watcher.ts +69 -69
- package/src/node/base-terminal-server.ts +173 -173
- package/src/node/buffering-stream.spec.ts +46 -46
- package/src/node/buffering-stream.ts +95 -95
- package/src/node/index.ts +17 -17
- package/src/node/shell-process.ts +102 -102
- package/src/node/shell-terminal-server.spec.ts +40 -40
- package/src/node/shell-terminal-server.ts +223 -223
- package/src/node/terminal-backend-contribution.slow-spec.ts +63 -63
- package/src/node/terminal-backend-contribution.ts +60 -60
- package/src/node/terminal-backend-module.ts +82 -82
- package/src/node/terminal-server.spec.ts +47 -47
- package/src/node/terminal-server.ts +52 -52
- package/src/node/test/terminal-test-container.ts +39 -39
- package/src/package.spec.ts +28 -28
|
@@ -1,1134 +1,1134 @@
|
|
|
1
|
-
// *****************************************************************************
|
|
2
|
-
// Copyright (C) 2017 TypeFox and others.
|
|
3
|
-
//
|
|
4
|
-
// This program and the accompanying materials are made available under the
|
|
5
|
-
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
//
|
|
8
|
-
// This Source Code may also be made available under the following Secondary
|
|
9
|
-
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
// with the GNU Classpath Exception which is available at
|
|
12
|
-
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
//
|
|
14
|
-
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
-
import {
|
|
19
|
-
CommandContribution,
|
|
20
|
-
Command,
|
|
21
|
-
CommandRegistry,
|
|
22
|
-
DisposableCollection,
|
|
23
|
-
MenuContribution,
|
|
24
|
-
MenuModelRegistry,
|
|
25
|
-
isOSX,
|
|
26
|
-
SelectionService,
|
|
27
|
-
Emitter,
|
|
28
|
-
Event,
|
|
29
|
-
ViewColumn,
|
|
30
|
-
OS,
|
|
31
|
-
CompoundMenuNodeRole
|
|
32
|
-
} from '@theia/core/lib/common';
|
|
33
|
-
import {
|
|
34
|
-
ApplicationShell, KeybindingContribution, KeyCode, Key, WidgetManager, PreferenceService,
|
|
35
|
-
KeybindingRegistry, LabelProvider, WidgetOpenerOptions, StorageService, QuickInputService,
|
|
36
|
-
codicon, CommonCommands, FrontendApplicationContribution, OnWillStopAction, Dialog, ConfirmDialog, FrontendApplication, PreferenceScope, Widget, SHELL_TABBAR_CONTEXT_MENU
|
|
37
|
-
} from '@theia/core/lib/browser';
|
|
38
|
-
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
|
39
|
-
import { TERMINAL_WIDGET_FACTORY_ID, TerminalWidgetFactoryOptions, TerminalWidgetImpl } from './terminal-widget-impl';
|
|
40
|
-
import { TerminalService } from './base/terminal-service';
|
|
41
|
-
import { TerminalWidgetOptions, TerminalWidget, TerminalLocation } from './base/terminal-widget';
|
|
42
|
-
import { ContributedTerminalProfileStore, NULL_PROFILE, TerminalProfile, TerminalProfileService, TerminalProfileStore, UserTerminalProfileStore } from './terminal-profile-service';
|
|
43
|
-
import { UriAwareCommandHandler } from '@theia/core/lib/common/uri-command-handler';
|
|
44
|
-
import { ShellTerminalServerProxy } from '../common/shell-terminal-protocol';
|
|
45
|
-
import URI from '@theia/core/lib/common/uri';
|
|
46
|
-
import { MAIN_MENU_BAR } from '@theia/core';
|
|
47
|
-
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
48
|
-
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
|
49
|
-
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
|
50
|
-
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
|
51
|
-
import { terminalAnsiColorMap } from './terminal-theme-service';
|
|
52
|
-
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
53
|
-
import { FileStat } from '@theia/filesystem/lib/common/files';
|
|
54
|
-
import { TerminalWatcher } from '../common/terminal-watcher';
|
|
55
|
-
import { nls } from '@theia/core/lib/common/nls';
|
|
56
|
-
import { Profiles, TerminalPreferences } from './terminal-preferences';
|
|
57
|
-
import { ShellTerminalProfile } from './shell-terminal-profile';
|
|
58
|
-
import { VariableResolverService } from '@theia/variable-resolver/lib/browser';
|
|
59
|
-
import { Color } from '@theia/core/lib/common/color';
|
|
60
|
-
|
|
61
|
-
export namespace TerminalMenus {
|
|
62
|
-
export const TERMINAL = [...MAIN_MENU_BAR, '7_terminal'];
|
|
63
|
-
export const TERMINAL_NEW = [...TERMINAL, '1_terminal'];
|
|
64
|
-
|
|
65
|
-
export const TERMINAL_TASKS = [...TERMINAL, '2_terminal'];
|
|
66
|
-
export const TERMINAL_TASKS_INFO = [...TERMINAL_TASKS, '3_terminal'];
|
|
67
|
-
export const TERMINAL_TASKS_CONFIG = [...TERMINAL_TASKS, '4_terminal'];
|
|
68
|
-
export const TERMINAL_NAVIGATOR_CONTEXT_MENU = ['navigator-context-menu', 'navigation'];
|
|
69
|
-
export const TERMINAL_OPEN_EDITORS_CONTEXT_MENU = ['open-editors-context-menu', 'navigation'];
|
|
70
|
-
|
|
71
|
-
export const TERMINAL_CONTEXT_MENU = ['terminal-context-menu'];
|
|
72
|
-
export const TERMINAL_CONTRIBUTIONS = [...TERMINAL_CONTEXT_MENU, '5_terminal_contributions'];
|
|
73
|
-
|
|
74
|
-
export const TERMINAL_TITLE_CONTRIBUTIONS = [...SHELL_TABBAR_CONTEXT_MENU, 'terminal_title_contributions'];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export namespace TerminalCommands {
|
|
78
|
-
const TERMINAL_CATEGORY = 'Terminal';
|
|
79
|
-
export const NEW = Command.toDefaultLocalizedCommand({
|
|
80
|
-
id: 'terminal:new',
|
|
81
|
-
category: TERMINAL_CATEGORY,
|
|
82
|
-
label: 'Create New Terminal'
|
|
83
|
-
});
|
|
84
|
-
export const PROFILE_NEW = Command.toLocalizedCommand({
|
|
85
|
-
id: 'terminal:new:profile',
|
|
86
|
-
category: TERMINAL_CATEGORY,
|
|
87
|
-
label: 'Create New Integrated Terminal from a Profile'
|
|
88
|
-
});
|
|
89
|
-
export const PROFILE_DEFAULT = Command.toLocalizedCommand({
|
|
90
|
-
id: 'terminal:profile:default',
|
|
91
|
-
category: TERMINAL_CATEGORY,
|
|
92
|
-
label: 'Choose the default Terminal Profile'
|
|
93
|
-
});
|
|
94
|
-
export const NEW_ACTIVE_WORKSPACE = Command.toDefaultLocalizedCommand({
|
|
95
|
-
id: 'terminal:new:active:workspace',
|
|
96
|
-
category: TERMINAL_CATEGORY,
|
|
97
|
-
label: 'Create New Terminal (In Active Workspace)'
|
|
98
|
-
});
|
|
99
|
-
export const TERMINAL_CLEAR = Command.toDefaultLocalizedCommand({
|
|
100
|
-
id: 'terminal:clear',
|
|
101
|
-
category: TERMINAL_CATEGORY,
|
|
102
|
-
label: 'Clear'
|
|
103
|
-
});
|
|
104
|
-
export const TERMINAL_CONTEXT = Command.toDefaultLocalizedCommand({
|
|
105
|
-
id: 'terminal:context',
|
|
106
|
-
category: TERMINAL_CATEGORY,
|
|
107
|
-
label: 'Open in Integrated Terminal'
|
|
108
|
-
});
|
|
109
|
-
export const SPLIT = Command.toDefaultLocalizedCommand({
|
|
110
|
-
id: 'terminal:split',
|
|
111
|
-
category: TERMINAL_CATEGORY,
|
|
112
|
-
label: 'Split Terminal'
|
|
113
|
-
});
|
|
114
|
-
export const TERMINAL_FIND_TEXT = Command.toDefaultLocalizedCommand({
|
|
115
|
-
id: 'terminal:find',
|
|
116
|
-
category: TERMINAL_CATEGORY,
|
|
117
|
-
label: 'Find'
|
|
118
|
-
});
|
|
119
|
-
export const TERMINAL_FIND_TEXT_CANCEL = Command.toDefaultLocalizedCommand({
|
|
120
|
-
id: 'terminal:find:cancel',
|
|
121
|
-
category: TERMINAL_CATEGORY,
|
|
122
|
-
label: 'Hide Find'
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
export const SCROLL_LINE_UP = Command.toDefaultLocalizedCommand({
|
|
126
|
-
id: 'terminal:scroll:line:up',
|
|
127
|
-
category: TERMINAL_CATEGORY,
|
|
128
|
-
label: 'Scroll Up (Line)'
|
|
129
|
-
});
|
|
130
|
-
export const SCROLL_LINE_DOWN = Command.toDefaultLocalizedCommand({
|
|
131
|
-
id: 'terminal:scroll:line:down',
|
|
132
|
-
category: TERMINAL_CATEGORY,
|
|
133
|
-
label: 'Scroll Down (Line)'
|
|
134
|
-
});
|
|
135
|
-
export const SCROLL_TO_TOP = Command.toDefaultLocalizedCommand({
|
|
136
|
-
id: 'terminal:scroll:top',
|
|
137
|
-
category: TERMINAL_CATEGORY,
|
|
138
|
-
label: 'Scroll to Top'
|
|
139
|
-
});
|
|
140
|
-
export const SCROLL_PAGE_UP = Command.toDefaultLocalizedCommand({
|
|
141
|
-
id: 'terminal:scroll:page:up',
|
|
142
|
-
category: TERMINAL_CATEGORY,
|
|
143
|
-
label: 'Scroll Up (Page)'
|
|
144
|
-
});
|
|
145
|
-
export const SCROLL_PAGE_DOWN = Command.toDefaultLocalizedCommand({
|
|
146
|
-
id: 'terminal:scroll:page:down',
|
|
147
|
-
category: TERMINAL_CATEGORY,
|
|
148
|
-
label: 'Scroll Down (Page)'
|
|
149
|
-
});
|
|
150
|
-
export const TOGGLE_TERMINAL = Command.toDefaultLocalizedCommand({
|
|
151
|
-
id: 'workbench.action.terminal.toggleTerminal',
|
|
152
|
-
category: TERMINAL_CATEGORY,
|
|
153
|
-
label: 'Toggle Terminal'
|
|
154
|
-
});
|
|
155
|
-
export const KILL_TERMINAL = Command.toDefaultLocalizedCommand({
|
|
156
|
-
id: 'terminal:kill',
|
|
157
|
-
category: TERMINAL_CATEGORY,
|
|
158
|
-
label: 'Kill Terminal'
|
|
159
|
-
});
|
|
160
|
-
export const SELECT_ALL: Command = {
|
|
161
|
-
id: 'terminal:select:all',
|
|
162
|
-
label: CommonCommands.SELECT_ALL.label,
|
|
163
|
-
category: TERMINAL_CATEGORY,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Command that displays all terminals that are currently opened
|
|
168
|
-
*/
|
|
169
|
-
export const SHOW_ALL_OPENED_TERMINALS = Command.toDefaultLocalizedCommand({
|
|
170
|
-
id: 'workbench.action.showAllTerminals',
|
|
171
|
-
category: CommonCommands.VIEW_CATEGORY,
|
|
172
|
-
label: 'Show All Opened Terminals'
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const ENVIRONMENT_VARIABLE_COLLECTIONS_KEY = 'terminal.integrated.environmentVariableCollections';
|
|
177
|
-
@injectable()
|
|
178
|
-
export class TerminalFrontendContribution implements FrontendApplicationContribution, TerminalService, CommandContribution, MenuContribution,
|
|
179
|
-
KeybindingContribution, TabBarToolbarContribution, ColorContribution {
|
|
180
|
-
|
|
181
|
-
@inject(ApplicationShell) protected readonly shell: ApplicationShell;
|
|
182
|
-
@inject(ShellTerminalServerProxy) protected readonly shellTerminalServer: ShellTerminalServerProxy;
|
|
183
|
-
@inject(WidgetManager) protected readonly widgetManager: WidgetManager;
|
|
184
|
-
@inject(FileService) protected readonly fileService: FileService;
|
|
185
|
-
@inject(SelectionService) protected readonly selectionService: SelectionService;
|
|
186
|
-
|
|
187
|
-
@inject(LabelProvider)
|
|
188
|
-
protected readonly labelProvider: LabelProvider;
|
|
189
|
-
|
|
190
|
-
@inject(QuickInputService) @optional()
|
|
191
|
-
protected readonly quickInputService: QuickInputService;
|
|
192
|
-
|
|
193
|
-
@inject(WorkspaceService)
|
|
194
|
-
protected readonly workspaceService: WorkspaceService;
|
|
195
|
-
|
|
196
|
-
@inject(TerminalProfileService)
|
|
197
|
-
protected readonly profileService: TerminalProfileService;
|
|
198
|
-
|
|
199
|
-
@inject(UserTerminalProfileStore)
|
|
200
|
-
protected readonly userProfileStore: TerminalProfileStore;
|
|
201
|
-
|
|
202
|
-
@inject(ContributedTerminalProfileStore)
|
|
203
|
-
protected readonly contributedProfileStore: TerminalProfileStore;
|
|
204
|
-
|
|
205
|
-
@inject(TerminalWatcher)
|
|
206
|
-
protected readonly terminalWatcher: TerminalWatcher;
|
|
207
|
-
|
|
208
|
-
@inject(VariableResolverService)
|
|
209
|
-
protected readonly variableResolver: VariableResolverService;
|
|
210
|
-
|
|
211
|
-
@inject(StorageService)
|
|
212
|
-
protected readonly storageService: StorageService;
|
|
213
|
-
|
|
214
|
-
@inject(PreferenceService)
|
|
215
|
-
protected readonly preferenceService: PreferenceService;
|
|
216
|
-
|
|
217
|
-
@inject(TerminalPreferences)
|
|
218
|
-
protected terminalPreferences: TerminalPreferences;
|
|
219
|
-
|
|
220
|
-
protected mergePreferencesPromise: Promise<void> = Promise.resolve();
|
|
221
|
-
|
|
222
|
-
protected readonly onDidCreateTerminalEmitter = new Emitter<TerminalWidget>();
|
|
223
|
-
readonly onDidCreateTerminal: Event<TerminalWidget> = this.onDidCreateTerminalEmitter.event;
|
|
224
|
-
|
|
225
|
-
protected readonly onDidChangeCurrentTerminalEmitter = new Emitter<TerminalWidget | undefined>();
|
|
226
|
-
readonly onDidChangeCurrentTerminal: Event<TerminalWidget | undefined> = this.onDidChangeCurrentTerminalEmitter.event;
|
|
227
|
-
|
|
228
|
-
@inject(ContextKeyService)
|
|
229
|
-
protected readonly contextKeyService: ContextKeyService;
|
|
230
|
-
|
|
231
|
-
@postConstruct()
|
|
232
|
-
protected init(): void {
|
|
233
|
-
this.shell.onDidChangeCurrentWidget(() => this.updateCurrentTerminal());
|
|
234
|
-
this.widgetManager.onDidCreateWidget(({ widget }) => {
|
|
235
|
-
if (widget instanceof TerminalWidget) {
|
|
236
|
-
this.updateCurrentTerminal();
|
|
237
|
-
this.onDidCreateTerminalEmitter.fire(widget);
|
|
238
|
-
this.setLastUsedTerminal(widget);
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const terminalFocusKey = this.contextKeyService.createKey<boolean>('terminalFocus', false);
|
|
243
|
-
const terminalSearchToggle = this.contextKeyService.createKey<boolean>('terminalHideSearch', false);
|
|
244
|
-
const updateFocusKey = () => {
|
|
245
|
-
terminalFocusKey.set(this.shell.activeWidget instanceof TerminalWidget);
|
|
246
|
-
terminalSearchToggle.set(this.terminalHideSearch);
|
|
247
|
-
};
|
|
248
|
-
updateFocusKey();
|
|
249
|
-
this.shell.onDidChangeActiveWidget(updateFocusKey);
|
|
250
|
-
|
|
251
|
-
this.terminalWatcher.onStoreTerminalEnvVariablesRequested(data => {
|
|
252
|
-
this.storageService.setData(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, data);
|
|
253
|
-
});
|
|
254
|
-
this.terminalWatcher.onUpdateTerminalEnvVariablesRequested(() => {
|
|
255
|
-
this.storageService.getData<string>(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY).then(data => {
|
|
256
|
-
if (data) {
|
|
257
|
-
this.shellTerminalServer.restorePersisted(data);
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
get terminalHideSearch(): boolean {
|
|
264
|
-
if (!(this.shell.activeWidget instanceof TerminalWidget)) {
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
const searchWidget = this.shell.activeWidget.getSearchBox();
|
|
268
|
-
return searchWidget.isVisible;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
async onStart(app: FrontendApplication): Promise<void> {
|
|
272
|
-
this.contributeDefaultProfiles();
|
|
273
|
-
|
|
274
|
-
this.terminalPreferences.onPreferenceChanged(e => {
|
|
275
|
-
if (e.preferenceName.startsWith('terminal.integrated.')) {
|
|
276
|
-
this.mergePreferencesPromise = this.mergePreferencesPromise.finally(() => this.mergePreferences());
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
this.mergePreferencesPromise = this.mergePreferencesPromise.finally(() => this.mergePreferences());
|
|
280
|
-
|
|
281
|
-
// extension contributions get read after this point: need to set the default profile if necessary
|
|
282
|
-
this.profileService.onAdded(id => {
|
|
283
|
-
let defaultProfileId: string | undefined;
|
|
284
|
-
switch (OS.backend.type()) {
|
|
285
|
-
case OS.Type.Windows: {
|
|
286
|
-
defaultProfileId = this.terminalPreferences['terminal.integrated.defaultProfile.windows'];
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
case OS.Type.Linux: {
|
|
290
|
-
defaultProfileId = this.terminalPreferences['terminal.integrated.defaultProfile.linux'];
|
|
291
|
-
break;
|
|
292
|
-
}
|
|
293
|
-
case OS.Type.OSX: {
|
|
294
|
-
defaultProfileId = this.terminalPreferences['terminal.integrated.defaultProfile.osx'];
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (defaultProfileId) {
|
|
299
|
-
this.profileService.setDefaultProfile(defaultProfileId);
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
async contributeDefaultProfiles(): Promise<void> {
|
|
305
|
-
if (OS.backend.isWindows) {
|
|
306
|
-
this.contributedProfileStore.registerTerminalProfile('cmd', new ShellTerminalProfile(this, {
|
|
307
|
-
shellPath: await this.resolveShellPath([
|
|
308
|
-
'${env:windir}\\Sysnative\\cmd.exe',
|
|
309
|
-
'${env:windir}\\System32\\cmd.exe'
|
|
310
|
-
])!
|
|
311
|
-
}));
|
|
312
|
-
} else {
|
|
313
|
-
this.contributedProfileStore.registerTerminalProfile('SHELL', new ShellTerminalProfile(this, {
|
|
314
|
-
shellPath: await this.resolveShellPath('${SHELL}')!,
|
|
315
|
-
shellArgs: ['-l'],
|
|
316
|
-
iconClass: 'codicon codicon-terminal'
|
|
317
|
-
}));
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// contribute default profiles based on legacy preferences
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
protected async mergePreferences(): Promise<void> {
|
|
324
|
-
let profiles: Profiles;
|
|
325
|
-
let defaultProfile: string;
|
|
326
|
-
let legacyShellPath: string | undefined;
|
|
327
|
-
let legacyShellArgs: string[] | undefined;
|
|
328
|
-
const removed = new Set(this.userProfileStore.all.map(([id, profile]) => id));
|
|
329
|
-
switch (OS.backend.type()) {
|
|
330
|
-
case OS.Type.Windows: {
|
|
331
|
-
profiles = this.terminalPreferences['terminal.integrated.profiles.windows'];
|
|
332
|
-
defaultProfile = this.terminalPreferences['terminal.integrated.defaultProfile.windows'];
|
|
333
|
-
legacyShellPath = this.terminalPreferences['terminal.integrated.shell.windows'] ?? undefined;
|
|
334
|
-
legacyShellArgs = this.terminalPreferences['terminal.integrated.shellArgs.windows'];
|
|
335
|
-
break;
|
|
336
|
-
}
|
|
337
|
-
case OS.Type.Linux: {
|
|
338
|
-
profiles = this.terminalPreferences['terminal.integrated.profiles.linux'];
|
|
339
|
-
defaultProfile = this.terminalPreferences['terminal.integrated.defaultProfile.linux'];
|
|
340
|
-
legacyShellPath = this.terminalPreferences['terminal.integrated.shell.linux'] ?? undefined;
|
|
341
|
-
legacyShellArgs = this.terminalPreferences['terminal.integrated.shellArgs.linux'];
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
case OS.Type.OSX: {
|
|
345
|
-
profiles = this.terminalPreferences['terminal.integrated.profiles.osx'];
|
|
346
|
-
defaultProfile = this.terminalPreferences['terminal.integrated.defaultProfile.osx'];
|
|
347
|
-
legacyShellPath = this.terminalPreferences['terminal.integrated.shell.osx'] ?? undefined;
|
|
348
|
-
legacyShellArgs = this.terminalPreferences['terminal.integrated.shellArgs.osx'];
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
if (profiles) {
|
|
353
|
-
for (const id of Object.getOwnPropertyNames(profiles)) {
|
|
354
|
-
const profile = profiles[id];
|
|
355
|
-
removed.delete(id);
|
|
356
|
-
if (profile) {
|
|
357
|
-
const shellPath = await this.resolveShellPath(profile.path);
|
|
358
|
-
|
|
359
|
-
if (shellPath) {
|
|
360
|
-
const options: TerminalWidgetOptions = {
|
|
361
|
-
shellPath: shellPath,
|
|
362
|
-
shellArgs: profile.args ? await this.variableResolver.resolve(profile.args) : undefined,
|
|
363
|
-
useServerTitle: profile.overrideName ? false : undefined,
|
|
364
|
-
env: profile.env ? await this.variableResolver.resolve(profile.env) : undefined,
|
|
365
|
-
title: profile.overrideName ? id : undefined
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
this.userProfileStore.registerTerminalProfile(id, new ShellTerminalProfile(this, options));
|
|
369
|
-
}
|
|
370
|
-
} else {
|
|
371
|
-
this.userProfileStore.registerTerminalProfile(id, NULL_PROFILE);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (legacyShellPath) {
|
|
377
|
-
this.userProfileStore.registerTerminalProfile('Legacy Shell Preferences', new ShellTerminalProfile(this, {
|
|
378
|
-
shellPath: legacyShellPath!,
|
|
379
|
-
shellArgs: legacyShellArgs
|
|
380
|
-
}));
|
|
381
|
-
// if no other default is set, use the legacy preferences as default if they exist
|
|
382
|
-
this.profileService.setDefaultProfile('Legacy Shell Preferences');
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (defaultProfile && this.profileService.getProfile(defaultProfile)) {
|
|
386
|
-
this.profileService.setDefaultProfile(defaultProfile);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
for (const id of removed) {
|
|
390
|
-
this.userProfileStore.unregisterTerminalProfile(id);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
protected async resolveShellPath(path: string | string[] | undefined): Promise<string | undefined> {
|
|
395
|
-
if (!path) {
|
|
396
|
-
return undefined;
|
|
397
|
-
}
|
|
398
|
-
if (typeof path === 'string') {
|
|
399
|
-
path = [path];
|
|
400
|
-
}
|
|
401
|
-
for (const p of path) {
|
|
402
|
-
const resolved = await this.variableResolver.resolve(p);
|
|
403
|
-
if (resolved) {
|
|
404
|
-
const resolvedURI = URI.fromFilePath(resolved);
|
|
405
|
-
if (await this.fileService.exists(resolvedURI)) {
|
|
406
|
-
return resolved;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
return undefined;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
onWillStop(): OnWillStopAction<number> | undefined {
|
|
414
|
-
const preferenceValue = this.terminalPreferences['terminal.integrated.confirmOnExit'];
|
|
415
|
-
if (preferenceValue !== 'never') {
|
|
416
|
-
const allTerminals = this.widgetManager.getWidgets(TERMINAL_WIDGET_FACTORY_ID) as TerminalWidget[];
|
|
417
|
-
if (allTerminals.length) {
|
|
418
|
-
return {
|
|
419
|
-
prepare: async () => {
|
|
420
|
-
if (preferenceValue === 'always') {
|
|
421
|
-
return allTerminals.length;
|
|
422
|
-
} else {
|
|
423
|
-
const activeTerminals = await Promise.all(allTerminals.map(widget => widget.hasChildProcesses()))
|
|
424
|
-
.then(hasChildProcesses => hasChildProcesses.filter(hasChild => hasChild));
|
|
425
|
-
return activeTerminals.length;
|
|
426
|
-
}
|
|
427
|
-
},
|
|
428
|
-
action: async activeTerminalCount => activeTerminalCount === 0 || this.confirmExitWithActiveTerminals(activeTerminalCount),
|
|
429
|
-
reason: 'Active integrated terminal',
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
protected async confirmExitWithActiveTerminals(activeTerminalCount: number): Promise<boolean> {
|
|
436
|
-
const msg = activeTerminalCount === 1
|
|
437
|
-
? nls.localizeByDefault('Do you want to terminate the active terminal session?')
|
|
438
|
-
: nls.localizeByDefault('Do you want to terminate the {0} active terminal sessions?', activeTerminalCount);
|
|
439
|
-
const safeToExit = await new ConfirmDialog({
|
|
440
|
-
title: '',
|
|
441
|
-
msg,
|
|
442
|
-
ok: nls.localizeByDefault('Terminate'),
|
|
443
|
-
cancel: Dialog.CANCEL,
|
|
444
|
-
}).open();
|
|
445
|
-
return safeToExit === true;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
protected _currentTerminal: TerminalWidget | undefined;
|
|
449
|
-
get currentTerminal(): TerminalWidget | undefined {
|
|
450
|
-
return this._currentTerminal;
|
|
451
|
-
}
|
|
452
|
-
protected setCurrentTerminal(current: TerminalWidget | undefined): void {
|
|
453
|
-
if (this._currentTerminal !== current) {
|
|
454
|
-
this._currentTerminal = current;
|
|
455
|
-
this.onDidChangeCurrentTerminalEmitter.fire(this._currentTerminal);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
protected updateCurrentTerminal(): void {
|
|
459
|
-
const widget = this.shell.currentWidget;
|
|
460
|
-
if (widget instanceof TerminalWidget) {
|
|
461
|
-
this.setCurrentTerminal(widget);
|
|
462
|
-
} else if (!this._currentTerminal || !this._currentTerminal.isVisible) {
|
|
463
|
-
this.setCurrentTerminal(undefined);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// IDs of the most recently used terminals
|
|
468
|
-
protected mostRecentlyUsedTerminalEntries: { id: string, disposables: DisposableCollection }[] = [];
|
|
469
|
-
|
|
470
|
-
protected getLastUsedTerminalId(): string | undefined {
|
|
471
|
-
const mostRecent = this.mostRecentlyUsedTerminalEntries[this.mostRecentlyUsedTerminalEntries.length - 1];
|
|
472
|
-
if (mostRecent) {
|
|
473
|
-
return mostRecent.id;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
get lastUsedTerminal(): TerminalWidget | undefined {
|
|
478
|
-
const id = this.getLastUsedTerminalId();
|
|
479
|
-
if (id) {
|
|
480
|
-
return this.getById(id);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
protected setLastUsedTerminal(lastUsedTerminal: TerminalWidget): void {
|
|
485
|
-
const lastUsedTerminalId = lastUsedTerminal.id;
|
|
486
|
-
const entryIndex = this.mostRecentlyUsedTerminalEntries.findIndex(entry => entry.id === lastUsedTerminalId);
|
|
487
|
-
let toDispose: DisposableCollection | undefined;
|
|
488
|
-
if (entryIndex >= 0) {
|
|
489
|
-
toDispose = this.mostRecentlyUsedTerminalEntries[entryIndex].disposables;
|
|
490
|
-
this.mostRecentlyUsedTerminalEntries.splice(entryIndex, 1);
|
|
491
|
-
} else {
|
|
492
|
-
toDispose = new DisposableCollection();
|
|
493
|
-
toDispose.push(
|
|
494
|
-
lastUsedTerminal.onDidChangeVisibility((isVisible: boolean) => {
|
|
495
|
-
if (isVisible) {
|
|
496
|
-
this.setLastUsedTerminal(lastUsedTerminal);
|
|
497
|
-
}
|
|
498
|
-
})
|
|
499
|
-
);
|
|
500
|
-
toDispose.push(
|
|
501
|
-
lastUsedTerminal.onDidDispose(() => {
|
|
502
|
-
const index = this.mostRecentlyUsedTerminalEntries.findIndex(entry => entry.id === lastUsedTerminalId);
|
|
503
|
-
if (index >= 0) {
|
|
504
|
-
this.mostRecentlyUsedTerminalEntries[index].disposables.dispose();
|
|
505
|
-
this.mostRecentlyUsedTerminalEntries.splice(index, 1);
|
|
506
|
-
}
|
|
507
|
-
})
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const newEntry = { id: lastUsedTerminalId, disposables: toDispose };
|
|
512
|
-
if (lastUsedTerminal.isVisible) {
|
|
513
|
-
this.mostRecentlyUsedTerminalEntries.push(newEntry);
|
|
514
|
-
} else {
|
|
515
|
-
this.mostRecentlyUsedTerminalEntries = [newEntry, ...this.mostRecentlyUsedTerminalEntries];
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
get all(): TerminalWidget[] {
|
|
520
|
-
return this.widgetManager.getWidgets(TERMINAL_WIDGET_FACTORY_ID) as TerminalWidget[];
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
getById(id: string): TerminalWidget | undefined {
|
|
524
|
-
return this.all.find(terminal => terminal.id === id);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
getByTerminalId(terminalId: number): TerminalWidget | undefined {
|
|
528
|
-
return this.all.find(terminal => terminal.terminalId === terminalId);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
getDefaultShell(): Promise<string> {
|
|
532
|
-
return this.shellTerminalServer.getDefaultShell();
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
registerCommands(commands: CommandRegistry): void {
|
|
536
|
-
commands.registerCommand(TerminalCommands.NEW, {
|
|
537
|
-
execute: () => this.openTerminal()
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
commands.registerCommand(TerminalCommands.PROFILE_NEW, {
|
|
541
|
-
execute: async () => {
|
|
542
|
-
const profile = await this.selectTerminalProfile(nls.localize('theia/terminal/selectProfile', 'Select a profile for the new terminal'));
|
|
543
|
-
if (!profile) {
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
this.openTerminal(undefined, profile[1]);
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
commands.registerCommand(TerminalCommands.PROFILE_DEFAULT, {
|
|
551
|
-
execute: () => this.chooseDefaultProfile()
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
commands.registerCommand(TerminalCommands.NEW_ACTIVE_WORKSPACE, {
|
|
555
|
-
execute: () => this.openActiveWorkspaceTerminal()
|
|
556
|
-
});
|
|
557
|
-
commands.registerCommand(TerminalCommands.SPLIT, {
|
|
558
|
-
execute: () => this.splitTerminal(),
|
|
559
|
-
isEnabled: w => this.withWidget(w, () => true),
|
|
560
|
-
isVisible: w => this.withWidget(w, () => true),
|
|
561
|
-
});
|
|
562
|
-
commands.registerCommand(TerminalCommands.TERMINAL_CLEAR);
|
|
563
|
-
commands.registerHandler(TerminalCommands.TERMINAL_CLEAR.id, {
|
|
564
|
-
execute: () => this.currentTerminal?.clearOutput()
|
|
565
|
-
});
|
|
566
|
-
commands.registerCommand(TerminalCommands.TERMINAL_CONTEXT, UriAwareCommandHandler.MonoSelect(this.selectionService, {
|
|
567
|
-
execute: uri => this.openInTerminal(uri)
|
|
568
|
-
}));
|
|
569
|
-
commands.registerCommand(TerminalCommands.TERMINAL_FIND_TEXT);
|
|
570
|
-
commands.registerHandler(TerminalCommands.TERMINAL_FIND_TEXT.id, {
|
|
571
|
-
isEnabled: () => {
|
|
572
|
-
if (this.shell.activeWidget instanceof TerminalWidget) {
|
|
573
|
-
return !this.shell.activeWidget.getSearchBox().isVisible;
|
|
574
|
-
}
|
|
575
|
-
return false;
|
|
576
|
-
},
|
|
577
|
-
execute: () => {
|
|
578
|
-
const termWidget = (this.shell.activeWidget as TerminalWidget);
|
|
579
|
-
const terminalSearchBox = termWidget.getSearchBox();
|
|
580
|
-
terminalSearchBox.show();
|
|
581
|
-
}
|
|
582
|
-
});
|
|
583
|
-
commands.registerCommand(TerminalCommands.TERMINAL_FIND_TEXT_CANCEL);
|
|
584
|
-
commands.registerHandler(TerminalCommands.TERMINAL_FIND_TEXT_CANCEL.id, {
|
|
585
|
-
isEnabled: () => {
|
|
586
|
-
if (this.shell.activeWidget instanceof TerminalWidget) {
|
|
587
|
-
return this.shell.activeWidget.getSearchBox().isVisible;
|
|
588
|
-
}
|
|
589
|
-
return false;
|
|
590
|
-
},
|
|
591
|
-
execute: () => {
|
|
592
|
-
const termWidget = (this.shell.activeWidget as TerminalWidget);
|
|
593
|
-
const terminalSearchBox = termWidget.getSearchBox();
|
|
594
|
-
terminalSearchBox.hide();
|
|
595
|
-
}
|
|
596
|
-
});
|
|
597
|
-
commands.registerCommand(TerminalCommands.SCROLL_LINE_UP, {
|
|
598
|
-
isEnabled: () => this.shell.activeWidget instanceof TerminalWidget,
|
|
599
|
-
isVisible: () => false,
|
|
600
|
-
execute: () => {
|
|
601
|
-
(this.shell.activeWidget as TerminalWidget).scrollLineUp();
|
|
602
|
-
}
|
|
603
|
-
});
|
|
604
|
-
commands.registerCommand(TerminalCommands.SCROLL_LINE_DOWN, {
|
|
605
|
-
isEnabled: () => this.shell.activeWidget instanceof TerminalWidget,
|
|
606
|
-
isVisible: () => false,
|
|
607
|
-
execute: () => {
|
|
608
|
-
(this.shell.activeWidget as TerminalWidget).scrollLineDown();
|
|
609
|
-
}
|
|
610
|
-
});
|
|
611
|
-
commands.registerCommand(TerminalCommands.SCROLL_TO_TOP, {
|
|
612
|
-
isEnabled: () => this.shell.activeWidget instanceof TerminalWidget,
|
|
613
|
-
isVisible: () => false,
|
|
614
|
-
execute: () => {
|
|
615
|
-
(this.shell.activeWidget as TerminalWidget).scrollToTop();
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
commands.registerCommand(TerminalCommands.SCROLL_PAGE_UP, {
|
|
619
|
-
isEnabled: () => this.shell.activeWidget instanceof TerminalWidget,
|
|
620
|
-
isVisible: () => false,
|
|
621
|
-
execute: () => {
|
|
622
|
-
(this.shell.activeWidget as TerminalWidget).scrollPageUp();
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
commands.registerCommand(TerminalCommands.SCROLL_PAGE_DOWN, {
|
|
626
|
-
isEnabled: () => this.shell.activeWidget instanceof TerminalWidget,
|
|
627
|
-
isVisible: () => false,
|
|
628
|
-
execute: () => {
|
|
629
|
-
(this.shell.activeWidget as TerminalWidget).scrollPageDown();
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
|
-
commands.registerCommand(TerminalCommands.TOGGLE_TERMINAL, {
|
|
633
|
-
execute: () => this.toggleTerminal()
|
|
634
|
-
});
|
|
635
|
-
commands.registerCommand(TerminalCommands.KILL_TERMINAL, {
|
|
636
|
-
isEnabled: () => !!this.currentTerminal,
|
|
637
|
-
execute: () => this.currentTerminal?.close()
|
|
638
|
-
});
|
|
639
|
-
commands.registerCommand(TerminalCommands.SELECT_ALL, {
|
|
640
|
-
isEnabled: () => !!this.currentTerminal,
|
|
641
|
-
execute: () => this.currentTerminal?.selectAll()
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
protected toggleTerminal(): void {
|
|
646
|
-
const terminals = this.shell.getWidgets('bottom').filter(w => w instanceof TerminalWidget);
|
|
647
|
-
|
|
648
|
-
if (terminals.length === 0) {
|
|
649
|
-
this.openTerminal();
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
if (!this.shell.isExpanded('bottom')) {
|
|
654
|
-
this.shell.expandPanel('bottom');
|
|
655
|
-
terminals[0].activate();
|
|
656
|
-
} else {
|
|
657
|
-
const visibleTerminal = terminals.find(t => t.isVisible);
|
|
658
|
-
if (!visibleTerminal) {
|
|
659
|
-
this.shell.bottomPanel.activateWidget(terminals[0]);
|
|
660
|
-
} else if (this.shell.activeWidget !== visibleTerminal) {
|
|
661
|
-
this.shell.bottomPanel.activateWidget(visibleTerminal);
|
|
662
|
-
} else {
|
|
663
|
-
this.shell.collapsePanel('bottom');
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
async openInTerminal(uri: URI): Promise<void> {
|
|
670
|
-
// Determine folder path of URI
|
|
671
|
-
let stat: FileStat;
|
|
672
|
-
try {
|
|
673
|
-
stat = await this.fileService.resolve(uri);
|
|
674
|
-
} catch {
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// Use folder if a file was selected
|
|
679
|
-
const cwd = (stat.isDirectory) ? uri.toString() : uri.parent.toString();
|
|
680
|
-
|
|
681
|
-
// Open terminal
|
|
682
|
-
const termWidget = await this.newTerminal({ cwd });
|
|
683
|
-
termWidget.start();
|
|
684
|
-
this.open(termWidget);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
registerMenus(menus: MenuModelRegistry): void {
|
|
688
|
-
menus.registerSubmenu(TerminalMenus.TERMINAL, TerminalWidgetImpl.LABEL);
|
|
689
|
-
menus.registerMenuAction(TerminalMenus.TERMINAL_NEW, {
|
|
690
|
-
commandId: TerminalCommands.NEW.id,
|
|
691
|
-
label: nls.localizeByDefault('New Terminal'),
|
|
692
|
-
order: '0'
|
|
693
|
-
});
|
|
694
|
-
menus.registerMenuAction(TerminalMenus.TERMINAL_NEW, {
|
|
695
|
-
commandId: TerminalCommands.PROFILE_NEW.id,
|
|
696
|
-
label: nls.localize('theia/terminal/profileNew', 'New Terminal (With Profile)...'),
|
|
697
|
-
order: '1'
|
|
698
|
-
});
|
|
699
|
-
|
|
700
|
-
menus.registerMenuAction(TerminalMenus.TERMINAL_NEW, {
|
|
701
|
-
commandId: TerminalCommands.PROFILE_DEFAULT.id,
|
|
702
|
-
label: nls.localize('theia/terminal/profileDefault', 'Choose Default Profile...'),
|
|
703
|
-
order: '3'
|
|
704
|
-
});
|
|
705
|
-
menus.registerMenuAction(TerminalMenus.TERMINAL_NEW, {
|
|
706
|
-
commandId: TerminalCommands.SPLIT.id,
|
|
707
|
-
order: '3'
|
|
708
|
-
});
|
|
709
|
-
menus.registerMenuAction(TerminalMenus.TERMINAL_NAVIGATOR_CONTEXT_MENU, {
|
|
710
|
-
commandId: TerminalCommands.TERMINAL_CONTEXT.id,
|
|
711
|
-
order: 'z'
|
|
712
|
-
});
|
|
713
|
-
menus.registerMenuAction(TerminalMenus.TERMINAL_OPEN_EDITORS_CONTEXT_MENU, {
|
|
714
|
-
commandId: TerminalCommands.TERMINAL_CONTEXT.id,
|
|
715
|
-
order: 'z'
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_1'], {
|
|
719
|
-
commandId: TerminalCommands.NEW_ACTIVE_WORKSPACE.id,
|
|
720
|
-
label: nls.localizeByDefault('New Terminal')
|
|
721
|
-
});
|
|
722
|
-
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_1'], {
|
|
723
|
-
commandId: TerminalCommands.SPLIT.id
|
|
724
|
-
});
|
|
725
|
-
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_2'], {
|
|
726
|
-
commandId: CommonCommands.COPY.id
|
|
727
|
-
});
|
|
728
|
-
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_2'], {
|
|
729
|
-
commandId: CommonCommands.PASTE.id
|
|
730
|
-
});
|
|
731
|
-
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_2'], {
|
|
732
|
-
commandId: TerminalCommands.SELECT_ALL.id
|
|
733
|
-
});
|
|
734
|
-
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_3'], {
|
|
735
|
-
commandId: TerminalCommands.TERMINAL_CLEAR.id
|
|
736
|
-
});
|
|
737
|
-
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_4'], {
|
|
738
|
-
commandId: TerminalCommands.KILL_TERMINAL.id
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
menus.registerSubmenu(TerminalMenus.TERMINAL_CONTRIBUTIONS, '', {
|
|
742
|
-
role: CompoundMenuNodeRole.Group
|
|
743
|
-
});
|
|
744
|
-
|
|
745
|
-
menus.registerSubmenu(TerminalMenus.TERMINAL_TITLE_CONTRIBUTIONS, '', {
|
|
746
|
-
role: CompoundMenuNodeRole.Group,
|
|
747
|
-
when: 'isTerminalTab'
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
registerToolbarItems(toolbar: TabBarToolbarRegistry): void {
|
|
752
|
-
toolbar.registerItem({
|
|
753
|
-
id: TerminalCommands.SPLIT.id,
|
|
754
|
-
command: TerminalCommands.SPLIT.id,
|
|
755
|
-
icon: codicon('split-horizontal'),
|
|
756
|
-
tooltip: TerminalCommands.SPLIT.label
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
registerKeybindings(keybindings: KeybindingRegistry): void {
|
|
761
|
-
/* Register passthrough keybindings for combinations recognized by
|
|
762
|
-
xterm.js and converted to control characters.
|
|
763
|
-
See: https://github.com/xtermjs/xterm.js/blob/v3/src/Terminal.ts#L1684 */
|
|
764
|
-
|
|
765
|
-
/* Register ctrl + k (the passed Key) as a passthrough command in the
|
|
766
|
-
context of the terminal. */
|
|
767
|
-
const regCtrl = (k: { keyCode: number, code: string }) => {
|
|
768
|
-
keybindings.registerKeybinding({
|
|
769
|
-
command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
|
|
770
|
-
keybinding: KeyCode.createKeyCode({ key: k, ctrl: true }).toString(),
|
|
771
|
-
when: 'terminalFocus',
|
|
772
|
-
});
|
|
773
|
-
};
|
|
774
|
-
|
|
775
|
-
/* Register alt + k (the passed Key) as a passthrough command in the
|
|
776
|
-
context of the terminal. */
|
|
777
|
-
const regAlt = (k: { keyCode: number, code: string }) => {
|
|
778
|
-
keybindings.registerKeybinding({
|
|
779
|
-
command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
|
|
780
|
-
keybinding: KeyCode.createKeyCode({ key: k, alt: true }).toString(),
|
|
781
|
-
when: 'terminalFocus'
|
|
782
|
-
});
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
/* ctrl-space (000 - NUL). */
|
|
786
|
-
regCtrl(Key.SPACE);
|
|
787
|
-
|
|
788
|
-
/* ctrl-A (001/1/0x1) through ctrl-Z (032/26/0x1A). */
|
|
789
|
-
for (let i = 0; i < 26; i++) {
|
|
790
|
-
regCtrl({
|
|
791
|
-
keyCode: Key.KEY_A.keyCode + i,
|
|
792
|
-
code: 'Key' + String.fromCharCode('A'.charCodeAt(0) + i)
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
/* ctrl-[ or ctrl-3 (033/27/0x1B - ESC). */
|
|
797
|
-
regCtrl(Key.BRACKET_LEFT);
|
|
798
|
-
regCtrl(Key.DIGIT3);
|
|
799
|
-
|
|
800
|
-
/* ctrl-\ or ctrl-4 (034/28/0x1C - FS). */
|
|
801
|
-
regCtrl(Key.BACKSLASH);
|
|
802
|
-
regCtrl(Key.DIGIT4);
|
|
803
|
-
|
|
804
|
-
/* ctrl-] or ctrl-5 (035/29/0x1D - GS). */
|
|
805
|
-
regCtrl(Key.BRACKET_RIGHT);
|
|
806
|
-
regCtrl(Key.DIGIT5);
|
|
807
|
-
|
|
808
|
-
/* ctrl-6 (036/30/0x1E - RS). */
|
|
809
|
-
regCtrl(Key.DIGIT6);
|
|
810
|
-
|
|
811
|
-
/* ctrl-7 (037/31/0x1F - US). */
|
|
812
|
-
regCtrl(Key.DIGIT7);
|
|
813
|
-
|
|
814
|
-
/* ctrl-8 (177/127/0x7F - DEL). */
|
|
815
|
-
regCtrl(Key.DIGIT8);
|
|
816
|
-
|
|
817
|
-
/* alt-A (0x1B 0x62) through alt-Z (0x1B 0x7A). */
|
|
818
|
-
for (let i = 0; i < 26; i++) {
|
|
819
|
-
regAlt({
|
|
820
|
-
keyCode: Key.KEY_A.keyCode + i,
|
|
821
|
-
code: 'Key' + String.fromCharCode('A'.charCodeAt(0) + i)
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
/* alt-` (0x1B 0x60). */
|
|
826
|
-
regAlt(Key.BACKQUOTE);
|
|
827
|
-
|
|
828
|
-
/* alt-0 (0x1B 0x30) through alt-9 (0x1B 0x39). */
|
|
829
|
-
for (let i = 0; i < 10; i++) {
|
|
830
|
-
regAlt({
|
|
831
|
-
keyCode: Key.DIGIT0.keyCode + i,
|
|
832
|
-
code: 'Digit' + String.fromCharCode('0'.charCodeAt(0) + i)
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
if (isOSX) {
|
|
836
|
-
// selectAll on OSX
|
|
837
|
-
keybindings.registerKeybinding({
|
|
838
|
-
command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
|
|
839
|
-
keybinding: 'ctrlcmd+a',
|
|
840
|
-
when: 'terminalFocus'
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
keybindings.registerKeybinding({
|
|
845
|
-
command: TerminalCommands.NEW.id,
|
|
846
|
-
keybinding: 'ctrl+shift+`'
|
|
847
|
-
});
|
|
848
|
-
keybindings.registerKeybinding({
|
|
849
|
-
command: TerminalCommands.NEW_ACTIVE_WORKSPACE.id,
|
|
850
|
-
keybinding: 'ctrl+`'
|
|
851
|
-
});
|
|
852
|
-
keybindings.registerKeybinding({
|
|
853
|
-
command: TerminalCommands.TERMINAL_CLEAR.id,
|
|
854
|
-
keybinding: 'ctrlcmd+k',
|
|
855
|
-
when: 'terminalFocus'
|
|
856
|
-
});
|
|
857
|
-
keybindings.registerKeybinding({
|
|
858
|
-
command: TerminalCommands.TERMINAL_FIND_TEXT.id,
|
|
859
|
-
keybinding: 'ctrlcmd+f',
|
|
860
|
-
when: 'terminalFocus'
|
|
861
|
-
});
|
|
862
|
-
keybindings.registerKeybinding({
|
|
863
|
-
command: TerminalCommands.TERMINAL_FIND_TEXT_CANCEL.id,
|
|
864
|
-
keybinding: 'esc',
|
|
865
|
-
when: 'terminalHideSearch'
|
|
866
|
-
});
|
|
867
|
-
keybindings.registerKeybinding({
|
|
868
|
-
command: TerminalCommands.SCROLL_LINE_UP.id,
|
|
869
|
-
keybinding: 'ctrl+shift+up',
|
|
870
|
-
when: 'terminalFocus'
|
|
871
|
-
});
|
|
872
|
-
keybindings.registerKeybinding({
|
|
873
|
-
command: TerminalCommands.SCROLL_LINE_DOWN.id,
|
|
874
|
-
keybinding: 'ctrl+shift+down',
|
|
875
|
-
when: 'terminalFocus'
|
|
876
|
-
});
|
|
877
|
-
keybindings.registerKeybinding({
|
|
878
|
-
command: TerminalCommands.SCROLL_TO_TOP.id,
|
|
879
|
-
keybinding: 'shift-home',
|
|
880
|
-
when: 'terminalFocus'
|
|
881
|
-
});
|
|
882
|
-
keybindings.registerKeybinding({
|
|
883
|
-
command: TerminalCommands.SCROLL_PAGE_UP.id,
|
|
884
|
-
keybinding: 'shift-pageUp',
|
|
885
|
-
when: 'terminalFocus'
|
|
886
|
-
});
|
|
887
|
-
keybindings.registerKeybinding({
|
|
888
|
-
command: TerminalCommands.SCROLL_PAGE_DOWN.id,
|
|
889
|
-
keybinding: 'shift-pageDown',
|
|
890
|
-
when: 'terminalFocus'
|
|
891
|
-
});
|
|
892
|
-
keybindings.registerKeybinding({
|
|
893
|
-
command: TerminalCommands.TOGGLE_TERMINAL.id,
|
|
894
|
-
keybinding: 'ctrl+`',
|
|
895
|
-
});
|
|
896
|
-
keybindings.registerKeybinding({
|
|
897
|
-
command: TerminalCommands.SELECT_ALL.id,
|
|
898
|
-
keybinding: 'ctrlcmd+a',
|
|
899
|
-
when: 'terminalFocus'
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
async newTerminal(options: TerminalWidgetOptions): Promise<TerminalWidget> {
|
|
904
|
-
const widget = <TerminalWidget>await this.widgetManager.getOrCreateWidget(TERMINAL_WIDGET_FACTORY_ID, <TerminalWidgetFactoryOptions>{
|
|
905
|
-
created: new Date().toISOString(),
|
|
906
|
-
...options
|
|
907
|
-
});
|
|
908
|
-
return widget;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// TODO: reuse WidgetOpenHandler.open
|
|
912
|
-
open(widget: TerminalWidget, options?: WidgetOpenerOptions): void {
|
|
913
|
-
const area = widget.location === TerminalLocation.Editor ? 'main' : 'bottom';
|
|
914
|
-
const widgetOptions: ApplicationShell.WidgetOptions = { area: area, ...options?.widgetOptions };
|
|
915
|
-
let preserveFocus = false;
|
|
916
|
-
|
|
917
|
-
if (typeof widget.location === 'object') {
|
|
918
|
-
if ('parentTerminal' in widget.location) {
|
|
919
|
-
widgetOptions.ref = this.getById(widget.location.parentTerminal);
|
|
920
|
-
widgetOptions.mode = 'split-right';
|
|
921
|
-
} else if ('viewColumn' in widget.location) {
|
|
922
|
-
preserveFocus = widget.location.preserveFocus ?? false;
|
|
923
|
-
switch (widget.location.viewColumn) {
|
|
924
|
-
case ViewColumn.Active:
|
|
925
|
-
widgetOptions.ref = this.shell.currentWidget;
|
|
926
|
-
widgetOptions.mode = 'tab-after';
|
|
927
|
-
break;
|
|
928
|
-
case ViewColumn.Beside:
|
|
929
|
-
widgetOptions.ref = this.shell.currentWidget;
|
|
930
|
-
widgetOptions.mode = 'split-right';
|
|
931
|
-
break;
|
|
932
|
-
default:
|
|
933
|
-
widgetOptions.area = 'main';
|
|
934
|
-
const mainAreaTerminals = this.shell.getWidgets('main').filter(w => w instanceof TerminalWidget && w.isVisible);
|
|
935
|
-
const column = Math.min(widget.location.viewColumn, mainAreaTerminals.length);
|
|
936
|
-
widgetOptions.mode = widget.location.viewColumn <= mainAreaTerminals.length ? 'split-left' : 'split-right';
|
|
937
|
-
widgetOptions.ref = mainAreaTerminals[column - 1];
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
const op: WidgetOpenerOptions = {
|
|
943
|
-
mode: 'activate',
|
|
944
|
-
...options,
|
|
945
|
-
widgetOptions: widgetOptions
|
|
946
|
-
};
|
|
947
|
-
if (!widget.isAttached) {
|
|
948
|
-
this.shell.addWidget(widget, op.widgetOptions);
|
|
949
|
-
}
|
|
950
|
-
if (op.mode === 'activate' && !preserveFocus) {
|
|
951
|
-
this.shell.activateWidget(widget.id);
|
|
952
|
-
} else if (op.mode === 'reveal' || preserveFocus) {
|
|
953
|
-
this.shell.revealWidget(widget.id);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
protected async selectTerminalCwd(): Promise<string | undefined> {
|
|
958
|
-
return new Promise(async resolve => {
|
|
959
|
-
const roots = this.workspaceService.tryGetRoots();
|
|
960
|
-
if (roots.length === 0) {
|
|
961
|
-
resolve(undefined);
|
|
962
|
-
} else if (roots.length === 1) {
|
|
963
|
-
resolve(roots[0].resource.toString());
|
|
964
|
-
} else {
|
|
965
|
-
const items = roots.map(({ resource }) => ({
|
|
966
|
-
label: this.labelProvider.getName(resource),
|
|
967
|
-
description: this.labelProvider.getLongName(resource),
|
|
968
|
-
resource
|
|
969
|
-
}));
|
|
970
|
-
const selectedItem = await this.quickInputService?.showQuickPick(items, {
|
|
971
|
-
placeholder: nls.localizeByDefault('Select current working directory for new terminal')
|
|
972
|
-
});
|
|
973
|
-
resolve(selectedItem?.resource?.toString());
|
|
974
|
-
}
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
protected async selectTerminalProfile(placeholder: string): Promise<[string, TerminalProfile] | undefined> {
|
|
979
|
-
return new Promise(async resolve => {
|
|
980
|
-
const profiles = this.profileService.all;
|
|
981
|
-
if (profiles.length === 0) {
|
|
982
|
-
resolve(undefined);
|
|
983
|
-
} else {
|
|
984
|
-
const items = profiles.map(([id, profile]) => ({
|
|
985
|
-
label: id,
|
|
986
|
-
profile
|
|
987
|
-
}));
|
|
988
|
-
const selectedItem = await this.quickInputService?.showQuickPick(items, {
|
|
989
|
-
placeholder
|
|
990
|
-
});
|
|
991
|
-
resolve(selectedItem ? [selectedItem.label, selectedItem.profile] : undefined);
|
|
992
|
-
}
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
protected async splitTerminal(referenceTerminal?: TerminalWidget): Promise<void> {
|
|
997
|
-
if (referenceTerminal || this.currentTerminal) {
|
|
998
|
-
const ref = referenceTerminal ?? this.currentTerminal;
|
|
999
|
-
await this.openTerminal({ ref, mode: 'split-right' });
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
protected async openTerminal(options?: ApplicationShell.WidgetOptions, terminalProfile?: TerminalProfile): Promise<void> {
|
|
1004
|
-
let profile = terminalProfile;
|
|
1005
|
-
if (!terminalProfile) {
|
|
1006
|
-
profile = this.profileService.defaultProfile;
|
|
1007
|
-
if (!profile) {
|
|
1008
|
-
throw new Error('There are no profiles registered');
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
if (profile instanceof ShellTerminalProfile) {
|
|
1013
|
-
if (this.workspaceService.workspace) {
|
|
1014
|
-
const cwd = await this.selectTerminalCwd();
|
|
1015
|
-
if (!cwd) {
|
|
1016
|
-
return;
|
|
1017
|
-
}
|
|
1018
|
-
profile = profile.modify({ cwd });
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
const termWidget = await profile?.start();
|
|
1023
|
-
if (!!termWidget) {
|
|
1024
|
-
this.open(termWidget, { widgetOptions: options });
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
protected async chooseDefaultProfile(): Promise<void> {
|
|
1029
|
-
const result = await this.selectTerminalProfile(nls.localizeByDefault('Select your default terminal profile'));
|
|
1030
|
-
if (!result) {
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
this.preferenceService.set(`terminal.integrated.defaultProfile.${OS.backend.type().toLowerCase()}`, result[0], PreferenceScope.User);
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
protected async openActiveWorkspaceTerminal(options?: ApplicationShell.WidgetOptions): Promise<void> {
|
|
1038
|
-
const termWidget = await this.newTerminal({});
|
|
1039
|
-
termWidget.start();
|
|
1040
|
-
this.open(termWidget, { widgetOptions: options });
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
protected withWidget<T>(widget: Widget | undefined, fn: (widget: TerminalWidget) => T): T | false {
|
|
1044
|
-
if (widget instanceof TerminalWidget) {
|
|
1045
|
-
return fn(widget);
|
|
1046
|
-
}
|
|
1047
|
-
return false;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
/**
|
|
1051
|
-
* It should be aligned with https://code.visualstudio.com/api/references/theme-color#integrated-terminal-colors
|
|
1052
|
-
*/
|
|
1053
|
-
registerColors(colors: ColorRegistry): void {
|
|
1054
|
-
colors.register({
|
|
1055
|
-
id: 'terminal.background',
|
|
1056
|
-
defaults: {
|
|
1057
|
-
dark: 'panel.background',
|
|
1058
|
-
light: 'panel.background',
|
|
1059
|
-
hcDark: 'panel.background',
|
|
1060
|
-
hcLight: 'panel.background'
|
|
1061
|
-
},
|
|
1062
|
-
description: 'The background color of the terminal, this allows coloring the terminal differently to the panel.'
|
|
1063
|
-
});
|
|
1064
|
-
colors.register({
|
|
1065
|
-
id: 'terminal.foreground',
|
|
1066
|
-
defaults: {
|
|
1067
|
-
light: '#333333',
|
|
1068
|
-
dark: '#CCCCCC',
|
|
1069
|
-
hcDark: '#FFFFFF',
|
|
1070
|
-
hcLight: '#292929'
|
|
1071
|
-
},
|
|
1072
|
-
description: 'The foreground color of the terminal.'
|
|
1073
|
-
});
|
|
1074
|
-
colors.register({
|
|
1075
|
-
id: 'terminalCursor.foreground',
|
|
1076
|
-
description: 'The foreground color of the terminal cursor.'
|
|
1077
|
-
});
|
|
1078
|
-
colors.register({
|
|
1079
|
-
id: 'terminalCursor.background',
|
|
1080
|
-
description: 'The background color of the terminal cursor. Allows customizing the color of a character overlapped by a block cursor.'
|
|
1081
|
-
});
|
|
1082
|
-
colors.register({
|
|
1083
|
-
id: 'terminal.selectionBackground',
|
|
1084
|
-
defaults: {
|
|
1085
|
-
light: 'editor.selectionBackground',
|
|
1086
|
-
dark: 'editor.selectionBackground',
|
|
1087
|
-
hcDark: 'editor.selectionBackground',
|
|
1088
|
-
hcLight: 'editor.selectionBackground'
|
|
1089
|
-
},
|
|
1090
|
-
description: 'The selection background color of the terminal.'
|
|
1091
|
-
});
|
|
1092
|
-
colors.register({
|
|
1093
|
-
id: 'terminal.inactiveSelectionBackground',
|
|
1094
|
-
defaults: {
|
|
1095
|
-
light: Color.transparent('terminal.selectionBackground', 0.5),
|
|
1096
|
-
dark: Color.transparent('terminal.selectionBackground', 0.5),
|
|
1097
|
-
hcDark: Color.transparent('terminal.selectionBackground', 0.7),
|
|
1098
|
-
hcLight: Color.transparent('terminal.selectionBackground', 0.5),
|
|
1099
|
-
},
|
|
1100
|
-
description: 'The selection background color of the terminal when it does not have focus.'
|
|
1101
|
-
});
|
|
1102
|
-
colors.register({
|
|
1103
|
-
id: 'terminal.selectionForeground',
|
|
1104
|
-
defaults: {
|
|
1105
|
-
light: undefined,
|
|
1106
|
-
dark: undefined,
|
|
1107
|
-
hcDark: '#000000',
|
|
1108
|
-
hcLight: '#ffffff'
|
|
1109
|
-
},
|
|
1110
|
-
// eslint-disable-next-line max-len
|
|
1111
|
-
description: 'The selection foreground color of the terminal. When this is null the selection foreground will be retained and have the minimum contrast ratio feature applied.'
|
|
1112
|
-
});
|
|
1113
|
-
colors.register({
|
|
1114
|
-
id: 'terminal.border',
|
|
1115
|
-
defaults: {
|
|
1116
|
-
light: 'panel.border',
|
|
1117
|
-
dark: 'panel.border',
|
|
1118
|
-
hcDark: 'panel.border',
|
|
1119
|
-
hcLight: 'panel.border'
|
|
1120
|
-
},
|
|
1121
|
-
description: 'The color of the border that separates split panes within the terminal. This defaults to panel.border.'
|
|
1122
|
-
});
|
|
1123
|
-
// eslint-disable-next-line guard-for-in
|
|
1124
|
-
for (const id in terminalAnsiColorMap) {
|
|
1125
|
-
const entry = terminalAnsiColorMap[id];
|
|
1126
|
-
const colorName = id.substring(13);
|
|
1127
|
-
colors.register({
|
|
1128
|
-
id,
|
|
1129
|
-
defaults: entry.defaults,
|
|
1130
|
-
description: `'${colorName}' ANSI color in the terminal.`
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2017 TypeFox and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
+
import {
|
|
19
|
+
CommandContribution,
|
|
20
|
+
Command,
|
|
21
|
+
CommandRegistry,
|
|
22
|
+
DisposableCollection,
|
|
23
|
+
MenuContribution,
|
|
24
|
+
MenuModelRegistry,
|
|
25
|
+
isOSX,
|
|
26
|
+
SelectionService,
|
|
27
|
+
Emitter,
|
|
28
|
+
Event,
|
|
29
|
+
ViewColumn,
|
|
30
|
+
OS,
|
|
31
|
+
CompoundMenuNodeRole
|
|
32
|
+
} from '@theia/core/lib/common';
|
|
33
|
+
import {
|
|
34
|
+
ApplicationShell, KeybindingContribution, KeyCode, Key, WidgetManager, PreferenceService,
|
|
35
|
+
KeybindingRegistry, LabelProvider, WidgetOpenerOptions, StorageService, QuickInputService,
|
|
36
|
+
codicon, CommonCommands, FrontendApplicationContribution, OnWillStopAction, Dialog, ConfirmDialog, FrontendApplication, PreferenceScope, Widget, SHELL_TABBAR_CONTEXT_MENU
|
|
37
|
+
} from '@theia/core/lib/browser';
|
|
38
|
+
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
|
39
|
+
import { TERMINAL_WIDGET_FACTORY_ID, TerminalWidgetFactoryOptions, TerminalWidgetImpl } from './terminal-widget-impl';
|
|
40
|
+
import { TerminalService } from './base/terminal-service';
|
|
41
|
+
import { TerminalWidgetOptions, TerminalWidget, TerminalLocation } from './base/terminal-widget';
|
|
42
|
+
import { ContributedTerminalProfileStore, NULL_PROFILE, TerminalProfile, TerminalProfileService, TerminalProfileStore, UserTerminalProfileStore } from './terminal-profile-service';
|
|
43
|
+
import { UriAwareCommandHandler } from '@theia/core/lib/common/uri-command-handler';
|
|
44
|
+
import { ShellTerminalServerProxy } from '../common/shell-terminal-protocol';
|
|
45
|
+
import URI from '@theia/core/lib/common/uri';
|
|
46
|
+
import { MAIN_MENU_BAR } from '@theia/core';
|
|
47
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
48
|
+
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
|
49
|
+
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
|
50
|
+
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
|
51
|
+
import { terminalAnsiColorMap } from './terminal-theme-service';
|
|
52
|
+
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
53
|
+
import { FileStat } from '@theia/filesystem/lib/common/files';
|
|
54
|
+
import { TerminalWatcher } from '../common/terminal-watcher';
|
|
55
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
56
|
+
import { Profiles, TerminalPreferences } from './terminal-preferences';
|
|
57
|
+
import { ShellTerminalProfile } from './shell-terminal-profile';
|
|
58
|
+
import { VariableResolverService } from '@theia/variable-resolver/lib/browser';
|
|
59
|
+
import { Color } from '@theia/core/lib/common/color';
|
|
60
|
+
|
|
61
|
+
export namespace TerminalMenus {
|
|
62
|
+
export const TERMINAL = [...MAIN_MENU_BAR, '7_terminal'];
|
|
63
|
+
export const TERMINAL_NEW = [...TERMINAL, '1_terminal'];
|
|
64
|
+
|
|
65
|
+
export const TERMINAL_TASKS = [...TERMINAL, '2_terminal'];
|
|
66
|
+
export const TERMINAL_TASKS_INFO = [...TERMINAL_TASKS, '3_terminal'];
|
|
67
|
+
export const TERMINAL_TASKS_CONFIG = [...TERMINAL_TASKS, '4_terminal'];
|
|
68
|
+
export const TERMINAL_NAVIGATOR_CONTEXT_MENU = ['navigator-context-menu', 'navigation'];
|
|
69
|
+
export const TERMINAL_OPEN_EDITORS_CONTEXT_MENU = ['open-editors-context-menu', 'navigation'];
|
|
70
|
+
|
|
71
|
+
export const TERMINAL_CONTEXT_MENU = ['terminal-context-menu'];
|
|
72
|
+
export const TERMINAL_CONTRIBUTIONS = [...TERMINAL_CONTEXT_MENU, '5_terminal_contributions'];
|
|
73
|
+
|
|
74
|
+
export const TERMINAL_TITLE_CONTRIBUTIONS = [...SHELL_TABBAR_CONTEXT_MENU, 'terminal_title_contributions'];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export namespace TerminalCommands {
|
|
78
|
+
const TERMINAL_CATEGORY = 'Terminal';
|
|
79
|
+
export const NEW = Command.toDefaultLocalizedCommand({
|
|
80
|
+
id: 'terminal:new',
|
|
81
|
+
category: TERMINAL_CATEGORY,
|
|
82
|
+
label: 'Create New Terminal'
|
|
83
|
+
});
|
|
84
|
+
export const PROFILE_NEW = Command.toLocalizedCommand({
|
|
85
|
+
id: 'terminal:new:profile',
|
|
86
|
+
category: TERMINAL_CATEGORY,
|
|
87
|
+
label: 'Create New Integrated Terminal from a Profile'
|
|
88
|
+
});
|
|
89
|
+
export const PROFILE_DEFAULT = Command.toLocalizedCommand({
|
|
90
|
+
id: 'terminal:profile:default',
|
|
91
|
+
category: TERMINAL_CATEGORY,
|
|
92
|
+
label: 'Choose the default Terminal Profile'
|
|
93
|
+
});
|
|
94
|
+
export const NEW_ACTIVE_WORKSPACE = Command.toDefaultLocalizedCommand({
|
|
95
|
+
id: 'terminal:new:active:workspace',
|
|
96
|
+
category: TERMINAL_CATEGORY,
|
|
97
|
+
label: 'Create New Terminal (In Active Workspace)'
|
|
98
|
+
});
|
|
99
|
+
export const TERMINAL_CLEAR = Command.toDefaultLocalizedCommand({
|
|
100
|
+
id: 'terminal:clear',
|
|
101
|
+
category: TERMINAL_CATEGORY,
|
|
102
|
+
label: 'Clear'
|
|
103
|
+
});
|
|
104
|
+
export const TERMINAL_CONTEXT = Command.toDefaultLocalizedCommand({
|
|
105
|
+
id: 'terminal:context',
|
|
106
|
+
category: TERMINAL_CATEGORY,
|
|
107
|
+
label: 'Open in Integrated Terminal'
|
|
108
|
+
});
|
|
109
|
+
export const SPLIT = Command.toDefaultLocalizedCommand({
|
|
110
|
+
id: 'terminal:split',
|
|
111
|
+
category: TERMINAL_CATEGORY,
|
|
112
|
+
label: 'Split Terminal'
|
|
113
|
+
});
|
|
114
|
+
export const TERMINAL_FIND_TEXT = Command.toDefaultLocalizedCommand({
|
|
115
|
+
id: 'terminal:find',
|
|
116
|
+
category: TERMINAL_CATEGORY,
|
|
117
|
+
label: 'Find'
|
|
118
|
+
});
|
|
119
|
+
export const TERMINAL_FIND_TEXT_CANCEL = Command.toDefaultLocalizedCommand({
|
|
120
|
+
id: 'terminal:find:cancel',
|
|
121
|
+
category: TERMINAL_CATEGORY,
|
|
122
|
+
label: 'Hide Find'
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
export const SCROLL_LINE_UP = Command.toDefaultLocalizedCommand({
|
|
126
|
+
id: 'terminal:scroll:line:up',
|
|
127
|
+
category: TERMINAL_CATEGORY,
|
|
128
|
+
label: 'Scroll Up (Line)'
|
|
129
|
+
});
|
|
130
|
+
export const SCROLL_LINE_DOWN = Command.toDefaultLocalizedCommand({
|
|
131
|
+
id: 'terminal:scroll:line:down',
|
|
132
|
+
category: TERMINAL_CATEGORY,
|
|
133
|
+
label: 'Scroll Down (Line)'
|
|
134
|
+
});
|
|
135
|
+
export const SCROLL_TO_TOP = Command.toDefaultLocalizedCommand({
|
|
136
|
+
id: 'terminal:scroll:top',
|
|
137
|
+
category: TERMINAL_CATEGORY,
|
|
138
|
+
label: 'Scroll to Top'
|
|
139
|
+
});
|
|
140
|
+
export const SCROLL_PAGE_UP = Command.toDefaultLocalizedCommand({
|
|
141
|
+
id: 'terminal:scroll:page:up',
|
|
142
|
+
category: TERMINAL_CATEGORY,
|
|
143
|
+
label: 'Scroll Up (Page)'
|
|
144
|
+
});
|
|
145
|
+
export const SCROLL_PAGE_DOWN = Command.toDefaultLocalizedCommand({
|
|
146
|
+
id: 'terminal:scroll:page:down',
|
|
147
|
+
category: TERMINAL_CATEGORY,
|
|
148
|
+
label: 'Scroll Down (Page)'
|
|
149
|
+
});
|
|
150
|
+
export const TOGGLE_TERMINAL = Command.toDefaultLocalizedCommand({
|
|
151
|
+
id: 'workbench.action.terminal.toggleTerminal',
|
|
152
|
+
category: TERMINAL_CATEGORY,
|
|
153
|
+
label: 'Toggle Terminal'
|
|
154
|
+
});
|
|
155
|
+
export const KILL_TERMINAL = Command.toDefaultLocalizedCommand({
|
|
156
|
+
id: 'terminal:kill',
|
|
157
|
+
category: TERMINAL_CATEGORY,
|
|
158
|
+
label: 'Kill Terminal'
|
|
159
|
+
});
|
|
160
|
+
export const SELECT_ALL: Command = {
|
|
161
|
+
id: 'terminal:select:all',
|
|
162
|
+
label: CommonCommands.SELECT_ALL.label,
|
|
163
|
+
category: TERMINAL_CATEGORY,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Command that displays all terminals that are currently opened
|
|
168
|
+
*/
|
|
169
|
+
export const SHOW_ALL_OPENED_TERMINALS = Command.toDefaultLocalizedCommand({
|
|
170
|
+
id: 'workbench.action.showAllTerminals',
|
|
171
|
+
category: CommonCommands.VIEW_CATEGORY,
|
|
172
|
+
label: 'Show All Opened Terminals'
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const ENVIRONMENT_VARIABLE_COLLECTIONS_KEY = 'terminal.integrated.environmentVariableCollections';
|
|
177
|
+
@injectable()
|
|
178
|
+
export class TerminalFrontendContribution implements FrontendApplicationContribution, TerminalService, CommandContribution, MenuContribution,
|
|
179
|
+
KeybindingContribution, TabBarToolbarContribution, ColorContribution {
|
|
180
|
+
|
|
181
|
+
@inject(ApplicationShell) protected readonly shell: ApplicationShell;
|
|
182
|
+
@inject(ShellTerminalServerProxy) protected readonly shellTerminalServer: ShellTerminalServerProxy;
|
|
183
|
+
@inject(WidgetManager) protected readonly widgetManager: WidgetManager;
|
|
184
|
+
@inject(FileService) protected readonly fileService: FileService;
|
|
185
|
+
@inject(SelectionService) protected readonly selectionService: SelectionService;
|
|
186
|
+
|
|
187
|
+
@inject(LabelProvider)
|
|
188
|
+
protected readonly labelProvider: LabelProvider;
|
|
189
|
+
|
|
190
|
+
@inject(QuickInputService) @optional()
|
|
191
|
+
protected readonly quickInputService: QuickInputService;
|
|
192
|
+
|
|
193
|
+
@inject(WorkspaceService)
|
|
194
|
+
protected readonly workspaceService: WorkspaceService;
|
|
195
|
+
|
|
196
|
+
@inject(TerminalProfileService)
|
|
197
|
+
protected readonly profileService: TerminalProfileService;
|
|
198
|
+
|
|
199
|
+
@inject(UserTerminalProfileStore)
|
|
200
|
+
protected readonly userProfileStore: TerminalProfileStore;
|
|
201
|
+
|
|
202
|
+
@inject(ContributedTerminalProfileStore)
|
|
203
|
+
protected readonly contributedProfileStore: TerminalProfileStore;
|
|
204
|
+
|
|
205
|
+
@inject(TerminalWatcher)
|
|
206
|
+
protected readonly terminalWatcher: TerminalWatcher;
|
|
207
|
+
|
|
208
|
+
@inject(VariableResolverService)
|
|
209
|
+
protected readonly variableResolver: VariableResolverService;
|
|
210
|
+
|
|
211
|
+
@inject(StorageService)
|
|
212
|
+
protected readonly storageService: StorageService;
|
|
213
|
+
|
|
214
|
+
@inject(PreferenceService)
|
|
215
|
+
protected readonly preferenceService: PreferenceService;
|
|
216
|
+
|
|
217
|
+
@inject(TerminalPreferences)
|
|
218
|
+
protected terminalPreferences: TerminalPreferences;
|
|
219
|
+
|
|
220
|
+
protected mergePreferencesPromise: Promise<void> = Promise.resolve();
|
|
221
|
+
|
|
222
|
+
protected readonly onDidCreateTerminalEmitter = new Emitter<TerminalWidget>();
|
|
223
|
+
readonly onDidCreateTerminal: Event<TerminalWidget> = this.onDidCreateTerminalEmitter.event;
|
|
224
|
+
|
|
225
|
+
protected readonly onDidChangeCurrentTerminalEmitter = new Emitter<TerminalWidget | undefined>();
|
|
226
|
+
readonly onDidChangeCurrentTerminal: Event<TerminalWidget | undefined> = this.onDidChangeCurrentTerminalEmitter.event;
|
|
227
|
+
|
|
228
|
+
@inject(ContextKeyService)
|
|
229
|
+
protected readonly contextKeyService: ContextKeyService;
|
|
230
|
+
|
|
231
|
+
@postConstruct()
|
|
232
|
+
protected init(): void {
|
|
233
|
+
this.shell.onDidChangeCurrentWidget(() => this.updateCurrentTerminal());
|
|
234
|
+
this.widgetManager.onDidCreateWidget(({ widget }) => {
|
|
235
|
+
if (widget instanceof TerminalWidget) {
|
|
236
|
+
this.updateCurrentTerminal();
|
|
237
|
+
this.onDidCreateTerminalEmitter.fire(widget);
|
|
238
|
+
this.setLastUsedTerminal(widget);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const terminalFocusKey = this.contextKeyService.createKey<boolean>('terminalFocus', false);
|
|
243
|
+
const terminalSearchToggle = this.contextKeyService.createKey<boolean>('terminalHideSearch', false);
|
|
244
|
+
const updateFocusKey = () => {
|
|
245
|
+
terminalFocusKey.set(this.shell.activeWidget instanceof TerminalWidget);
|
|
246
|
+
terminalSearchToggle.set(this.terminalHideSearch);
|
|
247
|
+
};
|
|
248
|
+
updateFocusKey();
|
|
249
|
+
this.shell.onDidChangeActiveWidget(updateFocusKey);
|
|
250
|
+
|
|
251
|
+
this.terminalWatcher.onStoreTerminalEnvVariablesRequested(data => {
|
|
252
|
+
this.storageService.setData(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, data);
|
|
253
|
+
});
|
|
254
|
+
this.terminalWatcher.onUpdateTerminalEnvVariablesRequested(() => {
|
|
255
|
+
this.storageService.getData<string>(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY).then(data => {
|
|
256
|
+
if (data) {
|
|
257
|
+
this.shellTerminalServer.restorePersisted(data);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
get terminalHideSearch(): boolean {
|
|
264
|
+
if (!(this.shell.activeWidget instanceof TerminalWidget)) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
const searchWidget = this.shell.activeWidget.getSearchBox();
|
|
268
|
+
return searchWidget.isVisible;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async onStart(app: FrontendApplication): Promise<void> {
|
|
272
|
+
this.contributeDefaultProfiles();
|
|
273
|
+
|
|
274
|
+
this.terminalPreferences.onPreferenceChanged(e => {
|
|
275
|
+
if (e.preferenceName.startsWith('terminal.integrated.')) {
|
|
276
|
+
this.mergePreferencesPromise = this.mergePreferencesPromise.finally(() => this.mergePreferences());
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
this.mergePreferencesPromise = this.mergePreferencesPromise.finally(() => this.mergePreferences());
|
|
280
|
+
|
|
281
|
+
// extension contributions get read after this point: need to set the default profile if necessary
|
|
282
|
+
this.profileService.onAdded(id => {
|
|
283
|
+
let defaultProfileId: string | undefined;
|
|
284
|
+
switch (OS.backend.type()) {
|
|
285
|
+
case OS.Type.Windows: {
|
|
286
|
+
defaultProfileId = this.terminalPreferences['terminal.integrated.defaultProfile.windows'];
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case OS.Type.Linux: {
|
|
290
|
+
defaultProfileId = this.terminalPreferences['terminal.integrated.defaultProfile.linux'];
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case OS.Type.OSX: {
|
|
294
|
+
defaultProfileId = this.terminalPreferences['terminal.integrated.defaultProfile.osx'];
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (defaultProfileId) {
|
|
299
|
+
this.profileService.setDefaultProfile(defaultProfileId);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async contributeDefaultProfiles(): Promise<void> {
|
|
305
|
+
if (OS.backend.isWindows) {
|
|
306
|
+
this.contributedProfileStore.registerTerminalProfile('cmd', new ShellTerminalProfile(this, {
|
|
307
|
+
shellPath: await this.resolveShellPath([
|
|
308
|
+
'${env:windir}\\Sysnative\\cmd.exe',
|
|
309
|
+
'${env:windir}\\System32\\cmd.exe'
|
|
310
|
+
])!
|
|
311
|
+
}));
|
|
312
|
+
} else {
|
|
313
|
+
this.contributedProfileStore.registerTerminalProfile('SHELL', new ShellTerminalProfile(this, {
|
|
314
|
+
shellPath: await this.resolveShellPath('${SHELL}')!,
|
|
315
|
+
shellArgs: ['-l'],
|
|
316
|
+
iconClass: 'codicon codicon-terminal'
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// contribute default profiles based on legacy preferences
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
protected async mergePreferences(): Promise<void> {
|
|
324
|
+
let profiles: Profiles;
|
|
325
|
+
let defaultProfile: string;
|
|
326
|
+
let legacyShellPath: string | undefined;
|
|
327
|
+
let legacyShellArgs: string[] | undefined;
|
|
328
|
+
const removed = new Set(this.userProfileStore.all.map(([id, profile]) => id));
|
|
329
|
+
switch (OS.backend.type()) {
|
|
330
|
+
case OS.Type.Windows: {
|
|
331
|
+
profiles = this.terminalPreferences['terminal.integrated.profiles.windows'];
|
|
332
|
+
defaultProfile = this.terminalPreferences['terminal.integrated.defaultProfile.windows'];
|
|
333
|
+
legacyShellPath = this.terminalPreferences['terminal.integrated.shell.windows'] ?? undefined;
|
|
334
|
+
legacyShellArgs = this.terminalPreferences['terminal.integrated.shellArgs.windows'];
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
case OS.Type.Linux: {
|
|
338
|
+
profiles = this.terminalPreferences['terminal.integrated.profiles.linux'];
|
|
339
|
+
defaultProfile = this.terminalPreferences['terminal.integrated.defaultProfile.linux'];
|
|
340
|
+
legacyShellPath = this.terminalPreferences['terminal.integrated.shell.linux'] ?? undefined;
|
|
341
|
+
legacyShellArgs = this.terminalPreferences['terminal.integrated.shellArgs.linux'];
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
case OS.Type.OSX: {
|
|
345
|
+
profiles = this.terminalPreferences['terminal.integrated.profiles.osx'];
|
|
346
|
+
defaultProfile = this.terminalPreferences['terminal.integrated.defaultProfile.osx'];
|
|
347
|
+
legacyShellPath = this.terminalPreferences['terminal.integrated.shell.osx'] ?? undefined;
|
|
348
|
+
legacyShellArgs = this.terminalPreferences['terminal.integrated.shellArgs.osx'];
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (profiles) {
|
|
353
|
+
for (const id of Object.getOwnPropertyNames(profiles)) {
|
|
354
|
+
const profile = profiles[id];
|
|
355
|
+
removed.delete(id);
|
|
356
|
+
if (profile) {
|
|
357
|
+
const shellPath = await this.resolveShellPath(profile.path);
|
|
358
|
+
|
|
359
|
+
if (shellPath) {
|
|
360
|
+
const options: TerminalWidgetOptions = {
|
|
361
|
+
shellPath: shellPath,
|
|
362
|
+
shellArgs: profile.args ? await this.variableResolver.resolve(profile.args) : undefined,
|
|
363
|
+
useServerTitle: profile.overrideName ? false : undefined,
|
|
364
|
+
env: profile.env ? await this.variableResolver.resolve(profile.env) : undefined,
|
|
365
|
+
title: profile.overrideName ? id : undefined
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
this.userProfileStore.registerTerminalProfile(id, new ShellTerminalProfile(this, options));
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
this.userProfileStore.registerTerminalProfile(id, NULL_PROFILE);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (legacyShellPath) {
|
|
377
|
+
this.userProfileStore.registerTerminalProfile('Legacy Shell Preferences', new ShellTerminalProfile(this, {
|
|
378
|
+
shellPath: legacyShellPath!,
|
|
379
|
+
shellArgs: legacyShellArgs
|
|
380
|
+
}));
|
|
381
|
+
// if no other default is set, use the legacy preferences as default if they exist
|
|
382
|
+
this.profileService.setDefaultProfile('Legacy Shell Preferences');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (defaultProfile && this.profileService.getProfile(defaultProfile)) {
|
|
386
|
+
this.profileService.setDefaultProfile(defaultProfile);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
for (const id of removed) {
|
|
390
|
+
this.userProfileStore.unregisterTerminalProfile(id);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
protected async resolveShellPath(path: string | string[] | undefined): Promise<string | undefined> {
|
|
395
|
+
if (!path) {
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
if (typeof path === 'string') {
|
|
399
|
+
path = [path];
|
|
400
|
+
}
|
|
401
|
+
for (const p of path) {
|
|
402
|
+
const resolved = await this.variableResolver.resolve(p);
|
|
403
|
+
if (resolved) {
|
|
404
|
+
const resolvedURI = URI.fromFilePath(resolved);
|
|
405
|
+
if (await this.fileService.exists(resolvedURI)) {
|
|
406
|
+
return resolved;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
onWillStop(): OnWillStopAction<number> | undefined {
|
|
414
|
+
const preferenceValue = this.terminalPreferences['terminal.integrated.confirmOnExit'];
|
|
415
|
+
if (preferenceValue !== 'never') {
|
|
416
|
+
const allTerminals = this.widgetManager.getWidgets(TERMINAL_WIDGET_FACTORY_ID) as TerminalWidget[];
|
|
417
|
+
if (allTerminals.length) {
|
|
418
|
+
return {
|
|
419
|
+
prepare: async () => {
|
|
420
|
+
if (preferenceValue === 'always') {
|
|
421
|
+
return allTerminals.length;
|
|
422
|
+
} else {
|
|
423
|
+
const activeTerminals = await Promise.all(allTerminals.map(widget => widget.hasChildProcesses()))
|
|
424
|
+
.then(hasChildProcesses => hasChildProcesses.filter(hasChild => hasChild));
|
|
425
|
+
return activeTerminals.length;
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
action: async activeTerminalCount => activeTerminalCount === 0 || this.confirmExitWithActiveTerminals(activeTerminalCount),
|
|
429
|
+
reason: 'Active integrated terminal',
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
protected async confirmExitWithActiveTerminals(activeTerminalCount: number): Promise<boolean> {
|
|
436
|
+
const msg = activeTerminalCount === 1
|
|
437
|
+
? nls.localizeByDefault('Do you want to terminate the active terminal session?')
|
|
438
|
+
: nls.localizeByDefault('Do you want to terminate the {0} active terminal sessions?', activeTerminalCount);
|
|
439
|
+
const safeToExit = await new ConfirmDialog({
|
|
440
|
+
title: '',
|
|
441
|
+
msg,
|
|
442
|
+
ok: nls.localizeByDefault('Terminate'),
|
|
443
|
+
cancel: Dialog.CANCEL,
|
|
444
|
+
}).open();
|
|
445
|
+
return safeToExit === true;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
protected _currentTerminal: TerminalWidget | undefined;
|
|
449
|
+
get currentTerminal(): TerminalWidget | undefined {
|
|
450
|
+
return this._currentTerminal;
|
|
451
|
+
}
|
|
452
|
+
protected setCurrentTerminal(current: TerminalWidget | undefined): void {
|
|
453
|
+
if (this._currentTerminal !== current) {
|
|
454
|
+
this._currentTerminal = current;
|
|
455
|
+
this.onDidChangeCurrentTerminalEmitter.fire(this._currentTerminal);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
protected updateCurrentTerminal(): void {
|
|
459
|
+
const widget = this.shell.currentWidget;
|
|
460
|
+
if (widget instanceof TerminalWidget) {
|
|
461
|
+
this.setCurrentTerminal(widget);
|
|
462
|
+
} else if (!this._currentTerminal || !this._currentTerminal.isVisible) {
|
|
463
|
+
this.setCurrentTerminal(undefined);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// IDs of the most recently used terminals
|
|
468
|
+
protected mostRecentlyUsedTerminalEntries: { id: string, disposables: DisposableCollection }[] = [];
|
|
469
|
+
|
|
470
|
+
protected getLastUsedTerminalId(): string | undefined {
|
|
471
|
+
const mostRecent = this.mostRecentlyUsedTerminalEntries[this.mostRecentlyUsedTerminalEntries.length - 1];
|
|
472
|
+
if (mostRecent) {
|
|
473
|
+
return mostRecent.id;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
get lastUsedTerminal(): TerminalWidget | undefined {
|
|
478
|
+
const id = this.getLastUsedTerminalId();
|
|
479
|
+
if (id) {
|
|
480
|
+
return this.getById(id);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
protected setLastUsedTerminal(lastUsedTerminal: TerminalWidget): void {
|
|
485
|
+
const lastUsedTerminalId = lastUsedTerminal.id;
|
|
486
|
+
const entryIndex = this.mostRecentlyUsedTerminalEntries.findIndex(entry => entry.id === lastUsedTerminalId);
|
|
487
|
+
let toDispose: DisposableCollection | undefined;
|
|
488
|
+
if (entryIndex >= 0) {
|
|
489
|
+
toDispose = this.mostRecentlyUsedTerminalEntries[entryIndex].disposables;
|
|
490
|
+
this.mostRecentlyUsedTerminalEntries.splice(entryIndex, 1);
|
|
491
|
+
} else {
|
|
492
|
+
toDispose = new DisposableCollection();
|
|
493
|
+
toDispose.push(
|
|
494
|
+
lastUsedTerminal.onDidChangeVisibility((isVisible: boolean) => {
|
|
495
|
+
if (isVisible) {
|
|
496
|
+
this.setLastUsedTerminal(lastUsedTerminal);
|
|
497
|
+
}
|
|
498
|
+
})
|
|
499
|
+
);
|
|
500
|
+
toDispose.push(
|
|
501
|
+
lastUsedTerminal.onDidDispose(() => {
|
|
502
|
+
const index = this.mostRecentlyUsedTerminalEntries.findIndex(entry => entry.id === lastUsedTerminalId);
|
|
503
|
+
if (index >= 0) {
|
|
504
|
+
this.mostRecentlyUsedTerminalEntries[index].disposables.dispose();
|
|
505
|
+
this.mostRecentlyUsedTerminalEntries.splice(index, 1);
|
|
506
|
+
}
|
|
507
|
+
})
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const newEntry = { id: lastUsedTerminalId, disposables: toDispose };
|
|
512
|
+
if (lastUsedTerminal.isVisible) {
|
|
513
|
+
this.mostRecentlyUsedTerminalEntries.push(newEntry);
|
|
514
|
+
} else {
|
|
515
|
+
this.mostRecentlyUsedTerminalEntries = [newEntry, ...this.mostRecentlyUsedTerminalEntries];
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
get all(): TerminalWidget[] {
|
|
520
|
+
return this.widgetManager.getWidgets(TERMINAL_WIDGET_FACTORY_ID) as TerminalWidget[];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
getById(id: string): TerminalWidget | undefined {
|
|
524
|
+
return this.all.find(terminal => terminal.id === id);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
getByTerminalId(terminalId: number): TerminalWidget | undefined {
|
|
528
|
+
return this.all.find(terminal => terminal.terminalId === terminalId);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
getDefaultShell(): Promise<string> {
|
|
532
|
+
return this.shellTerminalServer.getDefaultShell();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
registerCommands(commands: CommandRegistry): void {
|
|
536
|
+
commands.registerCommand(TerminalCommands.NEW, {
|
|
537
|
+
execute: () => this.openTerminal()
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
commands.registerCommand(TerminalCommands.PROFILE_NEW, {
|
|
541
|
+
execute: async () => {
|
|
542
|
+
const profile = await this.selectTerminalProfile(nls.localize('theia/terminal/selectProfile', 'Select a profile for the new terminal'));
|
|
543
|
+
if (!profile) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
this.openTerminal(undefined, profile[1]);
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
commands.registerCommand(TerminalCommands.PROFILE_DEFAULT, {
|
|
551
|
+
execute: () => this.chooseDefaultProfile()
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
commands.registerCommand(TerminalCommands.NEW_ACTIVE_WORKSPACE, {
|
|
555
|
+
execute: () => this.openActiveWorkspaceTerminal()
|
|
556
|
+
});
|
|
557
|
+
commands.registerCommand(TerminalCommands.SPLIT, {
|
|
558
|
+
execute: () => this.splitTerminal(),
|
|
559
|
+
isEnabled: w => this.withWidget(w, () => true),
|
|
560
|
+
isVisible: w => this.withWidget(w, () => true),
|
|
561
|
+
});
|
|
562
|
+
commands.registerCommand(TerminalCommands.TERMINAL_CLEAR);
|
|
563
|
+
commands.registerHandler(TerminalCommands.TERMINAL_CLEAR.id, {
|
|
564
|
+
execute: () => this.currentTerminal?.clearOutput()
|
|
565
|
+
});
|
|
566
|
+
commands.registerCommand(TerminalCommands.TERMINAL_CONTEXT, UriAwareCommandHandler.MonoSelect(this.selectionService, {
|
|
567
|
+
execute: uri => this.openInTerminal(uri)
|
|
568
|
+
}));
|
|
569
|
+
commands.registerCommand(TerminalCommands.TERMINAL_FIND_TEXT);
|
|
570
|
+
commands.registerHandler(TerminalCommands.TERMINAL_FIND_TEXT.id, {
|
|
571
|
+
isEnabled: () => {
|
|
572
|
+
if (this.shell.activeWidget instanceof TerminalWidget) {
|
|
573
|
+
return !this.shell.activeWidget.getSearchBox().isVisible;
|
|
574
|
+
}
|
|
575
|
+
return false;
|
|
576
|
+
},
|
|
577
|
+
execute: () => {
|
|
578
|
+
const termWidget = (this.shell.activeWidget as TerminalWidget);
|
|
579
|
+
const terminalSearchBox = termWidget.getSearchBox();
|
|
580
|
+
terminalSearchBox.show();
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
commands.registerCommand(TerminalCommands.TERMINAL_FIND_TEXT_CANCEL);
|
|
584
|
+
commands.registerHandler(TerminalCommands.TERMINAL_FIND_TEXT_CANCEL.id, {
|
|
585
|
+
isEnabled: () => {
|
|
586
|
+
if (this.shell.activeWidget instanceof TerminalWidget) {
|
|
587
|
+
return this.shell.activeWidget.getSearchBox().isVisible;
|
|
588
|
+
}
|
|
589
|
+
return false;
|
|
590
|
+
},
|
|
591
|
+
execute: () => {
|
|
592
|
+
const termWidget = (this.shell.activeWidget as TerminalWidget);
|
|
593
|
+
const terminalSearchBox = termWidget.getSearchBox();
|
|
594
|
+
terminalSearchBox.hide();
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
commands.registerCommand(TerminalCommands.SCROLL_LINE_UP, {
|
|
598
|
+
isEnabled: () => this.shell.activeWidget instanceof TerminalWidget,
|
|
599
|
+
isVisible: () => false,
|
|
600
|
+
execute: () => {
|
|
601
|
+
(this.shell.activeWidget as TerminalWidget).scrollLineUp();
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
commands.registerCommand(TerminalCommands.SCROLL_LINE_DOWN, {
|
|
605
|
+
isEnabled: () => this.shell.activeWidget instanceof TerminalWidget,
|
|
606
|
+
isVisible: () => false,
|
|
607
|
+
execute: () => {
|
|
608
|
+
(this.shell.activeWidget as TerminalWidget).scrollLineDown();
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
commands.registerCommand(TerminalCommands.SCROLL_TO_TOP, {
|
|
612
|
+
isEnabled: () => this.shell.activeWidget instanceof TerminalWidget,
|
|
613
|
+
isVisible: () => false,
|
|
614
|
+
execute: () => {
|
|
615
|
+
(this.shell.activeWidget as TerminalWidget).scrollToTop();
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
commands.registerCommand(TerminalCommands.SCROLL_PAGE_UP, {
|
|
619
|
+
isEnabled: () => this.shell.activeWidget instanceof TerminalWidget,
|
|
620
|
+
isVisible: () => false,
|
|
621
|
+
execute: () => {
|
|
622
|
+
(this.shell.activeWidget as TerminalWidget).scrollPageUp();
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
commands.registerCommand(TerminalCommands.SCROLL_PAGE_DOWN, {
|
|
626
|
+
isEnabled: () => this.shell.activeWidget instanceof TerminalWidget,
|
|
627
|
+
isVisible: () => false,
|
|
628
|
+
execute: () => {
|
|
629
|
+
(this.shell.activeWidget as TerminalWidget).scrollPageDown();
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
commands.registerCommand(TerminalCommands.TOGGLE_TERMINAL, {
|
|
633
|
+
execute: () => this.toggleTerminal()
|
|
634
|
+
});
|
|
635
|
+
commands.registerCommand(TerminalCommands.KILL_TERMINAL, {
|
|
636
|
+
isEnabled: () => !!this.currentTerminal,
|
|
637
|
+
execute: () => this.currentTerminal?.close()
|
|
638
|
+
});
|
|
639
|
+
commands.registerCommand(TerminalCommands.SELECT_ALL, {
|
|
640
|
+
isEnabled: () => !!this.currentTerminal,
|
|
641
|
+
execute: () => this.currentTerminal?.selectAll()
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
protected toggleTerminal(): void {
|
|
646
|
+
const terminals = this.shell.getWidgets('bottom').filter(w => w instanceof TerminalWidget);
|
|
647
|
+
|
|
648
|
+
if (terminals.length === 0) {
|
|
649
|
+
this.openTerminal();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (!this.shell.isExpanded('bottom')) {
|
|
654
|
+
this.shell.expandPanel('bottom');
|
|
655
|
+
terminals[0].activate();
|
|
656
|
+
} else {
|
|
657
|
+
const visibleTerminal = terminals.find(t => t.isVisible);
|
|
658
|
+
if (!visibleTerminal) {
|
|
659
|
+
this.shell.bottomPanel.activateWidget(terminals[0]);
|
|
660
|
+
} else if (this.shell.activeWidget !== visibleTerminal) {
|
|
661
|
+
this.shell.bottomPanel.activateWidget(visibleTerminal);
|
|
662
|
+
} else {
|
|
663
|
+
this.shell.collapsePanel('bottom');
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async openInTerminal(uri: URI): Promise<void> {
|
|
670
|
+
// Determine folder path of URI
|
|
671
|
+
let stat: FileStat;
|
|
672
|
+
try {
|
|
673
|
+
stat = await this.fileService.resolve(uri);
|
|
674
|
+
} catch {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Use folder if a file was selected
|
|
679
|
+
const cwd = (stat.isDirectory) ? uri.toString() : uri.parent.toString();
|
|
680
|
+
|
|
681
|
+
// Open terminal
|
|
682
|
+
const termWidget = await this.newTerminal({ cwd });
|
|
683
|
+
termWidget.start();
|
|
684
|
+
this.open(termWidget);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
registerMenus(menus: MenuModelRegistry): void {
|
|
688
|
+
menus.registerSubmenu(TerminalMenus.TERMINAL, TerminalWidgetImpl.LABEL);
|
|
689
|
+
menus.registerMenuAction(TerminalMenus.TERMINAL_NEW, {
|
|
690
|
+
commandId: TerminalCommands.NEW.id,
|
|
691
|
+
label: nls.localizeByDefault('New Terminal'),
|
|
692
|
+
order: '0'
|
|
693
|
+
});
|
|
694
|
+
menus.registerMenuAction(TerminalMenus.TERMINAL_NEW, {
|
|
695
|
+
commandId: TerminalCommands.PROFILE_NEW.id,
|
|
696
|
+
label: nls.localize('theia/terminal/profileNew', 'New Terminal (With Profile)...'),
|
|
697
|
+
order: '1'
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
menus.registerMenuAction(TerminalMenus.TERMINAL_NEW, {
|
|
701
|
+
commandId: TerminalCommands.PROFILE_DEFAULT.id,
|
|
702
|
+
label: nls.localize('theia/terminal/profileDefault', 'Choose Default Profile...'),
|
|
703
|
+
order: '3'
|
|
704
|
+
});
|
|
705
|
+
menus.registerMenuAction(TerminalMenus.TERMINAL_NEW, {
|
|
706
|
+
commandId: TerminalCommands.SPLIT.id,
|
|
707
|
+
order: '3'
|
|
708
|
+
});
|
|
709
|
+
menus.registerMenuAction(TerminalMenus.TERMINAL_NAVIGATOR_CONTEXT_MENU, {
|
|
710
|
+
commandId: TerminalCommands.TERMINAL_CONTEXT.id,
|
|
711
|
+
order: 'z'
|
|
712
|
+
});
|
|
713
|
+
menus.registerMenuAction(TerminalMenus.TERMINAL_OPEN_EDITORS_CONTEXT_MENU, {
|
|
714
|
+
commandId: TerminalCommands.TERMINAL_CONTEXT.id,
|
|
715
|
+
order: 'z'
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_1'], {
|
|
719
|
+
commandId: TerminalCommands.NEW_ACTIVE_WORKSPACE.id,
|
|
720
|
+
label: nls.localizeByDefault('New Terminal')
|
|
721
|
+
});
|
|
722
|
+
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_1'], {
|
|
723
|
+
commandId: TerminalCommands.SPLIT.id
|
|
724
|
+
});
|
|
725
|
+
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_2'], {
|
|
726
|
+
commandId: CommonCommands.COPY.id
|
|
727
|
+
});
|
|
728
|
+
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_2'], {
|
|
729
|
+
commandId: CommonCommands.PASTE.id
|
|
730
|
+
});
|
|
731
|
+
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_2'], {
|
|
732
|
+
commandId: TerminalCommands.SELECT_ALL.id
|
|
733
|
+
});
|
|
734
|
+
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_3'], {
|
|
735
|
+
commandId: TerminalCommands.TERMINAL_CLEAR.id
|
|
736
|
+
});
|
|
737
|
+
menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_4'], {
|
|
738
|
+
commandId: TerminalCommands.KILL_TERMINAL.id
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
menus.registerSubmenu(TerminalMenus.TERMINAL_CONTRIBUTIONS, '', {
|
|
742
|
+
role: CompoundMenuNodeRole.Group
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
menus.registerSubmenu(TerminalMenus.TERMINAL_TITLE_CONTRIBUTIONS, '', {
|
|
746
|
+
role: CompoundMenuNodeRole.Group,
|
|
747
|
+
when: 'isTerminalTab'
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
registerToolbarItems(toolbar: TabBarToolbarRegistry): void {
|
|
752
|
+
toolbar.registerItem({
|
|
753
|
+
id: TerminalCommands.SPLIT.id,
|
|
754
|
+
command: TerminalCommands.SPLIT.id,
|
|
755
|
+
icon: codicon('split-horizontal'),
|
|
756
|
+
tooltip: TerminalCommands.SPLIT.label
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
registerKeybindings(keybindings: KeybindingRegistry): void {
|
|
761
|
+
/* Register passthrough keybindings for combinations recognized by
|
|
762
|
+
xterm.js and converted to control characters.
|
|
763
|
+
See: https://github.com/xtermjs/xterm.js/blob/v3/src/Terminal.ts#L1684 */
|
|
764
|
+
|
|
765
|
+
/* Register ctrl + k (the passed Key) as a passthrough command in the
|
|
766
|
+
context of the terminal. */
|
|
767
|
+
const regCtrl = (k: { keyCode: number, code: string }) => {
|
|
768
|
+
keybindings.registerKeybinding({
|
|
769
|
+
command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
|
|
770
|
+
keybinding: KeyCode.createKeyCode({ key: k, ctrl: true }).toString(),
|
|
771
|
+
when: 'terminalFocus',
|
|
772
|
+
});
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
/* Register alt + k (the passed Key) as a passthrough command in the
|
|
776
|
+
context of the terminal. */
|
|
777
|
+
const regAlt = (k: { keyCode: number, code: string }) => {
|
|
778
|
+
keybindings.registerKeybinding({
|
|
779
|
+
command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
|
|
780
|
+
keybinding: KeyCode.createKeyCode({ key: k, alt: true }).toString(),
|
|
781
|
+
when: 'terminalFocus'
|
|
782
|
+
});
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
/* ctrl-space (000 - NUL). */
|
|
786
|
+
regCtrl(Key.SPACE);
|
|
787
|
+
|
|
788
|
+
/* ctrl-A (001/1/0x1) through ctrl-Z (032/26/0x1A). */
|
|
789
|
+
for (let i = 0; i < 26; i++) {
|
|
790
|
+
regCtrl({
|
|
791
|
+
keyCode: Key.KEY_A.keyCode + i,
|
|
792
|
+
code: 'Key' + String.fromCharCode('A'.charCodeAt(0) + i)
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/* ctrl-[ or ctrl-3 (033/27/0x1B - ESC). */
|
|
797
|
+
regCtrl(Key.BRACKET_LEFT);
|
|
798
|
+
regCtrl(Key.DIGIT3);
|
|
799
|
+
|
|
800
|
+
/* ctrl-\ or ctrl-4 (034/28/0x1C - FS). */
|
|
801
|
+
regCtrl(Key.BACKSLASH);
|
|
802
|
+
regCtrl(Key.DIGIT4);
|
|
803
|
+
|
|
804
|
+
/* ctrl-] or ctrl-5 (035/29/0x1D - GS). */
|
|
805
|
+
regCtrl(Key.BRACKET_RIGHT);
|
|
806
|
+
regCtrl(Key.DIGIT5);
|
|
807
|
+
|
|
808
|
+
/* ctrl-6 (036/30/0x1E - RS). */
|
|
809
|
+
regCtrl(Key.DIGIT6);
|
|
810
|
+
|
|
811
|
+
/* ctrl-7 (037/31/0x1F - US). */
|
|
812
|
+
regCtrl(Key.DIGIT7);
|
|
813
|
+
|
|
814
|
+
/* ctrl-8 (177/127/0x7F - DEL). */
|
|
815
|
+
regCtrl(Key.DIGIT8);
|
|
816
|
+
|
|
817
|
+
/* alt-A (0x1B 0x62) through alt-Z (0x1B 0x7A). */
|
|
818
|
+
for (let i = 0; i < 26; i++) {
|
|
819
|
+
regAlt({
|
|
820
|
+
keyCode: Key.KEY_A.keyCode + i,
|
|
821
|
+
code: 'Key' + String.fromCharCode('A'.charCodeAt(0) + i)
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/* alt-` (0x1B 0x60). */
|
|
826
|
+
regAlt(Key.BACKQUOTE);
|
|
827
|
+
|
|
828
|
+
/* alt-0 (0x1B 0x30) through alt-9 (0x1B 0x39). */
|
|
829
|
+
for (let i = 0; i < 10; i++) {
|
|
830
|
+
regAlt({
|
|
831
|
+
keyCode: Key.DIGIT0.keyCode + i,
|
|
832
|
+
code: 'Digit' + String.fromCharCode('0'.charCodeAt(0) + i)
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
if (isOSX) {
|
|
836
|
+
// selectAll on OSX
|
|
837
|
+
keybindings.registerKeybinding({
|
|
838
|
+
command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND,
|
|
839
|
+
keybinding: 'ctrlcmd+a',
|
|
840
|
+
when: 'terminalFocus'
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
keybindings.registerKeybinding({
|
|
845
|
+
command: TerminalCommands.NEW.id,
|
|
846
|
+
keybinding: 'ctrl+shift+`'
|
|
847
|
+
});
|
|
848
|
+
keybindings.registerKeybinding({
|
|
849
|
+
command: TerminalCommands.NEW_ACTIVE_WORKSPACE.id,
|
|
850
|
+
keybinding: 'ctrl+`'
|
|
851
|
+
});
|
|
852
|
+
keybindings.registerKeybinding({
|
|
853
|
+
command: TerminalCommands.TERMINAL_CLEAR.id,
|
|
854
|
+
keybinding: 'ctrlcmd+k',
|
|
855
|
+
when: 'terminalFocus'
|
|
856
|
+
});
|
|
857
|
+
keybindings.registerKeybinding({
|
|
858
|
+
command: TerminalCommands.TERMINAL_FIND_TEXT.id,
|
|
859
|
+
keybinding: 'ctrlcmd+f',
|
|
860
|
+
when: 'terminalFocus'
|
|
861
|
+
});
|
|
862
|
+
keybindings.registerKeybinding({
|
|
863
|
+
command: TerminalCommands.TERMINAL_FIND_TEXT_CANCEL.id,
|
|
864
|
+
keybinding: 'esc',
|
|
865
|
+
when: 'terminalHideSearch'
|
|
866
|
+
});
|
|
867
|
+
keybindings.registerKeybinding({
|
|
868
|
+
command: TerminalCommands.SCROLL_LINE_UP.id,
|
|
869
|
+
keybinding: 'ctrl+shift+up',
|
|
870
|
+
when: 'terminalFocus'
|
|
871
|
+
});
|
|
872
|
+
keybindings.registerKeybinding({
|
|
873
|
+
command: TerminalCommands.SCROLL_LINE_DOWN.id,
|
|
874
|
+
keybinding: 'ctrl+shift+down',
|
|
875
|
+
when: 'terminalFocus'
|
|
876
|
+
});
|
|
877
|
+
keybindings.registerKeybinding({
|
|
878
|
+
command: TerminalCommands.SCROLL_TO_TOP.id,
|
|
879
|
+
keybinding: 'shift-home',
|
|
880
|
+
when: 'terminalFocus'
|
|
881
|
+
});
|
|
882
|
+
keybindings.registerKeybinding({
|
|
883
|
+
command: TerminalCommands.SCROLL_PAGE_UP.id,
|
|
884
|
+
keybinding: 'shift-pageUp',
|
|
885
|
+
when: 'terminalFocus'
|
|
886
|
+
});
|
|
887
|
+
keybindings.registerKeybinding({
|
|
888
|
+
command: TerminalCommands.SCROLL_PAGE_DOWN.id,
|
|
889
|
+
keybinding: 'shift-pageDown',
|
|
890
|
+
when: 'terminalFocus'
|
|
891
|
+
});
|
|
892
|
+
keybindings.registerKeybinding({
|
|
893
|
+
command: TerminalCommands.TOGGLE_TERMINAL.id,
|
|
894
|
+
keybinding: 'ctrl+`',
|
|
895
|
+
});
|
|
896
|
+
keybindings.registerKeybinding({
|
|
897
|
+
command: TerminalCommands.SELECT_ALL.id,
|
|
898
|
+
keybinding: 'ctrlcmd+a',
|
|
899
|
+
when: 'terminalFocus'
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
async newTerminal(options: TerminalWidgetOptions): Promise<TerminalWidget> {
|
|
904
|
+
const widget = <TerminalWidget>await this.widgetManager.getOrCreateWidget(TERMINAL_WIDGET_FACTORY_ID, <TerminalWidgetFactoryOptions>{
|
|
905
|
+
created: new Date().toISOString(),
|
|
906
|
+
...options
|
|
907
|
+
});
|
|
908
|
+
return widget;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// TODO: reuse WidgetOpenHandler.open
|
|
912
|
+
open(widget: TerminalWidget, options?: WidgetOpenerOptions): void {
|
|
913
|
+
const area = widget.location === TerminalLocation.Editor ? 'main' : 'bottom';
|
|
914
|
+
const widgetOptions: ApplicationShell.WidgetOptions = { area: area, ...options?.widgetOptions };
|
|
915
|
+
let preserveFocus = false;
|
|
916
|
+
|
|
917
|
+
if (typeof widget.location === 'object') {
|
|
918
|
+
if ('parentTerminal' in widget.location) {
|
|
919
|
+
widgetOptions.ref = this.getById(widget.location.parentTerminal);
|
|
920
|
+
widgetOptions.mode = 'split-right';
|
|
921
|
+
} else if ('viewColumn' in widget.location) {
|
|
922
|
+
preserveFocus = widget.location.preserveFocus ?? false;
|
|
923
|
+
switch (widget.location.viewColumn) {
|
|
924
|
+
case ViewColumn.Active:
|
|
925
|
+
widgetOptions.ref = this.shell.currentWidget;
|
|
926
|
+
widgetOptions.mode = 'tab-after';
|
|
927
|
+
break;
|
|
928
|
+
case ViewColumn.Beside:
|
|
929
|
+
widgetOptions.ref = this.shell.currentWidget;
|
|
930
|
+
widgetOptions.mode = 'split-right';
|
|
931
|
+
break;
|
|
932
|
+
default:
|
|
933
|
+
widgetOptions.area = 'main';
|
|
934
|
+
const mainAreaTerminals = this.shell.getWidgets('main').filter(w => w instanceof TerminalWidget && w.isVisible);
|
|
935
|
+
const column = Math.min(widget.location.viewColumn, mainAreaTerminals.length);
|
|
936
|
+
widgetOptions.mode = widget.location.viewColumn <= mainAreaTerminals.length ? 'split-left' : 'split-right';
|
|
937
|
+
widgetOptions.ref = mainAreaTerminals[column - 1];
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const op: WidgetOpenerOptions = {
|
|
943
|
+
mode: 'activate',
|
|
944
|
+
...options,
|
|
945
|
+
widgetOptions: widgetOptions
|
|
946
|
+
};
|
|
947
|
+
if (!widget.isAttached) {
|
|
948
|
+
this.shell.addWidget(widget, op.widgetOptions);
|
|
949
|
+
}
|
|
950
|
+
if (op.mode === 'activate' && !preserveFocus) {
|
|
951
|
+
this.shell.activateWidget(widget.id);
|
|
952
|
+
} else if (op.mode === 'reveal' || preserveFocus) {
|
|
953
|
+
this.shell.revealWidget(widget.id);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
protected async selectTerminalCwd(): Promise<string | undefined> {
|
|
958
|
+
return new Promise(async resolve => {
|
|
959
|
+
const roots = this.workspaceService.tryGetRoots();
|
|
960
|
+
if (roots.length === 0) {
|
|
961
|
+
resolve(undefined);
|
|
962
|
+
} else if (roots.length === 1) {
|
|
963
|
+
resolve(roots[0].resource.toString());
|
|
964
|
+
} else {
|
|
965
|
+
const items = roots.map(({ resource }) => ({
|
|
966
|
+
label: this.labelProvider.getName(resource),
|
|
967
|
+
description: this.labelProvider.getLongName(resource),
|
|
968
|
+
resource
|
|
969
|
+
}));
|
|
970
|
+
const selectedItem = await this.quickInputService?.showQuickPick(items, {
|
|
971
|
+
placeholder: nls.localizeByDefault('Select current working directory for new terminal')
|
|
972
|
+
});
|
|
973
|
+
resolve(selectedItem?.resource?.toString());
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
protected async selectTerminalProfile(placeholder: string): Promise<[string, TerminalProfile] | undefined> {
|
|
979
|
+
return new Promise(async resolve => {
|
|
980
|
+
const profiles = this.profileService.all;
|
|
981
|
+
if (profiles.length === 0) {
|
|
982
|
+
resolve(undefined);
|
|
983
|
+
} else {
|
|
984
|
+
const items = profiles.map(([id, profile]) => ({
|
|
985
|
+
label: id,
|
|
986
|
+
profile
|
|
987
|
+
}));
|
|
988
|
+
const selectedItem = await this.quickInputService?.showQuickPick(items, {
|
|
989
|
+
placeholder
|
|
990
|
+
});
|
|
991
|
+
resolve(selectedItem ? [selectedItem.label, selectedItem.profile] : undefined);
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
protected async splitTerminal(referenceTerminal?: TerminalWidget): Promise<void> {
|
|
997
|
+
if (referenceTerminal || this.currentTerminal) {
|
|
998
|
+
const ref = referenceTerminal ?? this.currentTerminal;
|
|
999
|
+
await this.openTerminal({ ref, mode: 'split-right' });
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
protected async openTerminal(options?: ApplicationShell.WidgetOptions, terminalProfile?: TerminalProfile): Promise<void> {
|
|
1004
|
+
let profile = terminalProfile;
|
|
1005
|
+
if (!terminalProfile) {
|
|
1006
|
+
profile = this.profileService.defaultProfile;
|
|
1007
|
+
if (!profile) {
|
|
1008
|
+
throw new Error('There are no profiles registered');
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (profile instanceof ShellTerminalProfile) {
|
|
1013
|
+
if (this.workspaceService.workspace) {
|
|
1014
|
+
const cwd = await this.selectTerminalCwd();
|
|
1015
|
+
if (!cwd) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
profile = profile.modify({ cwd });
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const termWidget = await profile?.start();
|
|
1023
|
+
if (!!termWidget) {
|
|
1024
|
+
this.open(termWidget, { widgetOptions: options });
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
protected async chooseDefaultProfile(): Promise<void> {
|
|
1029
|
+
const result = await this.selectTerminalProfile(nls.localizeByDefault('Select your default terminal profile'));
|
|
1030
|
+
if (!result) {
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
this.preferenceService.set(`terminal.integrated.defaultProfile.${OS.backend.type().toLowerCase()}`, result[0], PreferenceScope.User);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
protected async openActiveWorkspaceTerminal(options?: ApplicationShell.WidgetOptions): Promise<void> {
|
|
1038
|
+
const termWidget = await this.newTerminal({});
|
|
1039
|
+
termWidget.start();
|
|
1040
|
+
this.open(termWidget, { widgetOptions: options });
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
protected withWidget<T>(widget: Widget | undefined, fn: (widget: TerminalWidget) => T): T | false {
|
|
1044
|
+
if (widget instanceof TerminalWidget) {
|
|
1045
|
+
return fn(widget);
|
|
1046
|
+
}
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* It should be aligned with https://code.visualstudio.com/api/references/theme-color#integrated-terminal-colors
|
|
1052
|
+
*/
|
|
1053
|
+
registerColors(colors: ColorRegistry): void {
|
|
1054
|
+
colors.register({
|
|
1055
|
+
id: 'terminal.background',
|
|
1056
|
+
defaults: {
|
|
1057
|
+
dark: 'panel.background',
|
|
1058
|
+
light: 'panel.background',
|
|
1059
|
+
hcDark: 'panel.background',
|
|
1060
|
+
hcLight: 'panel.background'
|
|
1061
|
+
},
|
|
1062
|
+
description: 'The background color of the terminal, this allows coloring the terminal differently to the panel.'
|
|
1063
|
+
});
|
|
1064
|
+
colors.register({
|
|
1065
|
+
id: 'terminal.foreground',
|
|
1066
|
+
defaults: {
|
|
1067
|
+
light: '#333333',
|
|
1068
|
+
dark: '#CCCCCC',
|
|
1069
|
+
hcDark: '#FFFFFF',
|
|
1070
|
+
hcLight: '#292929'
|
|
1071
|
+
},
|
|
1072
|
+
description: 'The foreground color of the terminal.'
|
|
1073
|
+
});
|
|
1074
|
+
colors.register({
|
|
1075
|
+
id: 'terminalCursor.foreground',
|
|
1076
|
+
description: 'The foreground color of the terminal cursor.'
|
|
1077
|
+
});
|
|
1078
|
+
colors.register({
|
|
1079
|
+
id: 'terminalCursor.background',
|
|
1080
|
+
description: 'The background color of the terminal cursor. Allows customizing the color of a character overlapped by a block cursor.'
|
|
1081
|
+
});
|
|
1082
|
+
colors.register({
|
|
1083
|
+
id: 'terminal.selectionBackground',
|
|
1084
|
+
defaults: {
|
|
1085
|
+
light: 'editor.selectionBackground',
|
|
1086
|
+
dark: 'editor.selectionBackground',
|
|
1087
|
+
hcDark: 'editor.selectionBackground',
|
|
1088
|
+
hcLight: 'editor.selectionBackground'
|
|
1089
|
+
},
|
|
1090
|
+
description: 'The selection background color of the terminal.'
|
|
1091
|
+
});
|
|
1092
|
+
colors.register({
|
|
1093
|
+
id: 'terminal.inactiveSelectionBackground',
|
|
1094
|
+
defaults: {
|
|
1095
|
+
light: Color.transparent('terminal.selectionBackground', 0.5),
|
|
1096
|
+
dark: Color.transparent('terminal.selectionBackground', 0.5),
|
|
1097
|
+
hcDark: Color.transparent('terminal.selectionBackground', 0.7),
|
|
1098
|
+
hcLight: Color.transparent('terminal.selectionBackground', 0.5),
|
|
1099
|
+
},
|
|
1100
|
+
description: 'The selection background color of the terminal when it does not have focus.'
|
|
1101
|
+
});
|
|
1102
|
+
colors.register({
|
|
1103
|
+
id: 'terminal.selectionForeground',
|
|
1104
|
+
defaults: {
|
|
1105
|
+
light: undefined,
|
|
1106
|
+
dark: undefined,
|
|
1107
|
+
hcDark: '#000000',
|
|
1108
|
+
hcLight: '#ffffff'
|
|
1109
|
+
},
|
|
1110
|
+
// eslint-disable-next-line max-len
|
|
1111
|
+
description: 'The selection foreground color of the terminal. When this is null the selection foreground will be retained and have the minimum contrast ratio feature applied.'
|
|
1112
|
+
});
|
|
1113
|
+
colors.register({
|
|
1114
|
+
id: 'terminal.border',
|
|
1115
|
+
defaults: {
|
|
1116
|
+
light: 'panel.border',
|
|
1117
|
+
dark: 'panel.border',
|
|
1118
|
+
hcDark: 'panel.border',
|
|
1119
|
+
hcLight: 'panel.border'
|
|
1120
|
+
},
|
|
1121
|
+
description: 'The color of the border that separates split panes within the terminal. This defaults to panel.border.'
|
|
1122
|
+
});
|
|
1123
|
+
// eslint-disable-next-line guard-for-in
|
|
1124
|
+
for (const id in terminalAnsiColorMap) {
|
|
1125
|
+
const entry = terminalAnsiColorMap[id];
|
|
1126
|
+
const colorName = id.substring(13);
|
|
1127
|
+
colors.register({
|
|
1128
|
+
id,
|
|
1129
|
+
defaults: entry.defaults,
|
|
1130
|
+
description: `'${colorName}' ANSI color in the terminal.`
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|