@theia/core 1.74.0-next.9 → 1.74.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/i18n/nls.cs.json +143 -16
- package/i18n/nls.de.json +143 -16
- package/i18n/nls.es.json +143 -16
- package/i18n/nls.fr.json +143 -16
- package/i18n/nls.hu.json +143 -16
- package/i18n/nls.it.json +143 -16
- package/i18n/nls.ja.json +143 -16
- package/i18n/nls.json +143 -16
- package/i18n/nls.ko.json +143 -16
- package/i18n/nls.pl.json +143 -16
- package/i18n/nls.pt-br.json +143 -16
- package/i18n/nls.ru.json +143 -16
- package/i18n/nls.tr.json +143 -16
- package/i18n/nls.zh-cn.json +143 -16
- package/i18n/nls.zh-tw.json +143 -16
- package/lib/browser/browser-clipboard-service.d.ts +3 -0
- package/lib/browser/browser-clipboard-service.d.ts.map +1 -1
- package/lib/browser/browser-clipboard-service.js +8 -1
- package/lib/browser/browser-clipboard-service.js.map +1 -1
- package/lib/browser/catalog.json +505 -46
- package/lib/browser/clipboard-service.d.ts +11 -0
- package/lib/browser/clipboard-service.d.ts.map +1 -1
- package/lib/browser/clipboard-service.js.map +1 -1
- package/lib/browser/common-frontend-contribution.d.ts +3 -0
- package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
- package/lib/browser/common-frontend-contribution.js +22 -10
- package/lib/browser/common-frontend-contribution.js.map +1 -1
- package/lib/browser/common-frontend-contribution.spec.d.ts +2 -0
- package/lib/browser/common-frontend-contribution.spec.d.ts.map +1 -0
- package/lib/browser/common-frontend-contribution.spec.js +71 -0
- package/lib/browser/common-frontend-contribution.spec.js.map +1 -0
- package/lib/browser/frontend-application-contribution.d.ts +1 -1
- package/lib/browser/frontend-application-module.d.ts +2 -2
- package/lib/browser/frontend-application-module.d.ts.map +1 -1
- package/lib/browser/frontend-application-module.js +13 -2
- package/lib/browser/frontend-application-module.js.map +1 -1
- package/lib/browser/frontend-application.d.ts.map +1 -1
- package/lib/browser/frontend-application.js +12 -2
- package/lib/browser/frontend-application.js.map +1 -1
- package/lib/browser/index.d.ts +1 -0
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +1 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/keybinding.d.ts.map +1 -1
- package/lib/browser/keybinding.js +12 -7
- package/lib/browser/keybinding.js.map +1 -1
- package/lib/browser/perspective-service.d.ts +173 -0
- package/lib/browser/perspective-service.d.ts.map +1 -0
- package/lib/browser/perspective-service.js +484 -0
- package/lib/browser/perspective-service.js.map +1 -0
- package/lib/browser/perspective-service.spec.d.ts +2 -0
- package/lib/browser/perspective-service.spec.d.ts.map +1 -0
- package/lib/browser/perspective-service.spec.js +1806 -0
- package/lib/browser/perspective-service.spec.js.map +1 -0
- package/lib/browser/preferences/preference-contribution.d.ts.map +1 -1
- package/lib/browser/preferences/preference-contribution.js +9 -1
- package/lib/browser/preferences/preference-contribution.js.map +1 -1
- package/lib/browser/preferences/preference-service.spec.js +63 -0
- package/lib/browser/preferences/preference-service.spec.js.map +1 -1
- package/lib/browser/shell/application-shell.d.ts +7 -0
- package/lib/browser/shell/application-shell.d.ts.map +1 -1
- package/lib/browser/shell/application-shell.js +38 -28
- package/lib/browser/shell/application-shell.js.map +1 -1
- package/lib/browser/shell/shell-layout-restorer.d.ts +9 -0
- package/lib/browser/shell/shell-layout-restorer.d.ts.map +1 -1
- package/lib/browser/shell/shell-layout-restorer.js +124 -14
- package/lib/browser/shell/shell-layout-restorer.js.map +1 -1
- package/lib/browser/shell/shell-layout-restorer.spec.d.ts +2 -0
- package/lib/browser/shell/shell-layout-restorer.spec.d.ts.map +1 -0
- package/lib/browser/shell/shell-layout-restorer.spec.js +539 -0
- package/lib/browser/shell/shell-layout-restorer.spec.js.map +1 -0
- package/lib/browser/shell/view-contribution.d.ts +2 -0
- package/lib/browser/shell/view-contribution.d.ts.map +1 -1
- package/lib/browser/shell/view-contribution.js +5 -1
- package/lib/browser/shell/view-contribution.js.map +1 -1
- package/lib/browser/status-bar/status-bar.d.ts +2 -0
- package/lib/browser/status-bar/status-bar.d.ts.map +1 -1
- package/lib/browser/status-bar/status-bar.js +11 -3
- package/lib/browser/status-bar/status-bar.js.map +1 -1
- package/lib/browser/tree/tree-consistency.spec.js +103 -0
- package/lib/browser/tree/tree-consistency.spec.js.map +1 -1
- package/lib/browser/tree/tree-widget.d.ts +7 -33
- package/lib/browser/tree/tree-widget.d.ts.map +1 -1
- package/lib/browser/tree/tree-widget.js +7 -54
- package/lib/browser/tree/tree-widget.js.map +1 -1
- package/lib/browser/tree/tree.d.ts.map +1 -1
- package/lib/browser/tree/tree.js +6 -0
- package/lib/browser/tree/tree.js.map +1 -1
- package/lib/browser/widgets/index.d.ts +1 -0
- package/lib/browser/widgets/index.d.ts.map +1 -1
- package/lib/browser/widgets/index.js +1 -0
- package/lib/browser/widgets/index.js.map +1 -1
- package/lib/browser/widgets/select-component.d.ts +15 -2
- package/lib/browser/widgets/select-component.d.ts.map +1 -1
- package/lib/browser/widgets/select-component.js +86 -24
- package/lib/browser/widgets/select-component.js.map +1 -1
- package/lib/browser/widgets/tab-bar-tracker.d.ts +23 -0
- package/lib/browser/widgets/tab-bar-tracker.d.ts.map +1 -0
- package/lib/browser/widgets/tab-bar-tracker.js +65 -0
- package/lib/browser/widgets/tab-bar-tracker.js.map +1 -0
- package/lib/browser/widgets/tab-bar-tracker.spec.d.ts +2 -0
- package/lib/browser/widgets/tab-bar-tracker.spec.d.ts.map +1 -0
- package/lib/browser/widgets/tab-bar-tracker.spec.js +109 -0
- package/lib/browser/widgets/tab-bar-tracker.spec.js.map +1 -0
- package/lib/browser/window/default-secondary-window-service.d.ts +2 -0
- package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-secondary-window-service.js +8 -2
- package/lib/browser/window/default-secondary-window-service.js.map +1 -1
- package/lib/browser/window/default-window-service.d.ts +19 -0
- package/lib/browser/window/default-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-window-service.js +34 -5
- package/lib/browser/window/default-window-service.js.map +1 -1
- package/lib/browser/window/default-window-service.spec.js +22 -0
- package/lib/browser/window/default-window-service.spec.js.map +1 -1
- package/lib/browser/window/secondary-window-service.d.ts +7 -0
- package/lib/browser/window/secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/window-service.d.ts +1 -1
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts +13 -2
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts.map +1 -1
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.js +31 -3
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.js.map +1 -1
- package/lib/common/preferences/index.d.ts +1 -0
- package/lib/common/preferences/index.d.ts.map +1 -1
- package/lib/common/preferences/index.js +1 -0
- package/lib/common/preferences/index.js.map +1 -1
- package/lib/common/preferences/preference-provider.d.ts +15 -0
- package/lib/common/preferences/preference-provider.d.ts.map +1 -1
- package/lib/common/preferences/preference-provider.js +21 -1
- package/lib/common/preferences/preference-provider.js.map +1 -1
- package/lib/common/preferences/preference-provider.spec.js +17 -0
- package/lib/common/preferences/preference-provider.spec.js.map +1 -1
- package/lib/common/preferences/preference-scope.d.ts +6 -1
- package/lib/common/preferences/preference-scope.d.ts.map +1 -1
- package/lib/common/preferences/preference-scope.js +5 -0
- package/lib/common/preferences/preference-scope.js.map +1 -1
- package/lib/common/preferences/preference-scope.spec.js +5 -3
- package/lib/common/preferences/preference-scope.spec.js.map +1 -1
- package/lib/common/preferences/preference-service.d.ts +19 -0
- package/lib/common/preferences/preference-service.d.ts.map +1 -1
- package/lib/common/preferences/preference-service.js +34 -5
- package/lib/common/preferences/preference-service.js.map +1 -1
- package/lib/common/preferences/session-preference-provider.d.ts +17 -0
- package/lib/common/preferences/session-preference-provider.d.ts.map +1 -0
- package/lib/common/preferences/session-preference-provider.js +77 -0
- package/lib/common/preferences/session-preference-provider.js.map +1 -0
- package/lib/common/preferences/session-preference-provider.spec.d.ts +2 -0
- package/lib/common/preferences/session-preference-provider.spec.d.ts.map +1 -0
- package/lib/common/preferences/session-preference-provider.spec.js +71 -0
- package/lib/common/preferences/session-preference-provider.spec.js.map +1 -0
- package/lib/electron-browser/electron-clipboard-service.d.ts +3 -0
- package/lib/electron-browser/electron-clipboard-service.d.ts.map +1 -1
- package/lib/electron-browser/electron-clipboard-service.js +6 -0
- package/lib/electron-browser/electron-clipboard-service.js.map +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.js +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.js.map +1 -1
- package/lib/electron-browser/preload.d.ts.map +1 -1
- package/lib/electron-browser/preload.js +2 -1
- package/lib/electron-browser/preload.js.map +1 -1
- package/lib/electron-browser/window/electron-secondary-window-service.js +1 -1
- package/lib/electron-browser/window/electron-secondary-window-service.js.map +1 -1
- package/lib/electron-browser/window/electron-window-service.js +3 -3
- package/lib/electron-browser/window/electron-window-service.js.map +1 -1
- package/lib/electron-common/electron-api.d.ts +2 -0
- package/lib/electron-common/electron-api.d.ts.map +1 -1
- package/lib/electron-common/electron-api.js +2 -1
- package/lib/electron-common/electron-api.js.map +1 -1
- package/lib/electron-main/electron-api-main.d.ts.map +1 -1
- package/lib/electron-main/electron-api-main.js +43 -0
- package/lib/electron-main/electron-api-main.js.map +1 -1
- package/lib/electron-main/electron-main-application.d.ts.map +1 -1
- package/lib/electron-main/electron-main-application.js +3 -0
- package/lib/electron-main/electron-main-application.js.map +1 -1
- package/lib/electron-node/hosting/electron-backend-hosting-module.d.ts.map +1 -1
- package/lib/electron-node/hosting/electron-backend-hosting-module.js +7 -0
- package/lib/electron-node/hosting/electron-backend-hosting-module.js.map +1 -1
- package/lib/electron-node/hosting/electron-backend-hosting-module.spec.d.ts +2 -0
- package/lib/electron-node/hosting/electron-backend-hosting-module.spec.d.ts.map +1 -0
- package/lib/electron-node/hosting/electron-backend-hosting-module.spec.js +39 -0
- package/lib/electron-node/hosting/electron-backend-hosting-module.spec.js.map +1 -0
- package/lib/node/hosting/backend-hosting-module.d.ts.map +1 -1
- package/lib/node/hosting/backend-hosting-module.js +4 -1
- package/lib/node/hosting/backend-hosting-module.js.map +1 -1
- package/lib/node/hosting/browser-connection-token.d.ts +29 -1
- package/lib/node/hosting/browser-connection-token.d.ts.map +1 -1
- package/lib/node/hosting/browser-connection-token.js +25 -1
- package/lib/node/hosting/browser-connection-token.js.map +1 -1
- package/lib/node/hosting/browser-connection-token.spec.js +70 -0
- package/lib/node/hosting/browser-connection-token.spec.js.map +1 -1
- package/lib/node/index.d.ts +1 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +3 -1
- package/lib/node/index.js.map +1 -1
- package/package.json +11 -11
- package/src/browser/browser-clipboard-service.ts +7 -1
- package/src/browser/clipboard-service.ts +11 -0
- package/src/browser/common-frontend-contribution.spec.ts +84 -0
- package/src/browser/common-frontend-contribution.ts +22 -10
- package/src/browser/frontend-application-contribution.ts +1 -1
- package/src/browser/frontend-application-module.ts +15 -3
- package/src/browser/frontend-application.ts +12 -4
- package/src/browser/index.ts +1 -0
- package/src/browser/keybinding.ts +12 -7
- package/src/browser/perspective-service.spec.ts +2270 -0
- package/src/browser/perspective-service.ts +625 -0
- package/src/browser/preferences/preference-contribution.ts +9 -1
- package/src/browser/preferences/preference-service.spec.ts +72 -0
- package/src/browser/shell/application-shell.ts +41 -26
- package/src/browser/shell/shell-layout-restorer.spec.ts +746 -0
- package/src/browser/shell/shell-layout-restorer.ts +134 -13
- package/src/browser/shell/view-contribution.ts +6 -1
- package/src/browser/status-bar/status-bar.tsx +13 -3
- package/src/browser/style/select-component.css +16 -0
- package/src/browser/style/sidepanel.css +5 -0
- package/src/browser/style/status-bar.css +1 -1
- package/src/browser/tree/tree-consistency.spec.ts +119 -0
- package/src/browser/tree/tree-widget.tsx +12 -78
- package/src/browser/tree/tree.ts +6 -0
- package/src/browser/widgets/index.ts +1 -0
- package/src/browser/widgets/select-component.tsx +90 -24
- package/src/browser/widgets/tab-bar-tracker.spec.ts +121 -0
- package/src/browser/widgets/tab-bar-tracker.ts +68 -0
- package/src/browser/window/default-secondary-window-service.ts +8 -2
- package/src/browser/window/default-window-service.spec.ts +22 -0
- package/src/browser/window/default-window-service.ts +37 -5
- package/src/browser/window/secondary-window-service.ts +7 -0
- package/src/browser/window/window-service.ts +1 -1
- package/src/browser-only/messaging/frontend-only-service-connection-provider.ts +32 -3
- package/src/common/i18n/nls.metadata.json +4149 -888
- package/src/common/preferences/index.ts +1 -0
- package/src/common/preferences/preference-provider.spec.ts +17 -0
- package/src/common/preferences/preference-provider.ts +22 -1
- package/src/common/preferences/preference-scope.spec.ts +5 -3
- package/src/common/preferences/preference-scope.ts +6 -1
- package/src/common/preferences/preference-service.ts +39 -5
- package/src/common/preferences/session-preference-provider.spec.ts +78 -0
- package/src/common/preferences/session-preference-provider.ts +69 -0
- package/src/electron-browser/electron-clipboard-service.ts +5 -0
- package/src/electron-browser/menu/electron-menu-contribution.ts +1 -1
- package/src/electron-browser/menu/electron-menu-style.css +3 -1
- package/src/electron-browser/preload.ts +5 -2
- package/src/electron-browser/window/electron-secondary-window-service.ts +1 -1
- package/src/electron-browser/window/electron-window-service.ts +3 -3
- package/src/electron-common/electron-api.ts +4 -0
- package/src/electron-main/electron-api-main.ts +56 -3
- package/src/electron-main/electron-main-application.ts +3 -0
- package/src/electron-node/hosting/electron-backend-hosting-module.spec.ts +42 -0
- package/src/electron-node/hosting/electron-backend-hosting-module.ts +8 -0
- package/src/node/hosting/backend-hosting-module.ts +5 -2
- package/src/node/hosting/browser-connection-token.spec.ts +92 -0
- package/src/node/hosting/browser-connection-token.ts +45 -1
- package/src/node/index.ts +1 -0
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
export * from './defaults-preference-provider';
|
|
18
18
|
export * from './preference-language-override-service';
|
|
19
|
+
export * from './session-preference-provider';
|
|
19
20
|
export * from './preference-provider-impl';
|
|
20
21
|
export * from './preference-provider';
|
|
21
22
|
export * from './preference-schema-service';
|
|
@@ -34,4 +34,21 @@ describe('PreferenceProviderImpl', () => {
|
|
|
34
34
|
);
|
|
35
35
|
expect(result).deep.equals({ 'configurations': [{ 'name': 'test1', 'request': 'launch' }, { 'name': 'test2' }], 'compounds': [] });
|
|
36
36
|
});
|
|
37
|
+
it('should ignore reserved keys when merging', () => {
|
|
38
|
+
const payload = JSON.parse('{"__proto__":{"injected":"yes"}}');
|
|
39
|
+
const result = PreferenceUtils.merge({ 'safe': true }, payload);
|
|
40
|
+
// A modified Object.prototype leaks onto every object, so a freshly created `{}`
|
|
41
|
+
// must not inherit the injected key and the result must stay a plain object.
|
|
42
|
+
expect(({} as Record<string, unknown>).injected).to.equal(undefined);
|
|
43
|
+
expect(Object.getPrototypeOf(result)).to.equal(Object.prototype);
|
|
44
|
+
expect(result).deep.equals({ 'safe': true });
|
|
45
|
+
});
|
|
46
|
+
it('should ignore nested reserved keys when merging', () => {
|
|
47
|
+
const payload = JSON.parse('{"parent":{"__proto__":{"injected":"yes"},"valid":"true"}}');
|
|
48
|
+
const result = PreferenceUtils.merge({ 'parent': { 'existing': true } }, payload);
|
|
49
|
+
// The nested reserved key is dropped while the sibling `valid` is still merged, and a
|
|
50
|
+
// freshly created `{}` must not inherit the injected key.
|
|
51
|
+
expect(({} as Record<string, unknown>).injected).to.equal(undefined);
|
|
52
|
+
expect(result).deep.equals({ 'parent': { 'existing': true, 'valid': 'true' } });
|
|
53
|
+
});
|
|
37
54
|
});
|
|
@@ -133,6 +133,23 @@ export interface PreferenceProvider extends Disposable {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
export namespace PreferenceUtils {
|
|
136
|
+
const RESERVED_MERGE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Recursively merges the values of `target` into `source` and returns the merged result.
|
|
140
|
+
*
|
|
141
|
+
* Note the direction: `source` is the accumulator that is mutated and returned, while the
|
|
142
|
+
* values of `target` take precedence for conflicting primitive values. Nested objects are
|
|
143
|
+
* merged recursively and arrays are concatenated (`source` entries first, then `target`).
|
|
144
|
+
* If `source` is not a JSON object, a deep copy of `target` is returned instead.
|
|
145
|
+
*
|
|
146
|
+
* Only own properties are considered and reserved keys are ignored, so merged results are
|
|
147
|
+
* always plain data objects.
|
|
148
|
+
*
|
|
149
|
+
* @param source the accumulator to merge into; mutated and returned unless it is not an object
|
|
150
|
+
* @param target the values that are merged into `source` and take precedence on conflicts
|
|
151
|
+
* @returns the merged result (the mutated `source`, or a deep copy of `target`)
|
|
152
|
+
*/
|
|
136
153
|
export function merge(source: JSONValue | undefined, target: JSONValue): JSONValue {
|
|
137
154
|
if (source === undefined || !JSONExt.isObject(source)) {
|
|
138
155
|
return JSONExt.deepCopy(target);
|
|
@@ -141,7 +158,11 @@ export namespace PreferenceUtils {
|
|
|
141
158
|
return {};
|
|
142
159
|
}
|
|
143
160
|
for (const [key, value] of Object.entries(target)) {
|
|
144
|
-
if (key
|
|
161
|
+
if (RESERVED_MERGE_KEYS.has(key)) {
|
|
162
|
+
// Skip reserved object keys so merged results stay plain data objects.
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
145
166
|
const sourceValue = source[key];
|
|
146
167
|
if (JSONExt.isObject(sourceValue) && JSONExt.isObject(value)) {
|
|
147
168
|
merge(sourceValue, value);
|
|
@@ -20,11 +20,11 @@ import { PreferenceScope } from './preference-scope';
|
|
|
20
20
|
describe('PreferenceScope', () => {
|
|
21
21
|
|
|
22
22
|
it('getScopes() should return numbers from broadest to narrowest', () => {
|
|
23
|
-
expect(PreferenceScope.getScopes()).deep.equal([0, 1, 2, 3]);
|
|
23
|
+
expect(PreferenceScope.getScopes()).deep.equal([0, 1, 2, 3, 4]);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('getReversedScopes() should return numbers from narrowest to broadest', () => {
|
|
27
|
-
expect(PreferenceScope.getReversedScopes()).deep.equal([3, 2, 1, 0]);
|
|
27
|
+
expect(PreferenceScope.getReversedScopes()).deep.equal([4, 3, 2, 1, 0]);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
it('getScopeNames() should return the names of scopes broader than the current one', () => {
|
|
@@ -36,11 +36,13 @@ describe('PreferenceScope', () => {
|
|
|
36
36
|
expect(PreferenceScope.is(PreferenceScope.User)).to.be.true;
|
|
37
37
|
expect(PreferenceScope.is(PreferenceScope.Workspace)).to.be.true;
|
|
38
38
|
expect(PreferenceScope.is(PreferenceScope.Folder)).to.be.true;
|
|
39
|
+
expect(PreferenceScope.is(PreferenceScope.Session)).to.be.true;
|
|
39
40
|
expect(PreferenceScope.is(0)).to.be.true;
|
|
40
41
|
expect(PreferenceScope.is(1)).to.be.true;
|
|
41
42
|
expect(PreferenceScope.is(2)).to.be.true;
|
|
42
43
|
expect(PreferenceScope.is(3)).to.be.true;
|
|
43
|
-
expect(PreferenceScope.is(4)).to.be.
|
|
44
|
+
expect(PreferenceScope.is(4)).to.be.true;
|
|
45
|
+
expect(PreferenceScope.is(5)).to.be.false;
|
|
44
46
|
expect(PreferenceScope.is(-1)).to.be.false;
|
|
45
47
|
expect(PreferenceScope.is({})).to.be.false;
|
|
46
48
|
expect(PreferenceScope.is('Default')).to.be.false;
|
|
@@ -23,7 +23,12 @@ export enum PreferenceScope {
|
|
|
23
23
|
Default,
|
|
24
24
|
User,
|
|
25
25
|
Workspace,
|
|
26
|
-
Folder
|
|
26
|
+
Folder,
|
|
27
|
+
/**
|
|
28
|
+
* Process-lifetime, in-memory scope used by `--session-preference` CLI overrides.
|
|
29
|
+
* Has the highest precedence and is never persisted to disk.
|
|
30
|
+
*/
|
|
31
|
+
Session
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
export namespace PreferenceScope {
|
|
@@ -254,6 +254,10 @@ export interface PreferenceInspection<T = JSONValue> {
|
|
|
254
254
|
* Value in folder scope.
|
|
255
255
|
*/
|
|
256
256
|
workspaceFolderValue: T | undefined,
|
|
257
|
+
/**
|
|
258
|
+
* Value in session scope (in-memory, set via `--session-preference`).
|
|
259
|
+
*/
|
|
260
|
+
sessionValue?: T | undefined,
|
|
257
261
|
/**
|
|
258
262
|
* The value that is active, i.e. the value set in the lowest scope available.
|
|
259
263
|
*/
|
|
@@ -453,11 +457,37 @@ export class PreferenceServiceImpl implements PreferenceService {
|
|
|
453
457
|
}
|
|
454
458
|
const provider = this.getProvider(resolvedScope);
|
|
455
459
|
if (provider && await provider.setPreference(preferenceName, value, resourceUri)) {
|
|
460
|
+
await this.evictSessionOverride(preferenceName, resolvedScope);
|
|
456
461
|
return;
|
|
457
462
|
}
|
|
458
463
|
throw new Error(`Unable to write to ${PreferenceScope[resolvedScope]} Settings.`);
|
|
459
464
|
}
|
|
460
465
|
|
|
466
|
+
/**
|
|
467
|
+
* When a preference is written to a persistent scope, drop any in-memory session
|
|
468
|
+
* override (set via `--session-preference`) for the same key so that the explicit
|
|
469
|
+
* change takes effect for the remainder of the session. Without this, the session
|
|
470
|
+
* scope would keep shadowing the user's change because it has higher precedence.
|
|
471
|
+
*
|
|
472
|
+
* Note: this fires for any write to a persistent scope, including programmatic
|
|
473
|
+
* writes from extensions. Extensions that race with startup CLI overrides will
|
|
474
|
+
* therefore clear them. This is intentional: a programmatic write is "explicit"
|
|
475
|
+
* from the preference system's point of view, but consumers should be aware.
|
|
476
|
+
*
|
|
477
|
+
* Session preferences are global (no per-resource scoping), so `resourceUri` is
|
|
478
|
+
* not threaded through.
|
|
479
|
+
*/
|
|
480
|
+
protected async evictSessionOverride(preferenceName: string, writtenScope: PreferenceScope): Promise<void> {
|
|
481
|
+
if (writtenScope === PreferenceScope.Session || writtenScope === PreferenceScope.Default) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const sessionProvider = this.getProvider(PreferenceScope.Session);
|
|
485
|
+
if (sessionProvider && sessionProvider.get(preferenceName) !== undefined) {
|
|
486
|
+
// Passing `undefined` clears the value in the session provider.
|
|
487
|
+
await sessionProvider.setPreference(preferenceName, undefined as unknown as JSONValue);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
461
491
|
getBoolean(preferenceName: string): boolean | undefined;
|
|
462
492
|
getBoolean(preferenceName: string, defaultValue: boolean): boolean;
|
|
463
493
|
getBoolean(preferenceName: string, defaultValue: boolean, resourceUri: string): boolean;
|
|
@@ -499,10 +529,11 @@ export class PreferenceServiceImpl implements PreferenceService {
|
|
|
499
529
|
const globalValue = this.inspectInScope<T>(preferenceName, PreferenceScope.User, resourceUri, forceLanguageOverride);
|
|
500
530
|
const workspaceValue = this.inspectInScope<T>(preferenceName, PreferenceScope.Workspace, resourceUri, forceLanguageOverride);
|
|
501
531
|
const workspaceFolderValue = this.inspectInScope<T>(preferenceName, PreferenceScope.Folder, resourceUri, forceLanguageOverride);
|
|
532
|
+
const sessionValue = this.inspectInScope<T>(preferenceName, PreferenceScope.Session, resourceUri, forceLanguageOverride);
|
|
502
533
|
|
|
503
|
-
const valueApplied = workspaceFolderValue ?? workspaceValue ?? globalValue ?? defaultValue;
|
|
534
|
+
const valueApplied = sessionValue ?? workspaceFolderValue ?? workspaceValue ?? globalValue ?? defaultValue;
|
|
504
535
|
|
|
505
|
-
return { preferenceName, defaultValue, globalValue, workspaceValue, workspaceFolderValue, value: valueApplied };
|
|
536
|
+
return { preferenceName, defaultValue, globalValue, workspaceValue, workspaceFolderValue, sessionValue, value: valueApplied };
|
|
506
537
|
}
|
|
507
538
|
|
|
508
539
|
inspectInScope<T extends JSONValue>(preferenceName: string, scope: PreferenceScope, resourceUri?: string, forceLanguageOverride?: boolean): T | undefined {
|
|
@@ -526,6 +557,8 @@ export class PreferenceServiceImpl implements PreferenceService {
|
|
|
526
557
|
return inspection.workspaceValue;
|
|
527
558
|
case PreferenceScope.Folder:
|
|
528
559
|
return inspection.workspaceFolderValue;
|
|
560
|
+
case PreferenceScope.Session:
|
|
561
|
+
return inspection.sessionValue;
|
|
529
562
|
}
|
|
530
563
|
unreachable(scope, 'Not all PreferenceScope enum variants handled.');
|
|
531
564
|
}
|
|
@@ -551,9 +584,10 @@ export class PreferenceServiceImpl implements PreferenceService {
|
|
|
551
584
|
}
|
|
552
585
|
|
|
553
586
|
// Scopes in ascending order of scope breadth.
|
|
554
|
-
const allScopes = [...this.schemaService.validScopes].reverse()
|
|
555
|
-
|
|
556
|
-
|
|
587
|
+
const allScopes = [...this.schemaService.validScopes].reverse()
|
|
588
|
+
// Default is read-only, and Session is populated only by `--session-preference`,
|
|
589
|
+
// so neither is a valid target for a regular user edit.
|
|
590
|
+
.filter(scope => scope !== PreferenceScope.Default && scope !== PreferenceScope.Session);
|
|
557
591
|
|
|
558
592
|
const isScopeDefined = (scope: PreferenceScope) => this.getScopedValueFromInspection(inspection, scope) !== undefined;
|
|
559
593
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { expect } from 'chai';
|
|
18
|
+
import { SessionPreferenceProvider } from './session-preference-provider';
|
|
19
|
+
import { PreferenceScope } from './preference-scope';
|
|
20
|
+
|
|
21
|
+
describe('SessionPreferenceProvider', () => {
|
|
22
|
+
|
|
23
|
+
let provider: SessionPreferenceProvider;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
provider = new SessionPreferenceProvider();
|
|
27
|
+
// PostConstruct emulation
|
|
28
|
+
(provider as unknown as { init(): void }).init();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('claims only the Session scope', () => {
|
|
32
|
+
expect(provider.canHandleScope(PreferenceScope.Session)).to.be.true;
|
|
33
|
+
expect(provider.canHandleScope(PreferenceScope.User)).to.be.false;
|
|
34
|
+
expect(provider.canHandleScope(PreferenceScope.Workspace)).to.be.false;
|
|
35
|
+
expect(provider.canHandleScope(PreferenceScope.Folder)).to.be.false;
|
|
36
|
+
expect(provider.canHandleScope(PreferenceScope.Default)).to.be.false;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('starts empty', () => {
|
|
40
|
+
expect(provider.getPreferences()).to.deep.equal({});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('stores set values in memory and exposes them via get/resolve', async () => {
|
|
44
|
+
const ok = await provider.setPreference('editor.fontSize', 18);
|
|
45
|
+
expect(ok).to.be.true;
|
|
46
|
+
expect(provider.get('editor.fontSize')).to.equal(18);
|
|
47
|
+
expect(provider.resolve('editor.fontSize').value).to.equal(18);
|
|
48
|
+
expect(provider.getPreferences()).to.deep.equal({ 'editor.fontSize': 18 });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('emits a change event on set', async () => {
|
|
52
|
+
let received: { name: string; newValue: unknown; oldValue: unknown; scope: PreferenceScope } | undefined;
|
|
53
|
+
provider.onDidPreferencesChanged(changes => {
|
|
54
|
+
const key = Object.keys(changes)[0];
|
|
55
|
+
received = {
|
|
56
|
+
name: changes[key].preferenceName,
|
|
57
|
+
newValue: changes[key].newValue,
|
|
58
|
+
oldValue: changes[key].oldValue,
|
|
59
|
+
scope: changes[key].scope
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
await provider.setPreference('foo', 'bar');
|
|
63
|
+
expect(received).to.deep.equal({ name: 'foo', newValue: 'bar', oldValue: undefined, scope: PreferenceScope.Session });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('removes a value when set to undefined', async () => {
|
|
67
|
+
await provider.setPreference('foo', 'bar');
|
|
68
|
+
const removed = await provider.setPreference('foo', undefined as never);
|
|
69
|
+
expect(removed).to.be.true;
|
|
70
|
+
expect(provider.get('foo')).to.equal(undefined);
|
|
71
|
+
expect(provider.getPreferences()).to.deep.equal({});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('returns false when removing a key that was never set', async () => {
|
|
75
|
+
const removed = await provider.setPreference('missing', undefined as never);
|
|
76
|
+
expect(removed).to.be.false;
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { JSONObject, JSONValue } from '@lumino/coreutils';
|
|
18
|
+
import { injectable, postConstruct } from 'inversify';
|
|
19
|
+
import { PreferenceProvider, PreferenceProviderDataChange } from './preference-provider';
|
|
20
|
+
import { PreferenceProviderImpl } from './preference-provider-impl';
|
|
21
|
+
import { PreferenceScope } from './preference-scope';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* In-memory preference provider used for process-lifetime overrides such as those
|
|
25
|
+
* supplied via the `--session-preference` CLI option. Values live in memory only
|
|
26
|
+
* and are dropped when the process exits.
|
|
27
|
+
*/
|
|
28
|
+
@injectable()
|
|
29
|
+
export class SessionPreferenceProvider extends PreferenceProviderImpl implements PreferenceProvider {
|
|
30
|
+
|
|
31
|
+
protected readonly preferences = new Map<string, JSONValue>();
|
|
32
|
+
|
|
33
|
+
@postConstruct()
|
|
34
|
+
protected init(): void {
|
|
35
|
+
this._ready.resolve();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override canHandleScope(scope: PreferenceScope): boolean {
|
|
39
|
+
return scope === PreferenceScope.Session;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getPreferences(): JSONObject {
|
|
43
|
+
const result: JSONObject = {};
|
|
44
|
+
for (const [name, value] of this.preferences) {
|
|
45
|
+
result[name] = value;
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async setPreference(key: string, value: JSONValue | undefined): Promise<boolean> {
|
|
51
|
+
const oldValue = this.preferences.get(key);
|
|
52
|
+
if (value === undefined) {
|
|
53
|
+
if (!this.preferences.has(key)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
this.preferences.delete(key);
|
|
57
|
+
} else {
|
|
58
|
+
this.preferences.set(key, value);
|
|
59
|
+
}
|
|
60
|
+
const change: PreferenceProviderDataChange = {
|
|
61
|
+
preferenceName: key,
|
|
62
|
+
newValue: value,
|
|
63
|
+
oldValue,
|
|
64
|
+
scope: PreferenceScope.Session
|
|
65
|
+
};
|
|
66
|
+
await this.emitPreferencesChangedEvent([change]);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -16,17 +16,22 @@
|
|
|
16
16
|
|
|
17
17
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
18
18
|
import { injectable } from 'inversify';
|
|
19
|
+
import { Emitter, Event } from '../common/event';
|
|
19
20
|
import { ClipboardService } from '../browser/clipboard-service';
|
|
20
21
|
|
|
21
22
|
@injectable()
|
|
22
23
|
export class ElectronClipboardService implements ClipboardService {
|
|
23
24
|
|
|
25
|
+
protected readonly onDidWriteTextEmitter = new Emitter<string>();
|
|
26
|
+
readonly onDidWriteText: Event<string> = this.onDidWriteTextEmitter.event;
|
|
27
|
+
|
|
24
28
|
readText(): string {
|
|
25
29
|
return window.electronTheiaCore.readClipboard();
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
writeText(value: string): void {
|
|
29
33
|
window.electronTheiaCore.writeClipboard(value);
|
|
34
|
+
this.onDidWriteTextEmitter.fire(value);
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
}
|
|
@@ -139,7 +139,7 @@ export class ElectronMenuContribution extends BrowserMenuBarContribution impleme
|
|
|
139
139
|
const disposeHandler = window.electronTheiaCore.onWindowEvent('focus', () => {
|
|
140
140
|
this.setMenu(app);
|
|
141
141
|
});
|
|
142
|
-
window.addEventListener('
|
|
142
|
+
window.addEventListener('pagehide', () => disposeHandler.dispose());
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
protected attachMenuBarVisibilityListener(): void {
|
|
@@ -60,7 +60,9 @@
|
|
|
60
60
|
position: absolute;
|
|
61
61
|
top: 0;
|
|
62
62
|
right: 0;
|
|
63
|
-
height:
|
|
63
|
+
height: var(--theia-private-menubar-height, 32px);
|
|
64
|
+
z-index: var(--theia-menu-z-index);
|
|
65
|
+
background: var(--theia-titleBar-activeBackground);
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
#window-controls .control-button {
|
|
@@ -27,7 +27,8 @@ import {
|
|
|
27
27
|
CHANNEL_REQUEST_RELOAD, CHANNEL_APP_STATE_CHANGED, CHANNEL_SHOW_ITEM_IN_FOLDER, CHANNEL_READ_CLIPBOARD, CHANNEL_WRITE_CLIPBOARD,
|
|
28
28
|
CHANNEL_KEYBOARD_LAYOUT_CHANGED, CHANNEL_IPC_CONNECTION, InternalMenuDto, CHANNEL_REQUEST_SECONDARY_CLOSE, CHANNEL_SET_BACKGROUND_COLOR,
|
|
29
29
|
CHANNEL_WC_METADATA, CHANNEL_ABOUT_TO_CLOSE, CHANNEL_OPEN_WITH_SYSTEM_APP,
|
|
30
|
-
CHANNEL_OPEN_URL, CHANNEL_SET_THEME, CHANNEL_OPEN_DEVTOOLS_FOR_WINDOW
|
|
30
|
+
CHANNEL_OPEN_URL, CHANNEL_SET_THEME, CHANNEL_OPEN_DEVTOOLS_FOR_WINDOW,
|
|
31
|
+
CHANNEL_UPDATE_RECENT_WORKSPACES
|
|
31
32
|
} from '../electron-common/electron-api';
|
|
32
33
|
|
|
33
34
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
@@ -246,7 +247,9 @@ const api: TheiaCoreAPI = {
|
|
|
246
247
|
sendData: data => {
|
|
247
248
|
ipcRenderer.send(CHANNEL_IPC_CONNECTION, data);
|
|
248
249
|
},
|
|
249
|
-
useNativeElements: !('THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS' in process.env && process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS === '1')
|
|
250
|
+
useNativeElements: !('THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS' in process.env && process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS === '1'),
|
|
251
|
+
|
|
252
|
+
updateRecentWorkspaces: (workspaceUris: string[], categoryName: string): void => { ipcRenderer.send(CHANNEL_UPDATE_RECENT_WORKSPACES, workspaceUris, categoryName); }
|
|
250
253
|
};
|
|
251
254
|
|
|
252
255
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -61,7 +61,7 @@ export class ElectronSecondaryWindowService extends DefaultSecondaryWindowServic
|
|
|
61
61
|
for (let i = this.secondaryWindows.length - 1; i >= 0; i--) {
|
|
62
62
|
const windowClosed = new Deferred<void>();
|
|
63
63
|
const win = this.secondaryWindows[i];
|
|
64
|
-
win.addEventListener('
|
|
64
|
+
win.addEventListener('pagehide', () => {
|
|
65
65
|
windowClosed.resolve();
|
|
66
66
|
});
|
|
67
67
|
promises.push(windowClosed.promise);
|
|
@@ -89,9 +89,9 @@ export class ElectronWindowService extends DefaultWindowService {
|
|
|
89
89
|
}
|
|
90
90
|
return willShutDown;
|
|
91
91
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
// Intentionally not calling `super`: in Electron the close request handler above takes the role of
|
|
93
|
+
// `beforeunload`, and there is no back/forward cache, so only the `pagehide` listener is shared.
|
|
94
|
+
this.registerPageHideListener();
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/**
|
|
@@ -104,6 +104,8 @@ export interface TheiaCoreAPI {
|
|
|
104
104
|
sendData(data: Uint8Array): void;
|
|
105
105
|
onData(handler: (data: Uint8Array) => void): Disposable;
|
|
106
106
|
useNativeElements: boolean;
|
|
107
|
+
|
|
108
|
+
updateRecentWorkspaces(workspaceUris: string[], categoryName: string): void;
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
declare global {
|
|
@@ -162,3 +164,5 @@ export const CHANNEL_WRITE_CLIPBOARD = 'WriteClipboard';
|
|
|
162
164
|
|
|
163
165
|
export const CHANNEL_KEYBOARD_LAYOUT_CHANGED = 'KeyboardLayoutChanged';
|
|
164
166
|
export const CHANNEL_IPC_CONNECTION = 'IpcConnection';
|
|
167
|
+
|
|
168
|
+
export const CHANNEL_UPDATE_RECENT_WORKSPACES = 'UpdateRecentWorkspaces';
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
|
-
ipcMain, BrowserWindow, Menu, MenuItemConstructorOptions, webContents, WebContents, session, shell, clipboard, IpcMainEvent
|
|
18
|
+
ipcMain, BrowserWindow, Menu, MenuItemConstructorOptions, webContents, WebContents, session, shell, clipboard, IpcMainEvent,
|
|
19
|
+
app, JumpListCategory, JumpListItem
|
|
19
20
|
} from '@theia/electron/shared/electron';
|
|
20
21
|
import * as nativeKeymap from '@theia/electron/shared/native-keymap';
|
|
21
22
|
|
|
@@ -57,11 +58,14 @@ import {
|
|
|
57
58
|
CHANNEL_OPEN_WITH_SYSTEM_APP,
|
|
58
59
|
CHANNEL_OPEN_URL,
|
|
59
60
|
CHANNEL_SET_THEME,
|
|
60
|
-
CHANNEL_OPEN_DEVTOOLS_FOR_WINDOW
|
|
61
|
+
CHANNEL_OPEN_DEVTOOLS_FOR_WINDOW,
|
|
62
|
+
CHANNEL_UPDATE_RECENT_WORKSPACES
|
|
61
63
|
} from '../electron-common/electron-api';
|
|
62
64
|
import { ElectronMainApplication, ElectronMainApplicationContribution } from './electron-main-application';
|
|
63
|
-
import { Disposable, DisposableCollection, isOSX, MaybePromise } from '../common';
|
|
65
|
+
import { Disposable, DisposableCollection, isOSX, isWindows, MaybePromise, URI } from '../common';
|
|
66
|
+
import { FileUri } from '../node';
|
|
64
67
|
import { createDisposableListener } from './event-utils';
|
|
68
|
+
import * as path from 'node:path';
|
|
65
69
|
|
|
66
70
|
@injectable()
|
|
67
71
|
export class TheiaMainApi implements ElectronMainApplicationContribution {
|
|
@@ -265,6 +269,55 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
|
|
|
265
269
|
webContent.send('keyboardLayoutChanged', newLayout);
|
|
266
270
|
}
|
|
267
271
|
});
|
|
272
|
+
|
|
273
|
+
ipcMain.on(CHANNEL_UPDATE_RECENT_WORKSPACES, (_event, workspaces: string[], categoryName: string) => {
|
|
274
|
+
if (!isWindows) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const jumpListSettings = app.getJumpListSettings();
|
|
279
|
+
const isDev = !app.isPackaged;
|
|
280
|
+
const appPath = app.getAppPath();
|
|
281
|
+
|
|
282
|
+
const items: JumpListItem[] = workspaces
|
|
283
|
+
.filter(w => new URI(w).scheme === 'file')
|
|
284
|
+
.map(workspace => {
|
|
285
|
+
const uri = new URI(workspace);
|
|
286
|
+
const wspathPretty = uri.path.fsPath();
|
|
287
|
+
const wspath = FileUri.fsPath(uri);
|
|
288
|
+
const item: JumpListItem = {
|
|
289
|
+
type: 'task',
|
|
290
|
+
// Windows is picky about the length of some attributes. See: https://github.com/microsoft/vscode/issues/111177#issuecomment-739942612
|
|
291
|
+
title: uri.path.base.substring(0, 255),
|
|
292
|
+
description: wspathPretty.substring(0, 255),
|
|
293
|
+
program: process.execPath,
|
|
294
|
+
args: isDev ? `"${path.resolve(appPath, 'theia-electron-main.js')}" "${wspath}"` : `"${wspath}"`,
|
|
295
|
+
iconPath: process.execPath,
|
|
296
|
+
iconIndex: 0
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// We need to remove items that the user has explicitly removed from the jump list. Otherwise
|
|
300
|
+
// Windows will not show our custom category at all.
|
|
301
|
+
if (jumpListSettings.removedItems.some(removedItem => removedItem.args === item.args)) {
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return item;
|
|
306
|
+
})
|
|
307
|
+
.filter(item => !!item);
|
|
308
|
+
|
|
309
|
+
const jumpList: JumpListCategory[] = [{
|
|
310
|
+
type: 'custom',
|
|
311
|
+
name: categoryName || 'Recent Workspaces',
|
|
312
|
+
items
|
|
313
|
+
}];
|
|
314
|
+
|
|
315
|
+
const result = app.setJumpList(jumpList);
|
|
316
|
+
|
|
317
|
+
if (result !== 'ok') {
|
|
318
|
+
console.warn(`Could not set Jump List with recent workspaces (result = "${result}")`);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
268
321
|
}
|
|
269
322
|
|
|
270
323
|
private isASCI(accelerator: string | undefined): boolean {
|
|
@@ -238,6 +238,9 @@ export class ElectronMainApplication {
|
|
|
238
238
|
const startupMeasurement = this.stopwatch.start('electron-main-startup');
|
|
239
239
|
this.useNativeWindowFrame = this.getTitleBarStyle(config) === 'native';
|
|
240
240
|
this._config = config;
|
|
241
|
+
if (isWindows && !!config.electron.appUserModelId) {
|
|
242
|
+
app.setAppUserModelId(config.electron.appUserModelId);
|
|
243
|
+
}
|
|
241
244
|
this.hookApplicationEvents();
|
|
242
245
|
this.showInitialWindow(argv.includes('--open-url') ? argv[argv.length - 1] : undefined);
|
|
243
246
|
const port = await this.stopwatch.startAsync('electron-main-start-backend', 'Starting backend', () => this.startBackend());
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { expect } from 'chai';
|
|
18
|
+
import { Container } from 'inversify';
|
|
19
|
+
import electronBackendHostingModule from './electron-backend-hosting-module';
|
|
20
|
+
import { HttpConnectionValidator } from '../../node/hosting/browser-connection-token';
|
|
21
|
+
|
|
22
|
+
describe('electron-backend-hosting-module', () => {
|
|
23
|
+
|
|
24
|
+
it('should bind a no-op HttpConnectionValidator that calls next() unconditionally', () => {
|
|
25
|
+
const container = new Container();
|
|
26
|
+
container.load(electronBackendHostingModule);
|
|
27
|
+
const validator = container.get<HttpConnectionValidator>(HttpConnectionValidator);
|
|
28
|
+
|
|
29
|
+
let nextCalled = false;
|
|
30
|
+
let sentStatus: number | undefined;
|
|
31
|
+
// No cookie provided: the no-op validator must still call next() and never send a status.
|
|
32
|
+
const req = { headers: {} as Record<string, string | undefined> };
|
|
33
|
+
const res = { sendStatus: (status: number) => { sentStatus = status; } };
|
|
34
|
+
const next = (): void => { nextCalled = true; };
|
|
35
|
+
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
validator.validateRequest(req as any, res as any, next as any);
|
|
38
|
+
|
|
39
|
+
expect(nextCalled).to.be.true;
|
|
40
|
+
expect(sentStatus).to.be.undefined;
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -16,9 +16,17 @@
|
|
|
16
16
|
|
|
17
17
|
import { ContainerModule } from 'inversify';
|
|
18
18
|
import { WsRequestValidatorContribution } from '../../node/ws-request-validators';
|
|
19
|
+
import { HttpConnectionValidator } from '../../node/hosting/browser-connection-token';
|
|
19
20
|
import { ElectronWsOriginValidator } from './electron-ws-origin-validator';
|
|
20
21
|
|
|
21
22
|
export default new ContainerModule(bind => {
|
|
22
23
|
bind(ElectronWsOriginValidator).toSelf().inSingletonScope();
|
|
23
24
|
bind(WsRequestValidatorContribution).toService(ElectronWsOriginValidator);
|
|
25
|
+
|
|
26
|
+
// Electron uses its own `ElectronSecurityToken`, so the connection-token cookie is not used.
|
|
27
|
+
// Bind a no-op validator so consumers can inject it uniformly.
|
|
28
|
+
const noopValidator: HttpConnectionValidator = {
|
|
29
|
+
validateRequest: (_req, _res, next) => next()
|
|
30
|
+
};
|
|
31
|
+
bind(HttpConnectionValidator).toConstantValue(noopValidator);
|
|
24
32
|
});
|
|
@@ -19,7 +19,7 @@ import { BackendApplicationContribution } from '../backend-application';
|
|
|
19
19
|
import { WsRequestValidatorContribution } from '../ws-request-validators';
|
|
20
20
|
import { BackendApplicationHosts } from './backend-application-hosts';
|
|
21
21
|
import {
|
|
22
|
-
BrowserConnectionToken, BrowserConnectionTokenBackendContribution, createBrowserConnectionToken
|
|
22
|
+
BrowserConnectionToken, BrowserConnectionTokenBackendContribution, HttpConnectionValidator, createBrowserConnectionToken
|
|
23
23
|
} from './browser-connection-token';
|
|
24
24
|
import { WsOriginValidator } from './ws-origin-validator';
|
|
25
25
|
|
|
@@ -28,9 +28,12 @@ export default new ContainerModule(bind => {
|
|
|
28
28
|
bind(WsOriginValidator).toSelf().inSingletonScope();
|
|
29
29
|
bind(WsRequestValidatorContribution).toService(WsOriginValidator);
|
|
30
30
|
|
|
31
|
-
// Cookie-based connection token
|
|
31
|
+
// Cookie-based connection token. It is validated on WebSocket upgrades and bootstrapped
|
|
32
|
+
// (set as a cookie) on every HTTP request; HTTP enforcement is opt-in per route via
|
|
33
|
+
// `HttpConnectionValidator`.
|
|
32
34
|
bind(BrowserConnectionToken).toConstantValue(createBrowserConnectionToken());
|
|
33
35
|
bind(BrowserConnectionTokenBackendContribution).toSelf().inSingletonScope();
|
|
34
36
|
bind(BackendApplicationContribution).toService(BrowserConnectionTokenBackendContribution);
|
|
35
37
|
bind(WsRequestValidatorContribution).toService(BrowserConnectionTokenBackendContribution);
|
|
38
|
+
bind(HttpConnectionValidator).toService(BrowserConnectionTokenBackendContribution);
|
|
36
39
|
});
|