@theia/terminal 1.76.0-next.7 → 1.77.0-next.0

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.
@@ -0,0 +1,28 @@
1
+ import { MessageService } from '@theia/core/lib/common/message-service';
2
+ import { ApplicationShell } from '@theia/core/lib/browser';
3
+ import URI from '@theia/core/lib/common/uri';
4
+ import { TerminalFrontendContribution } from '../browser/terminal-frontend-contribution';
5
+ import { TerminalWidgetOptions, TerminalWidget } from '../browser/base/terminal-widget';
6
+ import { TerminalProfile } from '../browser/terminal-profile-service';
7
+ /**
8
+ * A browser-only application has no backend to run a shell in, so it can't open terminals.
9
+ *
10
+ * `TerminalService`, `FrontendApplicationContribution`, `CommandContribution` and the other
11
+ * contribution points in `terminal-frontend-module.ts` are all bound with
12
+ * `toService(TerminalFrontendContribution)`, which resolves the token lazily on every lookup.
13
+ * Rebinding `TerminalFrontendContribution` itself - instead of only `TerminalService` - therefore
14
+ * redirects every one of those paths (UI commands, terminal profiles, and the `TerminalService`
15
+ * token used by plugins/tasks/debug) through this subclass, rather than just the minority of
16
+ * callers that happen to inject `TerminalService` directly.
17
+ */
18
+ export declare class BrowserOnlyTerminalFrontendContribution extends TerminalFrontendContribution {
19
+ protected readonly messageService: MessageService;
20
+ initializeLayout(): Promise<void>;
21
+ newTerminal(options: TerminalWidgetOptions): Promise<TerminalWidget>;
22
+ getDefaultShell(): Promise<string>;
23
+ openInTerminal(uri: URI): Promise<void>;
24
+ protected openTerminal(options?: ApplicationShell.WidgetOptions, terminalProfile?: TerminalProfile): Promise<void>;
25
+ protected openActiveWorkspaceTerminal(options?: ApplicationShell.WidgetOptions): Promise<void>;
26
+ protected showNotSupportedMessage(): void;
27
+ }
28
+ //# sourceMappingURL=browser-only-terminal-frontend-contribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-only-terminal-frontend-contribution.d.ts","sourceRoot":"","sources":["../../src/browser-only/browser-only-terminal-frontend-contribution.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAExE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,GAAG,MAAM,4BAA4B,CAAC;AAC7C,OAAO,EAAE,4BAA4B,EAAE,MAAM,2CAA2C,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACxF,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,qBACa,uCAAwC,SAAQ,4BAA4B;IAGrF,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IAInC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC,WAAW,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC;IAMpE,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAIlC,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;cAI7B,YAAY,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC,aAAa,EAAE,eAAe,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;cAIxG,2BAA2B,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7G,SAAS,CAAC,uBAAuB,IAAI,IAAI;CAK5C"}
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2026 Robert Jandow
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.BrowserOnlyTerminalFrontendContribution = void 0;
19
+ const tslib_1 = require("tslib");
20
+ const inversify_1 = require("@theia/core/shared/inversify");
21
+ const message_service_1 = require("@theia/core/lib/common/message-service");
22
+ const nls_1 = require("@theia/core/lib/common/nls");
23
+ const terminal_frontend_contribution_1 = require("../browser/terminal-frontend-contribution");
24
+ /**
25
+ * A browser-only application has no backend to run a shell in, so it can't open terminals.
26
+ *
27
+ * `TerminalService`, `FrontendApplicationContribution`, `CommandContribution` and the other
28
+ * contribution points in `terminal-frontend-module.ts` are all bound with
29
+ * `toService(TerminalFrontendContribution)`, which resolves the token lazily on every lookup.
30
+ * Rebinding `TerminalFrontendContribution` itself - instead of only `TerminalService` - therefore
31
+ * redirects every one of those paths (UI commands, terminal profiles, and the `TerminalService`
32
+ * token used by plugins/tasks/debug) through this subclass, rather than just the minority of
33
+ * callers that happen to inject `TerminalService` directly.
34
+ */
35
+ let BrowserOnlyTerminalFrontendContribution = class BrowserOnlyTerminalFrontendContribution extends terminal_frontend_contribution_1.TerminalFrontendContribution {
36
+ // There is no backend to restore or create a terminal for at startup, so skip it silently
37
+ // instead of letting the base implementation fail and log an error on every launch.
38
+ async initializeLayout() {
39
+ // no terminal to restore in a browser-only application
40
+ }
41
+ // Callers that inject the `TerminalService` token programmatically (the VS Code
42
+ // `window.createTerminal` API, tasks, debug sessions, AI terminal functions, ...) need a
43
+ // rejected promise so they can handle the failure themselves. This message is not localized
44
+ // since it is meant for those programmatic callers, not shown to the user directly.
45
+ async newTerminal(options) {
46
+ throw new Error('Terminals are not supported in a browser-only application.');
47
+ }
48
+ // The plugin host queries the default shell on startup; report none available instead of
49
+ // rejecting so that query doesn't fail plugin activation.
50
+ async getDefaultShell() {
51
+ return '';
52
+ }
53
+ async openInTerminal(uri) {
54
+ this.showNotSupportedMessage();
55
+ }
56
+ async openTerminal(options, terminalProfile) {
57
+ this.showNotSupportedMessage();
58
+ }
59
+ async openActiveWorkspaceTerminal(options) {
60
+ this.showNotSupportedMessage();
61
+ }
62
+ showNotSupportedMessage() {
63
+ this.messageService.error(nls_1.nls.localize('theia/terminal/notSupportedInBrowserOnly', 'Terminals are not supported in a browser-only application.'));
64
+ }
65
+ };
66
+ exports.BrowserOnlyTerminalFrontendContribution = BrowserOnlyTerminalFrontendContribution;
67
+ tslib_1.__decorate([
68
+ (0, inversify_1.inject)(message_service_1.MessageService),
69
+ tslib_1.__metadata("design:type", message_service_1.MessageService)
70
+ ], BrowserOnlyTerminalFrontendContribution.prototype, "messageService", void 0);
71
+ exports.BrowserOnlyTerminalFrontendContribution = BrowserOnlyTerminalFrontendContribution = tslib_1.__decorate([
72
+ (0, inversify_1.injectable)()
73
+ ], BrowserOnlyTerminalFrontendContribution);
74
+ //# sourceMappingURL=browser-only-terminal-frontend-contribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-only-terminal-frontend-contribution.js","sourceRoot":"","sources":["../../src/browser-only/browser-only-terminal-frontend-contribution.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,mCAAmC;AACnC,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;;;;AAEhF,4DAAkE;AAClE,4EAAwE;AACxE,oDAAiD;AAGjD,8FAAyF;AAIzF;;;;;;;;;;GAUG;AAEI,IAAM,uCAAuC,GAA7C,MAAM,uCAAwC,SAAQ,6DAA4B;IAKrF,0FAA0F;IAC1F,oFAAoF;IAC3E,KAAK,CAAC,gBAAgB;QAC3B,uDAAuD;IAC3D,CAAC;IAED,gFAAgF;IAChF,yFAAyF;IACzF,4FAA4F;IAC5F,oFAAoF;IAC3E,KAAK,CAAC,WAAW,CAAC,OAA8B;QACrD,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAClF,CAAC;IAED,yFAAyF;IACzF,0DAA0D;IACjD,KAAK,CAAC,eAAe;QAC1B,OAAO,EAAE,CAAC;IACd,CAAC;IAEQ,KAAK,CAAC,cAAc,CAAC,GAAQ;QAClC,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACnC,CAAC;IAEkB,KAAK,CAAC,YAAY,CAAC,OAAwC,EAAE,eAAiC;QAC7G,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACnC,CAAC;IAEkB,KAAK,CAAC,2BAA2B,CAAC,OAAwC;QACzF,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACnC,CAAC;IAES,uBAAuB;QAC7B,IAAI,CAAC,cAAc,CAAC,KAAK,CACrB,SAAG,CAAC,QAAQ,CAAC,0CAA0C,EAAE,4DAA4D,CAAC,CACzH,CAAC;IACN,CAAC;CACJ,CAAA;AA1CY,0FAAuC;AAG7B;IADlB,IAAA,kBAAM,EAAC,gCAAc,CAAC;sCACY,gCAAc;+EAAC;kDAHzC,uCAAuC;IADnD,IAAA,sBAAU,GAAE;GACA,uCAAuC,CA0CnD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=browser-only-terminal-frontend-contribution.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-only-terminal-frontend-contribution.spec.d.ts","sourceRoot":"","sources":["../../src/browser-only/browser-only-terminal-frontend-contribution.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2026 Robert Jandow
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ const jsdom_1 = require("@theia/core/lib/browser/test/jsdom");
19
+ // `ApplicationShell`, `ConfirmDialog` and `FileService` transitively import browser modules
20
+ // that touch `document` at load time.
21
+ const disableJSDOM = (0, jsdom_1.enableJSDOM)();
22
+ const frontend_application_config_provider_1 = require("@theia/core/lib/browser/frontend-application-config-provider");
23
+ // `WorkspaceService` transitively reads the frontend application config at module load time.
24
+ frontend_application_config_provider_1.FrontendApplicationConfigProvider.set({});
25
+ // `xterm` probes for a canvas 2d context when it is loaded, which JSDOM does not implement and
26
+ // loudly reports. Nothing under test renders, so an inert stub is enough to keep the output clean.
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ window.HTMLCanvasElement.prototype.getContext = () => undefined;
29
+ const chai_1 = require("chai");
30
+ const inversify_1 = require("@theia/core/shared/inversify");
31
+ const core_1 = require("@theia/core");
32
+ const browser_1 = require("@theia/core/lib/browser");
33
+ const clipboard_service_1 = require("@theia/core/lib/browser/clipboard-service");
34
+ const context_key_service_1 = require("@theia/core/lib/browser/context-key-service");
35
+ const common_1 = require("@theia/core/lib/common");
36
+ const mock_logger_1 = require("@theia/core/lib/common/test/mock-logger");
37
+ const file_service_1 = require("@theia/filesystem/lib/browser/file-service");
38
+ const browser_2 = require("@theia/workspace/lib/browser");
39
+ const browser_3 = require("@theia/variable-resolver/lib/browser");
40
+ const terminal_service_1 = require("../browser/base/terminal-service");
41
+ const terminal_frontend_contribution_1 = require("../browser/terminal-frontend-contribution");
42
+ const terminal_watcher_1 = require("../common/terminal-watcher");
43
+ const shell_terminal_protocol_1 = require("../common/shell-terminal-protocol");
44
+ const terminal_preferences_1 = require("../common/terminal-preferences");
45
+ const terminal_copy_on_selection_handler_1 = require("../browser/terminal-copy-on-selection-handler");
46
+ const terminal_creation_handler_1 = require("../browser/terminal-creation-handler");
47
+ const terminal_profile_service_1 = require("../browser/terminal-profile-service");
48
+ const browser_only_terminal_frontend_contribution_1 = require("./browser-only-terminal-frontend-contribution");
49
+ disableJSDOM();
50
+ /**
51
+ * `TerminalFrontendContribution` (and hence its browser-only subclass) has many injected
52
+ * dependencies, most of which are never touched by the browser-only overrides under test here.
53
+ * Only `@postConstruct() init()` reaches into a handful of them (`shell`, `widgetManager`,
54
+ * `contextKeyService`, `terminalWatcher`) - those get working fakes, everything else gets an
55
+ * inert placeholder just to satisfy the injector.
56
+ */
57
+ function bindTerminalTestDependencies(container, onError = () => { }) {
58
+ const inert = () => ({});
59
+ container.bind(browser_1.ApplicationShell).toConstantValue({
60
+ activeWidget: undefined,
61
+ onDidChangeCurrentWidget: core_1.Event.None,
62
+ onDidChangeActiveWidget: core_1.Event.None
63
+ });
64
+ container.bind(browser_1.WidgetManager).toConstantValue({ onDidCreateWidget: core_1.Event.None });
65
+ container.bind(context_key_service_1.ContextKeyService).toConstantValue({ createKey: () => ({ set: () => { } }) });
66
+ container.bind(terminal_watcher_1.TerminalWatcher).toConstantValue({
67
+ onStoreTerminalEnvVariablesRequested: core_1.Event.None,
68
+ onUpdateTerminalEnvVariablesRequested: core_1.Event.None
69
+ });
70
+ container.bind(core_1.MessageService).toConstantValue({
71
+ error: (message) => { onError(message); return Promise.resolve(undefined); }
72
+ });
73
+ container.bind(core_1.ILogger).toConstantValue(new mock_logger_1.MockLogger());
74
+ container.bind(core_1.ContributionProvider).toConstantValue({ getContributions: () => [] }).whenTargetNamed(terminal_creation_handler_1.TerminalCreationHandler);
75
+ container.bind(shell_terminal_protocol_1.ShellTerminalServerProxy).toConstantValue(inert());
76
+ container.bind(file_service_1.FileService).toConstantValue(inert());
77
+ container.bind(core_1.SelectionService).toConstantValue(inert());
78
+ container.bind(browser_1.LabelProvider).toConstantValue(inert());
79
+ container.bind(browser_2.WorkspaceService).toConstantValue(inert());
80
+ container.bind(terminal_profile_service_1.TerminalProfileService).toConstantValue(inert());
81
+ container.bind(terminal_profile_service_1.UserTerminalProfileStore).toConstantValue(inert());
82
+ container.bind(terminal_profile_service_1.ContributedTerminalProfileStore).toConstantValue(inert());
83
+ container.bind(browser_3.VariableResolverService).toConstantValue(inert());
84
+ container.bind(browser_1.StorageService).toConstantValue(inert());
85
+ container.bind(common_1.PreferenceService).toConstantValue(inert());
86
+ container.bind(terminal_preferences_1.TerminalPreferences).toConstantValue(inert());
87
+ container.bind(terminal_copy_on_selection_handler_1.TerminalCopyOnSelectionHandler).toConstantValue(inert());
88
+ container.bind(clipboard_service_1.ClipboardService).toConstantValue(inert());
89
+ }
90
+ describe('BrowserOnlyTerminalFrontendContribution', () => {
91
+ /** Records every message reported through `MessageService.error`. */
92
+ let errorMessages;
93
+ let contribution;
94
+ beforeEach(() => {
95
+ errorMessages = [];
96
+ const container = new inversify_1.Container();
97
+ container.bind(browser_only_terminal_frontend_contribution_1.BrowserOnlyTerminalFrontendContribution).toSelf().inSingletonScope();
98
+ container.bind(terminal_service_1.TerminalService).toService(browser_only_terminal_frontend_contribution_1.BrowserOnlyTerminalFrontendContribution);
99
+ bindTerminalTestDependencies(container, message => errorMessages.push(message));
100
+ contribution = container.get(browser_only_terminal_frontend_contribution_1.BrowserOnlyTerminalFrontendContribution);
101
+ });
102
+ it('rejects programmatic terminal creation instead of returning a dead widget', async () => {
103
+ try {
104
+ await contribution.newTerminal({});
105
+ chai_1.expect.fail('newTerminal() should have rejected');
106
+ }
107
+ catch (error) {
108
+ (0, chai_1.expect)(error.message).to.equal('Terminals are not supported in a browser-only application.');
109
+ }
110
+ });
111
+ it('resolves the default shell to an empty string without touching the (nonexistent) backend', async () => {
112
+ // `shellTerminalServer` is bound to an inert placeholder above: reaching for it would throw.
113
+ const shell = await contribution.getDefaultShell();
114
+ (0, chai_1.expect)(shell).to.equal('');
115
+ });
116
+ it('reports the limitation and creates no widget when opening a terminal from the UI', async () => {
117
+ // `openTerminal` is the entry point for the "Create New Terminal" command and the
118
+ // profile quick-pick; `widgetManager` is left as an inert placeholder above, so a
119
+ // widget-creating code path would throw here rather than quietly succeed.
120
+ await contribution.openTerminal();
121
+ (0, chai_1.expect)(errorMessages).to.deep.equal(['Terminals are not supported in a browser-only application.']);
122
+ });
123
+ it('reports the limitation when opening a terminal for the active workspace', async () => {
124
+ await contribution.openActiveWorkspaceTerminal();
125
+ (0, chai_1.expect)(errorMessages).to.deep.equal(['Terminals are not supported in a browser-only application.']);
126
+ });
127
+ it('reports the limitation when opening a terminal from a file/folder context menu', async () => {
128
+ await contribution.openInTerminal({ toString: () => 'file:///workspace' });
129
+ (0, chai_1.expect)(errorMessages).to.deep.equal(['Terminals are not supported in a browser-only application.']);
130
+ });
131
+ it('does not attempt to create or restore a terminal at startup', async () => {
132
+ // The base implementation calls `newTerminal` and logs an error when it fails; the
133
+ // browser-only override must skip that attempt entirely so no such error is logged.
134
+ await contribution.initializeLayout();
135
+ (0, chai_1.expect)(errorMessages).to.be.empty;
136
+ });
137
+ it('rebinding TerminalFrontendContribution redirects every toService(TerminalFrontendContribution) binding', () => {
138
+ // Mirrors how `terminal-frontend-module.ts` binds `TerminalService` and other contribution
139
+ // points via `toService(TerminalFrontendContribution)`, and how `terminal-frontend-only-module.ts`
140
+ // rebinds `TerminalFrontendContribution` itself so that every one of those lazily-resolved
141
+ // bindings is redirected to the browser-only implementation - not just `TerminalService`.
142
+ const module = new inversify_1.ContainerModule((bind, unbind, isBound, rebind) => {
143
+ bind(terminal_frontend_contribution_1.TerminalFrontendContribution).toSelf().inSingletonScope();
144
+ bind(terminal_service_1.TerminalService).toService(terminal_frontend_contribution_1.TerminalFrontendContribution);
145
+ });
146
+ const browserOnlyModule = new inversify_1.ContainerModule((bind, unbind, isBound, rebind) => {
147
+ rebind(terminal_frontend_contribution_1.TerminalFrontendContribution).to(browser_only_terminal_frontend_contribution_1.BrowserOnlyTerminalFrontendContribution).inSingletonScope();
148
+ });
149
+ const container = new inversify_1.Container();
150
+ container.load(module);
151
+ bindTerminalTestDependencies(container);
152
+ // simulates `terminal-frontend-only-module.ts` loading after `terminal-frontend-module.ts`
153
+ container.load(browserOnlyModule);
154
+ const resolved = container.get(terminal_service_1.TerminalService);
155
+ (0, chai_1.expect)(resolved).to.be.instanceOf(browser_only_terminal_frontend_contribution_1.BrowserOnlyTerminalFrontendContribution);
156
+ });
157
+ });
158
+ //# sourceMappingURL=browser-only-terminal-frontend-contribution.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-only-terminal-frontend-contribution.spec.js","sourceRoot":"","sources":["../../src/browser-only/browser-only-terminal-frontend-contribution.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { ContainerModule } from '@theia/core/shared/inversify';
2
+ declare const _default: ContainerModule;
3
+ export default _default;
4
+ //# sourceMappingURL=terminal-frontend-only-module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-frontend-only-module.d.ts","sourceRoot":"","sources":["../../src/browser-only/terminal-frontend-only-module.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;;AAI/D,wBAEG"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2026 Robert Jandow
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ const inversify_1 = require("@theia/core/shared/inversify");
19
+ const browser_only_terminal_frontend_contribution_1 = require("./browser-only-terminal-frontend-contribution");
20
+ const terminal_frontend_contribution_1 = require("../browser/terminal-frontend-contribution");
21
+ exports.default = new inversify_1.ContainerModule((bind, unbind, isBound, rebind) => {
22
+ rebind(terminal_frontend_contribution_1.TerminalFrontendContribution).to(browser_only_terminal_frontend_contribution_1.BrowserOnlyTerminalFrontendContribution).inSingletonScope();
23
+ });
24
+ //# sourceMappingURL=terminal-frontend-only-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-frontend-only-module.js","sourceRoot":"","sources":["../../src/browser-only/terminal-frontend-only-module.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,mCAAmC;AACnC,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;;AAEhF,4DAA+D;AAC/D,+GAAwG;AACxG,8FAAyF;AAEzF,kBAAe,IAAI,2BAAe,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;IACjE,MAAM,CAAC,6DAA4B,CAAC,CAAC,EAAE,CAAC,qFAAuC,CAAC,CAAC,gBAAgB,EAAE,CAAC;AACxG,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@theia/terminal",
3
- "version": "1.76.0-next.7+7c303b183",
3
+ "version": "1.77.0-next.0+d8106d7ff",
4
4
  "description": "Theia - Terminal Extension",
5
5
  "dependencies": {
6
- "@theia/core": "1.76.0-next.7+7c303b183",
7
- "@theia/editor": "1.76.0-next.7+7c303b183",
8
- "@theia/file-search": "1.76.0-next.7+7c303b183",
9
- "@theia/filesystem": "1.76.0-next.7+7c303b183",
10
- "@theia/process": "1.76.0-next.7+7c303b183",
11
- "@theia/variable-resolver": "1.76.0-next.7+7c303b183",
12
- "@theia/workspace": "1.76.0-next.7+7c303b183",
6
+ "@theia/core": "1.77.0-next.0+d8106d7ff",
7
+ "@theia/editor": "1.77.0-next.0+d8106d7ff",
8
+ "@theia/file-search": "1.77.0-next.0+d8106d7ff",
9
+ "@theia/filesystem": "1.77.0-next.0+d8106d7ff",
10
+ "@theia/process": "1.77.0-next.0+d8106d7ff",
11
+ "@theia/variable-resolver": "1.77.0-next.0+d8106d7ff",
12
+ "@theia/workspace": "1.77.0-next.0+d8106d7ff",
13
13
  "tslib": "^2.8.1",
14
14
  "xterm": "^5.3.0",
15
15
  "xterm-addon-fit": "^0.8.0",
@@ -24,6 +24,9 @@
24
24
  "frontend": "lib/browser/terminal-frontend-module",
25
25
  "secondaryWindow": "lib/browser/terminal-frontend-module",
26
26
  "backend": "lib/node/terminal-backend-module"
27
+ },
28
+ {
29
+ "frontendOnly": "lib/browser-only/terminal-frontend-only-module"
27
30
  }
28
31
  ],
29
32
  "keywords": [
@@ -51,10 +54,10 @@
51
54
  "watch": "theiaext watch"
52
55
  },
53
56
  "devDependencies": {
54
- "@theia/ext-scripts": "1.75.0"
57
+ "@theia/ext-scripts": "1.76.0"
55
58
  },
56
59
  "nyc": {
57
60
  "extends": "../../configs/nyc.json"
58
61
  },
59
- "gitHead": "7c303b1836421183d7013f0bd679a9a30ff0b5dc"
62
+ "gitHead": "d8106d7ffa62b3413ff1ca5b63a0e6c2fe0b0649"
60
63
  }
@@ -0,0 +1,182 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 Robert Jandow
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 { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
18
+ // `ApplicationShell`, `ConfirmDialog` and `FileService` transitively import browser modules
19
+ // that touch `document` at load time.
20
+ const disableJSDOM = enableJSDOM();
21
+
22
+ import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
23
+ // `WorkspaceService` transitively reads the frontend application config at module load time.
24
+ FrontendApplicationConfigProvider.set({});
25
+
26
+ // `xterm` probes for a canvas 2d context when it is loaded, which JSDOM does not implement and
27
+ // loudly reports. Nothing under test renders, so an inert stub is enough to keep the output clean.
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ (window.HTMLCanvasElement.prototype as any).getContext = () => undefined;
30
+
31
+ import { expect } from 'chai';
32
+ import { Container, ContainerModule } from '@theia/core/shared/inversify';
33
+ import { ContributionProvider, Event, ILogger, MessageService, SelectionService } from '@theia/core';
34
+ import { ApplicationShell, LabelProvider, StorageService, WidgetManager } from '@theia/core/lib/browser';
35
+ import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
36
+ import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
37
+ import { PreferenceService } from '@theia/core/lib/common';
38
+ import { MockLogger } from '@theia/core/lib/common/test/mock-logger';
39
+ import { FileService } from '@theia/filesystem/lib/browser/file-service';
40
+ import { WorkspaceService } from '@theia/workspace/lib/browser';
41
+ import { VariableResolverService } from '@theia/variable-resolver/lib/browser';
42
+ import { TerminalService } from '../browser/base/terminal-service';
43
+ import { TerminalFrontendContribution } from '../browser/terminal-frontend-contribution';
44
+ import { TerminalWatcher } from '../common/terminal-watcher';
45
+ import { ShellTerminalServerProxy } from '../common/shell-terminal-protocol';
46
+ import { TerminalPreferences } from '../common/terminal-preferences';
47
+ import { TerminalCopyOnSelectionHandler } from '../browser/terminal-copy-on-selection-handler';
48
+ import { TerminalCreationHandler } from '../browser/terminal-creation-handler';
49
+ import { ContributedTerminalProfileStore, TerminalProfileService, UserTerminalProfileStore } from '../browser/terminal-profile-service';
50
+ import { BrowserOnlyTerminalFrontendContribution } from './browser-only-terminal-frontend-contribution';
51
+
52
+ disableJSDOM();
53
+
54
+ /**
55
+ * `TerminalFrontendContribution` (and hence its browser-only subclass) has many injected
56
+ * dependencies, most of which are never touched by the browser-only overrides under test here.
57
+ * Only `@postConstruct() init()` reaches into a handful of them (`shell`, `widgetManager`,
58
+ * `contextKeyService`, `terminalWatcher`) - those get working fakes, everything else gets an
59
+ * inert placeholder just to satisfy the injector.
60
+ */
61
+ function bindTerminalTestDependencies(container: Container, onError: (message: string) => void = () => { /* no-op */ }): void {
62
+ const inert = <T>(): T => ({} as unknown as T);
63
+
64
+ container.bind(ApplicationShell).toConstantValue({
65
+ activeWidget: undefined,
66
+ onDidChangeCurrentWidget: Event.None,
67
+ onDidChangeActiveWidget: Event.None
68
+ } as unknown as ApplicationShell);
69
+ container.bind(WidgetManager).toConstantValue({ onDidCreateWidget: Event.None } as unknown as WidgetManager);
70
+ container.bind(ContextKeyService).toConstantValue({ createKey: () => ({ set: () => { /* no-op */ } }) } as unknown as ContextKeyService);
71
+ container.bind(TerminalWatcher).toConstantValue({
72
+ onStoreTerminalEnvVariablesRequested: Event.None,
73
+ onUpdateTerminalEnvVariablesRequested: Event.None
74
+ } as unknown as TerminalWatcher);
75
+ container.bind(MessageService).toConstantValue({
76
+ error: (message: string) => { onError(message); return Promise.resolve(undefined); }
77
+ } as unknown as MessageService);
78
+ container.bind(ILogger).toConstantValue(new MockLogger() as unknown as ILogger);
79
+ container.bind(ContributionProvider).toConstantValue({ getContributions: () => [] }).whenTargetNamed(TerminalCreationHandler);
80
+
81
+ container.bind(ShellTerminalServerProxy).toConstantValue(inert());
82
+ container.bind(FileService).toConstantValue(inert());
83
+ container.bind(SelectionService).toConstantValue(inert());
84
+ container.bind(LabelProvider).toConstantValue(inert());
85
+ container.bind(WorkspaceService).toConstantValue(inert());
86
+ container.bind(TerminalProfileService).toConstantValue(inert());
87
+ container.bind(UserTerminalProfileStore).toConstantValue(inert());
88
+ container.bind(ContributedTerminalProfileStore).toConstantValue(inert());
89
+ container.bind(VariableResolverService).toConstantValue(inert());
90
+ container.bind(StorageService).toConstantValue(inert());
91
+ container.bind(PreferenceService).toConstantValue(inert());
92
+ container.bind(TerminalPreferences).toConstantValue(inert());
93
+ container.bind(TerminalCopyOnSelectionHandler).toConstantValue(inert());
94
+ container.bind(ClipboardService).toConstantValue(inert());
95
+ }
96
+
97
+ describe('BrowserOnlyTerminalFrontendContribution', () => {
98
+
99
+ /** Records every message reported through `MessageService.error`. */
100
+ let errorMessages: string[];
101
+
102
+ let contribution: BrowserOnlyTerminalFrontendContribution;
103
+
104
+ beforeEach(() => {
105
+ errorMessages = [];
106
+
107
+ const container = new Container();
108
+ container.bind(BrowserOnlyTerminalFrontendContribution).toSelf().inSingletonScope();
109
+ container.bind(TerminalService).toService(BrowserOnlyTerminalFrontendContribution);
110
+ bindTerminalTestDependencies(container, message => errorMessages.push(message));
111
+
112
+ contribution = container.get(BrowserOnlyTerminalFrontendContribution);
113
+ });
114
+
115
+ it('rejects programmatic terminal creation instead of returning a dead widget', async () => {
116
+ try {
117
+ await contribution.newTerminal({});
118
+ expect.fail('newTerminal() should have rejected');
119
+ } catch (error) {
120
+ expect((error as Error).message).to.equal('Terminals are not supported in a browser-only application.');
121
+ }
122
+ });
123
+
124
+ it('resolves the default shell to an empty string without touching the (nonexistent) backend', async () => {
125
+ // `shellTerminalServer` is bound to an inert placeholder above: reaching for it would throw.
126
+ const shell = await contribution.getDefaultShell();
127
+ expect(shell).to.equal('');
128
+ });
129
+
130
+ it('reports the limitation and creates no widget when opening a terminal from the UI', async () => {
131
+ // `openTerminal` is the entry point for the "Create New Terminal" command and the
132
+ // profile quick-pick; `widgetManager` is left as an inert placeholder above, so a
133
+ // widget-creating code path would throw here rather than quietly succeed.
134
+ await (contribution as unknown as { openTerminal(): Promise<void> }).openTerminal();
135
+
136
+ expect(errorMessages).to.deep.equal(['Terminals are not supported in a browser-only application.']);
137
+ });
138
+
139
+ it('reports the limitation when opening a terminal for the active workspace', async () => {
140
+ await (contribution as unknown as { openActiveWorkspaceTerminal(): Promise<void> }).openActiveWorkspaceTerminal();
141
+
142
+ expect(errorMessages).to.deep.equal(['Terminals are not supported in a browser-only application.']);
143
+ });
144
+
145
+ it('reports the limitation when opening a terminal from a file/folder context menu', async () => {
146
+ await contribution.openInTerminal({ toString: () => 'file:///workspace' } as unknown as Parameters<TerminalFrontendContribution['openInTerminal']>[0]);
147
+
148
+ expect(errorMessages).to.deep.equal(['Terminals are not supported in a browser-only application.']);
149
+ });
150
+
151
+ it('does not attempt to create or restore a terminal at startup', async () => {
152
+ // The base implementation calls `newTerminal` and logs an error when it fails; the
153
+ // browser-only override must skip that attempt entirely so no such error is logged.
154
+ await contribution.initializeLayout();
155
+
156
+ expect(errorMessages).to.be.empty;
157
+ });
158
+
159
+ it('rebinding TerminalFrontendContribution redirects every toService(TerminalFrontendContribution) binding', () => {
160
+ // Mirrors how `terminal-frontend-module.ts` binds `TerminalService` and other contribution
161
+ // points via `toService(TerminalFrontendContribution)`, and how `terminal-frontend-only-module.ts`
162
+ // rebinds `TerminalFrontendContribution` itself so that every one of those lazily-resolved
163
+ // bindings is redirected to the browser-only implementation - not just `TerminalService`.
164
+ const module = new ContainerModule((bind, unbind, isBound, rebind) => {
165
+ bind(TerminalFrontendContribution).toSelf().inSingletonScope();
166
+ bind(TerminalService).toService(TerminalFrontendContribution);
167
+ });
168
+ const browserOnlyModule = new ContainerModule((bind, unbind, isBound, rebind) => {
169
+ rebind(TerminalFrontendContribution).to(BrowserOnlyTerminalFrontendContribution).inSingletonScope();
170
+ });
171
+
172
+ const container = new Container();
173
+ container.load(module);
174
+ bindTerminalTestDependencies(container);
175
+
176
+ // simulates `terminal-frontend-only-module.ts` loading after `terminal-frontend-module.ts`
177
+ container.load(browserOnlyModule);
178
+
179
+ const resolved = container.get(TerminalService);
180
+ expect(resolved).to.be.instanceOf(BrowserOnlyTerminalFrontendContribution);
181
+ });
182
+ });
@@ -0,0 +1,80 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 Robert Jandow
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 } from '@theia/core/shared/inversify';
18
+ import { MessageService } from '@theia/core/lib/common/message-service';
19
+ import { nls } from '@theia/core/lib/common/nls';
20
+ import { ApplicationShell } from '@theia/core/lib/browser';
21
+ import URI from '@theia/core/lib/common/uri';
22
+ import { TerminalFrontendContribution } from '../browser/terminal-frontend-contribution';
23
+ import { TerminalWidgetOptions, TerminalWidget } from '../browser/base/terminal-widget';
24
+ import { TerminalProfile } from '../browser/terminal-profile-service';
25
+
26
+ /**
27
+ * A browser-only application has no backend to run a shell in, so it can't open terminals.
28
+ *
29
+ * `TerminalService`, `FrontendApplicationContribution`, `CommandContribution` and the other
30
+ * contribution points in `terminal-frontend-module.ts` are all bound with
31
+ * `toService(TerminalFrontendContribution)`, which resolves the token lazily on every lookup.
32
+ * Rebinding `TerminalFrontendContribution` itself - instead of only `TerminalService` - therefore
33
+ * redirects every one of those paths (UI commands, terminal profiles, and the `TerminalService`
34
+ * token used by plugins/tasks/debug) through this subclass, rather than just the minority of
35
+ * callers that happen to inject `TerminalService` directly.
36
+ */
37
+ @injectable()
38
+ export class BrowserOnlyTerminalFrontendContribution extends TerminalFrontendContribution {
39
+
40
+ @inject(MessageService)
41
+ protected readonly messageService: MessageService;
42
+
43
+ // There is no backend to restore or create a terminal for at startup, so skip it silently
44
+ // instead of letting the base implementation fail and log an error on every launch.
45
+ override async initializeLayout(): Promise<void> {
46
+ // no terminal to restore in a browser-only application
47
+ }
48
+
49
+ // Callers that inject the `TerminalService` token programmatically (the VS Code
50
+ // `window.createTerminal` API, tasks, debug sessions, AI terminal functions, ...) need a
51
+ // rejected promise so they can handle the failure themselves. This message is not localized
52
+ // since it is meant for those programmatic callers, not shown to the user directly.
53
+ override async newTerminal(options: TerminalWidgetOptions): Promise<TerminalWidget> {
54
+ throw new Error('Terminals are not supported in a browser-only application.');
55
+ }
56
+
57
+ // The plugin host queries the default shell on startup; report none available instead of
58
+ // rejecting so that query doesn't fail plugin activation.
59
+ override async getDefaultShell(): Promise<string> {
60
+ return '';
61
+ }
62
+
63
+ override async openInTerminal(uri: URI): Promise<void> {
64
+ this.showNotSupportedMessage();
65
+ }
66
+
67
+ protected override async openTerminal(options?: ApplicationShell.WidgetOptions, terminalProfile?: TerminalProfile): Promise<void> {
68
+ this.showNotSupportedMessage();
69
+ }
70
+
71
+ protected override async openActiveWorkspaceTerminal(options?: ApplicationShell.WidgetOptions): Promise<void> {
72
+ this.showNotSupportedMessage();
73
+ }
74
+
75
+ protected showNotSupportedMessage(): void {
76
+ this.messageService.error(
77
+ nls.localize('theia/terminal/notSupportedInBrowserOnly', 'Terminals are not supported in a browser-only application.')
78
+ );
79
+ }
80
+ }
@@ -0,0 +1,23 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 Robert Jandow
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 { ContainerModule } from '@theia/core/shared/inversify';
18
+ import { BrowserOnlyTerminalFrontendContribution } from './browser-only-terminal-frontend-contribution';
19
+ import { TerminalFrontendContribution } from '../browser/terminal-frontend-contribution';
20
+
21
+ export default new ContainerModule((bind, unbind, isBound, rebind) => {
22
+ rebind(TerminalFrontendContribution).to(BrowserOnlyTerminalFrontendContribution).inSingletonScope();
23
+ });