@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.
- package/lib/browser-only/browser-only-terminal-frontend-contribution.d.ts +28 -0
- package/lib/browser-only/browser-only-terminal-frontend-contribution.d.ts.map +1 -0
- package/lib/browser-only/browser-only-terminal-frontend-contribution.js +74 -0
- package/lib/browser-only/browser-only-terminal-frontend-contribution.js.map +1 -0
- package/lib/browser-only/browser-only-terminal-frontend-contribution.spec.d.ts +2 -0
- package/lib/browser-only/browser-only-terminal-frontend-contribution.spec.d.ts.map +1 -0
- package/lib/browser-only/browser-only-terminal-frontend-contribution.spec.js +158 -0
- package/lib/browser-only/browser-only-terminal-frontend-contribution.spec.js.map +1 -0
- package/lib/browser-only/terminal-frontend-only-module.d.ts +4 -0
- package/lib/browser-only/terminal-frontend-only-module.d.ts.map +1 -0
- package/lib/browser-only/terminal-frontend-only-module.js +24 -0
- package/lib/browser-only/terminal-frontend-only-module.js.map +1 -0
- package/package.json +13 -10
- package/src/browser-only/browser-only-terminal-frontend-contribution.spec.ts +182 -0
- package/src/browser-only/browser-only-terminal-frontend-contribution.ts +80 -0
- package/src/browser-only/terminal-frontend-only-module.ts +23 -0
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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.
|
|
3
|
+
"version": "1.77.0-next.0+d8106d7ff",
|
|
4
4
|
"description": "Theia - Terminal Extension",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@theia/core": "1.
|
|
7
|
-
"@theia/editor": "1.
|
|
8
|
-
"@theia/file-search": "1.
|
|
9
|
-
"@theia/filesystem": "1.
|
|
10
|
-
"@theia/process": "1.
|
|
11
|
-
"@theia/variable-resolver": "1.
|
|
12
|
-
"@theia/workspace": "1.
|
|
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.
|
|
57
|
+
"@theia/ext-scripts": "1.76.0"
|
|
55
58
|
},
|
|
56
59
|
"nyc": {
|
|
57
60
|
"extends": "../../configs/nyc.json"
|
|
58
61
|
},
|
|
59
|
-
"gitHead": "
|
|
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
|
+
});
|