@theia/core 1.32.0-next.5 → 1.32.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 (113) hide show
  1. package/README.md +6 -6
  2. package/i18n/nls.cs.json +14 -13
  3. package/i18n/nls.de.json +14 -13
  4. package/i18n/nls.es.json +14 -13
  5. package/i18n/nls.fr.json +14 -13
  6. package/i18n/nls.hu.json +14 -13
  7. package/i18n/nls.it.json +14 -13
  8. package/i18n/nls.ja.json +14 -13
  9. package/i18n/nls.json +16 -15
  10. package/i18n/nls.pl.json +14 -13
  11. package/i18n/nls.pt-br.json +14 -13
  12. package/i18n/nls.pt-pt.json +14 -13
  13. package/i18n/nls.ru.json +14 -13
  14. package/i18n/nls.zh-cn.json +23 -22
  15. package/lib/browser/context-key-service.d.ts +20 -8
  16. package/lib/browser/context-key-service.d.ts.map +1 -1
  17. package/lib/browser/context-key-service.js +6 -0
  18. package/lib/browser/context-key-service.js.map +1 -1
  19. package/lib/browser/context-menu-renderer.d.ts +2 -0
  20. package/lib/browser/context-menu-renderer.d.ts.map +1 -1
  21. package/lib/browser/context-menu-renderer.js.map +1 -1
  22. package/lib/browser/core-preferences.d.ts.map +1 -1
  23. package/lib/browser/core-preferences.js +8 -9
  24. package/lib/browser/core-preferences.js.map +1 -1
  25. package/lib/browser/frontend-application-module.d.ts.map +1 -1
  26. package/lib/browser/frontend-application-module.js +4 -1
  27. package/lib/browser/frontend-application-module.js.map +1 -1
  28. package/lib/browser/hover-service.d.ts +41 -0
  29. package/lib/browser/hover-service.d.ts.map +1 -0
  30. package/lib/browser/hover-service.js +191 -0
  31. package/lib/browser/hover-service.js.map +1 -0
  32. package/lib/browser/icon-theme-service.js +1 -1
  33. package/lib/browser/icon-theme-service.js.map +1 -1
  34. package/lib/browser/index.d.ts +1 -0
  35. package/lib/browser/index.d.ts.map +1 -1
  36. package/lib/browser/index.js +1 -0
  37. package/lib/browser/index.js.map +1 -1
  38. package/lib/browser/menu/browser-context-menu-renderer.d.ts +1 -1
  39. package/lib/browser/menu/browser-context-menu-renderer.d.ts.map +1 -1
  40. package/lib/browser/menu/browser-context-menu-renderer.js +2 -2
  41. package/lib/browser/menu/browser-context-menu-renderer.js.map +1 -1
  42. package/lib/browser/menu/browser-menu-plugin.d.ts +4 -3
  43. package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
  44. package/lib/browser/menu/browser-menu-plugin.js +9 -6
  45. package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
  46. package/lib/browser/resource-context-key.d.ts +2 -3
  47. package/lib/browser/resource-context-key.d.ts.map +1 -1
  48. package/lib/browser/resource-context-key.js +2 -4
  49. package/lib/browser/resource-context-key.js.map +1 -1
  50. package/lib/browser/shell/application-shell.d.ts.map +1 -1
  51. package/lib/browser/shell/application-shell.js +7 -0
  52. package/lib/browser/shell/application-shell.js.map +1 -1
  53. package/lib/browser/shell/sidebar-menu-widget.d.ts +3 -0
  54. package/lib/browser/shell/sidebar-menu-widget.d.ts.map +1 -1
  55. package/lib/browser/shell/sidebar-menu-widget.js +15 -1
  56. package/lib/browser/shell/sidebar-menu-widget.js.map +1 -1
  57. package/lib/browser/shell/tab-bars.d.ts +4 -1
  58. package/lib/browser/shell/tab-bars.d.ts.map +1 -1
  59. package/lib/browser/shell/tab-bars.js +25 -12
  60. package/lib/browser/shell/tab-bars.js.map +1 -1
  61. package/lib/browser/status-bar/index.d.ts +0 -1
  62. package/lib/browser/status-bar/index.d.ts.map +1 -1
  63. package/lib/browser/status-bar/index.js +0 -3
  64. package/lib/browser/status-bar/index.js.map +1 -1
  65. package/lib/browser/status-bar/status-bar.d.ts +3 -3
  66. package/lib/browser/status-bar/status-bar.d.ts.map +1 -1
  67. package/lib/browser/status-bar/status-bar.js +10 -6
  68. package/lib/browser/status-bar/status-bar.js.map +1 -1
  69. package/lib/browser/view-container.js +1 -1
  70. package/lib/browser/view-container.js.map +1 -1
  71. package/lib/common/objects.spec.d.ts +2 -0
  72. package/lib/common/objects.spec.d.ts.map +1 -0
  73. package/lib/common/objects.spec.js +102 -0
  74. package/lib/common/objects.spec.js.map +1 -0
  75. package/lib/electron-browser/menu/electron-context-menu-renderer.js +2 -2
  76. package/lib/electron-browser/menu/electron-context-menu-renderer.js.map +1 -1
  77. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts +8 -2
  78. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  79. package/lib/electron-browser/menu/electron-main-menu-factory.js +8 -6
  80. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  81. package/lib/electron-main/messaging/electron-messaging-contribution.d.ts +1 -0
  82. package/lib/electron-main/messaging/electron-messaging-contribution.d.ts.map +1 -1
  83. package/lib/electron-main/messaging/electron-messaging-contribution.js +6 -2
  84. package/lib/electron-main/messaging/electron-messaging-contribution.js.map +1 -1
  85. package/package.json +6 -6
  86. package/src/browser/context-key-service.ts +29 -9
  87. package/src/browser/context-menu-renderer.ts +2 -0
  88. package/src/browser/core-preferences.ts +8 -12
  89. package/src/browser/frontend-application-module.ts +5 -1
  90. package/src/browser/hover-service.ts +189 -0
  91. package/src/browser/icon-theme-service.ts +1 -1
  92. package/src/browser/index.ts +1 -0
  93. package/src/browser/menu/browser-context-menu-renderer.ts +2 -2
  94. package/src/browser/menu/browser-menu-plugin.ts +10 -7
  95. package/src/browser/resource-context-key.ts +5 -7
  96. package/src/browser/shell/application-shell.ts +9 -2
  97. package/src/browser/shell/sidebar-menu-widget.tsx +93 -79
  98. package/src/browser/shell/tab-bars.ts +29 -6
  99. package/src/browser/status-bar/index.ts +0 -3
  100. package/src/browser/status-bar/status-bar.tsx +7 -3
  101. package/src/browser/style/hover-service.css +95 -0
  102. package/src/browser/style/status-bar.css +0 -49
  103. package/src/browser/view-container.ts +1 -1
  104. package/src/common/i18n/nls.metadata.json +7972 -6673
  105. package/src/common/objects.spec.ts +112 -0
  106. package/src/electron-browser/menu/electron-context-menu-renderer.ts +2 -2
  107. package/src/electron-browser/menu/electron-main-menu-factory.ts +14 -6
  108. package/src/electron-main/messaging/electron-messaging-contribution.ts +7 -2
  109. package/lib/browser/status-bar/status-bar-hover-manager.d.ts +0 -25
  110. package/lib/browser/status-bar/status-bar-hover-manager.d.ts.map +0 -1
  111. package/lib/browser/status-bar/status-bar-hover-manager.js +0 -126
  112. package/lib/browser/status-bar/status-bar-hover-manager.js.map +0 -1
  113. package/src/browser/status-bar/status-bar-hover-manager.ts +0 -113
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theia/core",
3
- "version": "1.32.0-next.5+000988a8c",
3
+ "version": "1.32.0",
4
4
  "description": "Theia is a cloud & desktop IDE framework implemented in TypeScript.",
5
5
  "main": "lib/common/index.js",
6
6
  "typings": "lib/common/index.d.ts",
@@ -16,8 +16,8 @@
16
16
  "@phosphor/signaling": "1",
17
17
  "@phosphor/virtualdom": "1",
18
18
  "@phosphor/widgets": "1",
19
- "@theia/application-package": "1.32.0-next.5+000988a8c",
20
- "@theia/request": "1.32.0-next.5+000988a8c",
19
+ "@theia/application-package": "1.32.0",
20
+ "@theia/request": "1.32.0",
21
21
  "@types/body-parser": "^1.16.4",
22
22
  "@types/cookie": "^0.3.3",
23
23
  "@types/dompurify": "^2.2.2",
@@ -195,12 +195,12 @@
195
195
  "watch": "theiaext watch"
196
196
  },
197
197
  "devDependencies": {
198
- "@theia/ext-scripts": "1.31.0",
199
- "@theia/re-exports": "1.31.0",
198
+ "@theia/ext-scripts": "1.32.0",
199
+ "@theia/re-exports": "1.32.0",
200
200
  "minimist": "^1.2.0"
201
201
  },
202
202
  "nyc": {
203
203
  "extends": "../../configs/nyc.json"
204
204
  },
205
- "gitHead": "000988a8ca23ef33909748f0bad976836f1d57c4"
205
+ "gitHead": "789d0148748b4dc9dea398520b5a3dd2998d71ec"
206
206
  }
@@ -18,7 +18,11 @@ import { injectable } from 'inversify';
18
18
  import { Disposable } from '../common';
19
19
  import { Emitter, Event } from '../common/event';
20
20
 
21
- export interface ContextKey<T> {
21
+ export type ContextKeyValue = null | undefined | boolean | number | string
22
+ | Array<null | undefined | boolean | number | string>
23
+ | Record<string, null | undefined | boolean | number | string>;
24
+
25
+ export interface ContextKey<T extends ContextKeyValue = ContextKeyValue> {
22
26
  set(value: T | undefined): void;
23
27
  reset(): void;
24
28
  get(): T | undefined;
@@ -38,15 +42,18 @@ export interface ContextKeyChangeEvent {
38
42
  }
39
43
 
40
44
  export const ContextKeyService = Symbol('ContextKeyService');
41
- export interface ContextKeyService extends Disposable {
42
- readonly onDidChange: Event<ContextKeyChangeEvent>;
43
-
44
- createKey<T>(key: string, defaultValue: T | undefined): ContextKey<T>;
45
45
 
46
+ export interface ContextMatcher extends Disposable {
46
47
  /**
47
48
  * Whether the expression is satisfied. If `context` provided, the service will attempt to retrieve a context object associated with that element.
48
49
  */
49
50
  match(expression: string, context?: HTMLElement): boolean;
51
+ }
52
+
53
+ export interface ContextKeyService extends ContextMatcher {
54
+ readonly onDidChange: Event<ContextKeyChangeEvent>;
55
+
56
+ createKey<T extends ContextKeyValue>(key: string, defaultValue: T | undefined): ContextKey<T>;
50
57
 
51
58
  /**
52
59
  * @returns a Set of the keys used by the given `expression` or `undefined` if none are used or the expression cannot be parsed.
@@ -54,8 +61,8 @@ export interface ContextKeyService extends Disposable {
54
61
  parseKeys(expression: string): Set<string> | undefined;
55
62
 
56
63
  /**
57
- * Creates a temporary context that will use the `values` passed in when evaluating `callback`
58
- * `callback` must be synchronous.
64
+ * Creates a temporary context that will use the `values` passed in when evaluating {@link callback}.
65
+ * {@link callback | The callback} must be synchronous.
59
66
  */
60
67
  with<T>(values: Record<string, unknown>, callback: () => T): T;
61
68
 
@@ -65,13 +72,19 @@ export interface ContextKeyService extends Disposable {
65
72
  */
66
73
  createScoped(target: HTMLElement): ScopedValueStore;
67
74
 
75
+ /**
76
+ * @param overlay values to be used in the new {@link ContextKeyService}. These values will be static.
77
+ * Creates a child service with a separate context and a set of fixed values to override parent values.
78
+ */
79
+ createOverlay(overlay: Iterable<[string, unknown]>): ContextMatcher;
80
+
68
81
  /**
69
82
  * Set or modify a value in the service's context.
70
83
  */
71
84
  setContext(key: string, value: unknown): void;
72
85
  }
73
86
 
74
- export type ScopedValueStore = Omit<ContextKeyService, 'onDidChange' | 'match' | 'parseKeys' | 'with'>;
87
+ export type ScopedValueStore = Omit<ContextKeyService, 'onDidChange' | 'match' | 'parseKeys' | 'with' | 'createOverlay'>;
75
88
 
76
89
  @injectable()
77
90
  export class ContextKeyServiceDummyImpl implements ContextKeyService {
@@ -82,7 +95,7 @@ export class ContextKeyServiceDummyImpl implements ContextKeyService {
82
95
  this.onDidChangeEmitter.fire(event);
83
96
  }
84
97
 
85
- createKey<T>(key: string, defaultValue: T | undefined): ContextKey<T> {
98
+ createKey<T extends ContextKeyValue>(key: string, defaultValue: T | undefined): ContextKey<T> {
86
99
  return ContextKey.None;
87
100
  }
88
101
  /**
@@ -114,6 +127,13 @@ export class ContextKeyServiceDummyImpl implements ContextKeyService {
114
127
  return this;
115
128
  }
116
129
 
130
+ /**
131
+ * Details should be implemented by an extension, e.g. the monaco extension.
132
+ */
133
+ createOverlay(overlay: Iterable<[string, unknown]>): ContextMatcher {
134
+ return this;
135
+ }
136
+
117
137
  /**
118
138
  * Details should be implemented by an extension, e.g. by the monaco extension.
119
139
  */
@@ -19,6 +19,7 @@
19
19
  import { injectable } from 'inversify';
20
20
  import { MenuPath } from '../common/menu';
21
21
  import { Disposable, DisposableCollection } from '../common/disposable';
22
+ import { ContextMatcher } from './context-key-service';
22
23
 
23
24
  export interface Coordinate { x: number; y: number; }
24
25
  export const Coordinate = Symbol('Coordinate');
@@ -117,5 +118,6 @@ export interface RenderContextMenuOptions {
117
118
  * of menu items registered for this item.
118
119
  */
119
120
  context?: HTMLElement;
121
+ contextKeyService?: ContextMatcher;
120
122
  onHide?: () => void;
121
123
  }
@@ -84,15 +84,15 @@ export const corePreferenceSchema: PreferenceSchema = {
84
84
  type: 'string',
85
85
  enum: ['classic', 'visible', 'hidden', 'compact'],
86
86
  markdownEnumDescriptions: [
87
- nls.localizeByDefault('Menu is only hidden in full screen mode.'),
88
- nls.localizeByDefault('Menu is always visible even in full screen mode.'),
87
+ nls.localizeByDefault('Menu is displayed at the top of the window and only hidden in full screen mode.'),
88
+ nls.localizeByDefault('Menu is always visible at the top of the window even in full screen mode.'),
89
89
  nls.localizeByDefault('Menu is always hidden.'),
90
90
  nls.localizeByDefault('Menu is displayed as a compact button in the sidebar. This value is ignored when `#window.titleBarStyle#` is `native`.')
91
91
  ],
92
92
  default: 'classic',
93
93
  scope: 'application',
94
94
  // eslint-disable-next-line max-len
95
- markdownDescription: nls.localizeByDefault("Control the visibility of the menu bar. A setting of 'toggle' means that the menu bar is hidden and a single press of the Alt key will show it. By default, the menu bar will be visible, unless the window is full screen."),
95
+ markdownDescription: nls.localizeByDefault('Control the visibility of the menu bar. A setting of \'toggle\' means that the menu bar is hidden and a single press of the Alt key will show it. A setting of \'compact\' will move the menu into the sidebar.'),
96
96
  included: !(isOSX && environment.electron.is())
97
97
  },
98
98
  'window.title': {
@@ -144,7 +144,7 @@ export const corePreferenceSchema: PreferenceSchema = {
144
144
  type: 'boolean',
145
145
  default: true,
146
146
  // eslint-disable-next-line max-len
147
- description: nls.localizeByDefault('Controls whether CA certificates should be loaded from the OS. (On Windows and macOS a reload of the window is required after turning this off.)'),
147
+ description: nls.localizeByDefault('Controls whether CA certificates should be loaded from the OS. (On Windows and macOS, a reload of the window is required after turning this off.)'),
148
148
  scope: 'application'
149
149
  },
150
150
  'workbench.list.openMode': {
@@ -155,7 +155,7 @@ export const corePreferenceSchema: PreferenceSchema = {
155
155
  ],
156
156
  default: 'singleClick',
157
157
  // eslint-disable-next-line max-len
158
- description: nls.localizeByDefault('Controls how to open items in trees and lists using the mouse (if supported). For parents with children in trees, this setting will control if a single click expands the parent or a double click. Note that some trees and lists might choose to ignore this setting if it is not applicable. ')
158
+ description: nls.localizeByDefault('Controls how to open items in trees and lists using the mouse (if supported). Note that some trees and lists might choose to ignore this setting if it is not applicable.')
159
159
  },
160
160
  'workbench.editor.highlightModifiedTabs': {
161
161
  'type': 'boolean',
@@ -222,19 +222,15 @@ export const corePreferenceSchema: PreferenceSchema = {
222
222
  default: 300,
223
223
  minimum: 0,
224
224
  maximum: 2000,
225
- // nls-todo: Will be available with VSCode API 1.55
226
- description: nls.localize('theia/core/sashDelay', 'Controls the hover feedback delay in milliseconds of the dragging area in between views/editors.')
225
+ description: nls.localizeByDefault('Controls the hover feedback delay in milliseconds of the dragging area in between views/editors.')
227
226
  },
228
227
  'workbench.sash.size': {
229
228
  type: 'number',
230
229
  default: 4,
231
230
  minimum: 1,
232
231
  maximum: 20,
233
- // nls-todo: Will be available with VSCode API 1.55
234
- description: nls.localize(
235
- 'theia/core/sashSize',
236
- 'Controls the feedback area size in pixels of the dragging area in between views/editors. Set it to a larger value if needed.'
237
- )
232
+ // eslint-disable-next-line max-len
233
+ description: nls.localizeByDefault('Controls the feedback area size in pixels of the dragging area in between views/editors. Set it to a larger value if you feel it\'s hard to resize views using the mouse.')
238
234
  },
239
235
  'workbench.tab.maximize': {
240
236
  type: 'boolean',
@@ -135,6 +135,7 @@ import { bindStatusBar } from './status-bar';
135
135
  import { MarkdownRenderer, MarkdownRendererFactory, MarkdownRendererImpl } from './markdown-rendering/markdown-renderer';
136
136
  import { StylingParticipant, StylingService } from './styling-service';
137
137
  import { bindCommonStylingParticipants } from './common-styling-participants';
138
+ import { HoverService } from './hover-service';
138
139
 
139
140
  export { bindResourceProvider, bindMessageService, bindPreferenceService };
140
141
 
@@ -186,7 +187,8 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is
186
187
  const selectionService = container.get(SelectionService);
187
188
  const commandService = container.get<CommandService>(CommandService);
188
189
  const corePreferences = container.get<CorePreferences>(CorePreferences);
189
- return new TabBarRenderer(contextMenuRenderer, tabBarDecoratorService, iconThemeService, selectionService, commandService, corePreferences);
190
+ const hoverService = container.get(HoverService);
191
+ return new TabBarRenderer(contextMenuRenderer, tabBarDecoratorService, iconThemeService, selectionService, commandService, corePreferences, hoverService);
190
192
  });
191
193
  bind(TheiaDockPanel.Factory).toFactory(({ container }) => options => {
192
194
  const corePreferences = container.get<CorePreferences>(CorePreferences);
@@ -433,6 +435,8 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is
433
435
  bind(SaveResourceService).toSelf().inSingletonScope();
434
436
  bind(UserWorkingDirectoryProvider).toSelf().inSingletonScope();
435
437
 
438
+ bind(HoverService).toSelf().inSingletonScope();
439
+
436
440
  bind(StylingService).toSelf().inSingletonScope();
437
441
  bindContributionProvider(bind, StylingParticipant);
438
442
  bind(FrontendApplicationContribution).toService(StylingService);
@@ -0,0 +1,189 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2022 Ericsson 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 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { inject, injectable } from 'inversify';
18
+ import { Disposable, DisposableCollection, disposableTimeout, isOSX } from '../common';
19
+ import { MarkdownString } from '../common/markdown-rendering/markdown-string';
20
+ import { animationFrame } from './browser';
21
+ import { MarkdownRenderer, MarkdownRendererFactory } from './markdown-rendering/markdown-renderer';
22
+ import { PreferenceService } from './preferences';
23
+
24
+ import '../../src/browser/style/hover-service.css';
25
+
26
+ export type HoverPosition = 'left' | 'right' | 'top' | 'bottom';
27
+
28
+ export namespace HoverPosition {
29
+ export function invertIfNecessary(position: HoverPosition, target: DOMRect, host: DOMRect, totalWidth: number, totalHeight: number): HoverPosition {
30
+ if (position === 'left') {
31
+ if (target.left - host.width - 5 < 0) {
32
+ return 'right';
33
+ }
34
+ } else if (position === 'right') {
35
+ if (target.right + host.width + 5 > totalWidth) {
36
+ return 'left';
37
+ }
38
+ } else if (position === 'top') {
39
+ if (target.top - host.height - 5 < 0) {
40
+ return 'bottom';
41
+ }
42
+ } else if (position === 'bottom') {
43
+ if (target.bottom + host.height + 5 > totalHeight) {
44
+ return 'top';
45
+ }
46
+ }
47
+ return position;
48
+ }
49
+ }
50
+
51
+ export interface HoverRequest {
52
+ content: string | MarkdownString | HTMLElement
53
+ target: HTMLElement
54
+ /**
55
+ * The position where the hover should appear.
56
+ * Note that the hover service will try to invert the position (i.e. right -> left)
57
+ * if the specified content does not fit in the window next to the target element
58
+ */
59
+ position: HoverPosition
60
+ }
61
+
62
+ @injectable()
63
+ export class HoverService {
64
+ protected static hostClassName = 'theia-hover';
65
+ protected static styleSheetId = 'theia-hover-style';
66
+ @inject(PreferenceService) protected readonly preferences: PreferenceService;
67
+ @inject(MarkdownRendererFactory) protected readonly markdownRendererFactory: MarkdownRendererFactory;
68
+
69
+ protected _markdownRenderer: MarkdownRenderer | undefined;
70
+ protected get markdownRenderer(): MarkdownRenderer {
71
+ this._markdownRenderer ||= this.markdownRendererFactory();
72
+ return this._markdownRenderer;
73
+ }
74
+
75
+ protected _hoverHost: HTMLElement | undefined;
76
+ protected get hoverHost(): HTMLElement {
77
+ if (!this._hoverHost) {
78
+ this._hoverHost = document.createElement('div');
79
+ this._hoverHost.classList.add(HoverService.hostClassName);
80
+ this._hoverHost.style.position = 'absolute';
81
+ }
82
+ return this._hoverHost;
83
+ }
84
+ protected pendingTimeout: Disposable | undefined;
85
+ protected hoverTarget: HTMLElement | undefined;
86
+ protected lastHidHover = Date.now();
87
+ protected readonly disposeOnHide = new DisposableCollection();
88
+
89
+ requestHover(request: HoverRequest): void {
90
+ if (request.target !== this.hoverTarget) {
91
+ this.cancelHover();
92
+ this.pendingTimeout = disposableTimeout(() => this.renderHover(request), this.getHoverDelay());
93
+ }
94
+ }
95
+
96
+ protected getHoverDelay(): number {
97
+ return Date.now() - this.lastHidHover < 200
98
+ ? 0
99
+ : this.preferences.get('workbench.hover.delay', isOSX ? 1500 : 500);
100
+ }
101
+
102
+ protected async renderHover(request: HoverRequest): Promise<void> {
103
+ const host = this.hoverHost;
104
+ const { target, content, position } = request;
105
+ this.hoverTarget = target;
106
+ if (content instanceof HTMLElement) {
107
+ host.appendChild(content);
108
+ } else if (typeof content === 'string') {
109
+ host.textContent = content;
110
+ } else {
111
+ const renderedContent = this.markdownRenderer.render(content);
112
+ this.disposeOnHide.push(renderedContent);
113
+ host.appendChild(renderedContent.element);
114
+ }
115
+ // browsers might insert linebreaks when the hover appears at the edge of the window
116
+ // resetting the position prevents that
117
+ host.style.left = '0px';
118
+ host.style.top = '0px';
119
+ document.body.append(host);
120
+ await animationFrame(); // Allow the browser to size the host
121
+ const updatedPosition = this.setHostPosition(target, host, position);
122
+
123
+ this.disposeOnHide.push({
124
+ dispose: () => {
125
+ this.lastHidHover = Date.now();
126
+ host.classList.remove(updatedPosition);
127
+ }
128
+ });
129
+
130
+ this.listenForMouseOut();
131
+ }
132
+
133
+ protected setHostPosition(target: HTMLElement, host: HTMLElement, position: HoverPosition): HoverPosition {
134
+ const targetDimensions = target.getBoundingClientRect();
135
+ const hostDimensions = host.getBoundingClientRect();
136
+ const documentWidth = document.body.getBoundingClientRect().width;
137
+ // document.body.getBoundingClientRect().height doesn't work as expected
138
+ // scrollHeight will always be accurate here: https://stackoverflow.com/a/44077777
139
+ const documentHeight = document.documentElement.scrollHeight;
140
+ position = HoverPosition.invertIfNecessary(position, targetDimensions, hostDimensions, documentWidth, documentHeight);
141
+ if (position === 'top' || position === 'bottom') {
142
+ const targetMiddleWidth = targetDimensions.left + (targetDimensions.width / 2);
143
+ const middleAlignment = targetMiddleWidth - (hostDimensions.width / 2);
144
+ const furthestRight = Math.min(documentWidth - hostDimensions.width, middleAlignment);
145
+ const left = Math.max(0, furthestRight);
146
+ const top = position === 'top'
147
+ ? targetDimensions.top - hostDimensions.height - 5
148
+ : targetDimensions.bottom + 5;
149
+ host.style.setProperty('--theia-hover-before-position', `${targetMiddleWidth - left - 5}px`);
150
+ host.style.top = `${top}px`;
151
+ host.style.left = `${left}px`;
152
+ } else {
153
+ const targetMiddleHeight = targetDimensions.top + (targetDimensions.height / 2);
154
+ const middleAlignment = targetMiddleHeight - (hostDimensions.height / 2);
155
+ const furthestTop = Math.min(documentHeight - hostDimensions.height, middleAlignment);
156
+ const top = Math.max(0, furthestTop);
157
+ const left = position === 'left'
158
+ ? targetDimensions.left - hostDimensions.width - 5
159
+ : targetDimensions.right + 5;
160
+ host.style.setProperty('--theia-hover-before-position', `${targetMiddleHeight - top - 5}px`);
161
+ host.style.left = `${left}px`;
162
+ host.style.top = `${top}px`;
163
+ }
164
+ host.classList.add(position);
165
+ return position;
166
+ }
167
+
168
+ protected listenForMouseOut(): void {
169
+ const handleMouseMove = (e: MouseEvent) => {
170
+ if (e.target instanceof Node && !this.hoverHost.contains(e.target) && !this.hoverTarget?.contains(e.target)) {
171
+ this.cancelHover();
172
+ }
173
+ };
174
+ document.addEventListener('mousemove', handleMouseMove);
175
+ this.disposeOnHide.push({ dispose: () => document.removeEventListener('mousemove', handleMouseMove) });
176
+ }
177
+
178
+ cancelHover(): void {
179
+ this.pendingTimeout?.dispose();
180
+ this.unRenderHover();
181
+ this.disposeOnHide.dispose();
182
+ this.hoverTarget = undefined;
183
+ }
184
+
185
+ protected unRenderHover(): void {
186
+ this.hoverHost.remove();
187
+ this.hoverHost.replaceChildren();
188
+ }
189
+ }
@@ -67,7 +67,7 @@ export class NoneIconTheme implements IconTheme, LabelProviderContribution {
67
67
  if (this.toDeactivate.disposed) {
68
68
  return 0;
69
69
  }
70
- return Number.MAX_SAFE_INTEGER;
70
+ return Number.MAX_SAFE_INTEGER - 1024;
71
71
  }
72
72
 
73
73
  getIcon(): string {
@@ -43,3 +43,4 @@ export * from './breadcrumbs';
43
43
  export * from './tooltip-service';
44
44
  export * from './decoration-style';
45
45
  export * from './styling-service';
46
+ export * from './hover-service';
@@ -34,8 +34,8 @@ export class BrowserContextMenuRenderer extends ContextMenuRenderer {
34
34
  super();
35
35
  }
36
36
 
37
- protected doRender({ menuPath, anchor, args, onHide, context }: RenderContextMenuOptions): ContextMenuAccess {
38
- const contextMenu = this.menuFactory.createContextMenu(menuPath, args, context);
37
+ protected doRender({ menuPath, anchor, args, onHide, context, contextKeyService }: RenderContextMenuOptions): ContextMenuAccess {
38
+ const contextMenu = this.menuFactory.createContextMenu(menuPath, args, context, contextKeyService);
39
39
  const { x, y } = coordinateFromAnchor(anchor);
40
40
  if (onHide) {
41
41
  contextMenu.aboutToClose.connect(() => onHide!());
@@ -23,7 +23,7 @@ import {
23
23
  } from '../../common';
24
24
  import { KeybindingRegistry } from '../keybinding';
25
25
  import { FrontendApplicationContribution, FrontendApplication } from '../frontend-application';
26
- import { ContextKeyService } from '../context-key-service';
26
+ import { ContextKeyService, ContextMatcher } from '../context-key-service';
27
27
  import { ContextMenuContext } from './context-menu-context';
28
28
  import { waitForRevealed } from '../widgets';
29
29
  import { ApplicationShell } from '../shell';
@@ -38,6 +38,7 @@ export abstract class MenuBarWidget extends MenuBar {
38
38
  export interface BrowserMenuOptions extends MenuWidget.IOptions {
39
39
  commands: MenuCommandRegistry,
40
40
  context?: HTMLElement,
41
+ contextKeyService?: ContextMatcher;
41
42
  rootMenuPath: MenuPath
42
43
  };
43
44
 
@@ -107,10 +108,10 @@ export class BrowserMainMenuFactory implements MenuWidgetFactory {
107
108
  }
108
109
  }
109
110
 
110
- createContextMenu(path: MenuPath, args?: unknown[], context?: HTMLElement): MenuWidget {
111
+ createContextMenu(path: MenuPath, args?: unknown[], context?: HTMLElement, contextKeyService?: ContextMatcher): MenuWidget {
111
112
  const menuModel = this.menuProvider.getMenu(path);
112
113
  const menuCommandRegistry = this.createMenuCommandRegistry(menuModel, args).snapshot(path);
113
- const contextMenu = this.createMenuWidget(menuModel, { commands: menuCommandRegistry, context, rootMenuPath: path });
114
+ const contextMenu = this.createMenuWidget(menuModel, { commands: menuCommandRegistry, context, rootMenuPath: path, contextKeyService });
114
115
  return contextMenu;
115
116
  }
116
117
 
@@ -286,7 +287,9 @@ export class DynamicMenuWidget extends MenuWidget {
286
287
  }
287
288
 
288
289
  protected buildSubMenus(parentItems: MenuWidget.IItemOptions[], menu: MenuNode, commands: MenuCommandRegistry): MenuWidget.IItemOptions[] {
289
- if (CompoundMenuNode.is(menu) && menu.children.length && this.undefinedOrMatch(menu.when, this.options.context)) {
290
+ if (CompoundMenuNode.is(menu)
291
+ && menu.children.length
292
+ && this.undefinedOrMatch(this.options.contextKeyService ?? this.services.contextKeyService, menu.when, this.options.context)) {
290
293
  const role = menu === this.menu ? CompoundMenuNodeRole.Group : CompoundMenuNode.getRole(menu);
291
294
  if (role === CompoundMenuNodeRole.Submenu) {
292
295
  const submenu = this.services.menuWidgetFactory.createMenuWidget(menu, this.options);
@@ -307,7 +310,7 @@ export class DynamicMenuWidget extends MenuWidget {
307
310
  }
308
311
  } else if (menu.command) {
309
312
  const node = menu.altNode && this.services.context.altPressed ? menu.altNode : (menu as MenuNode & CommandMenuNode);
310
- if (commands.isVisible(node.command) && this.undefinedOrMatch(node.when, this.options.context)) {
313
+ if (commands.isVisible(node.command) && this.undefinedOrMatch(this.options.contextKeyService ?? this.services.contextKeyService, node.when, this.options.context)) {
311
314
  parentItems.push({
312
315
  command: node.command,
313
316
  type: 'command'
@@ -317,8 +320,8 @@ export class DynamicMenuWidget extends MenuWidget {
317
320
  return parentItems;
318
321
  }
319
322
 
320
- protected undefinedOrMatch(expression?: string, context?: HTMLElement): boolean {
321
- if (expression) { return this.services.contextKeyService.match(expression, context); }
323
+ protected undefinedOrMatch(contextKeyService: ContextMatcher, expression?: string, context?: HTMLElement): boolean {
324
+ if (expression) { return contextKeyService.match(expression, context); }
322
325
  return true;
323
326
  }
324
327
 
@@ -16,7 +16,6 @@
16
16
 
17
17
  import { injectable, inject, postConstruct } from 'inversify';
18
18
  import URI from '../common/uri';
19
- import { URI as Uri } from 'vscode-uri';
20
19
  import { ContextKeyService, ContextKey } from './context-key-service';
21
20
  import { LanguageService } from './language-service';
22
21
 
@@ -29,7 +28,7 @@ export class ResourceContextKey {
29
28
  @inject(ContextKeyService)
30
29
  protected readonly contextKeyService: ContextKeyService;
31
30
 
32
- protected resource: ContextKey<Uri>;
31
+ protected resource: ContextKey<string>;
33
32
  protected resourceSchemeKey: ContextKey<string>;
34
33
  protected resourceFileName: ContextKey<string>;
35
34
  protected resourceExtname: ContextKey<string>;
@@ -40,7 +39,7 @@ export class ResourceContextKey {
40
39
 
41
40
  @postConstruct()
42
41
  protected init(): void {
43
- this.resource = this.contextKeyService.createKey<Uri>('resource', undefined);
42
+ this.resource = this.contextKeyService.createKey<string>('resource', undefined);
44
43
  this.resourceSchemeKey = this.contextKeyService.createKey<string>('resourceScheme', undefined);
45
44
  this.resourceFileName = this.contextKeyService.createKey<string>('resourceFilename', undefined);
46
45
  this.resourceExtname = this.contextKeyService.createKey<string>('resourceExtname', undefined);
@@ -50,13 +49,12 @@ export class ResourceContextKey {
50
49
  this.resourceSet = this.contextKeyService.createKey<boolean>('resourceSet', false);
51
50
  }
52
51
 
53
- get(): URI | undefined {
54
- const codeUri = this.resource.get();
55
- return codeUri && new URI(codeUri);
52
+ get(): string | undefined {
53
+ return this.resource.get();
56
54
  }
57
55
 
58
56
  set(resourceUri: URI | undefined): void {
59
- this.resource.set(resourceUri?.['codeUri']);
57
+ this.resource.set(resourceUri?.toString());
60
58
  this.resourceSchemeKey.set(resourceUri?.scheme);
61
59
  this.resourceFileName.set(resourceUri?.path.base);
62
60
  this.resourceExtname.set(resourceUri?.path.ext);
@@ -309,8 +309,8 @@ export class ApplicationShell extends Widget {
309
309
  }
310
310
 
311
311
  protected initFocusKeyContexts(): void {
312
- const sideBarFocus = this.contextKeyService.createKey('sideBarFocus', false);
313
- const panelFocus = this.contextKeyService.createKey('panelFocus', false);
312
+ const sideBarFocus = this.contextKeyService.createKey<boolean>('sideBarFocus', false);
313
+ const panelFocus = this.contextKeyService.createKey<boolean>('panelFocus', false);
314
314
  const updateFocusContextKeys = () => {
315
315
  const area = this.activeWidget && this.getAreaFor(this.activeWidget);
316
316
  sideBarFocus.set(area === 'left');
@@ -1849,6 +1849,13 @@ export class ApplicationShell extends Widget {
1849
1849
  }
1850
1850
  return true;
1851
1851
  } else if (ci === 0) {
1852
+ if (current && current.titles.length > 0) {
1853
+ current.currentIndex = current.titles.length - 1;
1854
+ if (current.currentTitle) {
1855
+ this.activateWidget(current.currentTitle.owner.id);
1856
+ }
1857
+ return true;
1858
+ }
1852
1859
  return this.activatePreviousTabBar(current);
1853
1860
  }
1854
1861
  }