@theia/core 1.70.0-next.7 → 1.70.0-next.71

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 (194) 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 +83 -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 +7 -7
  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/i18n/nls.metadata.json +25379 -24577
  167. package/src/common/logger-sanitizer.spec.ts +54 -1
  168. package/src/common/logger-sanitizer.ts +38 -3
  169. package/src/common/preferences/injectable-preference-proxy.ts +6 -3
  170. package/src/common/preferences/preference-configurations.ts +2 -2
  171. package/src/common/preferences/preference-provider-impl.ts +6 -3
  172. package/src/common/preferences/preference-proxy.ts +6 -4
  173. package/src/common/preferences/preference-service.ts +6 -3
  174. package/src/common/quick-pick-service.ts +65 -11
  175. package/src/common/uri.ts +1 -1
  176. package/src/electron-browser/menu/electron-main-menu-factory.ts +4 -6
  177. package/src/electron-browser/menu/electron-menu-contribution.ts +4 -4
  178. package/src/electron-browser/messaging/electron-local-ws-connection-source.ts +4 -2
  179. package/src/electron-browser/preload.ts +2 -2
  180. package/src/electron-browser/style/window-zoom-action-bar.css +57 -0
  181. package/src/electron-browser/window/electron-secondary-window-service.ts +32 -1
  182. package/src/electron-browser/window/electron-window-module.ts +4 -0
  183. package/src/electron-browser/window/electron-window-service.ts +10 -7
  184. package/src/electron-browser/window/window-zoom-action-bar.tsx +115 -0
  185. package/src/electron-browser/window/window-zoom-status-bar-item.ts +81 -0
  186. package/src/electron-common/electron-api.ts +1 -1
  187. package/src/electron-common/electron-main-window-service.ts +2 -1
  188. package/src/electron-common/electron-window-preferences.ts +19 -11
  189. package/src/electron-main/electron-api-main.ts +12 -2
  190. package/src/electron-main/electron-main-application.ts +7 -0
  191. package/src/electron-main/electron-main-window-service-impl.ts +7 -2
  192. package/lib/browser/tree/fuzzy-search.spec.d.ts.map +0 -1
  193. package/lib/browser/tree/fuzzy-search.spec.js.map +0 -1
  194. /package/lib/{browser/tree → common}/fuzzy-search.spec.d.ts +0 -0
@@ -21,7 +21,7 @@ import { ReactDialog } from './dialogs/react-dialog';
21
21
  import { ApplicationServer, ApplicationInfo, ExtensionInfo } from '../common/application-protocol';
22
22
  import { Message } from './widgets/widget';
23
23
  import { FrontendApplicationConfigProvider } from './frontend-application-config-provider';
24
- import { DEFAULT_SUPPORTED_API_VERSION } from '@theia/application-package/lib/api';
24
+ import { DEFAULT_SUPPORTED_API_VERSION, DEFAULT_SUPPORTED_MONACO_VERSION } from '@theia/application-package/lib/api';
25
25
  import { WindowService } from './window/window-service';
26
26
  import { Key, KeyCode } from './keys';
27
27
  import { nls } from '../common/nls';
@@ -73,6 +73,7 @@ export class AboutDialog extends ReactDialog<void> {
73
73
  const versionLabel = nls.localizeByDefault('Version');
74
74
  const defaultApiLabel = nls.localize('theia/core/about/defaultApi', 'Default {0} API', 'VS Code');
75
75
  const compatibilityLabel = nls.localize('theia/core/about/compatibility', '{0} Compatibility', 'VS Code');
76
+ const monacoLabel = nls.localize('theia/core/about/monacoEditor', 'Monaco Editor Version');
76
77
 
77
78
  return <>
78
79
  <h3>{detailsLabel}</h3>
@@ -88,6 +89,7 @@ export class AboutDialog extends ReactDialog<void> {
88
89
  {compatibilityLabel}
89
90
  </a>
90
91
  </p>
92
+ <p>{`${monacoLabel}: ${DEFAULT_SUPPORTED_MONACO_VERSION}`}</p>
91
93
  </div>
92
94
  </>;
93
95
  }
@@ -159,7 +159,6 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
159
159
  protected readonly undoRedoHandlerService: UndoRedoHandlerService;
160
160
 
161
161
  protected pinnedKey: ContextKey<boolean>;
162
- protected inputFocus: ContextKey<boolean>;
163
162
 
164
163
  async configure(app: FrontendApplication): Promise<void> {
165
164
  // FIXME: This request blocks valuable startup time (~200ms).
@@ -174,9 +173,9 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
174
173
  this.contextKeyService.createKey<boolean>('isMac', OS.type() === OS.Type.OSX);
175
174
  this.contextKeyService.createKey<boolean>('isWindows', OS.type() === OS.Type.Windows);
176
175
  this.contextKeyService.createKey<boolean>('isWeb', !this.isElectron());
177
- this.inputFocus = this.contextKeyService.createKey<boolean>('inputFocus', false);
178
- this.updateInputFocus();
179
- browser.onDomEvent(document, 'focusin', () => this.updateInputFocus());
176
+ // Note: the 'inputFocus' context key is tracked by Monaco's ContextKeyService
177
+ // which sets it for <input>, <textarea>, and elements with EditContext (Monaco editors).
178
+ // We no longer track it separately to avoid race conditions between two focusin listeners.
180
179
 
181
180
  this.pinnedKey = this.contextKeyService.createKey<boolean>('activeEditorIsPinned', false);
182
181
  this.updatePinnedKey();
@@ -234,15 +233,6 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
234
233
  }
235
234
  }
236
235
 
237
- protected updateInputFocus(): void {
238
- const activeElement = document.activeElement;
239
- if (activeElement) {
240
- const isInput = activeElement.tagName?.toLowerCase() === 'input'
241
- || activeElement.tagName?.toLowerCase() === 'textarea';
242
- this.inputFocus.set(isInput);
243
- }
244
- }
245
-
246
236
  protected updatePinnedKey(): void {
247
237
  const activeTab = this.shell.findTabBar();
248
238
  const pinningTarget = activeTab && this.shell.findTitle(activeTab);
@@ -498,7 +488,18 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
498
488
  commandRegistry.registerCommand(CommonCommands.CUT, {
499
489
  execute: () => {
500
490
  if (supportCut) {
501
- document.execCommand('cut');
491
+ const active = document.activeElement;
492
+ if (environment.electron.is() && (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement)) {
493
+ const start = active.selectionStart ?? 0;
494
+ const end = active.selectionEnd ?? 0;
495
+ const selectedText = active.value.substring(start, end);
496
+ if (selectedText) {
497
+ this.clipboardService.writeText(selectedText);
498
+ document.execCommand('insertText', false, '');
499
+ }
500
+ } else {
501
+ document.execCommand('cut');
502
+ }
502
503
  } else {
503
504
  this.messageService.warn(nls.localize('theia/core/cutWarn', "Please use the browser's cut command or shortcut."));
504
505
  }
@@ -507,16 +508,34 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
507
508
  commandRegistry.registerCommand(CommonCommands.COPY, {
508
509
  execute: () => {
509
510
  if (supportCopy) {
510
- document.execCommand('copy');
511
+ const active = document.activeElement;
512
+ if (environment.electron.is() && (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement)) {
513
+ const start = active.selectionStart ?? 0;
514
+ const end = active.selectionEnd ?? 0;
515
+ const selectedText = active.value.substring(start, end);
516
+ if (selectedText) {
517
+ this.clipboardService.writeText(selectedText);
518
+ }
519
+ } else {
520
+ document.execCommand('copy');
521
+ }
511
522
  } else {
512
523
  this.messageService.warn(nls.localize('theia/core/copyWarn', "Please use the browser's copy command or shortcut."));
513
524
  }
514
525
  }
515
526
  });
516
527
  commandRegistry.registerCommand(CommonCommands.PASTE, {
517
- execute: () => {
528
+ execute: async () => {
518
529
  if (supportPaste) {
519
- document.execCommand('paste');
530
+ const active = document.activeElement;
531
+ if (environment.electron.is() && (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement)) {
532
+ const text = await this.clipboardService.readText();
533
+ if (text) {
534
+ document.execCommand('insertText', false, text);
535
+ }
536
+ } else {
537
+ document.execCommand('paste');
538
+ }
520
539
  } else {
521
540
  this.messageService.warn(nls.localize('theia/core/pasteWarn', "Please use the browser's paste command or shortcut."));
522
541
  }
@@ -123,7 +123,7 @@ import { DockPanel, RendererHost } from './widgets';
123
123
  import { TooltipService, TooltipServiceImpl } from './tooltip-service';
124
124
  import { BackendRequestService, RequestService, REQUEST_SERVICE_PATH } from '@theia/request';
125
125
  import { bindFrontendStopwatch, bindBackendStopwatch } from './performance';
126
- import { SaveableService } from './saveable-service';
126
+ import { SaveableService, SaveErrorChecker } from './saveable-service';
127
127
  import { SecondaryWindowHandler } from './secondary-window-handler';
128
128
  import { UserWorkingDirectoryProvider } from './user-working-directory-provider';
129
129
  import { WindowTitleService } from './window/window-title-service';
@@ -461,6 +461,7 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is
461
461
 
462
462
  bind(SaveableService).toSelf().inSingletonScope();
463
463
  bind(FrontendApplicationContribution).toService(SaveableService);
464
+ bindRootContributionProvider(bind, SaveErrorChecker);
464
465
 
465
466
  bind(UserWorkingDirectoryProvider).toSelf().inSingletonScope();
466
467
  bind(FrontendApplicationContribution).toService(UserWorkingDirectoryProvider);
@@ -260,9 +260,11 @@ export class HoverService {
260
260
  * For interactive hovers, the hover remains visible to allow interaction with its elements.
261
261
  */
262
262
  protected listenForMouseClick(request: HoverRequest): void {
263
- const handleMouseDown = (_e: MouseEvent) => {
264
- const isInteractive = request.interactive;
265
- if (!isInteractive) {
263
+ const handleMouseDown = (e: MouseEvent) => {
264
+ if (this.hoverHost.contains(e.target as Node)) {
265
+ return;
266
+ }
267
+ if (!request.interactive) {
266
268
  this.cancelHover();
267
269
  }
268
270
  };
@@ -54,7 +54,7 @@ export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
54
54
  /** Use this directly if you aren't worried about circular dependencies in the Shell */
55
55
  export const MarkdownRenderer = Symbol('MarkdownRenderer');
56
56
  export interface MarkdownRenderer {
57
- render(markdown: MarkdownString | undefined, options?: MarkdownRenderOptions): MarkdownRenderResult;
57
+ render(markdown: MarkdownString, options?: MarkdownRenderOptions, outElement?: HTMLElement): MarkdownRenderResult;
58
58
  }
59
59
 
60
60
  /** Use this to avoid circular dependencies in the Shell */
@@ -345,7 +345,15 @@ export class DynamicMenuWidget extends MenuWidget {
345
345
  const enabled = node.isEnabled(nodePath, ...(this.args || []));
346
346
  const toggled = node.isToggled ? !!node.isToggled(nodePath, ...(this.args || [])) : false;
347
347
  phCommandRegistry.addCommand(id, {
348
- execute: () => { node.run(nodePath, ...(this.args || [])); },
348
+ execute: () => {
349
+ // Restore focus to the previously focused element before executing
350
+ // the command so that focus-dependent commands like clipboard
351
+ // operations target the correct element instead of the menu.
352
+ if (this.previousFocusedElement) {
353
+ this.previousFocusedElement.focus({ preventScroll: true });
354
+ }
355
+ node.run(nodePath, ...(this.args || []));
356
+ },
349
357
  isEnabled: () => enabled,
350
358
  isToggled: () => toggled,
351
359
  isVisible: () => true,
@@ -23,11 +23,14 @@ describe('quick-input-service', () => {
23
23
 
24
24
  describe('#findMatches', () => {
25
25
 
26
- it('should return the proper highlights when matches are found', () => {
26
+ it('should return a single contiguous range for substring matches', () => {
27
27
  expect(findMatches('abc', 'a')).deep.equal([{ start: 0, end: 1 }]);
28
- expect(findMatches('abc', 'ab')).deep.equal([{ start: 0, end: 1 }, { start: 1, end: 2 }]);
28
+ expect(findMatches('abc', 'ab')).deep.equal([{ start: 0, end: 2 }]);
29
+ expect(findMatches('abc', 'abc')).deep.equal([{ start: 0, end: 3 }]);
30
+ });
31
+
32
+ it('should return per-character ranges for fuzzy-only matches', () => {
29
33
  expect(findMatches('abc', 'ac')).deep.equal([{ start: 0, end: 1 }, { start: 2, end: 3 }]);
30
- expect(findMatches('abc', 'abc')).deep.equal([{ start: 0, end: 1 }, { start: 1, end: 2 }, { start: 2, end: 3 }]);
31
34
  });
32
35
 
33
36
  it('should fail when out of order', () => {
@@ -67,9 +70,7 @@ describe('quick-input-service', () => {
67
70
  label: 'abc',
68
71
  highlights: {
69
72
  label: [
70
- { start: 0, end: 1 },
71
- { start: 1, end: 2 },
72
- { start: 2, end: 3 }
73
+ { start: 0, end: 3 }
73
74
  ]
74
75
  }
75
76
  };
@@ -152,9 +153,7 @@ describe('quick-input-service', () => {
152
153
  label: 'abc',
153
154
  highlights: {
154
155
  label: [
155
- { start: 0, end: 1 },
156
- { start: 1, end: 2 },
157
- { start: 2, end: 3 }
156
+ { start: 0, end: 3 }
158
157
  ]
159
158
  }
160
159
  };
@@ -171,6 +170,56 @@ describe('quick-input-service', () => {
171
170
  expect(filterItems(items, 'yyy')).deep.equal([]);
172
171
  });
173
172
 
173
+ it('should rank substring matches before fuzzy-only matches', () => {
174
+ const testItems: QuickPickItem[] = [
175
+ { label: 'reformatting' },
176
+ { label: 'fontSize' },
177
+ { label: 'fontFamily' },
178
+ ];
179
+ const result = filterItems(testItems, 'font').filter(QuickPickItem.is);
180
+ expect(result).length(2);
181
+ expect(result[0].label).equal('fontSize');
182
+ expect(result[1].label).equal('fontFamily');
183
+ });
184
+
185
+ it('should rank prefix matches before other substring matches', () => {
186
+ const testItems: QuickPickItem[] = [
187
+ { label: 'setFont' },
188
+ { label: 'fontSize' },
189
+ { label: 'fontFamily' },
190
+ ];
191
+ const result = filterItems(testItems, 'font').filter(QuickPickItem.is);
192
+ expect(result).length(3);
193
+ expect(result[0].label).equal('fontSize');
194
+ expect(result[1].label).equal('fontFamily');
195
+ expect(result[2].label).equal('setFont');
196
+ });
197
+
198
+ it('should treat segmented prefix matches as prefix matches', () => {
199
+ const testItems: QuickPickItem[] = [
200
+ { label: 'backend-workspace-service.ts' },
201
+ { label: 'workspace-backend-service.ts' },
202
+ ];
203
+ const result = filterItems(testItems, 'works-ser').filter(QuickPickItem.is);
204
+ expect(result).length(2);
205
+ expect(result[0].label).equal('workspace-backend-service.ts');
206
+ expect(result[1].label).equal('backend-workspace-service.ts');
207
+ });
208
+
209
+ it('should drop separators when their group has no matches', () => {
210
+ const testItems = [
211
+ { type: 'separator' as const, label: 'Group A' },
212
+ { label: 'fontSize' },
213
+ { type: 'separator' as const, label: 'Group B' },
214
+ { label: 'xyz' },
215
+ ];
216
+ const result = filterItems(testItems, 'font');
217
+ // Group B's only item "xyz" doesn't match, so Group B separator is dropped
218
+ expect(result).length(2);
219
+ expect(result[0]).to.have.property('type', 'separator');
220
+ expect(result[1]).to.have.property('label', 'fontSize');
221
+ });
222
+
174
223
  });
175
224
 
176
225
  });
@@ -15,8 +15,8 @@
15
15
  ********************************************************************************/
16
16
 
17
17
  import type { ApplicationShell } from './shell';
18
- import { injectable } from 'inversify';
19
- import { UNTITLED_SCHEME, URI, Disposable, DisposableCollection, Emitter, Event } from '../common';
18
+ import { inject, injectable, named } from 'inversify';
19
+ import { ContributionProvider, UNTITLED_SCHEME, URI, Disposable, DisposableCollection, Emitter, Event } from '../common';
20
20
  import { Navigatable, NavigatableWidget } from './navigatable-types';
21
21
  import { AutoSaveMode, Saveable, SaveableSource, SaveableWidget, SaveOptions, SaveReason, setDirty, close, PostCreationSaveableWidget, ShouldSaveDialog } from './saveable';
22
22
  import { waitForClosed, Widget } from './widgets';
@@ -24,16 +24,40 @@ import { FrontendApplicationContribution } from './frontend-application-contribu
24
24
  import { FrontendApplication } from './frontend-application';
25
25
  import throttle = require('lodash.throttle');
26
26
 
27
+ export const SaveErrorChecker = Symbol('SaveErrorChecker');
28
+
29
+ /**
30
+ * Contribution point for checking whether a given URI has errors.
31
+ * When `files.autoSaveWhenNoErrors` is enabled, auto-save will be suppressed
32
+ * for files where any registered checker reports errors.
33
+ */
34
+ export interface SaveErrorChecker {
35
+ /**
36
+ * Returns `true` if the given URI has errors that should prevent auto-save.
37
+ */
38
+ hasErrors(uri: URI): boolean;
39
+ /**
40
+ * Event fired when the error state may have changed (e.g. diagnostics updated).
41
+ * The SaveableService listens to this to re-evaluate auto-save for dirty widgets.
42
+ */
43
+ onDidErrorStateChange: Event<void>;
44
+ }
45
+
27
46
  @injectable()
28
47
  export class SaveableService implements FrontendApplicationContribution {
29
48
 
49
+ @inject(ContributionProvider) @named(SaveErrorChecker)
50
+ protected readonly errorCheckers: ContributionProvider<SaveErrorChecker>;
51
+
30
52
  protected saveThrottles = new Map<Widget, AutoSaveThrottle>();
31
53
  protected saveMode: AutoSaveMode = 'off';
32
54
  protected saveDelay = 1000;
55
+ protected saveWhenNoErrors = false;
33
56
  protected shell: ApplicationShell;
34
57
 
35
58
  protected readonly onDidAutoSaveChangeEmitter = new Emitter<AutoSaveMode>();
36
59
  protected readonly onDidAutoSaveDelayChangeEmitter = new Emitter<number>();
60
+ protected readonly onDidAutoSaveConditionsChangeEmitter = new Emitter<void>();
37
61
 
38
62
  get onDidAutoSaveChange(): Event<AutoSaveMode> {
39
63
  return this.onDidAutoSaveChangeEmitter.event;
@@ -43,6 +67,10 @@ export class SaveableService implements FrontendApplicationContribution {
43
67
  return this.onDidAutoSaveDelayChangeEmitter.event;
44
68
  }
45
69
 
70
+ get onDidAutoSaveConditionsChange(): Event<void> {
71
+ return this.onDidAutoSaveConditionsChangeEmitter.event;
72
+ }
73
+
46
74
  get autoSave(): AutoSaveMode {
47
75
  return this.saveMode;
48
76
  }
@@ -59,8 +87,22 @@ export class SaveableService implements FrontendApplicationContribution {
59
87
  this.updateAutoSaveDelay(value);
60
88
  }
61
89
 
90
+ get autoSaveWhenNoErrors(): boolean {
91
+ return this.saveWhenNoErrors;
92
+ }
93
+
94
+ set autoSaveWhenNoErrors(value: boolean) {
95
+ this.saveWhenNoErrors = value;
96
+ }
97
+
62
98
  onDidInitializeLayout(app: FrontendApplication): void {
63
99
  this.shell = app.shell;
100
+ // Listen to error state changes from all registered error checkers
101
+ for (const checker of this.errorCheckers.getContributions()) {
102
+ checker.onDidErrorStateChange(() => {
103
+ this.onDidAutoSaveConditionsChangeEmitter.fire();
104
+ });
105
+ }
64
106
  // Register restored editors first
65
107
  for (const widget of this.shell.widgets) {
66
108
  const saveable = Saveable.get(widget);
@@ -165,9 +207,16 @@ export class SaveableService implements FrontendApplicationContribution {
165
207
  if (uri?.scheme === UNTITLED_SCHEME) {
166
208
  // Never auto-save untitled documents
167
209
  return false;
168
- } else {
169
- return saveable.autosaveable !== false && saveable.dirty;
170
210
  }
211
+ if (saveable.autosaveable === false || !saveable.dirty) {
212
+ return false;
213
+ }
214
+ if (this.saveWhenNoErrors && uri) {
215
+ if (this.errorCheckers.getContributions().some(checker => checker.hasErrors(uri))) {
216
+ return false;
217
+ }
218
+ }
219
+ return true;
171
220
  }
172
221
 
173
222
  protected applySaveableWidget(widget: Widget, saveable: Saveable): void {
@@ -314,6 +363,9 @@ export class AutoSaveThrottle implements Disposable {
314
363
  }),
315
364
  saveService.onDidAutoSaveDelayChange(() => {
316
365
  this.throttledSave(true);
366
+ }),
367
+ saveService.onDidAutoSaveConditionsChange(() => {
368
+ this.throttledSave();
317
369
  })
318
370
  );
319
371
  }
@@ -29,6 +29,7 @@
29
29
  border: 1px solid var(--theia-editorHoverWidget-border);
30
30
  padding: var(--theia-ui-padding);
31
31
  max-width: var(--theia-hover-max-width);
32
+ user-select: text;
32
33
  }
33
34
 
34
35
  /* overwrite potentially different default user agent styles */
@@ -78,13 +78,13 @@
78
78
 
79
79
  html,
80
80
  body {
81
+ overflow: hidden;
81
82
  height: 100vh;
82
83
  }
83
84
 
84
85
  body {
85
86
  margin: 0;
86
87
  padding: 0;
87
- overflow: hidden;
88
88
  font-family: var(--theia-ui-font-family);
89
89
  background: var(--theia-editor-background);
90
90
  color: var(--theia-foreground);
@@ -14,123 +14,5 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import * as fuzzy from 'fuzzy';
18
- import { injectable } from 'inversify';
19
-
20
- @injectable()
21
- export class FuzzySearch {
22
-
23
- private static readonly PRE = '\x01';
24
- private static readonly POST = '\x02';
25
-
26
- /**
27
- * Filters the input and returns with an array that contains all items that match the pattern.
28
- */
29
- async filter<T>(input: FuzzySearch.Input<T>): Promise<FuzzySearch.Match<T>[]> {
30
- return fuzzy.filter(input.pattern, input.items.slice(), {
31
- pre: FuzzySearch.PRE,
32
- post: FuzzySearch.POST,
33
- extract: input.transform
34
- }).sort(this.sortResults.bind(this)).map(this.mapResult.bind(this));
35
- }
36
-
37
- protected sortResults<T>(left: fuzzy.FilterResult<T>, right: fuzzy.FilterResult<T>): number {
38
- return left.index - right.index;
39
- }
40
-
41
- protected mapResult<T>(result: fuzzy.FilterResult<T>): FuzzySearch.Match<T> {
42
- return {
43
- item: result.original,
44
- ranges: this.mapRanges(result.string)
45
- };
46
- }
47
-
48
- protected mapRanges(input: string): ReadonlyArray<FuzzySearch.Range> {
49
- const copy = input.split('').filter(s => s !== '');
50
- const ranges: FuzzySearch.Range[] = [];
51
- const validate = (pre: number, post: number) => {
52
- if (preIndex > postIndex || (preIndex === -1) !== (postIndex === -1)) {
53
- throw new Error(`Error when trying to map ranges. Escaped string was: '${input}. [${[...input].join('|')}]'`);
54
- }
55
- };
56
- let preIndex = copy.indexOf(FuzzySearch.PRE);
57
- let postIndex = copy.indexOf(FuzzySearch.POST);
58
- validate(preIndex, postIndex);
59
- while (preIndex !== -1 && postIndex !== -1) {
60
- ranges.push({
61
- offset: preIndex,
62
- length: postIndex - preIndex - 1
63
- });
64
- copy.splice(postIndex, 1);
65
- copy.splice(preIndex, 1);
66
- preIndex = copy.indexOf(FuzzySearch.PRE);
67
- postIndex = copy.indexOf(FuzzySearch.POST);
68
- }
69
- if (ranges.length === 0) {
70
- throw new Error(`Unexpected zero ranges for match-string: ${input}.`);
71
- }
72
- return ranges;
73
- }
74
-
75
- }
76
-
77
- /**
78
- * Fuzzy searcher.
79
- */
80
- export namespace FuzzySearch {
81
-
82
- /**
83
- * A range representing the match region.
84
- */
85
- export interface Range {
86
-
87
- /**
88
- * The zero based offset of the match region.
89
- */
90
- readonly offset: number;
91
-
92
- /**
93
- * The length of the match region.
94
- */
95
- readonly length: number;
96
- }
97
-
98
- /**
99
- * A fuzzy search match.
100
- */
101
- export interface Match<T> {
102
-
103
- /**
104
- * The original item.
105
- */
106
- readonly item: T;
107
-
108
- /**
109
- * An array of ranges representing the match regions.
110
- */
111
- readonly ranges: ReadonlyArray<Range>;
112
- }
113
-
114
- /**
115
- * The fuzzy search input.
116
- */
117
- export interface Input<T> {
118
-
119
- /**
120
- * The pattern to match.
121
- */
122
- readonly pattern: string;
123
-
124
- /**
125
- * The items to filter based on the `pattern`.
126
- */
127
- readonly items: ReadonlyArray<T>;
128
-
129
- /**
130
- * Function that extracts the string from the inputs which will be used to evaluate the fuzzy matching filter.
131
- */
132
- readonly transform: (item: T) => string;
133
-
134
- }
135
-
136
- }
17
+ // Re-export from common location for backward compatibility.
18
+ export { FuzzySearch } from '../../common/fuzzy-search';
@@ -257,8 +257,7 @@ export class TreeViewWelcomeWidget extends TreeWidget {
257
257
  event.stopPropagation();
258
258
 
259
259
  if (value.startsWith('command:')) {
260
- const command = value.replace('command:', '');
261
- this.commands.executeCommand(command);
260
+ open(this.openerService, new URI(value));
262
261
  } else if (value.startsWith('file:')) {
263
262
  const uri = value.replace('file:', '');
264
263
  open(this.openerService, new URI(CodeUri.file(uri).toString()));
@@ -60,6 +60,7 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
60
60
  protected mountedListeners: Map<string, EventListenerOrEventListenerObject> = new Map();
61
61
  protected optimalWidth = 0;
62
62
  protected optimalHeight = 0;
63
+ protected resizeObserver: ResizeObserver | undefined;
63
64
 
64
65
  constructor(props: SelectComponentProps) {
65
66
  super(props);
@@ -148,14 +149,16 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
148
149
  return optimal + 20; // Just to be safe, add another 20 pixels here
149
150
  }
150
151
 
151
- protected attachListeners(): void {
152
- const hide = (event: MouseEvent) => {
152
+ protected attachListeners(): void {
153
+ const hide = (event: Event) => {
153
154
  if (!this.dropdownRef.current?.contains(event.target as Node)) {
154
155
  this.hide();
155
156
  }
156
157
  };
158
+ const hideOnResize = () => this.hide();
157
159
  this.mountedListeners.set('scroll', hide);
158
160
  this.mountedListeners.set('wheel', hide);
161
+ this.mountedListeners.set('resize', hideOnResize);
159
162
 
160
163
  let parent = this.fieldRef.current?.parentElement;
161
164
  while (parent) {
@@ -170,9 +173,43 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
170
173
  for (const [key, listener] of this.mountedListeners.entries()) {
171
174
  window.addEventListener(key, listener);
172
175
  }
176
+
177
+ // Catch Lumino sash drags globally - observe the closest lm-Widget panel
178
+ const fieldEl = this.fieldRef.current;
179
+ const resizablePanel = fieldEl?.closest('.lm-Widget') ?? fieldEl?.parentElement;
180
+
181
+ if (resizablePanel && typeof ResizeObserver !== 'undefined') {
182
+ let lastWidth = 0;
183
+ let lastHeight = 0;
184
+ let isFirstFire = true;
185
+
186
+ this.resizeObserver = new ResizeObserver(entries => {
187
+ for (const entry of entries) {
188
+ const { width, height } = entry.contentRect;
189
+
190
+ // Ignore the initial automatic fire when the observer attaches
191
+ if (isFirstFire) {
192
+ lastWidth = width;
193
+ lastHeight = height;
194
+ isFirstFire = false;
195
+ continue;
196
+ }
197
+
198
+ // Only hide if the panel dimensions actually changed by more than 2 pixels
199
+ if (this.state.dimensions && (Math.abs(width - lastWidth) > 2 || Math.abs(height - lastHeight) > 2)) {
200
+ this.hide();
201
+ }
202
+
203
+ lastWidth = width;
204
+ lastHeight = height;
205
+ }
206
+ });
207
+ this.resizeObserver.observe(resizablePanel);
208
+ }
173
209
  }
174
210
 
175
211
  override componentWillUnmount(): void {
212
+ this.resizeObserver?.disconnect();
176
213
  if (this.mountedListeners.size > 0) {
177
214
  const eventListener = this.mountedListeners.get('scroll')!;
178
215
  let parent = this.fieldRef.current?.parentElement;
@@ -53,8 +53,13 @@ export class DefaultWindowService implements WindowService, FrontendApplicationC
53
53
  return undefined;
54
54
  }
55
55
 
56
- openNewDefaultWindow(): void {
56
+ async openNewDefaultWindow(): Promise<number> {
57
57
  this.openNewWindow(`#${DEFAULT_WINDOW_HASH}`);
58
+ return -1;
59
+ }
60
+
61
+ closeWindow(windowId: number): void {
62
+ // No-op in browser-only mode
58
63
  }
59
64
 
60
65
  focus(): void {
@@ -20,7 +20,8 @@ import { WindowService } from '../window-service';
20
20
  @injectable()
21
21
  export class MockWindowService implements WindowService {
22
22
  openNewWindow(): undefined { return undefined; }
23
- openNewDefaultWindow(): void { }
23
+ async openNewDefaultWindow(): Promise<number> { return -1; }
24
+ closeWindow(): void { }
24
25
  focus(): void { }
25
26
  reload(): void { }
26
27
  isSafeToShutDown(): Promise<boolean> { return Promise.resolve(true); }