@theia/core 1.67.0-next.3 → 1.67.0-next.59

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 (82) hide show
  1. package/README.md +14 -12
  2. package/lib/browser/authentication-service.d.ts +1 -0
  3. package/lib/browser/authentication-service.d.ts.map +1 -1
  4. package/lib/browser/authentication-service.js.map +1 -1
  5. package/lib/browser/badges/badge-service.d.ts +14 -0
  6. package/lib/browser/badges/badge-service.d.ts.map +1 -0
  7. package/lib/browser/badges/badge-service.js +45 -0
  8. package/lib/browser/badges/badge-service.js.map +1 -0
  9. package/lib/browser/badges/index.d.ts +3 -0
  10. package/lib/browser/badges/index.d.ts.map +1 -0
  11. package/lib/browser/badges/index.js +21 -0
  12. package/lib/browser/badges/index.js.map +1 -0
  13. package/lib/browser/badges/tabbar-badge-decorator.d.ts +14 -0
  14. package/lib/browser/badges/tabbar-badge-decorator.d.ts.map +1 -0
  15. package/lib/browser/badges/tabbar-badge-decorator.js +68 -0
  16. package/lib/browser/badges/tabbar-badge-decorator.js.map +1 -0
  17. package/lib/browser/catalog.json +57 -42
  18. package/lib/browser/context-key-service.d.ts +4 -1
  19. package/lib/browser/context-key-service.d.ts.map +1 -1
  20. package/lib/browser/context-key-service.js +1 -0
  21. package/lib/browser/context-key-service.js.map +1 -1
  22. package/lib/browser/dialogs.d.ts.map +1 -1
  23. package/lib/browser/dialogs.js +2 -1
  24. package/lib/browser/dialogs.js.map +1 -1
  25. package/lib/browser/frontend-application-module.d.ts.map +1 -1
  26. package/lib/browser/frontend-application-module.js +2 -0
  27. package/lib/browser/frontend-application-module.js.map +1 -1
  28. package/lib/browser/index.d.ts +3 -0
  29. package/lib/browser/index.d.ts.map +1 -1
  30. package/lib/browser/index.js +3 -0
  31. package/lib/browser/index.js.map +1 -1
  32. package/lib/browser/markdown-rendering/markdown-renderer.d.ts.map +1 -1
  33. package/lib/browser/markdown-rendering/markdown-renderer.js +2 -1
  34. package/lib/browser/markdown-rendering/markdown-renderer.js.map +1 -1
  35. package/lib/browser/markdown-rendering/markdown.d.ts +127 -0
  36. package/lib/browser/markdown-rendering/markdown.d.ts.map +1 -0
  37. package/lib/browser/markdown-rendering/markdown.js +188 -0
  38. package/lib/browser/markdown-rendering/markdown.js.map +1 -0
  39. package/lib/browser/markdown-rendering/markdown.spec.d.ts +2 -0
  40. package/lib/browser/markdown-rendering/markdown.spec.d.ts.map +1 -0
  41. package/lib/browser/markdown-rendering/markdown.spec.js +243 -0
  42. package/lib/browser/markdown-rendering/markdown.spec.js.map +1 -0
  43. package/lib/browser/shell/tab-bars.d.ts +1 -1
  44. package/lib/browser/shell/tab-bars.d.ts.map +1 -1
  45. package/lib/browser/shell/tab-bars.js +52 -49
  46. package/lib/browser/shell/tab-bars.js.map +1 -1
  47. package/lib/browser/widgets/select-component.d.ts +1 -1
  48. package/lib/browser/widgets/select-component.d.ts.map +1 -1
  49. package/lib/browser/widgets/select-component.js +6 -2
  50. package/lib/browser/widgets/select-component.js.map +1 -1
  51. package/lib/browser/widgets/widget.d.ts +1 -0
  52. package/lib/browser/widgets/widget.d.ts.map +1 -1
  53. package/lib/browser/widgets/widget.js +5 -0
  54. package/lib/browser/widgets/widget.js.map +1 -1
  55. package/lib/common/markdown-rendering/markdown-string.d.ts +1 -1
  56. package/lib/common/markdown-rendering/markdown-string.d.ts.map +1 -1
  57. package/lib/common/markdown-rendering/markdown-string.js.map +1 -1
  58. package/lib/common/theme.d.ts +2 -0
  59. package/lib/common/theme.d.ts.map +1 -1
  60. package/lib/common/theme.js +14 -1
  61. package/lib/common/theme.js.map +1 -1
  62. package/package.json +21 -16
  63. package/shared/markdown-it-anchor/index.d.ts +2 -0
  64. package/shared/markdown-it-anchor/index.js +1 -0
  65. package/shared/markdown-it-emoji/index.d.ts +2 -0
  66. package/shared/markdown-it-emoji/index.js +1 -0
  67. package/src/browser/authentication-service.ts +1 -0
  68. package/src/browser/badges/badge-service.ts +44 -0
  69. package/src/browser/badges/index.ts +18 -0
  70. package/src/browser/badges/tabbar-badge-decorator.ts +63 -0
  71. package/src/browser/context-key-service.ts +5 -1
  72. package/src/browser/dialogs.ts +3 -2
  73. package/src/browser/frontend-application-module.ts +2 -0
  74. package/src/browser/index.ts +3 -0
  75. package/src/browser/markdown-rendering/markdown-renderer.ts +2 -1
  76. package/src/browser/markdown-rendering/markdown.spec.tsx +371 -0
  77. package/src/browser/markdown-rendering/markdown.tsx +282 -0
  78. package/src/browser/shell/tab-bars.ts +24 -26
  79. package/src/browser/widgets/select-component.tsx +6 -2
  80. package/src/browser/widgets/widget.ts +6 -0
  81. package/src/common/markdown-rendering/markdown-string.ts +1 -1
  82. package/src/common/theme.ts +13 -0
@@ -0,0 +1,282 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 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 * as React from 'react';
18
+ import { MarkdownRenderer, MarkdownRenderResult } from './markdown-renderer';
19
+ import { MarkdownString, MarkdownStringImpl } from '../../common/markdown-rendering/markdown-string';
20
+ import { nls } from '../../common/nls';
21
+ import { FormatType } from '../../common/i18n/localization';
22
+
23
+ export interface MarkdownProps {
24
+ /**
25
+ * The markdown content to render. Can be a string, a MarkdownString, or undefined.
26
+ * If undefined or empty, an empty div will be rendered.
27
+ */
28
+ markdown?: string | MarkdownString;
29
+
30
+ /**
31
+ * The MarkdownRenderer instance to use for rendering.
32
+ */
33
+ markdownRenderer: MarkdownRenderer;
34
+
35
+ /**
36
+ * Additional CSS class name(s) to apply to the container element.
37
+ */
38
+ className?: string;
39
+
40
+ /**
41
+ * Options to pass to MarkdownStringImpl if markdown is a string.
42
+ * Common options include:
43
+ * - supportHtml: Allow HTML in markdown (default: false)
44
+ * - supportThemeIcons: Allow theme icons (default: false)
45
+ * - isTrusted: Trust level for command execution (default: false)
46
+ */
47
+ markdownOptions?: {
48
+ supportHtml?: boolean;
49
+ supportThemeIcons?: boolean;
50
+ isTrusted?: boolean | { enabledCommands: string[] };
51
+ };
52
+
53
+ /**
54
+ * Optional callback that receives the rendered HTML element.
55
+ * Useful for post-processing or adding event listeners.
56
+ * Receives undefined when content is empty.
57
+ */
58
+ onRender?: (element: HTMLElement | undefined) => void;
59
+ }
60
+
61
+ /**
62
+ * A React component for rendering markdown content.
63
+ *
64
+ * @example Basic usage
65
+ * ```tsx
66
+ * const MyComponent = ({ markdownRenderer }: { markdownRenderer: MarkdownRenderer }) => {
67
+ * return (
68
+ * <Markdown
69
+ * markdown="Hello **World**!"
70
+ * markdownRenderer={markdownRenderer}
71
+ * className="my-content"
72
+ * markdownOptions={{ supportHtml: true }}
73
+ * />
74
+ * );
75
+ * };
76
+ * ```
77
+ *
78
+ * @example With localized content
79
+ * ```tsx
80
+ * const MyComponent = ({ markdownRenderer }: { markdownRenderer: MarkdownRenderer }) => {
81
+ * const content = nls.localize('my.key', 'Hello **{0}**!', 'World');
82
+ *
83
+ * return (
84
+ * <Markdown
85
+ * markdown={content}
86
+ * markdownRenderer={markdownRenderer}
87
+ * className="my-content"
88
+ * />
89
+ * );
90
+ * };
91
+ * ```
92
+ *
93
+ * @example With command links
94
+ * ```tsx
95
+ * const content = nls.localize(
96
+ * 'my.key',
97
+ * 'Open [settings]({0}) to configure.',
98
+ * `command:${CommonCommands.OPEN_PREFERENCES.id}`
99
+ * );
100
+ *
101
+ * return (
102
+ * <Markdown
103
+ * markdown={content}
104
+ * markdownRenderer={markdownRenderer}
105
+ * markdownOptions={{
106
+ * isTrusted: { enabledCommands: [CommonCommands.OPEN_PREFERENCES.id] }
107
+ * }}
108
+ * />
109
+ * );
110
+ * ```
111
+ */
112
+ const MarkdownComponent: React.FC<MarkdownProps> = ({
113
+ markdown,
114
+ markdownRenderer,
115
+ className,
116
+ markdownOptions,
117
+ onRender
118
+ }) => {
119
+ const ref = useMarkdown(markdown, markdownRenderer, markdownOptions, onRender);
120
+ return <div className={className} ref={ref} />;
121
+ };
122
+ MarkdownComponent.displayName = 'Markdown';
123
+
124
+ export const Markdown = React.memo(MarkdownComponent);
125
+
126
+ /**
127
+ * A React hook for rendering markdown content.
128
+ *
129
+ * This hook integrates MarkdownRenderer with React's lifecycle,
130
+ * ensuring that:
131
+ * - Markdown is rendered only when content or renderer changes
132
+ * - MarkdownRenderResult is properly disposed when component unmounts
133
+ * - DOM elements are correctly managed by React
134
+ * - Event listeners and other imperative DOM operations are preserved
135
+ *
136
+ * Returns a ref that should be attached to a DOM element.
137
+ *
138
+ * @example Basic usage
139
+ * ```tsx
140
+ * const MyComponent = ({ markdownRenderer }: { markdownRenderer: MarkdownRenderer }) => {
141
+ * const ref = useMarkdown('Hello **World**!', markdownRenderer, { supportHtml: true });
142
+ * return <div className="my-content" ref={ref} />;
143
+ * };
144
+ * ```
145
+ *
146
+ * @example With localized content
147
+ * ```tsx
148
+ * const MyComponent = ({ markdownRenderer }: { markdownRenderer: MarkdownRenderer }) => {
149
+ * const content = nls.localize('my.key', 'Hello **{0}**!', 'World');
150
+ * const ref = useMarkdown(content, markdownRenderer);
151
+ * return <div className="my-content" ref={ref} />;
152
+ * };
153
+ * ```
154
+ */
155
+ export function useMarkdown(
156
+ markdown: string | MarkdownString | undefined,
157
+ markdownRenderer: MarkdownRenderer,
158
+ markdownOptions?: MarkdownProps['markdownOptions'],
159
+ onRender?: (element: HTMLElement | undefined) => void
160
+ ): React.RefObject<HTMLDivElement> {
161
+ // eslint-disable-next-line no-null/no-null
162
+ const containerRef = React.useRef<HTMLDivElement>(null);
163
+ const renderResultRef = React.useRef<MarkdownRenderResult | undefined>();
164
+
165
+ const renderedElement = React.useMemo(() => {
166
+ renderResultRef.current?.dispose();
167
+ renderResultRef.current = undefined;
168
+
169
+ if (!markdown || (typeof markdown === 'string' && markdown.trim() === '')) {
170
+ return undefined;
171
+ }
172
+
173
+ const markdownString = typeof markdown === 'string'
174
+ ? new MarkdownStringImpl(markdown, markdownOptions)
175
+ : markdown;
176
+
177
+ const rendered = markdownRenderer.render(markdownString);
178
+ renderResultRef.current = rendered;
179
+
180
+ return rendered.element;
181
+ }, [markdown, markdownRenderer, markdownOptions]);
182
+
183
+ React.useEffect(() => {
184
+ if (containerRef.current && renderedElement) {
185
+ containerRef.current.replaceChildren(renderedElement);
186
+ onRender?.(renderedElement);
187
+ } else if (containerRef.current && !renderedElement) {
188
+ containerRef.current.replaceChildren();
189
+ onRender?.(undefined);
190
+ }
191
+ }, [renderedElement, onRender]);
192
+
193
+ React.useEffect(() => () => {
194
+ renderResultRef.current?.dispose();
195
+ }, []);
196
+
197
+ return containerRef;
198
+ }
199
+
200
+ export interface LocalizedMarkdownProps extends MarkdownProps {
201
+ /**
202
+ * The localization key for the markdown content.
203
+ */
204
+ localizationKey: string;
205
+
206
+ /**
207
+ * The default markdown content (in English) with placeholders.
208
+ * Use {0}, {1}, etc. for parameter substitution.
209
+ */
210
+ defaultMarkdown: string;
211
+
212
+ /**
213
+ * Arguments to substitute into the markdown template.
214
+ * Can be strings, numbers, booleans, or undefined.
215
+ */
216
+ args?: FormatType[];
217
+ }
218
+
219
+ /**
220
+ * A React component that combines localization with markdown rendering.
221
+ *
222
+ * This component automatically handles the localization of markdown content using `nls.localize`
223
+ * and then renders it using the Markdown component.
224
+ *
225
+ * @example Basic usage
226
+ * ```tsx
227
+ * <LocalizedMarkdown
228
+ * localizationKey="theia/mypackage/welcome"
229
+ * defaultMarkdown="Welcome to **Theia**!"
230
+ * markdownRenderer={this.markdownRenderer}
231
+ * className="welcome-message"
232
+ * />
233
+ * ```
234
+ *
235
+ * @example With parameters
236
+ * ```tsx
237
+ * <LocalizedMarkdown
238
+ * localizationKey="theia/mypackage/greeting"
239
+ * defaultMarkdown="Hello **{0}**! You have {1} new messages."
240
+ * args={['Alice', 5]}
241
+ * markdownRenderer={this.markdownRenderer}
242
+ * />
243
+ * ```
244
+ *
245
+ * @example With command links
246
+ * ```tsx
247
+ * <LocalizedMarkdown
248
+ * localizationKey="theia/mypackage/settings"
249
+ * defaultMarkdown="Open [settings]({0}) to configure."
250
+ * args={[`command:${CommonCommands.OPEN_PREFERENCES.id}`]}
251
+ * markdownRenderer={this.markdownRenderer}
252
+ * markdownOptions={{
253
+ * isTrusted: { enabledCommands: [CommonCommands.OPEN_PREFERENCES.id] }
254
+ * }}
255
+ * />
256
+ * ```
257
+ */
258
+ export const LocalizedMarkdown: React.FC<LocalizedMarkdownProps> = ({
259
+ localizationKey,
260
+ defaultMarkdown,
261
+ args = [],
262
+ markdownRenderer,
263
+ className,
264
+ markdownOptions,
265
+ onRender
266
+ }) => {
267
+ const localizedMarkdown = React.useMemo(
268
+ () => nls.localize(localizationKey, defaultMarkdown, ...args),
269
+ [localizationKey, defaultMarkdown, ...args]
270
+ );
271
+
272
+ return (
273
+ <Markdown
274
+ markdown={localizedMarkdown}
275
+ markdownRenderer={markdownRenderer}
276
+ className={className}
277
+ markdownOptions={markdownOptions}
278
+ onRender={onRender}
279
+ />
280
+ );
281
+ };
282
+ LocalizedMarkdown.displayName = 'LocalizedMarkdown';
@@ -183,11 +183,9 @@ export class TabBarRenderer extends TabBar.Renderer {
183
183
  ? nls.localizeByDefault('Unpin')
184
184
  : nls.localizeByDefault('Close');
185
185
 
186
- const hover = isHorizontal && this.corePreferences?.['window.tabbar.enhancedPreview'] === 'classic'
187
- ? { title: title.caption }
188
- : {
189
- onmouseenter: this.handleMouseEnterEvent
190
- };
186
+ const hover = {
187
+ onmouseenter: this.handleMouseEnterEvent
188
+ };
191
189
 
192
190
  const tabLabel = h.div(
193
191
  { className: 'theia-tab-icon-label' },
@@ -550,7 +548,7 @@ export class TabBarRenderer extends TabBar.Renderer {
550
548
  return h.div({ className: baseClassName, style }, data.title.iconLabel);
551
549
  }
552
550
 
553
- protected renderEnhancedPreview = (title: Title<Widget>) => {
551
+ protected renderEnhancedPreview(title: Title<Widget>): HTMLDivElement {
554
552
  const hoverBox = document.createElement('div');
555
553
  hoverBox.classList.add('theia-horizontal-tabBar-hover-div');
556
554
  const labelElement = document.createElement('p');
@@ -625,26 +623,26 @@ export class TabBarRenderer extends TabBar.Renderer {
625
623
  }
626
624
 
627
625
  protected handleMouseEnterEvent = (event: MouseEvent) => {
628
- if (this.tabBar && this.hoverService && event.currentTarget instanceof HTMLElement) {
629
- const id = event.currentTarget.id;
630
- const title = this.tabBar.titles.find(t => this.createTabId(t) === id);
631
- if (title) {
632
- if (this.tabBar.orientation === 'horizontal') {
633
- this.hoverService.requestHover({
634
- content: this.renderEnhancedPreview(title),
635
- target: event.currentTarget,
636
- position: 'bottom',
637
- cssClasses: ['extended-tab-preview'],
638
- visualPreview: this.corePreferences?.['window.tabbar.enhancedPreview'] === 'visual' ? width => this.renderVisualPreview(width, title) : undefined
639
- });
640
- } else if (title.caption) {
641
- this.hoverService.requestHover({
642
- content: title.caption,
643
- target: event.currentTarget,
644
- position: 'right'
645
- });
646
- }
647
- }
626
+ if (!this.tabBar || !this.hoverService || !(event.currentTarget instanceof HTMLElement)) { return; }
627
+ const id = event.currentTarget.id;
628
+ const title = this.tabBar.titles.find(t => this.createTabId(t) === id);
629
+ if (!title) { return; }
630
+ if (this.tabBar.orientation === 'horizontal' && this.corePreferences?.['window.tabbar.enhancedPreview'] !== 'classic' && EnhancedPreviewWidget.is(title.owner)) {
631
+ this.hoverService.requestHover({
632
+ content: this.renderEnhancedPreview(title),
633
+ target: event.currentTarget,
634
+ position: 'bottom',
635
+ cssClasses: ['extended-tab-preview'],
636
+ visualPreview: this.corePreferences?.['window.tabbar.enhancedPreview'] === 'visual' ? width => this.renderVisualPreview(width, title) : undefined
637
+ });
638
+ } else if (title.caption) {
639
+ const position = this.tabBar.orientation === 'horizontal' ? 'bottom' : 'right';
640
+ const tooltip = ArrayUtils.coalesce([title.caption, ...this.getDecorationData(title, 'tooltip')]).join(' - ');
641
+ this.hoverService.requestHover({
642
+ content: tooltip,
643
+ target: event.currentTarget,
644
+ position
645
+ });
648
646
  }
649
647
  };
650
648
 
@@ -266,7 +266,7 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
266
266
  this.selectOption(selected, this.props.options[selected]);
267
267
  }
268
268
  } else if (ev.key === 'Escape' || ev.key === 'Tab') {
269
- this.hide();
269
+ this.hide(undefined, true);
270
270
  }
271
271
  ev.stopPropagation();
272
272
  ev.nativeEvent.stopImmediatePropagation();
@@ -290,7 +290,7 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
290
290
  }
291
291
  }
292
292
 
293
- protected hide(index?: number): void {
293
+ protected hide(index?: number, releaseFocus = false): void {
294
294
  const selectedIndex = index === undefined ? this.state.original : index;
295
295
  this.setState({
296
296
  dimensions: undefined,
@@ -298,6 +298,10 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
298
298
  original: selectedIndex,
299
299
  hover: selectedIndex
300
300
  });
301
+ // Force releasing focus of the select element to allow closing via escape later on
302
+ if (releaseFocus && document.activeElement && document.activeElement === this.fieldRef.current) {
303
+ this.fieldRef.current.blur();
304
+ }
301
305
  }
302
306
 
303
307
  protected renderDropdown(): React.ReactNode {
@@ -121,6 +121,12 @@ export class BaseWidget extends Widget implements PreviewableWidget {
121
121
  super(options);
122
122
  }
123
123
 
124
+ override get isVisible(): boolean {
125
+ // Reverted to @lumino/widgets pre-2.7.0 behavior using the IsVisible flag instead of the recursive parent check.
126
+ // Theia relies on this flag-based implementation and we need to transition to the new behavior in a follow-up (GH-16585)
127
+ return this.testFlag(Widget.Flag.IsVisible);
128
+ }
129
+
124
130
  override dispose(): void {
125
131
  if (this.isDisposed) {
126
132
  return;
@@ -57,7 +57,7 @@ export class MarkdownStringImpl implements MarkdownString {
57
57
 
58
58
  constructor(
59
59
  value: string = '',
60
- isTrustedOrOptions: boolean | { isTrusted?: boolean; supportThemeIcons?: boolean; supportHtml?: boolean } = false,
60
+ isTrustedOrOptions: boolean | { isTrusted?: boolean | MarkdownStringTrustedOptions; supportThemeIcons?: boolean; supportHtml?: boolean } = false,
61
61
  ) {
62
62
  this.value = value;
63
63
  if (typeof this.value !== 'string') {
@@ -47,11 +47,24 @@ export interface ThemeColor {
47
47
  readonly id: string;
48
48
  }
49
49
 
50
+ // Copied from https://github.com/microsoft/vscode/blob/1.106.1/src/vs/base/common/themables.ts
51
+ export function isThemeColor(obj: unknown): obj is ThemeColor {
52
+ return !!obj && typeof obj === 'object' && typeof (<ThemeColor>obj).id === 'string';
53
+ }
54
+
50
55
  export interface ThemeIcon {
51
56
  readonly id: string;
52
57
  readonly color?: ThemeColor;
53
58
  }
54
59
 
60
+ // Copied and modified from https://github.com/microsoft/vscode/blob/1.106.1/src/vs/base/common/themables.ts
61
+ export function isThemeIcon(obj: unknown): obj is ThemeIcon {
62
+ return !!obj &&
63
+ typeof obj === 'object' &&
64
+ typeof (<ThemeIcon>obj).id === 'string' &&
65
+ (typeof (<ThemeIcon>obj).color === 'undefined' || isThemeColor((<ThemeIcon>obj).color));
66
+ }
67
+
55
68
  export interface IconDefinition {
56
69
  font?: IconFontContribution; // undefined for the default font (codicon)
57
70
  fontCharacter: string;