@theia/terminal 1.34.2 → 1.34.3

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