@theia/core 1.70.0-next.6 → 1.70.0-next.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +3 -3
  2. package/lib/browser/about-dialog.d.ts.map +1 -1
  3. package/lib/browser/about-dialog.js +3 -1
  4. package/lib/browser/about-dialog.js.map +1 -1
  5. package/lib/browser/catalog.json +97 -7
  6. package/lib/browser/common-frontend-contribution.d.ts +0 -2
  7. package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
  8. package/lib/browser/common-frontend-contribution.js +39 -15
  9. package/lib/browser/common-frontend-contribution.js.map +1 -1
  10. package/lib/browser/frontend-application-module.d.ts.map +1 -1
  11. package/lib/browser/frontend-application-module.js +1 -0
  12. package/lib/browser/frontend-application-module.js.map +1 -1
  13. package/lib/browser/hover-service.d.ts.map +1 -1
  14. package/lib/browser/hover-service.js +5 -3
  15. package/lib/browser/hover-service.js.map +1 -1
  16. package/lib/browser/markdown-rendering/markdown-renderer.d.ts +1 -1
  17. package/lib/browser/markdown-rendering/markdown-renderer.d.ts.map +1 -1
  18. package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
  19. package/lib/browser/menu/browser-menu-plugin.js +9 -1
  20. package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
  21. package/lib/browser/quick-input/quick-input-service.spec.js +53 -9
  22. package/lib/browser/quick-input/quick-input-service.spec.js.map +1 -1
  23. package/lib/browser/saveable-service.d.ts +24 -1
  24. package/lib/browser/saveable-service.d.ts.map +1 -1
  25. package/lib/browser/saveable-service.js +34 -3
  26. package/lib/browser/saveable-service.js.map +1 -1
  27. package/lib/browser/tree/fuzzy-search.d.ts +1 -60
  28. package/lib/browser/tree/fuzzy-search.d.ts.map +1 -1
  29. package/lib/browser/tree/fuzzy-search.js +3 -58
  30. package/lib/browser/tree/fuzzy-search.js.map +1 -1
  31. package/lib/browser/tree/tree-view-welcome-widget.d.ts.map +1 -1
  32. package/lib/browser/tree/tree-view-welcome-widget.js +1 -2
  33. package/lib/browser/tree/tree-view-welcome-widget.js.map +1 -1
  34. package/lib/browser/widgets/select-component.d.ts +1 -0
  35. package/lib/browser/widgets/select-component.d.ts.map +1 -1
  36. package/lib/browser/widgets/select-component.js +30 -0
  37. package/lib/browser/widgets/select-component.js.map +1 -1
  38. package/lib/browser/window/default-window-service.d.ts +2 -1
  39. package/lib/browser/window/default-window-service.d.ts.map +1 -1
  40. package/lib/browser/window/default-window-service.js +5 -1
  41. package/lib/browser/window/default-window-service.js.map +1 -1
  42. package/lib/browser/window/test/mock-window-service.d.ts +2 -1
  43. package/lib/browser/window/test/mock-window-service.d.ts.map +1 -1
  44. package/lib/browser/window/test/mock-window-service.js +2 -1
  45. package/lib/browser/window/test/mock-window-service.js.map +1 -1
  46. package/lib/browser/window/window-service.d.ts +8 -1
  47. package/lib/browser/window/window-service.d.ts.map +1 -1
  48. package/lib/common/fuzzy-match-utils.d.ts +27 -0
  49. package/lib/common/fuzzy-match-utils.d.ts.map +1 -0
  50. package/lib/common/fuzzy-match-utils.js +96 -0
  51. package/lib/common/fuzzy-match-utils.js.map +1 -0
  52. package/lib/common/fuzzy-match-utils.spec.d.ts +2 -0
  53. package/lib/common/fuzzy-match-utils.spec.d.ts.map +1 -0
  54. package/lib/common/fuzzy-match-utils.spec.js +109 -0
  55. package/lib/common/fuzzy-match-utils.spec.js.map +1 -0
  56. package/lib/common/fuzzy-search.d.ts +63 -0
  57. package/lib/common/fuzzy-search.d.ts.map +1 -0
  58. package/lib/common/fuzzy-search.js +101 -0
  59. package/lib/common/fuzzy-search.js.map +1 -0
  60. package/lib/common/fuzzy-search.spec.d.ts.map +1 -0
  61. package/lib/{browser/tree → common}/fuzzy-search.spec.js +20 -1
  62. package/lib/common/fuzzy-search.spec.js.map +1 -0
  63. package/lib/common/logger-sanitizer.d.ts +9 -0
  64. package/lib/common/logger-sanitizer.d.ts.map +1 -1
  65. package/lib/common/logger-sanitizer.js +26 -3
  66. package/lib/common/logger-sanitizer.js.map +1 -1
  67. package/lib/common/logger-sanitizer.spec.js +41 -0
  68. package/lib/common/logger-sanitizer.spec.js.map +1 -1
  69. package/lib/common/preferences/injectable-preference-proxy.d.ts +5 -3
  70. package/lib/common/preferences/injectable-preference-proxy.d.ts.map +1 -1
  71. package/lib/common/preferences/injectable-preference-proxy.js +9 -6
  72. package/lib/common/preferences/injectable-preference-proxy.js.map +1 -1
  73. package/lib/common/preferences/preference-configurations.d.ts +2 -2
  74. package/lib/common/preferences/preference-configurations.d.ts.map +1 -1
  75. package/lib/common/preferences/preference-configurations.js +1 -1
  76. package/lib/common/preferences/preference-configurations.js.map +1 -1
  77. package/lib/common/preferences/preference-provider-impl.d.ts +4 -3
  78. package/lib/common/preferences/preference-provider-impl.d.ts.map +1 -1
  79. package/lib/common/preferences/preference-provider-impl.js +9 -6
  80. package/lib/common/preferences/preference-provider-impl.js.map +1 -1
  81. package/lib/common/preferences/preference-proxy.d.ts +4 -2
  82. package/lib/common/preferences/preference-proxy.d.ts.map +1 -1
  83. package/lib/common/preferences/preference-proxy.js +4 -5
  84. package/lib/common/preferences/preference-proxy.js.map +1 -1
  85. package/lib/common/preferences/preference-service.d.ts +4 -3
  86. package/lib/common/preferences/preference-service.d.ts.map +1 -1
  87. package/lib/common/preferences/preference-service.js +13 -10
  88. package/lib/common/preferences/preference-service.js.map +1 -1
  89. package/lib/common/quick-pick-service.d.ts +18 -2
  90. package/lib/common/quick-pick-service.d.ts.map +1 -1
  91. package/lib/common/quick-pick-service.js +53 -8
  92. package/lib/common/quick-pick-service.js.map +1 -1
  93. package/lib/common/uri.js +1 -1
  94. package/lib/common/uri.js.map +1 -1
  95. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  96. package/lib/electron-browser/menu/electron-main-menu-factory.js +4 -6
  97. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  98. package/lib/electron-browser/menu/electron-menu-contribution.js +3 -3
  99. package/lib/electron-browser/menu/electron-menu-contribution.js.map +1 -1
  100. package/lib/electron-browser/messaging/electron-local-ws-connection-source.d.ts +2 -0
  101. package/lib/electron-browser/messaging/electron-local-ws-connection-source.d.ts.map +1 -1
  102. package/lib/electron-browser/messaging/electron-local-ws-connection-source.js +5 -3
  103. package/lib/electron-browser/messaging/electron-local-ws-connection-source.js.map +1 -1
  104. package/lib/electron-browser/preload.js +2 -2
  105. package/lib/electron-browser/preload.js.map +1 -1
  106. package/lib/electron-browser/window/electron-secondary-window-service.d.ts +4 -0
  107. package/lib/electron-browser/window/electron-secondary-window-service.d.ts.map +1 -1
  108. package/lib/electron-browser/window/electron-secondary-window-service.js +32 -0
  109. package/lib/electron-browser/window/electron-secondary-window-service.js.map +1 -1
  110. package/lib/electron-browser/window/electron-window-module.d.ts +1 -0
  111. package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
  112. package/lib/electron-browser/window/electron-window-module.js +4 -0
  113. package/lib/electron-browser/window/electron-window-module.js.map +1 -1
  114. package/lib/electron-browser/window/electron-window-service.d.ts +3 -2
  115. package/lib/electron-browser/window/electron-window-service.d.ts.map +1 -1
  116. package/lib/electron-browser/window/electron-window-service.js +7 -4
  117. package/lib/electron-browser/window/electron-window-service.js.map +1 -1
  118. package/lib/electron-browser/window/window-zoom-action-bar.d.ts +21 -0
  119. package/lib/electron-browser/window/window-zoom-action-bar.d.ts.map +1 -0
  120. package/lib/electron-browser/window/window-zoom-action-bar.js +71 -0
  121. package/lib/electron-browser/window/window-zoom-action-bar.js.map +1 -0
  122. package/lib/electron-browser/window/window-zoom-status-bar-item.d.ts +14 -0
  123. package/lib/electron-browser/window/window-zoom-status-bar-item.d.ts.map +1 -0
  124. package/lib/electron-browser/window/window-zoom-status-bar-item.js +87 -0
  125. package/lib/electron-browser/window/window-zoom-status-bar-item.js.map +1 -0
  126. package/lib/electron-common/electron-api.d.ts +1 -1
  127. package/lib/electron-common/electron-api.d.ts.map +1 -1
  128. package/lib/electron-common/electron-main-window-service.d.ts +2 -1
  129. package/lib/electron-common/electron-main-window-service.d.ts.map +1 -1
  130. package/lib/electron-common/electron-window-preferences.d.ts +5 -2
  131. package/lib/electron-common/electron-window-preferences.d.ts.map +1 -1
  132. package/lib/electron-common/electron-window-preferences.js +16 -10
  133. package/lib/electron-common/electron-window-preferences.js.map +1 -1
  134. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  135. package/lib/electron-main/electron-api-main.js +14 -2
  136. package/lib/electron-main/electron-api-main.js.map +1 -1
  137. package/lib/electron-main/electron-main-application.d.ts +1 -0
  138. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  139. package/lib/electron-main/electron-main-application.js +6 -0
  140. package/lib/electron-main/electron-main-application.js.map +1 -1
  141. package/lib/electron-main/electron-main-window-service-impl.d.ts +2 -1
  142. package/lib/electron-main/electron-main-window-service-impl.d.ts.map +1 -1
  143. package/lib/electron-main/electron-main-window-service-impl.js +6 -2
  144. package/lib/electron-main/electron-main-window-service-impl.js.map +1 -1
  145. package/package.json +6 -6
  146. package/src/browser/about-dialog.tsx +3 -1
  147. package/src/browser/common-frontend-contribution.ts +36 -17
  148. package/src/browser/frontend-application-module.ts +2 -1
  149. package/src/browser/hover-service.ts +5 -3
  150. package/src/browser/markdown-rendering/markdown-renderer.ts +1 -1
  151. package/src/browser/menu/browser-menu-plugin.ts +9 -1
  152. package/src/browser/quick-input/quick-input-service.spec.ts +58 -9
  153. package/src/browser/saveable-service.ts +56 -4
  154. package/src/browser/style/hover-service.css +1 -0
  155. package/src/browser/style/index.css +1 -1
  156. package/src/browser/tree/fuzzy-search.ts +2 -120
  157. package/src/browser/tree/tree-view-welcome-widget.tsx +1 -2
  158. package/src/browser/widgets/select-component.tsx +39 -2
  159. package/src/browser/window/default-window-service.ts +6 -1
  160. package/src/browser/window/test/mock-window-service.ts +2 -1
  161. package/src/browser/window/window-service.ts +9 -1
  162. package/src/common/fuzzy-match-utils.spec.ts +141 -0
  163. package/src/common/fuzzy-match-utils.ts +78 -0
  164. package/src/{browser/tree → common}/fuzzy-search.spec.ts +23 -1
  165. package/src/common/fuzzy-search.ts +158 -0
  166. package/src/common/logger-sanitizer.spec.ts +54 -1
  167. package/src/common/logger-sanitizer.ts +38 -3
  168. package/src/common/preferences/injectable-preference-proxy.ts +6 -3
  169. package/src/common/preferences/preference-configurations.ts +2 -2
  170. package/src/common/preferences/preference-provider-impl.ts +6 -3
  171. package/src/common/preferences/preference-proxy.ts +6 -4
  172. package/src/common/preferences/preference-service.ts +6 -3
  173. package/src/common/quick-pick-service.ts +65 -11
  174. package/src/common/uri.ts +1 -1
  175. package/src/electron-browser/menu/electron-main-menu-factory.ts +4 -6
  176. package/src/electron-browser/menu/electron-menu-contribution.ts +4 -4
  177. package/src/electron-browser/messaging/electron-local-ws-connection-source.ts +4 -2
  178. package/src/electron-browser/preload.ts +2 -2
  179. package/src/electron-browser/style/window-zoom-action-bar.css +57 -0
  180. package/src/electron-browser/window/electron-secondary-window-service.ts +32 -1
  181. package/src/electron-browser/window/electron-window-module.ts +4 -0
  182. package/src/electron-browser/window/electron-window-service.ts +10 -7
  183. package/src/electron-browser/window/window-zoom-action-bar.tsx +115 -0
  184. package/src/electron-browser/window/window-zoom-status-bar-item.ts +81 -0
  185. package/src/electron-common/electron-api.ts +1 -1
  186. package/src/electron-common/electron-main-window-service.ts +2 -1
  187. package/src/electron-common/electron-window-preferences.ts +19 -11
  188. package/src/electron-main/electron-api-main.ts +12 -2
  189. package/src/electron-main/electron-main-application.ts +7 -0
  190. package/src/electron-main/electron-main-window-service-impl.ts +7 -2
  191. package/lib/browser/tree/fuzzy-search.spec.d.ts.map +0 -1
  192. package/lib/browser/tree/fuzzy-search.spec.js.map +0 -1
  193. /package/lib/{browser/tree → common}/fuzzy-search.spec.d.ts +0 -0
@@ -19,6 +19,7 @@ import { Event } from './event';
19
19
  import { KeySequence } from './keys';
20
20
  import { CancellationToken } from './cancellation';
21
21
  import { Severity } from './severity';
22
+ import { findSubstringIndex, matchRank } from './fuzzy-match-utils';
22
23
 
23
24
  export const quickPickServicePath = '/services/quickPick';
24
25
  export const QuickPickService = Symbol('QuickPickService');
@@ -92,6 +93,26 @@ export interface QuickPickValue<V> extends QuickPickItem {
92
93
  value: V
93
94
  }
94
95
 
96
+ /**
97
+ * Specifies the location where a {@link QuickInputButton} should be rendered.
98
+ */
99
+ export enum QuickInputButtonLocation {
100
+ /**
101
+ * The button is rendered in the title bar.
102
+ */
103
+ Title = 1,
104
+
105
+ /**
106
+ * The button is rendered inline to the right of the input box.
107
+ */
108
+ Inline = 2,
109
+
110
+ /**
111
+ * The button is rendered at the far end inside the input box.
112
+ */
113
+ Input = 3
114
+ }
115
+
95
116
  export interface QuickInputButton {
96
117
  iconClass?: string;
97
118
  tooltip?: string;
@@ -101,9 +122,8 @@ export interface QuickInputButton {
101
122
  alwaysVisible?: boolean;
102
123
  /**
103
124
  * The location where the button should be rendered.
104
- * @monaco-uplift: consider using a typed enum matching Monaco's QuickInputButtonLocation instead of number.
105
125
  */
106
- location?: number;
126
+ location?: QuickInputButtonLocation;
107
127
  /**
108
128
  * When present, indicates that the button is a toggle button.
109
129
  */
@@ -303,24 +323,52 @@ export function filterItems(items: QuickPickItemOrSeparator[], filter: string):
303
323
  return items;
304
324
  }
305
325
 
306
- const filteredItems: QuickPickItemOrSeparator[] = [];
326
+ function matchesFilter(item: QuickPickItem): boolean {
327
+ return fuzzy.test(filter, item.label) ||
328
+ (!!item.description && fuzzy.test(filter, item.description)) ||
329
+ (!!item.detail && fuzzy.test(filter, item.detail));
330
+ }
331
+
332
+ function itemMatchRank(item: QuickPickItem): number {
333
+ return Math.min(
334
+ matchRank(item.label, filter),
335
+ item.description ? matchRank(item.description, filter) : 2,
336
+ item.detail ? matchRank(item.detail, filter) : 2
337
+ );
338
+ }
339
+
340
+ // Process items in separator groups, sorted by match rank within each group.
341
+ const result: QuickPickItemOrSeparator[] = [];
342
+ let currentSeparator: QuickPickSeparator | undefined;
343
+ let groupMatches: { item: QuickPickItem; rank: number }[] = [];
344
+
345
+ const flushGroup = (): void => {
346
+ if (groupMatches.length > 0) {
347
+ if (currentSeparator) {
348
+ result.push(currentSeparator);
349
+ }
350
+ groupMatches.sort((a, b) => a.rank - b.rank);
351
+ result.push(...groupMatches.map(m => m.item));
352
+ }
353
+ groupMatches = [];
354
+ };
355
+
307
356
  for (const item of items) {
308
357
  if (item.type === 'separator') {
309
- filteredItems.push(item);
310
- } else if (
311
- fuzzy.test(filter, item.label) ||
312
- (item.description && fuzzy.test(filter, item.description)) ||
313
- (item.detail && fuzzy.test(filter, item.detail))
314
- ) {
358
+ flushGroup();
359
+ currentSeparator = item;
360
+ } else if (matchesFilter(item)) {
315
361
  item.highlights = {
316
362
  label: findMatches(item.label, filter),
317
363
  description: item.description ? findMatches(item.description, filter) : undefined,
318
364
  detail: item.detail ? findMatches(item.detail, filter) : undefined
319
365
  };
320
- filteredItems.push(item);
366
+ groupMatches.push({ item, rank: itemMatchRank(item) });
321
367
  }
322
368
  }
323
- return filteredItems;
369
+ flushGroup();
370
+
371
+ return result;
324
372
  }
325
373
 
326
374
  /**
@@ -337,6 +385,12 @@ export function findMatches(word: string, pattern: string): Array<{ start: numbe
337
385
  return undefined;
338
386
  }
339
387
 
388
+ // Prefer a contiguous substring highlight over scattered fuzzy character highlights.
389
+ const substringIndex = findSubstringIndex(word, pattern);
390
+ if (substringIndex !== -1) {
391
+ return [{ start: substringIndex, end: substringIndex + pattern.length }];
392
+ }
393
+
340
394
  const delimiter = '\u0000'; // null byte that shouldn't appear in the input and is used to denote matches.
341
395
  const matchResult = fuzzy.match(pattern.replace(/\u0000/gu, ''), word, { pre: delimiter, post: delimiter });
342
396
  if (!matchResult) {
package/src/common/uri.ts CHANGED
@@ -22,7 +22,7 @@ export class URI {
22
22
  public static fromComponents(components: UriComponents): URI;
23
23
  public static fromComponents(components: undefined): undefined;
24
24
  public static fromComponents(components: UriComponents | undefined): URI | undefined {
25
- return components ? new URI(Uri.revive(components)) : undefined;
25
+ return components ? new URI(Uri.from(components)) : undefined;
26
26
  }
27
27
 
28
28
  public static fromFilePath(path: string): URI {
@@ -233,12 +233,10 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory {
233
233
  }
234
234
  };
235
235
 
236
- if (isOSX) {
237
- const role = this.roleFor(menu.id);
238
- if (role) {
239
- menuItem.role = role;
240
- delete menuItem.execute;
241
- }
236
+ const role = this.roleFor(menu.id);
237
+ if (role) {
238
+ menuItem.role = role;
239
+ delete menuItem.execute;
242
240
  }
243
241
  parentItems.push(menuItem);
244
242
  }
@@ -26,7 +26,7 @@ import {
26
26
  import { ElectronMainMenuFactory } from './electron-main-menu-factory';
27
27
  import { FrontendApplicationStateService, FrontendApplicationState } from '../../browser/frontend-application-state';
28
28
  import { FrontendApplicationConfigProvider } from '../../browser/frontend-application-config-provider';
29
- import { ZoomLevel } from '../../electron-common/electron-window-preferences';
29
+ import { PREF_WINDOW_ZOOM_LEVEL, ZoomLevel } from '../../electron-common/electron-window-preferences';
30
30
  import { BrowserMenuBarContribution } from '../../browser/menu/browser-menu-plugin';
31
31
  import { WindowService } from '../../browser/window/window-service';
32
32
  import { WindowTitleService } from '../../browser/window/window-title-service';
@@ -316,7 +316,7 @@ export class ElectronMenuContribution extends BrowserMenuBarContribution impleme
316
316
  zoomLevel = ZoomLevel.MAX;
317
317
  return;
318
318
  };
319
- this.preferenceService.set('window.zoomLevel', zoomLevel, PreferenceScope.User);
319
+ this.preferenceService.set(PREF_WINDOW_ZOOM_LEVEL, zoomLevel, PreferenceScope.User);
320
320
  }
321
321
  });
322
322
  registry.registerCommand(ElectronCommands.ZOOM_OUT, {
@@ -328,11 +328,11 @@ export class ElectronMenuContribution extends BrowserMenuBarContribution impleme
328
328
  zoomLevel = ZoomLevel.MIN;
329
329
  return;
330
330
  };
331
- this.preferenceService.set('window.zoomLevel', zoomLevel, PreferenceScope.User);
331
+ this.preferenceService.set(PREF_WINDOW_ZOOM_LEVEL, zoomLevel, PreferenceScope.User);
332
332
  }
333
333
  });
334
334
  registry.registerCommand(ElectronCommands.RESET_ZOOM, {
335
- execute: () => this.preferenceService.set('window.zoomLevel', ZoomLevel.DEFAULT, PreferenceScope.User)
335
+ execute: () => this.preferenceService.set(PREF_WINDOW_ZOOM_LEVEL, ZoomLevel.DEFAULT, PreferenceScope.User)
336
336
  });
337
337
 
338
338
  registry.registerCommand(ElectronCommands.TOGGLE_FULL_SCREEN, {
@@ -18,14 +18,16 @@ import { injectable } from 'inversify';
18
18
  import { Endpoint } from '../../browser/endpoint';
19
19
  import { WebSocketConnectionSource } from '../../browser/messaging/ws-connection-source';
20
20
 
21
+ export const LOCAL_PORT_PARAM = 'localPort';
21
22
  export function getLocalPort(): string | undefined {
22
23
  const params = new URLSearchParams(location.search);
23
- return params.get('localPort') ?? undefined;
24
+ return params.get(LOCAL_PORT_PARAM) ?? undefined;
24
25
  }
25
26
 
27
+ export const CURRENT_PORT_PARAM = 'port';
26
28
  export function getCurrentPort(): string | undefined {
27
29
  const params = new URLSearchParams(location.search);
28
- return params.get('port') ?? undefined;
30
+ return params.get(CURRENT_PORT_PARAM) ?? undefined;
29
31
  }
30
32
 
31
33
  @injectable()
@@ -207,8 +207,8 @@ const api: TheiaCoreAPI = {
207
207
  return ipcRenderer.invoke(CHANNEL_GET_ZOOM_LEVEL);
208
208
  },
209
209
 
210
- setZoomLevel: function (desired: number): void {
211
- ipcRenderer.send(CHANNEL_SET_ZOOM_LEVEL, desired);
210
+ setZoomLevel: function (desired: number, windowName?: string): void {
211
+ ipcRenderer.send(CHANNEL_SET_ZOOM_LEVEL, desired, windowName);
212
212
  },
213
213
  isFullScreenable: function (): boolean {
214
214
  return ipcRenderer.sendSync(CHANNEL_IS_FULL_SCREENABLE);
@@ -0,0 +1,57 @@
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
+ .window-zoom-action-bar {
18
+ display: flex;
19
+ align-items: center;
20
+ gap: calc(var(--theia-ui-padding) / 3);
21
+ line-height: 1;
22
+ }
23
+
24
+ .window-zoom-action-bar .codicon {
25
+ font-size: inherit;
26
+ }
27
+
28
+ .window-zoom-display {
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ min-width: 1.5em;
33
+ padding: calc(var(--theia-ui-padding) / 3);
34
+ cursor: default;
35
+ user-select: none;
36
+ }
37
+
38
+ .window-zoom-button {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ padding: calc(var(--theia-ui-padding) / 3);
43
+ border-radius: calc(var(--theia-ui-padding) / 2);
44
+ white-space: nowrap;
45
+ }
46
+
47
+ .window-zoom-button:hover {
48
+ background-color: var(--theia-toolbar-hoverBackground);
49
+ }
50
+
51
+ .window-zoom-button:focus-visible {
52
+ outline: 1px solid var(--theia-focusBorder);
53
+ }
54
+
55
+ #status-bar-window-zoom-status {
56
+ padding-inline: calc(var(--theia-ui-padding) * 2 / 3);
57
+ }
@@ -14,14 +14,41 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { injectable } from 'inversify';
17
+ import { inject, injectable, postConstruct } from 'inversify';
18
18
  import { DefaultSecondaryWindowService } from '../../browser/window/default-secondary-window-service';
19
19
  import { ApplicationShell, ExtractableWidget } from '../../browser';
20
20
  import { ElectronWindowService } from './electron-window-service';
21
21
  import { Deferred, timeout } from '../../common/promise-util';
22
+ import { ElectronWindowPreferences, PREF_WINDOW_ZOOM_LEVEL } from '../../electron-common/electron-window-preferences';
22
23
 
23
24
  @injectable()
24
25
  export class ElectronSecondaryWindowService extends DefaultSecondaryWindowService {
26
+
27
+ @inject(ElectronWindowPreferences)
28
+ protected readonly electronWindowPreferences: ElectronWindowPreferences;
29
+
30
+ @postConstruct()
31
+ override init(): void {
32
+ super.init();
33
+ this.electronWindowPreferences.ready.then(() => {
34
+ const initialZoomLevel = this.electronWindowPreferences.get(PREF_WINDOW_ZOOM_LEVEL);
35
+ this.updateWindowZoomLevel(initialZoomLevel);
36
+
37
+ this.electronWindowPreferences.onPreferenceChanged(e => {
38
+ if (e.preferenceName === PREF_WINDOW_ZOOM_LEVEL) {
39
+ const zoomLevel = this.electronWindowPreferences.get(PREF_WINDOW_ZOOM_LEVEL, 0);
40
+ this.updateWindowZoomLevel(zoomLevel);
41
+ }
42
+ });
43
+ });
44
+ }
45
+
46
+ protected async updateWindowZoomLevel(zoomLevel: number): Promise<void> {
47
+ this.secondaryWindows.forEach((win: Window) => {
48
+ window.electronTheiaCore.setZoomLevel(zoomLevel, win.name);
49
+ });
50
+ }
51
+
25
52
  override focus(win: Window): void {
26
53
  window.electronTheiaCore.focusWindow(win.name);
27
54
  }
@@ -50,6 +77,10 @@ export class ElectronSecondaryWindowService extends DefaultSecondaryWindowServic
50
77
  window.electronTheiaCore.setMenuBarVisible(false, newWindow.name);
51
78
  window.electronTheiaCore.setSecondaryWindowCloseRequestHandler(newWindow.name, () => this.canClose(widget, shell, newWindow));
52
79
 
80
+ // Apply current zoom level to newly created secondary window
81
+ const currentZoomLevel = this.electronWindowPreferences.get(PREF_WINDOW_ZOOM_LEVEL, 0);
82
+ window.electronTheiaCore.setZoomLevel(currentZoomLevel, newWindow.name);
83
+
53
84
  // Below code may be used to debug contents of secondary window
54
85
  // window.electronTheiaCore.openDevToolsForWindow(newWindow.name);
55
86
  }
@@ -32,6 +32,8 @@ import { ExternalAppOpenHandler } from './external-app-open-handler';
32
32
  import { ElectronUriHandlerContribution } from '../electron-uri-handler';
33
33
  import { bindRootContributionProvider } from '../../common';
34
34
  import { WindowTitleContribution } from '../../browser/window/window-title-service';
35
+ import { WindowZoomStatusBarItem } from './window-zoom-status-bar-item';
36
+ import '../../../src/electron-browser/style/window-zoom-action-bar.css';
35
37
 
36
38
  export default new ContainerModule((bind, unbind, isBound, rebind) => {
37
39
  bind(ElectronMainWindowService).toDynamicValue(context =>
@@ -48,4 +50,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
48
50
  bind(ExternalAppOpenHandler).toSelf().inSingletonScope();
49
51
  bind(OpenHandler).toService(ExternalAppOpenHandler);
50
52
  bindRootContributionProvider(bind, WindowTitleContribution);
53
+ bind(WindowZoomStatusBarItem).toSelf().inSingletonScope();
54
+ bind(FrontendApplicationContribution).toService(WindowZoomStatusBarItem);
51
55
  });
@@ -15,10 +15,10 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { injectable, inject, postConstruct } from 'inversify';
18
- import { NewWindowOptions, WindowSearchParams } from '../../common/window';
18
+ import { NewWindowOptions } from '../../common/window';
19
19
  import { DefaultWindowService } from '../../browser/window/default-window-service';
20
20
  import { ElectronMainWindowService } from '../../electron-common/electron-main-window-service';
21
- import { ElectronWindowPreferences } from '../../electron-common/electron-window-preferences';
21
+ import { ElectronWindowPreferences, PREF_WINDOW_ZOOM_LEVEL } from '../../electron-common/electron-window-preferences';
22
22
  import { ConnectionCloseService } from '../../common/messaging/connection-management';
23
23
  import { FrontendIdProvider } from '../../browser/messaging/frontend-id-provider';
24
24
  import { WindowReloadOptions } from '../../browser/window/window-service';
@@ -57,8 +57,12 @@ export class ElectronWindowService extends DefaultWindowService {
57
57
  return undefined;
58
58
  }
59
59
 
60
- override openNewDefaultWindow(params?: WindowSearchParams): void {
61
- this.delegate.openNewDefaultWindow(params);
60
+ override async openNewDefaultWindow(params?: WindowReloadOptions): Promise<number> {
61
+ return this.delegate.openNewDefaultWindow(params?.search);
62
+ }
63
+
64
+ override closeWindow(windowId: number): void {
65
+ this.delegate.closeWindow(windowId);
62
66
  }
63
67
 
64
68
  override focus(): void {
@@ -68,7 +72,7 @@ export class ElectronWindowService extends DefaultWindowService {
68
72
  protected init(): void {
69
73
  // Update the default zoom level on startup when the preferences event is fired.
70
74
  this.electronWindowPreferences.onPreferenceChanged(e => {
71
- if (e.preferenceName === 'window.zoomLevel') {
75
+ if (e.preferenceName === PREF_WINDOW_ZOOM_LEVEL) {
72
76
  this.updateWindowZoomLevel();
73
77
  }
74
78
  });
@@ -94,7 +98,7 @@ export class ElectronWindowService extends DefaultWindowService {
94
98
  * Updates the window zoom level based on the preference value.
95
99
  */
96
100
  protected async updateWindowZoomLevel(): Promise<void> {
97
- const preferredZoomLevel = this.electronWindowPreferences['window.zoomLevel'];
101
+ const preferredZoomLevel = this.electronWindowPreferences[PREF_WINDOW_ZOOM_LEVEL];
98
102
  if (await window.electronTheiaCore.getZoomLevel() !== preferredZoomLevel) {
99
103
  window.electronTheiaCore.setZoomLevel(preferredZoomLevel);
100
104
  }
@@ -116,4 +120,3 @@ export class ElectronWindowService extends DefaultWindowService {
116
120
  }
117
121
  }
118
122
  }
119
-
@@ -0,0 +1,115 @@
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
+ import { createRoot } from 'react-dom/client';
17
+ import * as React from '../../../shared/react';
18
+ import { ACTION_ITEM, codicon, CommonCommands, KeybindingRegistry } from '../../browser';
19
+ import { Command, CommandRegistry, nls } from '../../common';
20
+ import { PREF_WINDOW_ZOOM_LEVEL, ZoomLevel } from '../../electron-common/electron-window-preferences';
21
+ import { ElectronCommands } from '../menu/electron-menu-contribution';
22
+
23
+ export interface WindowZoomActionBarProps {
24
+ container: HTMLElement
25
+ zoomLevel: number;
26
+ commandRegistry: CommandRegistry;
27
+ keybindingRegistry: KeybindingRegistry;
28
+ }
29
+
30
+ export class WindowZoomActionBar extends React.Component<WindowZoomActionBarProps> {
31
+
32
+ protected getTitleWithKeybinding(command: Command): string {
33
+ const bindings = this.props.keybindingRegistry.getKeybindingsForCommand(command.id);
34
+ // Only consider the first active keybinding.
35
+ if (bindings.length) {
36
+ const binding = bindings.find(b => this.props.keybindingRegistry.isEnabledInScope(b, this.props.container));
37
+ if (binding) {
38
+ const accelerator = this.props.keybindingRegistry.acceleratorFor(binding, '+', true);
39
+ return `${command.label} (${accelerator})`;
40
+ }
41
+ }
42
+ return command.label!;
43
+ }
44
+
45
+ protected renderActionButton(command: Command, iconName?: string, commandArgs: unknown[] = []): React.ReactNode {
46
+ return (
47
+ <div
48
+ className={`${ACTION_ITEM} window-zoom-button`}
49
+ role='button'
50
+ tabIndex={0}
51
+ aria-label={command.label}
52
+ title={this.getTitleWithKeybinding(command)}
53
+ onClick={() => this.props.commandRegistry.executeCommand(command.id, ...commandArgs)}
54
+ onKeyDown={e => {
55
+ if (e.key === 'Enter' || e.key === ' ') {
56
+ e.preventDefault();
57
+ this.props.commandRegistry.executeCommand(command.id, ...commandArgs);
58
+ }
59
+ }}
60
+ >
61
+ {iconName ? <div className={codicon(iconName)}></div> : <div>{command.label}</div>}
62
+ </div>
63
+ );
64
+ }
65
+
66
+ protected renderZoomDisplay(): React.ReactNode {
67
+ const percentage = Math.round(100 * Math.pow(ZoomLevel.ZOOM_BASE, this.props.zoomLevel));
68
+ const zoomLevelText = nls.localizeByDefault('Zoom Level: {0} ({1}%)', this.props.zoomLevel.toString(), percentage.toString());
69
+ return (
70
+ <div
71
+ className='window-zoom-display'
72
+ role='status'
73
+ aria-live='polite'
74
+ aria-atomic='true'
75
+ aria-label={zoomLevelText}
76
+ title={zoomLevelText}
77
+ >
78
+ <div>{this.props.zoomLevel}</div>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ override render(): React.ReactNode {
84
+ return (
85
+ <>
86
+ {this.renderActionButton(ElectronCommands.ZOOM_OUT, 'remove')}
87
+ {this.renderZoomDisplay()}
88
+ {this.renderActionButton(ElectronCommands.ZOOM_IN, 'plus')}
89
+ {this.renderActionButton(ElectronCommands.RESET_ZOOM)}
90
+ {this.renderActionButton(CommonCommands.OPEN_PREFERENCES, 'settings-gear', [PREF_WINDOW_ZOOM_LEVEL])}
91
+ </>
92
+ );
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Helper function to render the WindowZoomActionBar React component into a DOM container element.
98
+ * This function can be called from a TypeScript file without JSX.
99
+ */
100
+ export function renderWindowZoomActionBar(
101
+ container: HTMLElement,
102
+ zoomLevel: number,
103
+ commandRegistry: CommandRegistry,
104
+ keybindingRegistry: KeybindingRegistry,
105
+ ): void {
106
+ const root = createRoot(container);
107
+ root.render(
108
+ <WindowZoomActionBar
109
+ container={container}
110
+ zoomLevel={zoomLevel}
111
+ commandRegistry={commandRegistry}
112
+ keybindingRegistry={keybindingRegistry}
113
+ />
114
+ );
115
+ }
@@ -0,0 +1,81 @@
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 { inject, injectable } from 'inversify';
18
+ import { FrontendApplicationContribution, KeybindingRegistry, StatusBar, StatusBarAlignment } from '../../browser';
19
+ import { CommandRegistry, nls, PreferenceService } from '../../common';
20
+ import { PREF_WINDOW_ZOOM_LEVEL } from '../../electron-common/electron-window-preferences';
21
+ import { renderWindowZoomActionBar } from './window-zoom-action-bar';
22
+
23
+ @injectable()
24
+ export class WindowZoomStatusBarItem implements FrontendApplicationContribution {
25
+
26
+ static readonly ID = 'window-zoom-status';
27
+
28
+ @inject(StatusBar)
29
+ protected readonly statusBar: StatusBar;
30
+
31
+ @inject(PreferenceService)
32
+ protected readonly preferenceService: PreferenceService;
33
+
34
+ @inject(CommandRegistry)
35
+ protected readonly commandRegistry: CommandRegistry;
36
+
37
+ @inject(KeybindingRegistry)
38
+ protected readonly keybindingRegistry: KeybindingRegistry;
39
+
40
+ onStart(): void {
41
+ this.preferenceService.ready.then(() => {
42
+ this.updateZoomStatusBarItem();
43
+
44
+ this.preferenceService.onPreferenceChanged(e => {
45
+ if (e.preferenceName === PREF_WINDOW_ZOOM_LEVEL) {
46
+ this.updateZoomStatusBarItem();
47
+ }
48
+ });
49
+ });
50
+ }
51
+
52
+ protected updateZoomStatusBarItem(): void {
53
+ const zoomLevel = this.getZoomLevel();
54
+
55
+ if (zoomLevel === 0) {
56
+ // Hide the status bar item when zoom is at default level
57
+ this.statusBar.removeElement(WindowZoomStatusBarItem.ID);
58
+ } else {
59
+ this.statusBar.setElement(WindowZoomStatusBarItem.ID, {
60
+ name: nls.localizeByDefault('Window Zoom'),
61
+ text: zoomLevel > 0 ? '$(codicon-zoom-in)' : '$(codicon-zoom-out)',
62
+ alignment: StatusBarAlignment.RIGHT,
63
+ priority: 110,
64
+ tooltip: () => this.createTooltip(zoomLevel),
65
+ backgroundColor: 'var(--theia-statusBarItem-prominentBackground)',
66
+ color: 'var(--theia-statusBarItem-prominentForeground)'
67
+ });
68
+ }
69
+ }
70
+
71
+ protected getZoomLevel(): number {
72
+ return this.preferenceService.get(PREF_WINDOW_ZOOM_LEVEL, 0);
73
+ }
74
+
75
+ protected createTooltip(zoomLevel: number): HTMLElement {
76
+ const container = document.createElement('div');
77
+ container.className = 'window-zoom-action-bar';
78
+ renderWindowZoomActionBar(container, zoomLevel, this.commandRegistry, this.keybindingRegistry);
79
+ return container;
80
+ }
81
+ }
@@ -85,7 +85,7 @@ export interface TheiaCoreAPI {
85
85
  toggleDevTools(): void;
86
86
  openDevToolsForWindow(windowName: string): void;
87
87
  getZoomLevel(): Promise<number>;
88
- setZoomLevel(desired: number): void;
88
+ setZoomLevel(desired: number, windowName?: string): void;
89
89
 
90
90
  isFullScreenable(): boolean; // TODO: this should really be async, since it blocks the renderer process
91
91
  isFullScreen(): boolean; // TODO: this should really be async, since it blocks the renderer process
@@ -20,5 +20,6 @@ export const electronMainWindowServicePath = '/services/electron-window';
20
20
  export const ElectronMainWindowService = Symbol('ElectronMainWindowService');
21
21
  export interface ElectronMainWindowService {
22
22
  openNewWindow(url: string, options?: NewWindowOptions): undefined;
23
- openNewDefaultWindow(params?: WindowSearchParams): void;
23
+ openNewDefaultWindow(params?: WindowSearchParams): Promise<number>;
24
+ closeWindow(windowId: number): void;
24
25
  }
@@ -27,20 +27,28 @@ export namespace ZoomLevel {
27
27
  export const MAX = 9;
28
28
  // amount to increment or decrement the window zoom level.
29
29
  export const VARIATION = 0.5;
30
+ // Chromium's base for zoom factor calculation: zoomFactor = pow(ZOOM_BASE, zoomLevel)
31
+ // See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/common/page/page_zoom.cc
32
+ export const ZOOM_BASE = 1.2;
30
33
  }
31
34
 
35
+ export const PREF_WINDOW_ZOOM_LEVEL = 'window.zoomLevel';
36
+ export const PREF_WINDOW_TITLE_BAR_STYLE = 'window.titleBarStyle';
37
+
32
38
  export const electronWindowPreferencesSchema: PreferenceSchema = {
33
39
  properties: {
34
- 'window.zoomLevel': {
35
- 'type': 'number',
36
- 'default': ZoomLevel.DEFAULT,
37
- 'minimum': ZoomLevel.MIN,
38
- 'maximum': ZoomLevel.MAX,
39
- 'scope': PreferenceScope.User,
40
- // eslint-disable-next-line max-len
41
- 'description': nls.localizeByDefault("Adjust the default zoom level for all windows. Each increment above `0` (e.g. `1`) or below (e.g. `-1`) represents zooming `20%` larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity. See {0} for configuring if the 'Zoom In' and 'Zoom Out' commands apply the zoom level to all windows or only the active window.")
40
+ [PREF_WINDOW_ZOOM_LEVEL]: {
41
+ type: 'number',
42
+ default: ZoomLevel.DEFAULT,
43
+ minimum: ZoomLevel.MIN,
44
+ maximum: ZoomLevel.MAX,
45
+ scope: PreferenceScope.User,
46
+ markdownDescription: nls.localize('theia/core/window/zoomLevelPref',
47
+ 'Adjust the default zoom level for all windows.\
48
+ Each increment of `0.5` above `0` (e.g. `0.5`) or below (e.g. `-0.5`) represents zooming approximately `10%` larger or smaller.\
49
+ You can also enter other decimal values to adjust the zoom level with a finer granularity.')
42
50
  },
43
- 'window.titleBarStyle': {
51
+ [PREF_WINDOW_TITLE_BAR_STYLE]: {
44
52
  type: 'string',
45
53
  enum: ['native', 'custom'],
46
54
  default: isWindows ? 'custom' : 'native',
@@ -52,8 +60,8 @@ export const electronWindowPreferencesSchema: PreferenceSchema = {
52
60
  };
53
61
 
54
62
  export class ElectronWindowConfiguration {
55
- 'window.zoomLevel': number;
56
- 'window.titleBarStyle': 'native' | 'custom';
63
+ [PREF_WINDOW_ZOOM_LEVEL]: number;
64
+ [PREF_WINDOW_TITLE_BAR_STYLE]: 'native' | 'custom';
57
65
  }
58
66
 
59
67
  export const ElectronWindowPreferenceContribution = Symbol('ElectronWindowPreferenceContribution');