@theia/core 1.63.0-next.52 → 1.63.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +7 -7
  2. package/i18n/nls.cs.json +112 -5
  3. package/i18n/nls.de.json +112 -5
  4. package/i18n/nls.es.json +112 -5
  5. package/i18n/nls.fr.json +112 -5
  6. package/i18n/nls.hu.json +112 -5
  7. package/i18n/nls.it.json +112 -5
  8. package/i18n/nls.ja.json +112 -5
  9. package/i18n/nls.json +116 -9
  10. package/i18n/nls.ko.json +112 -5
  11. package/i18n/nls.pl.json +112 -5
  12. package/i18n/nls.pt-br.json +112 -5
  13. package/i18n/nls.ru.json +112 -5
  14. package/i18n/nls.tr.json +112 -5
  15. package/i18n/nls.zh-cn.json +112 -5
  16. package/i18n/nls.zh-tw.json +112 -5
  17. package/lib/browser/catalog.json +830 -737
  18. package/lib/browser/context-menu-renderer.d.ts.map +1 -1
  19. package/lib/browser/context-menu-renderer.js +4 -1
  20. package/lib/browser/context-menu-renderer.js.map +1 -1
  21. package/lib/browser/core-preferences.d.ts +1 -0
  22. package/lib/browser/core-preferences.d.ts.map +1 -1
  23. package/lib/browser/core-preferences.js +12 -0
  24. package/lib/browser/core-preferences.js.map +1 -1
  25. package/lib/browser/hover-service.d.ts +5 -0
  26. package/lib/browser/hover-service.d.ts.map +1 -1
  27. package/lib/browser/hover-service.js +12 -0
  28. package/lib/browser/hover-service.js.map +1 -1
  29. package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
  30. package/lib/browser/menu/browser-menu-plugin.js +4 -5
  31. package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
  32. package/lib/browser/menu/menu.spec.js +2 -0
  33. package/lib/browser/menu/menu.spec.js.map +1 -1
  34. package/lib/browser/shell/application-shell.d.ts.map +1 -1
  35. package/lib/browser/shell/application-shell.js +3 -2
  36. package/lib/browser/shell/application-shell.js.map +1 -1
  37. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts +3 -3
  38. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts.map +1 -1
  39. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js +3 -2
  40. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js.map +1 -1
  41. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts.map +1 -1
  42. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +11 -9
  43. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
  44. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts +1 -1
  45. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts.map +1 -1
  46. package/lib/browser/shell/tab-bars.d.ts.map +1 -1
  47. package/lib/browser/shell/tab-bars.js +21 -8
  48. package/lib/browser/shell/tab-bars.js.map +1 -1
  49. package/lib/browser/widgets/select-component.d.ts +2 -0
  50. package/lib/browser/widgets/select-component.d.ts.map +1 -1
  51. package/lib/browser/widgets/select-component.js +21 -7
  52. package/lib/browser/widgets/select-component.js.map +1 -1
  53. package/lib/common/index.d.ts +1 -0
  54. package/lib/common/index.d.ts.map +1 -1
  55. package/lib/common/index.js +1 -0
  56. package/lib/common/index.js.map +1 -1
  57. package/lib/common/menu/menu-model-registry.d.ts +2 -2
  58. package/lib/common/menu/menu-model-registry.d.ts.map +1 -1
  59. package/lib/common/menu/menu-model-registry.js +25 -32
  60. package/lib/common/menu/menu-model-registry.js.map +1 -1
  61. package/lib/common/menu/menu-types.d.ts +2 -2
  62. package/lib/common/menu/menu-types.d.ts.map +1 -1
  63. package/lib/common/menu/menu-types.js.map +1 -1
  64. package/lib/common/resource.d.ts +3 -3
  65. package/lib/common/resource.d.ts.map +1 -1
  66. package/lib/common/resource.js +4 -6
  67. package/lib/common/resource.js.map +1 -1
  68. package/lib/electron-browser/preload.d.ts.map +1 -1
  69. package/lib/electron-browser/preload.js +17 -0
  70. package/lib/electron-browser/preload.js.map +1 -1
  71. package/lib/electron-common/electron-api.d.ts +1 -0
  72. package/lib/electron-common/electron-api.d.ts.map +1 -1
  73. package/lib/electron-common/electron-api.js.map +1 -1
  74. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  75. package/lib/electron-main/electron-main-application.js +21 -7
  76. package/lib/electron-main/electron-main-application.js.map +1 -1
  77. package/package.json +6 -6
  78. package/src/browser/context-menu-renderer.ts +5 -2
  79. package/src/browser/core-preferences.ts +13 -0
  80. package/src/browser/hover-service.ts +13 -0
  81. package/src/browser/menu/browser-menu-plugin.ts +4 -5
  82. package/src/browser/menu/menu.spec.ts +3 -1
  83. package/src/browser/shell/application-shell.ts +3 -2
  84. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx +5 -5
  85. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +14 -11
  86. package/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx +1 -1
  87. package/src/browser/shell/tab-bars.ts +32 -15
  88. package/src/browser/style/tabs.css +13 -2
  89. package/src/browser/widgets/select-component.tsx +22 -6
  90. package/src/common/index.ts +1 -0
  91. package/src/common/menu/menu-model-registry.ts +27 -33
  92. package/src/common/menu/menu-types.ts +2 -2
  93. package/src/common/resource.ts +5 -7
  94. package/src/electron-browser/preload.ts +3 -1
  95. package/src/electron-common/electron-api.ts +2 -0
  96. package/src/electron-main/electron-main-application.ts +22 -7
@@ -121,6 +121,18 @@ export const corePreferenceSchema: PreferenceSchema = {
121
121
  scope: 'application',
122
122
  markdownDescription: nls.localizeByDefault('Separator used by {0}.', '`#window.title#`')
123
123
  },
124
+ 'window.tabCloseIconPlacement': {
125
+ type: 'string',
126
+ enum: ['end', 'start'],
127
+ enumDescriptions: [
128
+ nls.localize('theia/core/window/tabCloseIconPlacement/end', 'Place the close icon at the end of the label. In left-to-right languages, this is the right side of the tab.'),
129
+ nls.localize('theia/core/window/tabCloseIconPlacement/start', 'Place the close icon at the start of the label. In left-to-right languages, this is the left side of the tab.'),
130
+ ],
131
+ default: 'end',
132
+ scope: 'application',
133
+ description: nls.localize('theia/core/window/tabCloseIconPlacement/description', 'Place the close icons on tab titles at the start or end of the tab. The default is end on all platforms.'),
134
+ included: isOSX
135
+ },
124
136
  'window.secondaryWindowPlacement': {
125
137
  type: 'string',
126
138
  enum: ['originalSize', 'halfWidth', 'fullSize'],
@@ -305,6 +317,7 @@ export interface CoreConfiguration {
305
317
  'window.menuBarVisibility': 'classic' | 'visible' | 'hidden' | 'compact';
306
318
  'window.title': string;
307
319
  'window.titleSeparator': string;
320
+ 'window.tabCloseIconPlacement': 'end' | 'start';
308
321
  'workbench.list.openMode': 'singleClick' | 'doubleClick';
309
322
  'workbench.commandPalette.history': number;
310
323
  'workbench.editor.highlightModifiedTabs': boolean;
@@ -107,6 +107,7 @@ export class HoverService {
107
107
  this.pendingTimeout = disposableTimeout(() => this.renderHover(request), this.getHoverDelay());
108
108
  this.hoverTarget = request.target;
109
109
  this.listenForMouseOut();
110
+ this.listenForMouseClick();
110
111
  }
111
112
  }
112
113
 
@@ -222,6 +223,18 @@ export class HoverService {
222
223
  this.hoverTarget = undefined;
223
224
  }
224
225
 
226
+ /**
227
+ * Listen for any mouse click (mousedown) event and cancel the hover if detected.
228
+ * This ensures the hover is dismissed when the user clicks anywhere (including on the target or elsewhere).
229
+ */
230
+ protected listenForMouseClick(): void {
231
+ const handleMouseDown = (e: MouseEvent) => {
232
+ this.cancelHover();
233
+ };
234
+ document.addEventListener('mousedown', handleMouseDown, true);
235
+ this.disposeOnHide.push({ dispose: () => document.removeEventListener('mousedown', handleMouseDown, true) });
236
+ }
237
+
225
238
  protected unRenderHover(): void {
226
239
  if (this.hoverHost.matches(':popover-open')) {
227
240
  this.hoverHost.hidePopover();
@@ -329,18 +329,17 @@ export class DynamicMenuWidget extends MenuWidget {
329
329
  if (submenu.items.length > 0) {
330
330
  result.push({ type: 'submenu', submenu });
331
331
  }
332
- } else {
332
+ } else if (node.id !== 'inline') {
333
333
  const items = this.createItems(nodePath, node.children, phCommandRegistry, contextMatcher, context);
334
334
  if (items.length > 0) {
335
- if (node.id !== 'inline') {
335
+ if (result[result.length - 1]?.type !== 'separator') {
336
336
  result.push({ type: 'separator' });
337
337
  }
338
338
  result.push(...items);
339
- if (node.id !== 'inline') {
340
- result.push({ type: 'separator' });
341
- }
339
+ result.push({ type: 'separator' });
342
340
  }
343
341
  }
342
+
344
343
  } else if (CommandMenu.is(node)) {
345
344
  const id = !phCommandRegistry.hasCommand(node.id) ? node.id : `${node.id}:${DynamicMenuWidget.nextCommmandId++}`;
346
345
  phCommandRegistry.addCommand(id, {
@@ -50,7 +50,6 @@ class TestMenuNodeFactory implements MenuNodeFactory {
50
50
  }
51
51
 
52
52
  describe('menu-model-registry', () => {
53
-
54
53
  describe('01 #register', () => {
55
54
  it('Should allow to register menu actions.', () => {
56
55
  const fileMenu = ['main', 'File'];
@@ -86,6 +85,9 @@ describe('menu-model-registry', () => {
86
85
  const openGroup = file.children[0] as Submenu;
87
86
  expect(openGroup.children.length).equals(2);
88
87
  expect(openGroup.label).undefined;
88
+
89
+ expect(service.getMenuNode([...fileOpenMenu, 'open'])).exist;
90
+ expect(service.getMenuNode([...fileOpenMenu, 'Gurkensalat'])).undefined;
89
91
  });
90
92
 
91
93
  it('Should not allow to register cyclic menus.', () => {
@@ -601,8 +601,9 @@ export class ApplicationShell extends Widget {
601
601
  // the files were dragged from the outside the workspace
602
602
  Array.from(event.dataTransfer.files).forEach(async file => {
603
603
  if (environment.electron.is()) {
604
- if (file.path) {
605
- const fileUri = URI.fromFilePath(file.path);
604
+ const path = window.electronTheiaCore.getPathForFile(file);
605
+ if (path) {
606
+ const fileUri = URI.fromFilePath(path);
606
607
  openUri(fileUri);
607
608
  }
608
609
  } else {
@@ -38,7 +38,7 @@ abstract class AbstractToolbarMenuWrapper {
38
38
  }
39
39
 
40
40
  protected abstract menuPath?: MenuPath;
41
- protected abstract menuNode: MenuNode;
41
+ protected abstract menuNode?: MenuNode;
42
42
  protected abstract id: string;
43
43
  protected abstract icon: string | undefined;
44
44
  protected abstract tooltip: string | undefined;
@@ -61,7 +61,7 @@ abstract class AbstractToolbarMenuWrapper {
61
61
  return this.renderMenuItem(widget);
62
62
  }
63
63
 
64
- toMenuNode?(): MenuNode {
64
+ toMenuNode?(): MenuNode | undefined {
65
65
  return this.menuNode;
66
66
  }
67
67
 
@@ -194,7 +194,7 @@ export class ToolbarSubmenuWrapper extends AbstractToolbarMenuWrapper implements
194
194
  if (this.toolbarItem.isVisible && !this.toolbarItem.isVisible(widget)) {
195
195
  return false;
196
196
  }
197
- if (!menuNode.isVisible(this.effectiveMenuPath, this.contextKeyService, widget.node, widget)) {
197
+ if (!menuNode?.isVisible(this.effectiveMenuPath, this.contextKeyService, widget.node, widget)) {
198
198
  return false;
199
199
  }
200
200
  if (this.toolbarItem.command) {
@@ -225,14 +225,14 @@ export class ToolbarSubmenuWrapper extends AbstractToolbarMenuWrapper implements
225
225
  get tooltip(): string | undefined { return this.toolbarItem.tooltip; }
226
226
  get text(): string | undefined { return (this.toolbarItem.group === NAVIGATION || this.toolbarItem.group === undefined) ? undefined : this.toolbarItem.text; }
227
227
  get onDidChange(): Event<void> | undefined {
228
- return this.menuNode.onDidChange;
228
+ return this.menuNode?.onDidChange;
229
229
  }
230
230
 
231
231
  get menuPath(): MenuPath {
232
232
  return this.toolbarItem.menuPath!;
233
233
  }
234
234
 
235
- get menuNode(): MenuNode {
235
+ get menuNode(): MenuNode | undefined {
236
236
  return this.menuRegistry.getMenu(this.menuPath);
237
237
  }
238
238
  }
@@ -138,19 +138,22 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
138
138
 
139
139
  for (const delegate of this.menuDelegates.values()) {
140
140
  if (delegate.isVisible(widget)) {
141
- const menu = this.menuRegistry.getMenu(delegate.menuPath)!;
142
- for (const child of menu.children) {
143
- if (child.isVisible([...delegate.menuPath, child.id], this.contextKeyService, widget.node)) {
144
- if (CompoundMenuNode.is(child)) {
145
- for (const grandchild of child.children) {
146
- if (grandchild.isVisible([...delegate.menuPath, child.id, grandchild.id], this.contextKeyService, widget.node) && RenderedMenuNode.is(grandchild)) {
147
- result.push(new ToolbarMenuNodeWrapper([...delegate.menuPath, child.id, grandchild.id], this.commandRegistry, this.menuRegistry,
148
- this.contextKeyService, this.contextMenuRenderer, grandchild, child.id, delegate.menuPath));
141
+ const menu = this.menuRegistry.getMenu(delegate.menuPath);
142
+ if (menu) {
143
+ for (const child of menu.children) {
144
+ if (child.isVisible([...delegate.menuPath, child.id], this.contextKeyService, widget.node)) {
145
+ if (CompoundMenuNode.is(child)) {
146
+ for (const grandchild of child.children) {
147
+ if (grandchild.isVisible([...delegate.menuPath, child.id, grandchild.id],
148
+ this.contextKeyService, widget.node) && RenderedMenuNode.is(grandchild)) {
149
+ result.push(new ToolbarMenuNodeWrapper([...delegate.menuPath, child.id, grandchild.id], this.commandRegistry, this.menuRegistry,
150
+ this.contextKeyService, this.contextMenuRenderer, grandchild, child.id, delegate.menuPath));
151
+ }
149
152
  }
153
+ } else if (CommandMenu.is(child)) {
154
+ result.push(new ToolbarMenuNodeWrapper([...delegate.menuPath, child.id], this.commandRegistry, this.menuRegistry,
155
+ this.contextKeyService, this.contextMenuRenderer, child, undefined, delegate.menuPath));
150
156
  }
151
- } else if (CommandMenu.is(child)) {
152
- result.push(new ToolbarMenuNodeWrapper([...delegate.menuPath, child.id], this.commandRegistry, this.menuRegistry,
153
- this.contextKeyService, this.contextMenuRenderer, child, undefined, delegate.menuPath));
154
157
  }
155
158
  }
156
159
  }
@@ -34,7 +34,7 @@ export interface TabBarToolbarItem {
34
34
  onDidChange?: Event<void>;
35
35
  group?: string;
36
36
  priority?: number;
37
- toMenuNode?(): MenuNode;
37
+ toMenuNode?(): MenuNode | undefined;
38
38
  }
39
39
 
40
40
  /**
@@ -119,6 +119,15 @@ export class TabBarRenderer extends TabBar.Renderer {
119
119
  }
120
120
  }));
121
121
  }
122
+ if (this.corePreferences) {
123
+ this.toDispose.push(
124
+ this.corePreferences.onPreferenceChanged(event => {
125
+ if (event.preferenceName === 'window.tabCloseIconPlacement' && this._tabBar) {
126
+ this._tabBar.update();
127
+ }
128
+ })
129
+ );
130
+ }
122
131
  }
123
132
 
124
133
  dispose(): void {
@@ -160,22 +169,42 @@ export class TabBarRenderer extends TabBar.Renderer {
160
169
  * @returns {VirtualElement} The virtual element of the rendered tab.
161
170
  */
162
171
  override renderTab(data: SideBarRenderData, isInSidePanel?: boolean, isPartOfHiddenTabBar?: boolean): VirtualElement {
172
+ // Putting the close icon at the start is only pertinent to the horizontal orientation
173
+ const isHorizontal = this.tabBar?.orientation === 'horizontal';
174
+ const tabCloseIconStart = isHorizontal && this.corePreferences?.['window.tabCloseIconPlacement'] === 'start';
175
+
163
176
  const title = data.title;
164
177
  const id = this.createTabId(title, isPartOfHiddenTabBar);
165
178
  const key = this.createTabKey(data);
166
179
  const style = this.createTabStyle(data);
167
- const className = this.createTabClass(data);
180
+ const className = `${this.createTabClass(data)}${tabCloseIconStart ? ' closeIcon-start' : ''}`;
168
181
  const dataset = this.createTabDataset(data);
169
182
  const closeIconTitle = data.title.className.includes(PINNED_CLASS)
170
183
  ? nls.localizeByDefault('Unpin')
171
184
  : nls.localizeByDefault('Close');
172
185
 
173
- const hover = this.tabBar && (this.tabBar.orientation === 'horizontal' && this.corePreferences?.['window.tabbar.enhancedPreview'] === 'classic')
186
+ const hover = isHorizontal && this.corePreferences?.['window.tabbar.enhancedPreview'] === 'classic'
174
187
  ? { title: title.caption }
175
188
  : {
176
189
  onmouseenter: this.handleMouseEnterEvent
177
190
  };
178
191
 
192
+ const tabLabel = h.div(
193
+ { className: 'theia-tab-icon-label' },
194
+ this.renderIcon(data, isInSidePanel),
195
+ this.renderLabel(data, isInSidePanel),
196
+ this.renderTailDecorations(data, isInSidePanel),
197
+ this.renderBadge(data, isInSidePanel),
198
+ this.renderLock(data, isInSidePanel)
199
+ );
200
+ const tabCloseIcon = h.div({
201
+ className: 'lm-TabBar-tabCloseIcon action-label',
202
+ title: closeIconTitle,
203
+ onclick: this.handleCloseClickEvent,
204
+ });
205
+
206
+ const tabContents = tabCloseIconStart ? [tabCloseIcon, tabLabel] : [tabLabel, tabCloseIcon];
207
+
179
208
  return h.li(
180
209
  {
181
210
  ...hover,
@@ -187,19 +216,7 @@ export class TabBarRenderer extends TabBar.Renderer {
187
216
  e.preventDefault();
188
217
  }
189
218
  },
190
- h.div(
191
- { className: 'theia-tab-icon-label' },
192
- this.renderIcon(data, isInSidePanel),
193
- this.renderLabel(data, isInSidePanel),
194
- this.renderTailDecorations(data, isInSidePanel),
195
- this.renderBadge(data, isInSidePanel),
196
- this.renderLock(data, isInSidePanel)
197
- ),
198
- h.div({
199
- className: 'lm-TabBar-tabCloseIcon action-label',
200
- title: closeIconTitle,
201
- onclick: this.handleCloseClickEvent
202
- })
219
+ ...tabContents
203
220
  );
204
221
  }
205
222
 
@@ -237,6 +237,12 @@
237
237
  -ms-user-select: none;
238
238
  }
239
239
 
240
+ .lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable.closeIcon-start > .lm-TabBar-tabCloseIcon,
241
+ .lm-TabBar.theia-app-centers .lm-TabBar-tab.theia-mod-pinned.closeIcon-start > .lm-TabBar-tabCloseIcon {
242
+ margin-left: inherit;
243
+ margin-right: 4px;
244
+ }
245
+
240
246
  .lm-TabBar.theia-app-centers.dynamic-tabs .lm-TabBar-tab.lm-mod-closable>.lm-TabBar-tabCloseIcon,
241
247
  .lm-TabBar.theia-app-centers.dynamic-tabs .lm-TabBar-tab.theia-mod-pinned>.lm-TabBar-tabCloseIcon {
242
248
  /* hide close icon for dynamic tabs strategy*/
@@ -254,11 +260,16 @@
254
260
  background-color: rgba(50%, 50%, 50%, 0.2);
255
261
  }
256
262
 
257
- .lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable,
258
- .lm-TabBar.theia-app-centers .lm-TabBar-tab.theia-mod-pinned {
263
+ .lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable:not(.closeIcon-start),
264
+ .lm-TabBar.theia-app-centers .lm-TabBar-tab.theia-mod-pinned:not(.closeIcon-start) {
259
265
  padding-right: 4px;
260
266
  }
261
267
 
268
+ .lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable.closeIcon-start,
269
+ .lm-TabBar.theia-app-centers .lm-TabBar-tab.theia-mod-pinned.closeIcon-start {
270
+ padding-left: 4px;
271
+ }
272
+
262
273
  .lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable:not(.theia-mod-dirty):hover>.lm-TabBar-tabCloseIcon:before,
263
274
  .lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable:not(.theia-mod-dirty).lm-TabBar-tab.lm-mod-current>.lm-TabBar-tabCloseIcon:before,
264
275
  .lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable.theia-mod-dirty>.lm-TabBar-tabCloseIcon:hover:before {
@@ -61,12 +61,7 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
61
61
 
62
62
  constructor(props: SelectComponentProps) {
63
63
  super(props);
64
- let selected = 0;
65
- if (typeof props.defaultValue === 'number') {
66
- selected = props.defaultValue;
67
- } else if (typeof props.defaultValue === 'string') {
68
- selected = Math.max(props.options.findIndex(e => e.value === props.defaultValue), 0);
69
- }
64
+ const selected = this.getInitialSelectedIndex(props);
70
65
  this.state = {
71
66
  selected,
72
67
  original: selected,
@@ -83,6 +78,27 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
83
78
  this.dropdownElement = list;
84
79
  }
85
80
 
81
+ protected getInitialSelectedIndex(props: SelectComponentProps): number {
82
+ let selected = 0;
83
+ if (typeof props.defaultValue === 'number') {
84
+ selected = props.defaultValue;
85
+ } else if (typeof props.defaultValue === 'string') {
86
+ selected = Math.max(props.options.findIndex(e => e.value === props.defaultValue), 0);
87
+ }
88
+ return selected;
89
+ }
90
+
91
+ override componentDidUpdate(prevProps: SelectComponentProps): void {
92
+ if (prevProps.defaultValue !== this.props.defaultValue || prevProps.options !== this.props.options) {
93
+ const selected = this.getInitialSelectedIndex(this.props);
94
+ this.setState({
95
+ selected,
96
+ original: selected,
97
+ hover: selected
98
+ });
99
+ }
100
+ }
101
+
86
102
  get options(): readonly SelectOption[] {
87
103
  return this.props.options;
88
104
  }
@@ -22,6 +22,7 @@ export * from './contribution-filter';
22
22
  export * from './contribution-provider';
23
23
  export * from './disposable';
24
24
  export * from './event';
25
+ export * from './listener';
25
26
  export * from './logger';
26
27
  export * from './lsp-types';
27
28
  export * from './menu';
@@ -132,21 +132,15 @@ export class MenuModelRegistry {
132
132
  */
133
133
  registerCommandMenu(menuPath: MenuPath, item: CommandMenu): Disposable {
134
134
  const parent = this.root.getOrCreate(menuPath, 0, menuPath.length);
135
- const existing = parent.children.find(node => node.id === menuPath[menuPath.length - 1]);
136
- if (existing) {
137
- throw new Error(`A menu node with path ${JSON.stringify(menuPath)} already exists`);
138
- } else {
139
- parent.addNode(item);
140
- return Disposable.create(() => {
141
- parent.removeNode(item);
142
- this.fireChangeEvent({
143
- kind: ChangeKind.REMOVED,
144
- path: menuPath.slice(0, menuPath.length - 1),
145
- affectedChildId: item.id
146
- });
135
+ parent.addNode(item);
136
+ return Disposable.create(() => {
137
+ parent.removeNode(item);
138
+ this.fireChangeEvent({
139
+ kind: ChangeKind.REMOVED,
140
+ path: menuPath.slice(0, menuPath.length - 1),
141
+ affectedChildId: item.id
147
142
  });
148
- }
149
-
143
+ });
150
144
  }
151
145
 
152
146
  /**
@@ -156,21 +150,16 @@ export class MenuModelRegistry {
156
150
  */
157
151
  registerMenuAction(menuPath: MenuPath, item: MenuAction): Disposable {
158
152
  const parent = this.root.getOrCreate(menuPath, 0, menuPath.length);
159
- const existing = parent.children.find(node => node.id === item.commandId);
160
- if (existing) {
161
- throw new Error(`A menu node with id ${item.commandId} in path ${JSON.stringify(menuPath)} already exists`);
162
- } else {
163
- const node = this.menuNodeFactory.createCommandMenu(item);
164
- parent.addNode(node);
165
- return Disposable.create(() => {
166
- parent.removeNode(node);
167
- this.fireChangeEvent({
168
- kind: ChangeKind.REMOVED,
169
- path: menuPath.slice(0, menuPath.length - 1),
170
- affectedChildId: node.id
171
- });
153
+ const node = this.menuNodeFactory.createCommandMenu(item);
154
+ parent.addNode(node);
155
+ return Disposable.create(() => {
156
+ parent.removeNode(node);
157
+ this.fireChangeEvent({
158
+ kind: ChangeKind.REMOVED,
159
+ path: menuPath.slice(0, menuPath.length - 1),
160
+ affectedChildId: node.id
172
161
  });
173
- }
162
+ });
174
163
 
175
164
  }
176
165
 
@@ -311,13 +300,15 @@ export class MenuModelRegistry {
311
300
  }
312
301
  }
313
302
 
314
- protected findInNode(root: CompoundMenuNode, menuPath: MenuPath, pathIndex: number): MenuNode | undefined {
303
+ protected findInNode(root: MenuNode, menuPath: MenuPath, pathIndex: number): MenuNode | undefined {
315
304
  if (pathIndex === menuPath.length) {
316
305
  return root;
317
306
  }
318
- const child = root.children.find(c => c.id === menuPath[pathIndex]);
319
- if (CompoundMenuNode.is(child)) {
320
- return this.findInNode(child, menuPath, pathIndex + 1);
307
+ if (CompoundMenuNode.is(root)) {
308
+ const child = root.children.find(c => c.id === menuPath[pathIndex]);
309
+ if (child) {
310
+ return this.findInNode(child, menuPath, pathIndex + 1);
311
+ }
321
312
  }
322
313
  return undefined;
323
314
  }
@@ -326,8 +317,11 @@ export class MenuModelRegistry {
326
317
  return this.findInNode(this.root, menuPath, 0);
327
318
  }
328
319
 
329
- getMenu(menuPath: MenuPath): CompoundMenuNode {
320
+ getMenu(menuPath: MenuPath): CompoundMenuNode | undefined {
330
321
  const node = this.getMenuNode(menuPath);
322
+ if (!node) {
323
+ return undefined;
324
+ }
331
325
  if (!CompoundMenuNode.is(node)) {
332
326
  throw new Error(`not a compound menu node: ${JSON.stringify(menuPath)}`);
333
327
  }
@@ -97,7 +97,7 @@ export interface RenderedMenuNode extends MenuNode {
97
97
  }
98
98
 
99
99
  export namespace RenderedMenuNode {
100
- export function is(node: object): node is RenderedMenuNode {
100
+ export function is(node: unknown): node is RenderedMenuNode {
101
101
  return isObject<RenderedMenuNode>(node) && typeof node.label === 'string';
102
102
  }
103
103
  }
@@ -105,7 +105,7 @@ export namespace RenderedMenuNode {
105
105
  export type CommandMenu = MenuNode & RenderedMenuNode & Action;
106
106
 
107
107
  export namespace CommandMenu {
108
- export function is(node: MenuNode): node is CommandMenu {
108
+ export function is(node: MenuNode | undefined): node is CommandMenu {
109
109
  return RenderedMenuNode.is(node) && Action.is(node);
110
110
  }
111
111
  }
@@ -367,11 +367,11 @@ export class UntitledResourceResolver implements ResourceResolver {
367
367
  }
368
368
  }
369
369
 
370
- async createUntitledResource(content?: string, extension?: string, uri?: URI): Promise<UntitledResource> {
370
+ async createUntitledResource(content?: string, extension?: string, uri?: URI, encoding?: string): Promise<UntitledResource> {
371
371
  if (!uri) {
372
372
  uri = this.createUntitledURI(extension);
373
373
  }
374
- return new UntitledResource(this.resources, uri, content);
374
+ return new UntitledResource(this.resources, uri, content, encoding);
375
375
  }
376
376
 
377
377
  createUntitledURI(extension?: string, parent?: URI): URI {
@@ -394,13 +394,15 @@ export class UntitledResource implements Resource {
394
394
  protected readonly onDidChangeContentsEmitter = new Emitter<void>();
395
395
  readonly initiallyDirty: boolean;
396
396
  readonly autosaveable = false;
397
+ readonly encoding: string | undefined;
397
398
  get onDidChangeContents(): Event<void> {
398
399
  return this.onDidChangeContentsEmitter.event;
399
400
  }
400
401
 
401
- constructor(private resources: Map<string, UntitledResource>, public uri: URI, private content?: string) {
402
+ constructor(private resources: Map<string, UntitledResource>, public uri: URI, private content?: string, encoding?: string) {
402
403
  this.initiallyDirty = (content !== undefined && content.length > 0);
403
404
  this.resources.set(this.uri.toString(), this);
405
+ this.encoding = encoding;
404
406
  }
405
407
 
406
408
  dispose(): void {
@@ -429,10 +431,6 @@ export class UntitledResource implements Resource {
429
431
  get version(): ResourceVersion | undefined {
430
432
  return undefined;
431
433
  }
432
-
433
- get encoding(): string | undefined {
434
- return undefined;
435
- }
436
434
  }
437
435
 
438
436
  /**
@@ -13,7 +13,7 @@
13
13
  //
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  //
16
- import { IpcRendererEvent } from '@theia/electron/shared/electron';
16
+ import { IpcRendererEvent, webUtils } from '@theia/electron/shared/electron';
17
17
  import { Disposable } from '../common/disposable';
18
18
  import { StopReason } from '../common/frontend-application-state';
19
19
  import { NativeKeyboardLayout } from '../common/keyboard/keyboard-layout-provider';
@@ -90,6 +90,8 @@ const api: TheiaCoreAPI = {
90
90
  showItemInFolder: fsPath => {
91
91
  ipcRenderer.send(CHANNEL_SHOW_ITEM_IN_FOLDER, fsPath);
92
92
  },
93
+
94
+ getPathForFile: (file: File) => webUtils.getPathForFile(file),
93
95
  openWithSystemApp: location => {
94
96
  ipcRenderer.send(CHANNEL_OPEN_WITH_SYSTEM_APP, location);
95
97
  },
@@ -58,6 +58,8 @@ export interface TheiaCoreAPI {
58
58
 
59
59
  showItemInFolder(fsPath: string): void;
60
60
 
61
+ getPathForFile(file: File): string;
62
+
61
63
  /**
62
64
  * @param location The location to open with the system app. This can be a file path or a URL.
63
65
  */
@@ -450,11 +450,17 @@ export class ElectronMainApplication {
450
450
  const windowState = previousWindowState?.screenLayout === this.getCurrentScreenLayout()
451
451
  ? previousWindowState
452
452
  : this.getDefaultTheiaWindowOptions();
453
- return {
453
+ const result = {
454
454
  frame: this.useNativeWindowFrame,
455
455
  ...this.getDefaultOptions(),
456
456
  ...windowState
457
457
  };
458
+
459
+ result.webPreferences = {
460
+ ...result.webPreferences,
461
+ preload: path.resolve(this.globals.THEIA_APP_PROJECT_PATH, 'lib', 'frontend', 'preload.js').toString()
462
+ };
463
+ return result;
458
464
  }
459
465
 
460
466
  protected avoidOverlap(options: TheiaBrowserWindowOptions): TheiaBrowserWindowOptions {
@@ -489,7 +495,7 @@ export class ElectronMainApplication {
489
495
  // Issue: https://github.com/eclipse-theia/theia/issues/8577
490
496
  nodeIntegrationInWorker: false,
491
497
  backgroundThrottling: false,
492
- preload: path.resolve(this.globals.THEIA_APP_PROJECT_PATH, 'lib', 'frontend', 'preload.js').toString()
498
+ enableDeprecatedPaste: true
493
499
  },
494
500
  ...this.config.electron?.windowOptions || {},
495
501
  };
@@ -565,13 +571,18 @@ export class ElectronMainApplication {
565
571
  }
566
572
 
567
573
  protected getDefaultTheiaWindowOptions(): TheiaBrowserWindowOptions {
568
- return {
574
+ const result = {
569
575
  frame: this.useNativeWindowFrame,
570
576
  isFullScreen: false,
571
577
  isMaximized: false,
572
578
  ...this.getDefaultTheiaWindowBounds(),
573
- ...this.getDefaultOptions()
579
+ ...this.getDefaultOptions(),
580
+ };
581
+ result.webPreferences = {
582
+ ...result.webPreferences || {},
583
+ preload: path.resolve(this.globals.THEIA_APP_PROJECT_PATH, 'lib', 'frontend', 'preload.js').toString()
574
584
  };
585
+ return result;
575
586
  }
576
587
 
577
588
  protected getDefaultTheiaSecondaryWindowBounds(): TheiaBrowserWindowOptions {
@@ -780,16 +791,20 @@ export class ElectronMainApplication {
780
791
  webContents.setWindowOpenHandler(details => {
781
792
  // if it's a secondary window, allow it to open
782
793
  if (new URI(details.url).path.fsPath() === new Path(this.globals.THEIA_SECONDARY_WINDOW_HTML_PATH).fsPath()) {
783
- const { minWidth, minHeight } = this.getDefaultOptions();
794
+ const defaultOptions = this.getDefaultOptions();
784
795
  const options: BrowserWindowConstructorOptions = {
785
796
  ...this.getDefaultTheiaSecondaryWindowBounds(),
786
797
  // We always need the native window frame for now because the secondary window does not have Theia's title bar by default.
787
798
  // In 'custom' title bar mode this would leave the window without any window controls (close, min, max)
788
799
  // TODO set to this.useNativeWindowFrame when secondary windows support a custom title bar.
789
800
  frame: true,
790
- minWidth,
791
- minHeight
801
+ minWidth: defaultOptions.minWidth,
802
+ minHeight: defaultOptions.minHeight,
803
+ webPreferences: {
804
+ enableDeprecatedPaste: defaultOptions.webPreferences?.enableDeprecatedPaste
805
+ }
792
806
  };
807
+
793
808
  if (!this.useNativeWindowFrame) {
794
809
  // If the main window does not have a native window frame, do not show an icon in the secondary window's native title bar.
795
810
  // The data url is a 1x1 transparent png