@theia/core 1.73.0-next.9 → 1.74.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/README.md +26 -16
  2. package/i18n/nls.cs.json +268 -36
  3. package/i18n/nls.de.json +268 -36
  4. package/i18n/nls.es.json +268 -36
  5. package/i18n/nls.fr.json +268 -36
  6. package/i18n/nls.hu.json +268 -36
  7. package/i18n/nls.it.json +268 -36
  8. package/i18n/nls.ja.json +268 -36
  9. package/i18n/nls.json +268 -36
  10. package/i18n/nls.ko.json +268 -36
  11. package/i18n/nls.pl.json +268 -36
  12. package/i18n/nls.pt-br.json +268 -36
  13. package/i18n/nls.ru.json +268 -36
  14. package/i18n/nls.tr.json +268 -36
  15. package/i18n/nls.zh-cn.json +268 -36
  16. package/i18n/nls.zh-tw.json +268 -36
  17. package/lib/browser/authentication-service.d.ts +2 -0
  18. package/lib/browser/authentication-service.d.ts.map +1 -1
  19. package/lib/browser/authentication-service.js +8 -2
  20. package/lib/browser/authentication-service.js.map +1 -1
  21. package/lib/browser/catalog.json +269 -72
  22. package/lib/browser/frontend-application.d.ts +2 -0
  23. package/lib/browser/frontend-application.d.ts.map +1 -1
  24. package/lib/browser/frontend-application.js +15 -9
  25. package/lib/browser/frontend-application.js.map +1 -1
  26. package/lib/browser/json-schema-store.d.ts +2 -0
  27. package/lib/browser/json-schema-store.d.ts.map +1 -1
  28. package/lib/browser/json-schema-store.js +8 -2
  29. package/lib/browser/json-schema-store.js.map +1 -1
  30. package/lib/browser/keybinding.js +2 -1
  31. package/lib/browser/keybinding.js.map +1 -1
  32. package/lib/browser/markdown-rendering/markdown.d.ts +1 -1
  33. package/lib/browser/markdown-rendering/markdown.d.ts.map +1 -1
  34. package/lib/browser/markdown-rendering/markdown.js +1 -1
  35. package/lib/browser/markdown-rendering/markdown.js.map +1 -1
  36. package/lib/browser/menu/composite-menu-node.js +4 -4
  37. package/lib/browser/menu/composite-menu-node.js.map +1 -1
  38. package/lib/browser/messaging/ws-connection-source.d.ts.map +1 -1
  39. package/lib/browser/messaging/ws-connection-source.js +1 -6
  40. package/lib/browser/messaging/ws-connection-source.js.map +1 -1
  41. package/lib/browser/preferences/frontend-config-preference-contributions.d.ts +2 -0
  42. package/lib/browser/preferences/frontend-config-preference-contributions.d.ts.map +1 -1
  43. package/lib/browser/preferences/frontend-config-preference-contributions.js +7 -1
  44. package/lib/browser/preferences/frontend-config-preference-contributions.js.map +1 -1
  45. package/lib/browser/preferences/preference-proxy.spec.js +7 -0
  46. package/lib/browser/preferences/preference-proxy.spec.js.map +1 -1
  47. package/lib/browser/preferences/preference-schema-provider.spec.js +3 -0
  48. package/lib/browser/preferences/preference-schema-provider.spec.js.map +1 -1
  49. package/lib/browser/preferences/preference-validation-service.d.ts +2 -0
  50. package/lib/browser/preferences/preference-validation-service.d.ts.map +1 -1
  51. package/lib/browser/preferences/preference-validation-service.js +11 -5
  52. package/lib/browser/preferences/preference-validation-service.js.map +1 -1
  53. package/lib/browser/preferences/preference-validation-service.spec.js +2 -0
  54. package/lib/browser/preferences/preference-validation-service.spec.js.map +1 -1
  55. package/lib/browser/secondary-window-handler.d.ts +2 -0
  56. package/lib/browser/secondary-window-handler.d.ts.map +1 -1
  57. package/lib/browser/secondary-window-handler.js +12 -6
  58. package/lib/browser/secondary-window-handler.js.map +1 -1
  59. package/lib/browser/shell/application-shell.d.ts +2 -0
  60. package/lib/browser/shell/application-shell.d.ts.map +1 -1
  61. package/lib/browser/shell/application-shell.js +9 -3
  62. package/lib/browser/shell/application-shell.js.map +1 -1
  63. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js +6 -6
  64. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js.map +1 -1
  65. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +2 -2
  66. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
  67. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js +131 -0
  68. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js.map +1 -1
  69. package/lib/browser/status-bar/status-bar.d.ts +2 -2
  70. package/lib/browser/status-bar/status-bar.d.ts.map +1 -1
  71. package/lib/browser/status-bar/status-bar.js.map +1 -1
  72. package/lib/browser/test/jsdom.d.ts +4 -0
  73. package/lib/browser/test/jsdom.d.ts.map +1 -1
  74. package/lib/browser/test/jsdom.js +18 -0
  75. package/lib/browser/test/jsdom.js.map +1 -1
  76. package/lib/browser/tree/tree-container.spec.js +4 -0
  77. package/lib/browser/tree/tree-container.spec.js.map +1 -1
  78. package/lib/browser/tree/tree-model.d.ts +1 -0
  79. package/lib/browser/tree/tree-model.d.ts.map +1 -1
  80. package/lib/browser/tree/tree-model.js +3 -0
  81. package/lib/browser/tree/tree-model.js.map +1 -1
  82. package/lib/browser/tree/tree-view-welcome-widget.d.ts +1 -0
  83. package/lib/browser/tree/tree-view-welcome-widget.d.ts.map +1 -1
  84. package/lib/browser/tree/tree-view-welcome-widget.js +8 -6
  85. package/lib/browser/tree/tree-view-welcome-widget.js.map +1 -1
  86. package/lib/browser/tree/tree-view-welcome-widget.spec.d.ts +2 -0
  87. package/lib/browser/tree/tree-view-welcome-widget.spec.d.ts.map +1 -0
  88. package/lib/browser/tree/tree-view-welcome-widget.spec.js +68 -0
  89. package/lib/browser/tree/tree-view-welcome-widget.spec.js.map +1 -0
  90. package/lib/browser/tree/tree-widget.js +2 -2
  91. package/lib/browser/tree/tree-widget.js.map +1 -1
  92. package/lib/browser/tree/tree.d.ts +24 -2
  93. package/lib/browser/tree/tree.d.ts.map +1 -1
  94. package/lib/browser/tree/tree.js +20 -2
  95. package/lib/browser/tree/tree.js.map +1 -1
  96. package/lib/browser/tree/tree.spec.js +27 -0
  97. package/lib/browser/tree/tree.spec.js.map +1 -1
  98. package/lib/browser/window/default-window-service.d.ts +2 -1
  99. package/lib/browser/window/default-window-service.d.ts.map +1 -1
  100. package/lib/browser/window/default-window-service.js +10 -5
  101. package/lib/browser/window/default-window-service.js.map +1 -1
  102. package/lib/browser/window/default-window-service.spec.js +2 -0
  103. package/lib/browser/window/default-window-service.spec.js.map +1 -1
  104. package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts +2 -0
  105. package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts.map +1 -1
  106. package/lib/browser-only/messaging/frontend-only-service-connection-provider.js +8 -2
  107. package/lib/browser-only/messaging/frontend-only-service-connection-provider.js.map +1 -1
  108. package/lib/common/command.d.ts +2 -0
  109. package/lib/common/command.d.ts.map +1 -1
  110. package/lib/common/command.js +10 -4
  111. package/lib/common/command.js.map +1 -1
  112. package/lib/common/preferences/injectable-preference-proxy.d.ts +3 -3
  113. package/lib/common/preferences/injectable-preference-proxy.d.ts.map +1 -1
  114. package/lib/common/preferences/injectable-preference-proxy.js +6 -1
  115. package/lib/common/preferences/injectable-preference-proxy.js.map +1 -1
  116. package/lib/common/preferences/preference-proxy.d.ts.map +1 -1
  117. package/lib/common/preferences/preference-proxy.js +6 -1
  118. package/lib/common/preferences/preference-proxy.js.map +1 -1
  119. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  120. package/lib/electron-main/electron-main-application.js +4 -2
  121. package/lib/electron-main/electron-main-application.js.map +1 -1
  122. package/lib/electron-node/hosting/electron-ws-origin-validator.d.ts.map +1 -1
  123. package/lib/electron-node/hosting/electron-ws-origin-validator.js +5 -2
  124. package/lib/electron-node/hosting/electron-ws-origin-validator.js.map +1 -1
  125. package/lib/electron-node/hosting/electron-ws-origin-validator.spec.d.ts +2 -0
  126. package/lib/electron-node/hosting/electron-ws-origin-validator.spec.d.ts.map +1 -0
  127. package/lib/electron-node/hosting/electron-ws-origin-validator.spec.js +59 -0
  128. package/lib/electron-node/hosting/electron-ws-origin-validator.spec.js.map +1 -0
  129. package/lib/electron-node/request/electron-backend-request-service.d.ts +2 -0
  130. package/lib/electron-node/request/electron-backend-request-service.d.ts.map +1 -1
  131. package/lib/electron-node/request/electron-backend-request-service.js +7 -1
  132. package/lib/electron-node/request/electron-backend-request-service.js.map +1 -1
  133. package/lib/electron-node/token/electron-token-backend-contribution.d.ts +2 -0
  134. package/lib/electron-node/token/electron-token-backend-contribution.d.ts.map +1 -1
  135. package/lib/electron-node/token/electron-token-backend-contribution.js +7 -1
  136. package/lib/electron-node/token/electron-token-backend-contribution.js.map +1 -1
  137. package/lib/electron-node/token/electron-token-validator.d.ts +2 -0
  138. package/lib/electron-node/token/electron-token-validator.d.ts.map +1 -1
  139. package/lib/electron-node/token/electron-token-validator.js +7 -1
  140. package/lib/electron-node/token/electron-token-validator.js.map +1 -1
  141. package/lib/node/backend-application-module.d.ts.map +1 -1
  142. package/lib/node/backend-application-module.js +2 -0
  143. package/lib/node/backend-application-module.js.map +1 -1
  144. package/lib/node/backend-application.d.ts +11 -0
  145. package/lib/node/backend-application.d.ts.map +1 -1
  146. package/lib/node/backend-application.js +40 -12
  147. package/lib/node/backend-application.js.map +1 -1
  148. package/lib/node/backend-application.spec.js +4 -2
  149. package/lib/node/backend-application.spec.js.map +1 -1
  150. package/lib/node/hosting/backend-hosting-module.d.ts.map +1 -1
  151. package/lib/node/hosting/backend-hosting-module.js +7 -0
  152. package/lib/node/hosting/backend-hosting-module.js.map +1 -1
  153. package/lib/node/hosting/browser-connection-token.d.ts +46 -0
  154. package/lib/node/hosting/browser-connection-token.d.ts.map +1 -0
  155. package/lib/node/hosting/browser-connection-token.js +120 -0
  156. package/lib/node/hosting/browser-connection-token.js.map +1 -0
  157. package/lib/node/hosting/browser-connection-token.spec.d.ts +2 -0
  158. package/lib/node/hosting/browser-connection-token.spec.d.ts.map +1 -0
  159. package/lib/node/hosting/browser-connection-token.spec.js +128 -0
  160. package/lib/node/hosting/browser-connection-token.spec.js.map +1 -0
  161. package/lib/node/hosting/ws-origin-validator.d.ts.map +1 -1
  162. package/lib/node/hosting/ws-origin-validator.js +21 -4
  163. package/lib/node/hosting/ws-origin-validator.js.map +1 -1
  164. package/lib/node/hosting/ws-origin-validator.spec.d.ts +2 -0
  165. package/lib/node/hosting/ws-origin-validator.spec.d.ts.map +1 -0
  166. package/lib/node/hosting/ws-origin-validator.spec.js +131 -0
  167. package/lib/node/hosting/ws-origin-validator.spec.js.map +1 -0
  168. package/lib/node/index.d.ts +1 -0
  169. package/lib/node/index.d.ts.map +1 -1
  170. package/lib/node/index.js +3 -1
  171. package/lib/node/index.js.map +1 -1
  172. package/lib/node/logger-cli-contribution.d.ts +6 -0
  173. package/lib/node/logger-cli-contribution.d.ts.map +1 -1
  174. package/lib/node/logger-cli-contribution.js +28 -5
  175. package/lib/node/logger-cli-contribution.js.map +1 -1
  176. package/lib/node/logger-cli-contribution.spec.js +54 -0
  177. package/lib/node/logger-cli-contribution.spec.js.map +1 -1
  178. package/lib/node/messaging/default-messaging-service.d.ts +2 -0
  179. package/lib/node/messaging/default-messaging-service.d.ts.map +1 -1
  180. package/lib/node/messaging/default-messaging-service.js +8 -2
  181. package/lib/node/messaging/default-messaging-service.js.map +1 -1
  182. package/lib/node/messaging/test/default-messaging-service.spec.js +2 -0
  183. package/lib/node/messaging/test/default-messaging-service.spec.js.map +1 -1
  184. package/lib/node/messaging/websocket-endpoint.d.ts +7 -0
  185. package/lib/node/messaging/websocket-endpoint.d.ts.map +1 -1
  186. package/lib/node/messaging/websocket-endpoint.js +21 -6
  187. package/lib/node/messaging/websocket-endpoint.js.map +1 -1
  188. package/lib/node/process-utils.d.ts +2 -0
  189. package/lib/node/process-utils.d.ts.map +1 -1
  190. package/lib/node/process-utils.js +8 -2
  191. package/lib/node/process-utils.js.map +1 -1
  192. package/lib/node/process-utils.spec.js +11 -9
  193. package/lib/node/process-utils.spec.js.map +1 -1
  194. package/lib/node/request/backend-request-facade.d.ts +19 -0
  195. package/lib/node/request/backend-request-facade.d.ts.map +1 -1
  196. package/lib/node/request/backend-request-facade.js +93 -3
  197. package/lib/node/request/backend-request-facade.js.map +1 -1
  198. package/lib/node/request/backend-request-facade.spec.d.ts +2 -0
  199. package/lib/node/request/backend-request-facade.spec.d.ts.map +1 -0
  200. package/lib/node/request/backend-request-facade.spec.js +171 -0
  201. package/lib/node/request/backend-request-facade.spec.js.map +1 -0
  202. package/package.json +29 -20
  203. package/src/browser/authentication-service.ts +6 -3
  204. package/src/browser/frontend-application.ts +13 -9
  205. package/src/browser/json-schema-store.ts +6 -2
  206. package/src/browser/keybinding.ts +2 -2
  207. package/src/browser/markdown-rendering/markdown.tsx +2 -2
  208. package/src/browser/menu/composite-menu-node.ts +4 -4
  209. package/src/browser/messaging/ws-connection-source.ts +1 -6
  210. package/src/browser/preferences/frontend-config-preference-contributions.ts +7 -2
  211. package/src/browser/preferences/preference-proxy.spec.ts +8 -0
  212. package/src/browser/preferences/preference-schema-provider.spec.ts +5 -0
  213. package/src/browser/preferences/preference-validation-service.spec.ts +3 -1
  214. package/src/browser/preferences/preference-validation-service.ts +9 -6
  215. package/src/browser/secondary-window-handler.ts +11 -7
  216. package/src/browser/shell/application-shell.ts +8 -4
  217. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx +6 -6
  218. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +2 -2
  219. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.ts +159 -1
  220. package/src/browser/status-bar/status-bar.tsx +3 -3
  221. package/src/browser/test/jsdom.ts +18 -0
  222. package/src/browser/tree/tree-container.spec.ts +6 -0
  223. package/src/browser/tree/tree-model.ts +4 -0
  224. package/src/browser/tree/tree-view-welcome-widget.spec.ts +82 -0
  225. package/src/browser/tree/tree-view-welcome-widget.tsx +10 -11
  226. package/src/browser/tree/tree-widget.tsx +2 -2
  227. package/src/browser/tree/tree.spec.ts +30 -0
  228. package/src/browser/tree/tree.ts +31 -4
  229. package/src/browser/window/default-window-service.spec.ts +3 -1
  230. package/src/browser/window/default-window-service.ts +9 -6
  231. package/src/browser-only/messaging/frontend-only-service-connection-provider.ts +8 -3
  232. package/src/common/command.ts +8 -4
  233. package/src/common/i18n/nls.metadata.json +2659 -374
  234. package/src/common/preferences/injectable-preference-proxy.ts +10 -4
  235. package/src/common/preferences/preference-proxy.ts +10 -4
  236. package/src/electron-main/electron-main-application.ts +4 -2
  237. package/src/electron-node/hosting/electron-ws-origin-validator.spec.ts +69 -0
  238. package/src/electron-node/hosting/electron-ws-origin-validator.ts +5 -2
  239. package/src/electron-node/request/electron-backend-request-service.ts +6 -2
  240. package/src/electron-node/token/electron-token-backend-contribution.ts +6 -2
  241. package/src/electron-node/token/electron-token-validator.ts +6 -2
  242. package/src/node/backend-application-module.ts +4 -2
  243. package/src/node/backend-application.spec.ts +5 -3
  244. package/src/node/backend-application.ts +33 -11
  245. package/src/node/hosting/backend-hosting-module.ts +10 -0
  246. package/src/node/hosting/browser-connection-token.spec.ts +167 -0
  247. package/src/node/hosting/browser-connection-token.ts +126 -0
  248. package/src/node/hosting/ws-origin-validator.spec.ts +193 -0
  249. package/src/node/hosting/ws-origin-validator.ts +23 -4
  250. package/src/node/index.ts +1 -0
  251. package/src/node/logger-cli-contribution.spec.ts +65 -0
  252. package/src/node/logger-cli-contribution.ts +32 -5
  253. package/src/node/messaging/default-messaging-service.ts +6 -2
  254. package/src/node/messaging/test/default-messaging-service.spec.ts +3 -1
  255. package/src/node/messaging/websocket-endpoint.ts +23 -7
  256. package/src/node/process-utils.spec.ts +12 -9
  257. package/src/node/process-utils.ts +7 -3
  258. package/src/node/request/backend-request-facade.spec.ts +203 -0
  259. package/src/node/request/backend-request-facade.ts +108 -3
@@ -15,11 +15,12 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { JSONObject, JSONValue } from '../../../shared/@lumino/coreutils';
18
- import { inject, injectable } from '../../../shared/inversify';
18
+ import { inject, injectable, named } from '../../../shared/inversify';
19
19
  import { IJSONSchema, JsonType } from '../../common/json-schema';
20
20
  import { deepClone, unreachable } from '../../common';
21
21
  import { PreferenceLanguageOverrideService } from '../../common/preferences/preference-language-override-service';
22
22
  import { PreferenceSchemaService, PreferenceScope, PreferenceUtils, PreferenceDataProperty } from '../../common/preferences';
23
+ import { ILogger } from '../../common/logger';
23
24
 
24
25
  export interface PreferenceValidator<T> {
25
26
  name: string;
@@ -40,6 +41,8 @@ type ValidatablePreferenceTuple = IJSONSchema & ({ items: IJSONSchema[] } | { pr
40
41
  export class PreferenceValidationService {
41
42
  @inject(PreferenceSchemaService) protected readonly schemaService: PreferenceSchemaService;
42
43
  @inject(PreferenceLanguageOverrideService) protected readonly languageOverrideService: PreferenceLanguageOverrideService;
44
+ @inject(ILogger) @named('core:PreferenceValidationService')
45
+ protected readonly logger: ILogger;
43
46
 
44
47
  validateOptions(options: Record<string, JSONValue>): Record<string, JSONValue> {
45
48
  const valid: Record<string, JSONValue> = {};
@@ -58,7 +61,7 @@ export class PreferenceValidationService {
58
61
  const validValue = this.doValidateByName(preferenceName, value);
59
62
  // If value is undefined, it means the preference wasn't set, not that a bad value was set.
60
63
  if (validValue !== value && value !== undefined) {
61
- console.warn(`While validating options, found impermissible value for ${preferenceName}. Using valid value`, validValue, 'instead of configured value', value);
64
+ this.logger.warn(`While validating options, found impermissible value for ${preferenceName}. Using valid value`, validValue, 'instead of configured value', value);
62
65
  }
63
66
  return validValue;
64
67
  }
@@ -71,7 +74,7 @@ export class PreferenceValidationService {
71
74
  validateBySchema(key: string, value: JSONValue, schema: IJSONSchema | undefined): JSONValue {
72
75
  try {
73
76
  if (!schema) {
74
- console.warn('Request to validate preference with no schema registered:', key);
77
+ this.logger.warn('Request to validate preference with no schema registered:', key);
75
78
  return value;
76
79
  }
77
80
  if (schema.const !== undefined) {
@@ -87,7 +90,7 @@ export class PreferenceValidationService {
87
90
  return this.validateOneOf(key, value, schema as IJSONSchema & { oneOf: IJSONSchema[] });
88
91
  }
89
92
  if (schema.type === undefined) {
90
- console.warn('Request to validate preference with no type information:', key);
93
+ this.logger.warn('Request to validate preference with no type information:', key);
91
94
  return value;
92
95
  }
93
96
  if (Array.isArray(schema.type)) {
@@ -112,7 +115,7 @@ export class PreferenceValidationService {
112
115
  unreachable(schema.type, `Request to validate preference with unknown type in schema: ${key}`);
113
116
  }
114
117
  } catch (e) {
115
- console.error('Encountered an error while validating', key, 'with value', value, 'against schema', schema, e);
118
+ this.logger.error('Encountered an error while validating', key, 'with value', value, 'against schema', schema, e);
116
119
  return value;
117
120
  }
118
121
  }
@@ -193,7 +196,7 @@ export class PreferenceValidationService {
193
196
  return [];
194
197
  }
195
198
  if (!schema.items && !schema.prefixItems) {
196
- console.warn('Requested validation of array without item specification:', key);
199
+ this.logger.warn('Requested validation of array without item specification:', key);
197
200
  return candidate;
198
201
  }
199
202
  if (Array.isArray(schema.items) || Array.isArray(schema.prefixItems)) {
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import debounce = require('lodash.debounce');
18
- import { inject, injectable } from 'inversify';
18
+ import { inject, injectable, named } from 'inversify';
19
19
  import { BoxLayout, ExtractableWidget, TabBar, Widget } from './widgets';
20
20
  import { MessageService } from '../common/message-service';
21
21
  import { ApplicationShell, DockPanelRenderer, MAIN_AREA_CLASS, MAIN_BOTTOM_AREA_CLASS } from './shell/application-shell';
@@ -23,6 +23,7 @@ import { Emitter } from '../common/event';
23
23
  import { isSecondaryWindow, SecondaryWindowRootWidget, SecondaryWindowService } from './window/secondary-window-service';
24
24
  import { KeybindingRegistry } from './keybinding';
25
25
  import { MAIN_AREA_ID, TheiaDockPanel } from './shell/theia-dock-panel';
26
+ import { ILogger } from '../common/logger';
26
27
  import { nls } from '../common/nls';
27
28
 
28
29
  /** Widgets to be contained inside a DockPanel in the secondary window. */
@@ -104,6 +105,9 @@ export class SecondaryWindowHandler {
104
105
  @inject(TheiaDockPanel.Factory)
105
106
  protected dockPanelFactory: TheiaDockPanel.Factory;
106
107
 
108
+ @inject(ILogger) @named('core:SecondaryWindowHandler')
109
+ protected readonly logger: ILogger;
110
+
107
111
  protected readonly onWillAddWidgetEmitter = new Emitter<[Widget, Window]>();
108
112
  /** Subscribe to get notified when a widget is added to this handler, i.e. the widget was moved to an secondary window . */
109
113
  readonly onWillAddWidget = this.onWillAddWidgetEmitter.event;
@@ -157,11 +161,11 @@ export class SecondaryWindowHandler {
157
161
  */
158
162
  moveWidgetToSecondaryWindow(widget: ExtractableWidget): void {
159
163
  if (!this.applicationShell) {
160
- console.error('Widget cannot be extracted because the WidgetExtractionHandler has not been initialized.');
164
+ this.logger.error('Widget cannot be extracted because the WidgetExtractionHandler has not been initialized.');
161
165
  return;
162
166
  }
163
167
  if (!widget.isExtractable) {
164
- console.error('Widget is not extractable.', widget.id);
168
+ this.logger.error('Widget is not extractable.', widget.id);
165
169
  return;
166
170
  }
167
171
 
@@ -185,7 +189,7 @@ export class SecondaryWindowHandler {
185
189
 
186
190
  const element = newWindow.document.getElementById('widget-host');
187
191
  if (!element) {
188
- console.error('Could not find dom element to attach to in secondary window');
192
+ this.logger.error('Could not find dom element to attach to in secondary window');
189
193
  return;
190
194
  }
191
195
 
@@ -240,18 +244,18 @@ export class SecondaryWindowHandler {
240
244
  addWidgetToSecondaryWindow(widget: Widget, secondaryWindow: Window, options?: TheiaDockPanel.AddOptions): void {
241
245
  const rootWidget = isSecondaryWindow(secondaryWindow) ? secondaryWindow.rootWidget : undefined;
242
246
  if (!rootWidget) {
243
- console.error('Given secondary window no known root.');
247
+ this.logger.error('Given secondary window no known root.');
244
248
  return;
245
249
  }
246
250
 
247
251
  // we allow to add any widget to an existing secondary window unless it is marked as not extractable or is already extracted
248
252
  if (ExtractableWidget.is(widget)) {
249
253
  if (!widget.isExtractable) {
250
- console.error('Widget is not extractable.', widget.id);
254
+ this.logger.error('Widget is not extractable.', widget.id);
251
255
  return;
252
256
  }
253
257
  if (widget.secondaryWindow !== undefined) {
254
- console.error('Widget is extracted already.', widget.id);
258
+ this.logger.error('Widget is extracted already.', widget.id);
255
259
  return;
256
260
  }
257
261
  widget.secondaryWindow = secondaryWindow;
@@ -14,7 +14,7 @@
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, inject, optional, postConstruct } from 'inversify';
17
+ import { injectable, inject, optional, postConstruct, named } from 'inversify';
18
18
  import { ArrayExt, find, toArray, each } from '@lumino/algorithm';
19
19
  import {
20
20
  BoxLayout, BoxPanel, DockLayout, DockPanel, FocusTracker, Layout, Panel, SplitLayout,
@@ -46,6 +46,7 @@ import { OpenerService } from '../opener-service';
46
46
  import { PreviewableWidget } from '../widgets/previewable-widget';
47
47
  import { WindowService } from '../window/window-service';
48
48
  import { TheiaSplitPanel } from './theia-split-panel';
49
+ import { ILogger } from '../../common/logger';
49
50
 
50
51
  /** The class name added to ApplicationShell instances. */
51
52
  export const APPLICATION_SHELL_CLASS = 'theia-ApplicationShell';
@@ -242,6 +243,9 @@ export class ApplicationShell extends Widget {
242
243
  @inject(UntitledResourceResolver)
243
244
  protected readonly untitledResourceResolver: UntitledResourceResolver;
244
245
 
246
+ @inject(ILogger) @named('core:ApplicationShell')
247
+ protected readonly logger: ILogger;
248
+
245
249
  protected readonly onDidAddWidgetEmitter = new Emitter<Widget>();
246
250
  readonly onDidAddWidget = this.onDidAddWidgetEmitter.event;
247
251
  protected fireDidAddWidget(widget: Widget): void {
@@ -595,7 +599,7 @@ export class ApplicationShell extends Widget {
595
599
  const opener = await this.openerService.getOpener(fileUri);
596
600
  opener.open(fileUri);
597
601
  } catch (e) {
598
- console.info(`no opener found for '${fileUri}'`);
602
+ this.logger.info(`no opener found for '${fileUri}'`);
599
603
  }
600
604
  };
601
605
 
@@ -975,7 +979,7 @@ export class ApplicationShell extends Widget {
975
979
  */
976
980
  async addWidget(widget: Widget, options?: Readonly<ApplicationShell.WidgetOptions>): Promise<void> {
977
981
  if (!widget.id) {
978
- console.error('Widgets added to the application shell must have a unique id property.');
982
+ this.logger.error('Widgets added to the application shell must have a unique id property.');
979
983
  return;
980
984
  }
981
985
  const { area, addOptions } = this.getInsertionOptions(options);
@@ -1430,7 +1434,7 @@ export class ApplicationShell extends Widget {
1430
1434
  if (delta < this.activationTimeout) {
1431
1435
  request = setTimeout(step, 0);
1432
1436
  } else {
1433
- console.warn(`Widget was activated, but did not accept focus after ${this.activationTimeout}ms: ${widget.id}`);
1437
+ this.logger.warn(`Widget was activated, but did not accept focus after ${this.activationTimeout}ms: ${widget.id}`);
1434
1438
  }
1435
1439
  };
1436
1440
  let request = setTimeout(step, 0);
@@ -96,9 +96,9 @@ abstract class AbstractToolbarMenuWrapper {
96
96
  const icon = this.icon || 'ellipsis';
97
97
  const contextMatcher: ContextMatcher = this.contextKeyService;
98
98
  const className = `${icon} ${ACTION_ITEM}`;
99
- if (CompoundMenuNode.is(this.menuNode) && !this.menuNode.isEmpty(this.effectiveMenuPath, this.contextKeyService, widget.node)) {
99
+ if (CompoundMenuNode.is(this.menuNode) && !this.menuNode.isEmpty(this.effectiveMenuPath, this.contextKeyService, widget.node, widget)) {
100
100
  return <div key={this.id} className={TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM + ' enabled menu'}>
101
- <div className={className}
101
+ <div id={this.id} className={className}
102
102
  title={this.tooltip || this.text}
103
103
  onClick={e => this.executeCommand(widget, e)}
104
104
  />
@@ -108,7 +108,7 @@ abstract class AbstractToolbarMenuWrapper {
108
108
  </div>;
109
109
  } else {
110
110
  return <div key={this.id} className={TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM + ' enabled menu'}>
111
- <div className={className}
111
+ <div id={this.id} className={className}
112
112
  title={this.tooltip || this.text}
113
113
  onClick={e => this.executeCommand(widget, e)}
114
114
  />
@@ -308,7 +308,7 @@ abstract class AbstractMenuNodeAsToolbarItemWrapper<T extends MenuNode> {
308
308
  }
309
309
 
310
310
  isVisible<K>(effectiveMenuPath: MenuPath, contextMatcher: ContextExpressionMatcher<K>, context: K | undefined, ...args: unknown[]): boolean {
311
- return this.menuNode!.isVisible(this.effectiveMenuPath, contextMatcher, context, args);
311
+ return this.menuNode!.isVisible(this.effectiveMenuPath, contextMatcher, context, ...args);
312
312
  }
313
313
  }
314
314
 
@@ -321,7 +321,7 @@ class ToolbarItemAsSubmenuWrapper extends AbstractMenuNodeAsToolbarItemWrapper<C
321
321
  return this.menuNode.contextKeyOverlays;
322
322
  }
323
323
  isEmpty<T>(effectiveMenuPath: MenuPath, contextMatcher: ContextExpressionMatcher<T>, context: T | undefined, ...args: unknown[]): boolean {
324
- return this.menuNode.isEmpty(this.effectiveMenuPath, contextMatcher, context, args);
324
+ return this.menuNode.isEmpty(this.effectiveMenuPath, contextMatcher, context, ...args);
325
325
  }
326
326
  get children(): MenuNode[] {
327
327
  return this.menuNode.children;
@@ -340,7 +340,7 @@ class ToolbarItemAsCommandMenuWrapper extends AbstractMenuNodeAsToolbarItemWrapp
340
340
  return this.menuNode.isToggled(this.effectiveMenuPath, ...args);
341
341
  }
342
342
  run(effectiveMenuPath: MenuPath, ...args: unknown[]): Promise<void> {
343
- return this.menuNode.run(this.effectiveMenuPath, args);
343
+ return this.menuNode.run(this.effectiveMenuPath, ...args);
344
344
  }
345
345
 
346
346
  override get label(): string {
@@ -141,11 +141,11 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
141
141
  const menu = this.menuRegistry.getMenu(delegate.menuPath);
142
142
  if (menu) {
143
143
  for (const child of menu.children) {
144
- if (child.isVisible([...delegate.menuPath, child.id], this.contextKeyService, widget.node)) {
144
+ if (child.isVisible([...delegate.menuPath, child.id], this.contextKeyService, widget.node, widget)) {
145
145
  if (CompoundMenuNode.is(child)) {
146
146
  for (const grandchild of child.children) {
147
147
  if (grandchild.isVisible([...delegate.menuPath, child.id, grandchild.id],
148
- this.contextKeyService, widget.node) && RenderedMenuNode.is(grandchild)) {
148
+ this.contextKeyService, widget.node, widget) && RenderedMenuNode.is(grandchild)) {
149
149
  if (CommandMenu.is(grandchild)) {
150
150
  result.push(new CommandMenuAsToolbarItemWrapper([...delegate.menuPath, child.id, grandchild.id], this.commandRegistry,
151
151
  this.menuRegistry, this.contextKeyService, this.contextMenuRenderer, grandchild, child.id));
@@ -18,7 +18,16 @@ import { enableJSDOM } from '../../test/jsdom';
18
18
 
19
19
  let disableJSDOM = enableJSDOM();
20
20
  import { expect } from 'chai';
21
- import { TabBarToolbarAction } from './tab-bar-toolbar-types';
21
+ import {
22
+ CommandMenu, CommandRegistry, CompoundMenuNode, Group, GroupImpl, MenuAction, MenuModelRegistry, MenuNode, MenuNodeFactory, MutableCompoundMenuNode,
23
+ Submenu, SubmenuImpl, SubMenuLink
24
+ } from '../../../common';
25
+ import { ContextKeyServiceDummyImpl } from '../../context-key-service';
26
+ import { ContextMenuRenderer } from '../../context-menu-renderer';
27
+ import { Widget } from '../../widgets';
28
+ import { TOOLBAR_WRAPPER_ID_SUFFIX } from './tab-bar-toolbar-menu-adapters';
29
+ import { TabBarToolbarRegistry } from './tab-bar-toolbar-registry';
30
+ import { TAB_BAR_TOOLBAR_CONTEXT_MENU, TabBarToolbarAction } from './tab-bar-toolbar-types';
22
31
 
23
32
  disableJSDOM();
24
33
 
@@ -59,4 +68,153 @@ describe('tab-bar-toolbar', () => {
59
68
 
60
69
  });
61
70
 
71
+ describe('menu delegates', () => {
72
+
73
+ const TEST_MENU_PATH = ['test-toolbar-delegate'];
74
+ const TEST_COMMAND = 'test.toolbar.command';
75
+ const TEST_SUBMENU_COMMAND = 'test.toolbar.submenu.command';
76
+
77
+ let contextKeyService: ContextKeyServiceDummyImpl;
78
+
79
+ before(() => {
80
+ disableJSDOM = enableJSDOM();
81
+ });
82
+
83
+ beforeEach(() => {
84
+ contextKeyService = new ContextKeyServiceDummyImpl();
85
+ });
86
+
87
+ after(() => {
88
+ disableJSDOM();
89
+ });
90
+
91
+ it('passes the delegated widget to command visibility', () => {
92
+ const testWidget = new TestToolbarWidget();
93
+ const commands = createCommandRegistry();
94
+ commands.registerCommand({ id: TEST_COMMAND, label: 'Test Command' }, {
95
+ execute: () => { },
96
+ isVisible: widget => TestToolbarWidget.is(widget) && widget === testWidget
97
+ });
98
+ const menuRegistry = createMenuRegistry(commands);
99
+ menuRegistry.registerMenuAction([...TEST_MENU_PATH, 'other'], { commandId: TEST_COMMAND });
100
+ const registry = createToolbarRegistry(commands, menuRegistry, contextKeyService);
101
+ registry.registerMenuDelegate(TEST_MENU_PATH, TestToolbarWidget.is);
102
+
103
+ const commandItem = registry.visibleItems(testWidget).find(item => item.id === `${TEST_COMMAND}${TOOLBAR_WRAPPER_ID_SUFFIX}`);
104
+
105
+ expect(commandItem).to.exist;
106
+ testWidget.dispose();
107
+ });
108
+
109
+ it('preserves the widget for wrapped command menu visibility, enablement, toggled state, and execution', async () => {
110
+ const testWidget = new TestToolbarWidget();
111
+ let executedWith: unknown;
112
+ const commands = createCommandRegistry();
113
+ commands.registerCommand({ id: TEST_COMMAND, label: 'Test Command' }, {
114
+ execute: widget => executedWith = widget,
115
+ isVisible: widget => TestToolbarWidget.is(widget) && widget === testWidget,
116
+ isEnabled: widget => TestToolbarWidget.is(widget) && widget === testWidget,
117
+ isToggled: widget => TestToolbarWidget.is(widget) && widget === testWidget
118
+ });
119
+ const menuRegistry = createMenuRegistry(commands);
120
+ menuRegistry.registerMenuAction([...TEST_MENU_PATH, 'other'], { commandId: TEST_COMMAND });
121
+ const registry = createToolbarRegistry(commands, menuRegistry, contextKeyService);
122
+ registry.registerMenuDelegate(TEST_MENU_PATH, TestToolbarWidget.is);
123
+ const commandItem = registry.visibleItems(testWidget).find(item => item.id === `${TEST_COMMAND}${TOOLBAR_WRAPPER_ID_SUFFIX}`);
124
+ const node = commandItem?.toMenuNode?.();
125
+
126
+ expect(CommandMenu.is(node)).to.be.true;
127
+ if (!CommandMenu.is(node)) {
128
+ throw new Error('Expected a command menu node.');
129
+ }
130
+ expect(node.isVisible(TAB_BAR_TOOLBAR_CONTEXT_MENU, contextKeyService, testWidget.node, testWidget)).to.be.true;
131
+ expect(node.isEnabled(TAB_BAR_TOOLBAR_CONTEXT_MENU, testWidget)).to.be.true;
132
+ expect(node.isToggled(TAB_BAR_TOOLBAR_CONTEXT_MENU, testWidget)).to.be.true;
133
+ await node.run(TAB_BAR_TOOLBAR_CONTEXT_MENU, testWidget);
134
+ expect(executedWith).to.equal(testWidget);
135
+ testWidget.dispose();
136
+ });
137
+
138
+ it('preserves the widget for wrapped submenu emptiness checks', () => {
139
+ const testWidget = new TestToolbarWidget();
140
+ const commands = createCommandRegistry();
141
+ commands.registerCommand({ id: TEST_SUBMENU_COMMAND, label: 'Test Submenu Command' }, {
142
+ execute: () => { },
143
+ isVisible: widget => TestToolbarWidget.is(widget) && widget === testWidget
144
+ });
145
+ const menuRegistry = createMenuRegistry(commands);
146
+ menuRegistry.registerSubmenu([...TEST_MENU_PATH, 'other', 'test-submenu'], 'Test Submenu');
147
+ menuRegistry.registerMenuAction([...TEST_MENU_PATH, 'other', 'test-submenu'], { commandId: TEST_SUBMENU_COMMAND });
148
+ const registry = createToolbarRegistry(commands, menuRegistry, contextKeyService);
149
+ registry.registerMenuDelegate(TEST_MENU_PATH, TestToolbarWidget.is);
150
+ const submenuItem = registry.visibleItems(testWidget).find(item => item.id === `test-submenu${TOOLBAR_WRAPPER_ID_SUFFIX}`);
151
+ const node = submenuItem?.toMenuNode?.();
152
+
153
+ expect(CompoundMenuNode.is(node)).to.be.true;
154
+ if (!CompoundMenuNode.is(node)) {
155
+ throw new Error('Expected a compound menu node.');
156
+ }
157
+ expect(node.isEmpty(TAB_BAR_TOOLBAR_CONTEXT_MENU, contextKeyService, testWidget.node, testWidget)).to.be.false;
158
+ testWidget.dispose();
159
+ });
160
+
161
+ });
162
+
62
163
  });
164
+
165
+ class TestToolbarWidget extends Widget {
166
+ static is(candidate?: Widget): candidate is TestToolbarWidget {
167
+ return candidate instanceof TestToolbarWidget;
168
+ }
169
+ }
170
+
171
+ class TestMenuNodeFactory implements MenuNodeFactory {
172
+
173
+ constructor(protected readonly commands: CommandRegistry) { }
174
+
175
+ createGroup(id: string, orderString?: string, when?: string): Group & MutableCompoundMenuNode {
176
+ return new GroupImpl(id, orderString, when);
177
+ }
178
+
179
+ createSubmenu(id: string, label: string, contextKeyOverlays: Record<string, string> | undefined, orderString?: string, icon?: string, when?: string):
180
+ Submenu & MutableCompoundMenuNode {
181
+ return new SubmenuImpl(id, label, contextKeyOverlays, orderString, icon, when);
182
+ }
183
+
184
+ createSubmenuLink(delegate: Submenu, sortString?: string, when?: string): MenuNode {
185
+ return new SubMenuLink(delegate, sortString, when);
186
+ }
187
+
188
+ createCommandMenu(item: MenuAction): CommandMenu {
189
+ return {
190
+ isVisible: (_path, _contextMatcher, _context, ...args) => this.commands.isVisible(item.commandId, ...args),
191
+ isEnabled: (_path, ...args) => this.commands.isEnabled(item.commandId, ...args),
192
+ isToggled: (_path, ...args) => this.commands.isToggled(item.commandId, ...args),
193
+ id: item.commandId,
194
+ label: item.label || this.commands.getCommand(item.commandId)?.label || '',
195
+ icon: item.icon,
196
+ when: item.when,
197
+ sortString: item.order || '',
198
+ run: async (_path, ...args) => { await this.commands.executeCommand(item.commandId, ...args); }
199
+ };
200
+ }
201
+ }
202
+
203
+ function createCommandRegistry(): CommandRegistry {
204
+ return new CommandRegistry({ getContributions: () => [] });
205
+ }
206
+
207
+ function createMenuRegistry(commands: CommandRegistry): MenuModelRegistry {
208
+ return new MenuModelRegistry({ getContributions: () => [] }, commands, new TestMenuNodeFactory(commands));
209
+ }
210
+
211
+ function createToolbarRegistry(commands: CommandRegistry, menuRegistry: MenuModelRegistry, contextKeyService: ContextKeyServiceDummyImpl): TabBarToolbarRegistry {
212
+ const registry = new TabBarToolbarRegistry();
213
+ Reflect.set(registry, 'commandRegistry', commands);
214
+ Reflect.set(registry, 'contextKeyService', contextKeyService);
215
+ Reflect.set(registry, 'menuRegistry', menuRegistry);
216
+ Reflect.set(registry, 'keybindingRegistry', {});
217
+ Reflect.set(registry, 'labelParser', {});
218
+ Reflect.set(registry, 'contextMenuRenderer', { render: () => undefined } as unknown as ContextMenuRenderer);
219
+ return registry;
220
+ }
@@ -99,7 +99,7 @@ export class StatusBarImpl extends ReactWidget implements StatusBar {
99
99
  protected internalSetColor(color?: string): void {
100
100
  this.color = color;
101
101
  }
102
- protected render(): JSX.Element {
102
+ protected render(): React.JSX.Element {
103
103
  const leftEntries = Array.from(this.viewModel.getLeft(), entry => this.renderElement(entry));
104
104
  const rightEntries = Array.from(this.viewModel.getRight(), entry => this.renderElement(entry));
105
105
 
@@ -192,9 +192,9 @@ export class StatusBarImpl extends ReactWidget implements StatusBar {
192
192
  return attrs;
193
193
  }
194
194
 
195
- protected renderElement(entry: StatusBarViewEntry): JSX.Element {
195
+ protected renderElement(entry: StatusBarViewEntry): React.JSX.Element {
196
196
  const childStrings = this.entryService.parse(entry.entry.text);
197
- const children: JSX.Element[] = [];
197
+ const children: React.JSX.Element[] = [];
198
198
 
199
199
  childStrings.forEach((val, key) => {
200
200
  if (LabelIcon.is(val)) {
@@ -72,3 +72,21 @@ export function enableJSDOM(): () => void {
72
72
  };
73
73
  return disableJSDOM;
74
74
  }
75
+
76
+ /**
77
+ * Enables React's act test environment and restores the previous value when disposed.
78
+ */
79
+ export function enableReactActEnvironment(): () => void {
80
+ const reactActEnvironment = globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean };
81
+ const hadReactActEnvironment = Object.prototype.hasOwnProperty.call(reactActEnvironment, 'IS_REACT_ACT_ENVIRONMENT');
82
+ const previousReactActEnvironment = reactActEnvironment.IS_REACT_ACT_ENVIRONMENT;
83
+ reactActEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
84
+
85
+ return () => {
86
+ if (hadReactActEnvironment) {
87
+ reactActEnvironment.IS_REACT_ACT_ENVIRONMENT = previousReactActEnvironment;
88
+ } else {
89
+ delete reactActEnvironment.IS_REACT_ACT_ENVIRONMENT;
90
+ }
91
+ };
92
+ }
@@ -19,6 +19,8 @@ import { Container } from 'inversify';
19
19
  import { createTreeContainer, isTreeServices } from './tree-container';
20
20
  import { TreeSearch } from './tree-search';
21
21
  import { defaultTreeProps, TreeProps } from './tree-widget';
22
+ import { ILogger } from '../../common/logger';
23
+ import { MockLogger } from '../../common/test/mock-logger';
22
24
 
23
25
  describe('TreeContainer', () => {
24
26
  describe('IsTreeServices should accurately distinguish TreeProps from TreeContainerProps', () => {
@@ -33,11 +35,15 @@ describe('TreeContainer', () => {
33
35
  const nonDefault = { search: !defaultTreeProps.search, contextMenu: ['no-default-for-this'] };
34
36
  it('should use props passed in as just props', () => {
35
37
  const parent = new Container();
38
+ parent.bind(ILogger).to(MockLogger).inSingletonScope();
39
+
36
40
  const child = createTreeContainer(parent, nonDefault);
37
41
  assert.deepStrictEqual(child.get(TreeProps), { ...defaultTreeProps, ...nonDefault });
38
42
  });
39
43
  it('should use props passed in as part of TreeContainerProps', () => {
40
44
  const parent = new Container();
45
+ parent.bind(ILogger).to(MockLogger).inSingletonScope();
46
+
41
47
  const child = createTreeContainer(parent, { props: nonDefault });
42
48
  assert.deepStrictEqual(child.get(TreeProps), { ...defaultTreeProps, ...nonDefault });
43
49
  });
@@ -250,6 +250,10 @@ export class TreeModelImpl implements TreeModel, SelectionProvider<ReadonlyArray
250
250
  return this.tree.validateNode(node);
251
251
  }
252
252
 
253
+ removeNode(node: TreeNode | undefined): void {
254
+ this.tree.removeNode(node);
255
+ }
256
+
253
257
  async refresh(parent?: Readonly<CompositeTreeNode>): Promise<CompositeTreeNode | undefined> {
254
258
  if (parent) {
255
259
  return this.tree.refresh(parent);
@@ -0,0 +1,82 @@
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 { enableJSDOM } from '../test/jsdom';
18
+
19
+ // The widget module transitively imports `@lumino/widgets`, which touches `document` at load time.
20
+ const disableJSDOM = enableJSDOM();
21
+
22
+ import { Container } from 'inversify';
23
+ import { expect } from 'chai';
24
+ import { ReactElement, ReactNode } from 'react';
25
+ import { CommandService } from '../../common';
26
+ import { LabelParser } from '../label-parser';
27
+ import { codicon } from '../widgets';
28
+ import { TreeViewWelcomeWidget } from './tree-view-welcome-widget';
29
+
30
+ disableJSDOM();
31
+
32
+ let labelParser: LabelParser;
33
+
34
+ before(() => {
35
+ const container = new Container();
36
+ container.bind(LabelParser).toSelf().inSingletonScope();
37
+ container.bind(CommandService).toDynamicValue(() => ({
38
+ executeCommand<T>(): Promise<T | undefined> {
39
+ return Promise.resolve(undefined);
40
+ }
41
+ })).inSingletonScope();
42
+ labelParser = container.get(LabelParser);
43
+ });
44
+
45
+ describe('TreeViewWelcomeWidget#renderLabelWithIcons', () => {
46
+
47
+ type SpanElement = ReactElement<{ className?: string; children?: ReactNode }>;
48
+
49
+ // Exercise the protected helper without standing up the full TreeWidget DI graph.
50
+ function render(label: string): SpanElement[] {
51
+ const widget = Object.create(TreeViewWelcomeWidget.prototype) as {
52
+ labelParser: LabelParser;
53
+ renderLabelWithIcons(label: string): SpanElement[];
54
+ };
55
+ widget.labelParser = labelParser;
56
+ return widget.renderLabelWithIcons(label);
57
+ }
58
+
59
+ it('renders a leading $(codicon) as a codicon span and keeps the trailing text', () => {
60
+ const nodes = render('$(robot) Launch AI Agent');
61
+ expect(nodes).to.have.lengthOf(2);
62
+ expect(nodes[0].props.className).to.equal(codicon('robot'));
63
+ expect(nodes[0].props.children).to.be.undefined;
64
+ expect(nodes[1].props.className).to.be.undefined;
65
+ expect(nodes[1].props.children).to.equal(' Launch AI Agent');
66
+ });
67
+
68
+ it('renders a plain label as a single text span with no codicon', () => {
69
+ const nodes = render('Open Documentation');
70
+ expect(nodes).to.have.lengthOf(1);
71
+ expect(nodes[0].props.className).to.be.undefined;
72
+ expect(nodes[0].props.children).to.equal('Open Documentation');
73
+ });
74
+
75
+ it('renders multiple codicons interleaved with text', () => {
76
+ const nodes = render('$(package) Import $(gear) Library');
77
+ const iconNodes = nodes.filter(n => typeof n.props.className === 'string' && n.props.className.includes('codicon-'));
78
+ expect(iconNodes).to.have.lengthOf(2);
79
+ expect(iconNodes[0].props.className).to.equal(codicon('package'));
80
+ expect(iconNodes[1].props.className).to.equal(codicon('gear'));
81
+ });
82
+ });
@@ -209,22 +209,14 @@ export class TreeViewWelcomeWidget extends TreeWidget {
209
209
  className='theia-button theia-WelcomeViewButton'
210
210
  disabled={!this.isEnabledClick(enablement)}
211
211
  onClick={e => this.openLinkOrCommand(e, node.href)}>
212
- {node.label}
212
+ {this.renderLabelWithIcons(node.label)}
213
213
  </button>
214
214
  </div>
215
215
  );
216
216
  }
217
217
 
218
218
  protected renderTextNode(node: string, textKey: string | number): React.ReactNode {
219
- return <span key={`text-${textKey}`}>
220
- {this.labelParser.parse(node)
221
- .map((segment, index) =>
222
- LabelIcon.is(segment)
223
- ? <span
224
- key={index}
225
- className={codicon(segment.name)}
226
- />
227
- : <span key={index}>{segment}</span>)}</span>;
219
+ return <span key={`text-${textKey}`}>{this.renderLabelWithIcons(node)}</span>;
228
220
  }
229
221
 
230
222
  protected renderLinkNode(node: ILink, linkKey: string | number, enablement: string | undefined): React.ReactNode {
@@ -233,11 +225,18 @@ export class TreeViewWelcomeWidget extends TreeWidget {
233
225
  className={this.getLinkClassName(node.href, enablement)}
234
226
  title={node.title || ''}
235
227
  onClick={e => this.openLinkOrCommand(e, node.href)}>
236
- {node.label}
228
+ {this.renderLabelWithIcons(node.label)}
237
229
  </a>
238
230
  );
239
231
  }
240
232
 
233
+ protected renderLabelWithIcons(label: string): React.ReactNode[] {
234
+ return this.labelParser.parse(label).map((segment, index) =>
235
+ LabelIcon.is(segment)
236
+ ? <span key={index} className={codicon(segment.name)} />
237
+ : <span key={index}>{segment}</span>);
238
+ }
239
+
241
240
  protected getLinkClassName(href: string, enablement: string | undefined): string {
242
241
  const classNames = ['theia-WelcomeViewCommandLink'];
243
242
  // Only command-backed links can be disabled. All other, https:, file: remain enabled
@@ -545,7 +545,7 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
545
545
  return <this.ScrollingRowRenderer rows={rows} />;
546
546
  }
547
547
  return <TreeWidget.View
548
- ref={view => this.view = (view || undefined)}
548
+ ref={view => { this.view = (view || undefined); }}
549
549
  width={this.node.offsetWidth}
550
550
  height={this.node.offsetHeight}
551
551
  rows={rows}
@@ -1689,7 +1689,7 @@ export namespace TreeWidget {
1689
1689
  override render(): React.ReactNode {
1690
1690
  const { rows, width, height, scrollToRow, renderNodeRow, onScrollEmitter, ...other } = this.props;
1691
1691
  return <Virtuoso
1692
- ref={list => {
1692
+ ref={(list: VirtuosoHandle | null) => {
1693
1693
  this.list = (list || undefined);
1694
1694
  this.scrollIntoViewIfNeeded();
1695
1695
  }}