@theia/plugin-ext 1.77.0-next.4 → 1.77.0-next.47
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.
- package/lib/main/browser/view/plugin-view-registry.d.ts +10 -0
- package/lib/main/browser/view/plugin-view-registry.d.ts.map +1 -1
- package/lib/main/browser/view/plugin-view-registry.js +42 -14
- package/lib/main/browser/view/plugin-view-registry.js.map +1 -1
- package/lib/main/browser/view/plugin-view-registry.spec.js +37 -8
- package/lib/main/browser/view/plugin-view-registry.spec.js.map +1 -1
- package/lib/plugin/plugin-context.js +1 -1
- package/lib/plugin/plugin-context.js.map +1 -1
- package/lib/plugin/types-impl.d.ts +10 -0
- package/lib/plugin/types-impl.d.ts.map +1 -1
- package/lib/plugin/types-impl.js +6 -0
- package/lib/plugin/types-impl.js.map +1 -1
- package/lib/plugin/types-impl.spec.js +22 -0
- package/lib/plugin/types-impl.spec.js.map +1 -1
- package/package.json +32 -32
- package/src/main/browser/view/plugin-view-registry.spec.ts +51 -9
- package/src/main/browser/view/plugin-view-registry.ts +41 -13
- package/src/plugin/plugin-context.ts +1 -1
- package/src/plugin/types-impl.spec.ts +25 -0
- package/src/plugin/types-impl.ts +7 -0
|
@@ -23,7 +23,7 @@ import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/front
|
|
|
23
23
|
FrontendApplicationConfigProvider.set({});
|
|
24
24
|
|
|
25
25
|
import { expect } from 'chai';
|
|
26
|
-
import { Disposable } from '@theia/core/lib/common';
|
|
26
|
+
import { Command, Disposable } from '@theia/core/lib/common';
|
|
27
27
|
import { PluginViewRegistry, ViewContainerInfo, PLUGIN_VIEW_DATA_FACTORY_ID } from './plugin-view-registry';
|
|
28
28
|
import type { ViewWelcome } from '../../../common';
|
|
29
29
|
|
|
@@ -54,7 +54,6 @@ describe('PluginViewRegistry - view menu labels', () => {
|
|
|
54
54
|
let registry: PluginViewRegistry;
|
|
55
55
|
let menus: RecordingMenuModelRegistry;
|
|
56
56
|
|
|
57
|
-
const toggleCommandId = (id: string): string => `plugin.view-container.${id}.toggle`;
|
|
58
57
|
const internals = (): {
|
|
59
58
|
viewContainers: Map<string, ViewContainerInfo>;
|
|
60
59
|
registerViewMenuAction(containerId: string, label: string): Disposable;
|
|
@@ -76,21 +75,21 @@ describe('PluginViewRegistry - view menu labels', () => {
|
|
|
76
75
|
|
|
77
76
|
it('uses the plain label for a single container', () => {
|
|
78
77
|
registerContainer('a', 'Claude Code', 'left');
|
|
79
|
-
expect(menus.actions.get(
|
|
78
|
+
expect(menus.actions.get('a')?.label).to.equal('Claude Code');
|
|
80
79
|
});
|
|
81
80
|
|
|
82
81
|
it('leaves distinct labels unsuffixed', () => {
|
|
83
82
|
registerContainer('a', 'Explorer', 'left');
|
|
84
83
|
registerContainer('b', 'Claude Code', 'right');
|
|
85
|
-
expect(menus.actions.get(
|
|
86
|
-
expect(menus.actions.get(
|
|
84
|
+
expect(menus.actions.get('a')?.label).to.equal('Explorer');
|
|
85
|
+
expect(menus.actions.get('b')?.label).to.equal('Claude Code');
|
|
87
86
|
});
|
|
88
87
|
|
|
89
88
|
it('suffixes the location when two containers share a label', () => {
|
|
90
89
|
registerContainer('a', 'Claude Code', 'left');
|
|
91
90
|
registerContainer('b', 'Claude Code', 'right');
|
|
92
|
-
expect(menus.actions.get(
|
|
93
|
-
expect(menus.actions.get(
|
|
91
|
+
expect(menus.actions.get('a')?.label).to.equal('Claude Code (Side Bar)');
|
|
92
|
+
expect(menus.actions.get('b')?.label).to.equal('Claude Code (Secondary Side Bar)');
|
|
94
93
|
});
|
|
95
94
|
|
|
96
95
|
it('drops the suffix from the remaining container once the duplicate is removed', () => {
|
|
@@ -99,8 +98,8 @@ describe('PluginViewRegistry - view menu labels', () => {
|
|
|
99
98
|
|
|
100
99
|
disposeB.dispose();
|
|
101
100
|
|
|
102
|
-
expect(menus.actions.get(
|
|
103
|
-
expect(menus.actions.has(
|
|
101
|
+
expect(menus.actions.get('a')?.label).to.equal('Claude Code');
|
|
102
|
+
expect(menus.actions.has('b')).to.equal(false);
|
|
104
103
|
});
|
|
105
104
|
|
|
106
105
|
});
|
|
@@ -171,3 +170,46 @@ describe('PluginViewRegistry - welcome-only views', () => {
|
|
|
171
170
|
});
|
|
172
171
|
|
|
173
172
|
});
|
|
173
|
+
|
|
174
|
+
describe('PluginViewRegistry - VS Code view commands', () => {
|
|
175
|
+
|
|
176
|
+
let registry: PluginViewRegistry;
|
|
177
|
+
let commands: Map<string, Command>;
|
|
178
|
+
|
|
179
|
+
const internals = (): {
|
|
180
|
+
doRegisterViewContainer(id: string, location: string, options: { label: string }): Disposable;
|
|
181
|
+
} => registry as unknown as {
|
|
182
|
+
doRegisterViewContainer(id: string, location: string, options: { label: string }): Disposable;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
beforeEach(() => {
|
|
186
|
+
registry = new PluginViewRegistry();
|
|
187
|
+
commands = new Map();
|
|
188
|
+
(registry as unknown as { commands: unknown }).commands = {
|
|
189
|
+
registerCommand: (command: Command) => {
|
|
190
|
+
commands.set(command.id, command);
|
|
191
|
+
return Disposable.create(() => commands.delete(command.id));
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
(registry as unknown as { menus: unknown }).menus = new RecordingMenuModelRegistry();
|
|
195
|
+
(registry as unknown as { quickView: unknown }).quickView = { registerItem: () => Disposable.NULL };
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('registers the container id and the view focus and open commands once a view is added', () => {
|
|
199
|
+
const containerId = 'workbench.view.extension.sample';
|
|
200
|
+
internals().doRegisterViewContainer(containerId, 'left', { label: 'Sample' });
|
|
201
|
+
expect(commands.has(containerId)).to.equal(false);
|
|
202
|
+
|
|
203
|
+
const disposeView = registry.registerView('sample', { id: 'sample.view', name: 'Sample View' });
|
|
204
|
+
|
|
205
|
+
expect(commands.get(containerId)).to.include({ label: 'Toggle Sample', category: 'View' });
|
|
206
|
+
expect(commands.get('plugin.view-container.workbench.view.extension.sample.toggle')?.label).to.equal(undefined);
|
|
207
|
+
expect(commands.get('sample.view.focus')).to.include({ label: 'Focus on Sample View View', category: 'Sample' });
|
|
208
|
+
expect(commands.has('sample.view.open')).to.equal(true);
|
|
209
|
+
|
|
210
|
+
disposeView.dispose();
|
|
211
|
+
expect(commands.has('sample.view.focus')).to.equal(false);
|
|
212
|
+
expect(commands.has('sample.view.open')).to.equal(false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
});
|
|
@@ -347,15 +347,30 @@ export class PluginViewRegistry implements FrontendApplicationContribution {
|
|
|
347
347
|
return toDispose;
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Toggles the view container the same way as {@link AbstractViewContribution.toggleView}:
|
|
352
|
+
* collapses its panel if it is the visible tab, otherwise opens and activates it.
|
|
353
|
+
*/
|
|
350
354
|
protected async toggleViewContainer(id: string): Promise<void> {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
const existing = await this.getPluginViewContainer(id);
|
|
356
|
+
const tabBar = existing && this.shell.getTabBarFor(existing);
|
|
357
|
+
const area = existing && this.shell.getAreaFor(existing);
|
|
358
|
+
if (existing && tabBar && area && this.shell.isExpanded(area) && tabBar.currentTitle === existing.title) {
|
|
359
|
+
if (area === 'left' || area === 'right') {
|
|
360
|
+
await this.shell.collapsePanel(area);
|
|
361
|
+
} else if (area === 'bottom') {
|
|
362
|
+
// Don't collapse the bottom panel if it's currently split
|
|
363
|
+
if (this.shell.bottomAreaTabBars.length === 1) {
|
|
364
|
+
await this.shell.collapsePanel('bottom');
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
existing.dispose();
|
|
358
368
|
}
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const widget = await this.openViewContainer(id);
|
|
372
|
+
if (widget) {
|
|
373
|
+
await this.shell.activateWidget(widget.id);
|
|
359
374
|
}
|
|
360
375
|
}
|
|
361
376
|
|
|
@@ -369,18 +384,22 @@ export class PluginViewRegistry implements FrontendApplicationContribution {
|
|
|
369
384
|
toDispose.push(Disposable.create(() => this.viewContainerClauseContexts.delete(id)));
|
|
370
385
|
}
|
|
371
386
|
}
|
|
372
|
-
const toggleCommandId = `plugin.view-container.${id}.toggle`;
|
|
373
387
|
// Some plugins may register empty view containers.
|
|
374
388
|
// We should not register commands for them immediately, as that leads to bad UX.
|
|
375
389
|
// Instead, we register commands the first time we add a view to them.
|
|
376
390
|
let activate = () => {
|
|
391
|
+
// Like VS Code, use the container's id, e.g. `workbench.view.extension.${id}`, as the command id
|
|
377
392
|
toDispose.push(this.commands.registerCommand({
|
|
378
|
-
id
|
|
393
|
+
id,
|
|
379
394
|
category: nls.localizeByDefault('View'),
|
|
380
395
|
label: nls.localizeByDefault('Toggle {0}', options.label)
|
|
381
396
|
}, {
|
|
382
397
|
execute: () => this.toggleViewContainer(id)
|
|
383
398
|
}));
|
|
399
|
+
// Kept for backward compatibility with keybindings that use the former command id
|
|
400
|
+
toDispose.push(this.commands.registerCommand({ id: `plugin.view-container.${id}.toggle` }, {
|
|
401
|
+
execute: () => this.toggleViewContainer(id)
|
|
402
|
+
}));
|
|
384
403
|
toDispose.push(this.registerViewMenuAction(id, options.label));
|
|
385
404
|
toDispose.push(this.quickView?.registerItem({
|
|
386
405
|
label: options.label,
|
|
@@ -486,7 +505,7 @@ export class PluginViewRegistry implements FrontendApplicationContribution {
|
|
|
486
505
|
}
|
|
487
506
|
}
|
|
488
507
|
const disposable = this.menus.registerMenuAction(CommonMenus.VIEW_VIEWS, {
|
|
489
|
-
commandId:
|
|
508
|
+
commandId: id,
|
|
490
509
|
label: menuLabel,
|
|
491
510
|
when: containerInfo.when
|
|
492
511
|
});
|
|
@@ -561,15 +580,24 @@ export class PluginViewRegistry implements FrontendApplicationContribution {
|
|
|
561
580
|
when: view.when,
|
|
562
581
|
open: () => this.openView(view.id, { activate: true })
|
|
563
582
|
}));
|
|
564
|
-
toDispose.push(this.commands.registerCommand({
|
|
565
|
-
|
|
583
|
+
toDispose.push(this.commands.registerCommand({
|
|
584
|
+
id: `${view.id}.focus`,
|
|
585
|
+
category: this.viewContainers.get(viewContainerId)?.options.label,
|
|
586
|
+
label: nls.localizeByDefault('Focus on {0} View', view.name)
|
|
587
|
+
}, {
|
|
588
|
+
execute: async (options?: { preserveFocus?: boolean }) => { await this.openView(view.id, this.toOpenViewOptions(options)); },
|
|
589
|
+
isVisible: () => this.isViewVisible(view.id)
|
|
566
590
|
}));
|
|
567
591
|
toDispose.push(this.commands.registerCommand({ id: `${view.id}.open` }, {
|
|
568
|
-
execute: async () => { await this.openView(view.id,
|
|
592
|
+
execute: async (options?: { preserveFocus?: boolean }) => { await this.openView(view.id, this.toOpenViewOptions(options)); }
|
|
569
593
|
}));
|
|
570
594
|
return toDispose;
|
|
571
595
|
}
|
|
572
596
|
|
|
597
|
+
protected toOpenViewOptions(options?: { preserveFocus?: boolean }): { activate?: boolean, reveal?: boolean } {
|
|
598
|
+
return options?.preserveFocus ? { reveal: true } : { activate: true };
|
|
599
|
+
}
|
|
600
|
+
|
|
573
601
|
async resolveWebviewView(viewId: string, webview: WebviewView, cancellation: CancellationToken): Promise<void> {
|
|
574
602
|
const resolver = this.webviewViewResolvers.get(viewId);
|
|
575
603
|
if (resolver) {
|
|
@@ -504,7 +504,7 @@ export function createAPIFactory(
|
|
|
504
504
|
preserveFocus?: boolean
|
|
505
505
|
): Promise<theia.TextEditor> {
|
|
506
506
|
let documentOptions: theia.TextDocumentShowOptions | undefined;
|
|
507
|
-
const uri: URI = documentArg
|
|
507
|
+
const uri: URI = URI.isUri(documentArg) ? URI.revive(documentArg) : documentArg.uri;
|
|
508
508
|
if (typeof columnOrOptions === 'number') {
|
|
509
509
|
documentOptions = {
|
|
510
510
|
viewColumn: columnOrOptions
|
|
@@ -65,6 +65,31 @@ describe('API Type Implementations:', () => {
|
|
|
65
65
|
types.URI.joinPath(types.URI.from({ scheme: 'myScheme', authority: 'authority', path: '/path', query: 'query', fragment: 'fragment' }), '/file.js').toString(),
|
|
66
66
|
'myScheme://authority/path/file.js?query#fragment');
|
|
67
67
|
});
|
|
68
|
+
|
|
69
|
+
// Issue: #14976
|
|
70
|
+
it('should return an instance of the API URI class from every factory', () => {
|
|
71
|
+
const components = { scheme: 'file', authority: '', path: '/foo/bar.js', query: '', fragment: '' };
|
|
72
|
+
assert.ok(types.URI.parse('file:///foo/bar.js') instanceof types.URI, 'parse');
|
|
73
|
+
assert.ok(types.URI.file('/foo/bar.js') instanceof types.URI, 'file');
|
|
74
|
+
assert.ok(types.URI.revive(components) instanceof types.URI, 'revive');
|
|
75
|
+
assert.ok(types.URI.from(components) instanceof types.URI, 'from');
|
|
76
|
+
assert.ok(types.URI.joinPath(types.URI.file('/foo'), 'bar.js') instanceof types.URI, 'joinPath');
|
|
77
|
+
assert.ok(types.URI.file('/foo/bar.js').with({ fragment: 'baz' }) instanceof types.URI, 'with');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should build an equivalent URI with from()', () => {
|
|
81
|
+
const uriString = 'scheme://authority.com/foo/bar/zoz?query#fragment';
|
|
82
|
+
const uri = types.URI.parse(uriString);
|
|
83
|
+
const from = types.URI.from({
|
|
84
|
+
scheme: uri.scheme,
|
|
85
|
+
authority: uri.authority,
|
|
86
|
+
path: uri.path,
|
|
87
|
+
query: uri.query,
|
|
88
|
+
fragment: uri.fragment
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
assert.strictEqual(from.toString(), uriString);
|
|
92
|
+
});
|
|
68
93
|
});
|
|
69
94
|
|
|
70
95
|
describe('RelativePattern:', () => {
|
package/src/plugin/types-impl.ts
CHANGED
|
@@ -95,6 +95,13 @@ export class URI extends CodeURI implements theia.Uri {
|
|
|
95
95
|
return new URI(CodeURI.file(path));
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Override to create the correct class.
|
|
100
|
+
*/
|
|
101
|
+
static override from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string }): URI {
|
|
102
|
+
return new URI(CodeURI.from(components));
|
|
103
|
+
}
|
|
104
|
+
|
|
98
105
|
/**
|
|
99
106
|
* There is quite some magic in to vscode URI class related to
|
|
100
107
|
* transferring via JSON.stringify(). Making the CodeURI instance
|