@theia/plugin-ext 1.71.0-next.8 → 1.72.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/lib/common/plugin-api-rpc.d.ts +76 -0
  2. package/lib/common/plugin-api-rpc.d.ts.map +1 -1
  3. package/lib/common/plugin-api-rpc.js +9 -1
  4. package/lib/common/plugin-api-rpc.js.map +1 -1
  5. package/lib/hosted/browser/hosted-plugin.d.ts.map +1 -1
  6. package/lib/hosted/browser/hosted-plugin.js +13 -6
  7. package/lib/hosted/browser/hosted-plugin.js.map +1 -1
  8. package/lib/main/browser/main-context.d.ts.map +1 -1
  9. package/lib/main/browser/main-context.js +2 -6
  10. package/lib/main/browser/main-context.js.map +1 -1
  11. package/lib/main/browser/main-file-system-event-service.d.ts +10 -2
  12. package/lib/main/browser/main-file-system-event-service.d.ts.map +1 -1
  13. package/lib/main/browser/main-file-system-event-service.js +19 -2
  14. package/lib/main/browser/main-file-system-event-service.js.map +1 -1
  15. package/lib/main/browser/menus/menus-contribution-handler.d.ts.map +1 -1
  16. package/lib/main/browser/menus/menus-contribution-handler.js +2 -0
  17. package/lib/main/browser/menus/menus-contribution-handler.js.map +1 -1
  18. package/lib/main/browser/menus/plugin-menu-command-adapter.d.ts +1 -0
  19. package/lib/main/browser/menus/plugin-menu-command-adapter.d.ts.map +1 -1
  20. package/lib/main/browser/menus/plugin-menu-command-adapter.js +13 -1
  21. package/lib/main/browser/menus/plugin-menu-command-adapter.js.map +1 -1
  22. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts +1 -1
  23. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts.map +1 -1
  24. package/lib/main/browser/menus/vscode-theia-menu-mappings.js +9 -2
  25. package/lib/main/browser/menus/vscode-theia-menu-mappings.js.map +1 -1
  26. package/lib/main/browser/scm-main.d.ts +33 -2
  27. package/lib/main/browser/scm-main.d.ts.map +1 -1
  28. package/lib/main/browser/scm-main.js +237 -3
  29. package/lib/main/browser/scm-main.js.map +1 -1
  30. package/lib/main/browser/scm-main.spec.d.ts +2 -0
  31. package/lib/main/browser/scm-main.spec.d.ts.map +1 -0
  32. package/lib/main/browser/scm-main.spec.js +87 -0
  33. package/lib/main/browser/scm-main.spec.js.map +1 -0
  34. package/lib/main/browser/test-main.d.ts +3 -2
  35. package/lib/main/browser/test-main.d.ts.map +1 -1
  36. package/lib/main/browser/test-main.js +12 -1
  37. package/lib/main/browser/test-main.js.map +1 -1
  38. package/lib/plugin/file-system-event-service-ext-impl.d.ts +11 -5
  39. package/lib/plugin/file-system-event-service-ext-impl.d.ts.map +1 -1
  40. package/lib/plugin/file-system-event-service-ext-impl.js +28 -9
  41. package/lib/plugin/file-system-event-service-ext-impl.js.map +1 -1
  42. package/lib/plugin/plugin-context.js +4 -4
  43. package/lib/plugin/plugin-context.js.map +1 -1
  44. package/lib/plugin/scm.d.ts +8 -2
  45. package/lib/plugin/scm.d.ts.map +1 -1
  46. package/lib/plugin/scm.js +188 -5
  47. package/lib/plugin/scm.js.map +1 -1
  48. package/lib/plugin/scm.spec.d.ts +2 -0
  49. package/lib/plugin/scm.spec.d.ts.map +1 -0
  50. package/lib/plugin/scm.spec.js +461 -0
  51. package/lib/plugin/scm.spec.js.map +1 -0
  52. package/lib/plugin/terminal-ext.d.ts +13 -3
  53. package/lib/plugin/terminal-ext.d.ts.map +1 -1
  54. package/lib/plugin/terminal-ext.js +51 -10
  55. package/lib/plugin/terminal-ext.js.map +1 -1
  56. package/lib/plugin/terminal-ext.spec.d.ts +2 -0
  57. package/lib/plugin/terminal-ext.spec.d.ts.map +1 -0
  58. package/lib/plugin/terminal-ext.spec.js +285 -0
  59. package/lib/plugin/terminal-ext.spec.js.map +1 -0
  60. package/lib/plugin/test-item.d.ts.map +1 -1
  61. package/lib/plugin/test-item.js +8 -3
  62. package/lib/plugin/test-item.js.map +1 -1
  63. package/lib/plugin/tests.d.ts.map +1 -1
  64. package/lib/plugin/tests.js +15 -3
  65. package/lib/plugin/tests.js.map +1 -1
  66. package/lib/plugin/type-converters.d.ts +2 -2
  67. package/lib/plugin/type-converters.d.ts.map +1 -1
  68. package/lib/plugin/type-converters.js +3 -9
  69. package/lib/plugin/type-converters.js.map +1 -1
  70. package/lib/plugin/types-impl.d.ts +1 -1
  71. package/lib/plugin/types-impl.d.ts.map +1 -1
  72. package/lib/plugin/types-impl.js +1 -1
  73. package/lib/plugin/types-impl.js.map +1 -1
  74. package/lib/plugin/workspace.d.ts.map +1 -1
  75. package/lib/plugin/workspace.js +17 -3
  76. package/lib/plugin/workspace.js.map +1 -1
  77. package/package.json +39 -39
  78. package/src/common/plugin-api-rpc.ts +78 -0
  79. package/src/hosted/browser/hosted-plugin.ts +13 -6
  80. package/src/main/browser/main-context.ts +3 -7
  81. package/src/main/browser/main-file-system-event-service.ts +26 -6
  82. package/src/main/browser/menus/menus-contribution-handler.ts +2 -0
  83. package/src/main/browser/menus/plugin-menu-command-adapter.ts +15 -2
  84. package/src/main/browser/menus/vscode-theia-menu-mappings.ts +12 -3
  85. package/src/main/browser/scm-main.spec.ts +105 -0
  86. package/src/main/browser/scm-main.ts +272 -4
  87. package/src/main/browser/test-main.ts +13 -3
  88. package/src/plugin/file-system-event-service-ext-impl.ts +40 -14
  89. package/src/plugin/plugin-context.ts +7 -7
  90. package/src/plugin/scm.spec.ts +615 -0
  91. package/src/plugin/scm.ts +224 -6
  92. package/src/plugin/terminal-ext.spec.ts +350 -0
  93. package/src/plugin/terminal-ext.ts +58 -12
  94. package/src/plugin/test-item.ts +8 -3
  95. package/src/plugin/tests.ts +14 -3
  96. package/src/plugin/type-converters.ts +7 -13
  97. package/src/plugin/types-impl.ts +2 -2
  98. package/src/plugin/workspace.ts +17 -3
@@ -38,6 +38,13 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
38
38
 
39
39
  private readonly _terminals = new Map<string, TerminalExtImpl>();
40
40
 
41
+ /**
42
+ * Stores the API objects (Proxies) returned to plugins for each terminal.
43
+ * This ensures that events fire with the same object instance that plugins received,
44
+ * allowing strict equality checks (===) to work correctly.
45
+ */
46
+ private readonly _terminalApiObjects = new Map<string, theia.Terminal>();
47
+
41
48
  private readonly _pseudoTerminals = new Map<string, PseudoTerminal>();
42
49
 
43
50
  private static nextProviderId = 0;
@@ -66,8 +73,8 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
66
73
  this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.TERMINAL_MAIN);
67
74
  }
68
75
 
69
- get terminals(): TerminalExtImpl[] {
70
- return [...this._terminals.values()];
76
+ get terminals(): theia.Terminal[] {
77
+ return [...this._terminals.keys()].map(id => this.getApiObject(id)).filter((t): t is theia.Terminal => t !== undefined);
71
78
  }
72
79
 
73
80
  get defaultShell(): string {
@@ -84,7 +91,9 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
84
91
  createTerminal(
85
92
  plugin: Plugin,
86
93
  nameOrOptions: theia.TerminalOptions | theia.PseudoTerminalOptions | theia.ExtensionTerminalOptions | string | undefined,
87
- shellPath?: string, shellArgs?: string[] | string
94
+ shellPath?: string,
95
+ shellArgs?: string[] | string,
96
+ apiObjectWrapper?: (terminal: TerminalExtImpl) => theia.Terminal
88
97
  ): theia.Terminal {
89
98
  const id = `plugin-terminal-${UUID.uuid4()}`;
90
99
  let options: TerminalOptions;
@@ -110,7 +119,15 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
110
119
  let parentId;
111
120
  if (options.location && typeof options.location === 'object' && 'parentTerminal' in options.location) {
112
121
  const parentTerminal = options.location.parentTerminal;
113
- if (parentTerminal instanceof TerminalExtImpl) {
122
+ // Check both the API proxy objects and the raw terminals, since
123
+ // plugins receive the proxy but _terminals stores the raw object.
124
+ for (const [k, v] of this._terminalApiObjects) {
125
+ if (v === parentTerminal) {
126
+ parentId = k;
127
+ break;
128
+ }
129
+ }
130
+ if (!parentId) {
114
131
  for (const [k, v] of this._terminals) {
115
132
  if (v === parentTerminal) {
116
133
  parentId = k;
@@ -136,7 +153,16 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
136
153
  if (typeof nameOrOptions === 'object' && 'pty' in nameOrOptions) {
137
154
  creationOptions = nameOrOptions;
138
155
  }
139
- return this.obtainTerminal(id, options.name || 'Terminal', creationOptions);
156
+ const terminal = this.obtainTerminal(id, options.name || 'Terminal', creationOptions);
157
+
158
+ // If a wrapper function is provided, wrap the terminal and register the API object
159
+ // This ensures events fire with the same object instance that plugins received
160
+ if (apiObjectWrapper) {
161
+ const apiObject = apiObjectWrapper(terminal);
162
+ this._terminalApiObjects.set(id, apiObject);
163
+ return apiObject;
164
+ }
165
+ return terminal;
140
166
  }
141
167
 
142
168
  attachPtyToTerminal(terminalId: number, pty: theia.Pseudoterminal): void {
@@ -153,6 +179,13 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
153
179
  return terminal;
154
180
  }
155
181
 
182
+ /**
183
+ * Gets the API object for a terminal by ID, falling back to the raw terminal if not set.
184
+ */
185
+ protected getApiObject(id: string): theia.Terminal | undefined {
186
+ return this._terminalApiObjects.get(id) ?? this._terminals.get(id);
187
+ }
188
+
156
189
  $terminalOnInput(id: string, data: string): void {
157
190
  const terminal = this._pseudoTerminals.get(id);
158
191
  if (!terminal) {
@@ -168,7 +201,10 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
168
201
  }
169
202
  if (!terminal.state.isInteractedWith) {
170
203
  terminal.state = { ...terminal.state, isInteractedWith: true };
171
- this.onDidChangeTerminalStateEmitter.fire(terminal);
204
+ const apiObject = this.getApiObject(id);
205
+ if (apiObject) {
206
+ this.onDidChangeTerminalStateEmitter.fire(apiObject);
207
+ }
172
208
  }
173
209
  }
174
210
 
@@ -179,7 +215,10 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
179
215
  }
180
216
  if (terminal.state.shell !== shellType) {
181
217
  terminal.state = { ...terminal.state, shell: shellType };
182
- this.onDidChangeTerminalStateEmitter.fire(terminal);
218
+ const apiObject = this.getApiObject(id);
219
+ if (apiObject) {
220
+ this.onDidChangeTerminalStateEmitter.fire(apiObject);
221
+ }
183
222
  }
184
223
  }
185
224
 
@@ -194,7 +233,10 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
194
233
  $terminalCreated(id: string, name: string): void {
195
234
  const terminal = this.obtainTerminal(id, name);
196
235
  terminal.id.resolve(id);
197
- this.onDidOpenTerminalEmitter.fire(terminal);
236
+ const apiObject = this.getApiObject(id);
237
+ if (apiObject) {
238
+ this.onDidOpenTerminalEmitter.fire(apiObject);
239
+ }
198
240
  }
199
241
 
200
242
  $terminalNameChanged(id: string, name: string): void {
@@ -234,8 +276,12 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
234
276
  const terminal = this._terminals.get(id);
235
277
  if (terminal) {
236
278
  terminal.exitStatus = exitStatus ?? { code: undefined, reason: TerminalExitReason.Unknown };
237
- this.onDidCloseTerminalEmitter.fire(terminal);
279
+ const apiObject = this.getApiObject(id);
280
+ if (apiObject) {
281
+ this.onDidCloseTerminalEmitter.fire(apiObject);
282
+ }
238
283
  this._terminals.delete(id);
284
+ this._terminalApiObjects.delete(id);
239
285
  }
240
286
  const pseudoTerminal = this._pseudoTerminals.get(id);
241
287
  if (pseudoTerminal) {
@@ -245,8 +291,8 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
245
291
  }
246
292
 
247
293
  private activeTerminalId: string | undefined;
248
- get activeTerminal(): TerminalExtImpl | undefined {
249
- return this.activeTerminalId && this._terminals.get(this.activeTerminalId) || undefined;
294
+ get activeTerminal(): theia.Terminal | undefined {
295
+ return this.activeTerminalId && this.getApiObject(this.activeTerminalId) || undefined;
250
296
  }
251
297
  $currentTerminalChanged(id: string | undefined): void {
252
298
  this.activeTerminalId = id;
@@ -319,7 +365,7 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
319
365
 
320
366
  async $provideTerminalLinks(line: string, terminalId: string, token: theia.CancellationToken): Promise<ProvidedTerminalLink[]> {
321
367
  const links: ProvidedTerminalLink[] = [];
322
- const terminal = this._terminals.get(terminalId);
368
+ const terminal = this.getApiObject(terminalId);
323
369
  if (terminal) {
324
370
  for (const [providerId, provider] of this.terminalLinkProviders) {
325
371
  const providedLinks = await provider.provideTerminalLinks({ line, terminal }, token);
@@ -132,9 +132,14 @@ export class TestItemCollection implements theia.TestItemCollection {
132
132
  return this.values.size;
133
133
  }
134
134
  replace(items: readonly theia.TestItem[]): void {
135
- const toRemove = this.values.values.map(item => item.id);
136
- items.forEach(item => this.add(item));
137
- toRemove.forEach(key => this.delete(key));
135
+ const toDelete = new Set(this.values.values.map(item => item.id));
136
+ for (const item of items) {
137
+ toDelete.delete(item.id);
138
+ this.add(item);
139
+ }
140
+ for (const id of toDelete) {
141
+ this.delete(id);
142
+ }
138
143
  }
139
144
 
140
145
  forEach(callback: (item: theia.TestItem, collection: theia.TestItemCollection) => unknown, thisArg?: unknown): void {
@@ -299,6 +299,10 @@ export class TestRun implements theia.TestRun {
299
299
  }
300
300
 
301
301
  end(): void {
302
+ if (this.ended) {
303
+ return;
304
+ }
305
+ this.changeBatcher.flush();
302
306
  this.ended = true;
303
307
  this.proxy.$notifyTestRunEnded(this.controller.id, this.id);
304
308
  }
@@ -428,9 +432,16 @@ export class TestingExtImpl implements TestingExt {
428
432
  $onResolveChildren(controllerId: string, path: string[]): void {
429
433
  const controller = this.withController(controllerId);
430
434
  if (controller.resolveHandler) {
431
- const item = controller.items.find(path);
432
- if (item?.canResolveChildren) { // the item and resolve handler might have been been changed, but not sent to the front end
433
- controller.resolveHandler?.(item);
435
+ if (path.length === 0) {
436
+ // Root-level resolve: discover top-level test items
437
+ controller.resolveHandler(undefined);
438
+ } else {
439
+ const item = controller.items.find(path);
440
+ // The `main` side should only request resolution for items with `canResolveChildren`, but with event batching,
441
+ // the flag can be out of sync with the state on the `plugin` side. The state on the `plugin` side is authoritative.
442
+ if (item?.canResolveChildren) {
443
+ controller.resolveHandler(item);
444
+ }
434
445
  }
435
446
  }
436
447
  }
@@ -22,7 +22,7 @@ import {
22
22
  DecorationOptions, EditorPosition, Plugin, Position, WorkspaceTextEditDto, WorkspaceFileEditDto, Selection, TaskDto, WorkspaceEditDto
23
23
  } from '../common/plugin-api-rpc';
24
24
  import * as model from '../common/plugin-api-rpc-model';
25
- import { LanguageFilter, LanguageSelector, RelativePattern } from '@theia/editor/lib/common/language-selector';
25
+ import { LanguageFilter, LanguageSelector } from '@theia/editor/lib/common/language-selector';
26
26
  import { MarkdownString as PluginMarkdownStringImpl } from './markdown-string';
27
27
  import * as types from './types-impl';
28
28
  import { UriComponents } from '../common/uri-components';
@@ -246,27 +246,21 @@ export function fromDocumentSelector(selector: theia.DocumentSelector | undefine
246
246
  return {
247
247
  language: selector.language,
248
248
  scheme: selector.scheme,
249
- pattern: fromGlobPattern(selector.pattern!)
249
+ pattern: selector.pattern && fromGlobPattern(selector.pattern)
250
250
  } as LanguageFilter;
251
251
  }
252
252
 
253
253
  }
254
254
 
255
- export function fromGlobPattern(pattern: theia.GlobPattern): string | RelativePattern {
255
+ export function fromGlobPattern(pattern: theia.GlobPattern): string | types.RelativePattern {
256
256
  if (typeof pattern === 'string') {
257
257
  return pattern;
258
258
  }
259
259
 
260
- if (isRelativePattern(pattern)) {
261
- return new types.RelativePattern(pattern.baseUri, pattern.pattern);
262
- }
263
-
264
- return pattern;
265
- }
266
-
267
- function isRelativePattern(obj: {}): obj is theia.RelativePattern {
268
- const rp = obj as theia.RelativePattern;
269
- return rp && typeof rp.baseUri === 'string' && typeof rp.pattern === 'string';
260
+ return new types.RelativePattern(
261
+ pattern.baseUri ?? pattern.base, // preserve backwards compatibility with older extensions: legacy relative pattern shape did not have the `baseUri` property
262
+ pattern.pattern
263
+ );
270
264
  }
271
265
 
272
266
  export function fromCompletionItemKind(kind?: types.CompletionItemKind): model.CompletionItemKind {
@@ -737,7 +737,7 @@ export class ThemeIcon {
737
737
 
738
738
  static readonly Folder: ThemeIcon = new ThemeIcon('folder');
739
739
 
740
- private constructor(public id: string, public color?: ThemeColor) {
740
+ constructor(public id: string, public color?: ThemeColor) {
741
741
  }
742
742
 
743
743
  }
@@ -816,7 +816,7 @@ export class RelativePattern {
816
816
  }
817
817
  set baseUri(baseUri: URI) {
818
818
  this._baseUri = baseUri;
819
- this.base = baseUri.fsPath;
819
+ this._base = baseUri.fsPath;
820
820
  }
821
821
 
822
822
  constructor(base: theia.WorkspaceFolder | URI | string, public pattern: string) {
@@ -39,7 +39,6 @@ import { Disposable, URI } from './types-impl';
39
39
  import { normalize } from '@theia/core/lib/common/paths';
40
40
  import { relative } from '../common/paths-util';
41
41
  import { Schemes, UriComponents } from '../common/uri-components';
42
- import { toWorkspaceFolder } from './type-converters';
43
42
  import { MessageRegistryExt } from './message-registry';
44
43
  import * as Converter from './type-converters';
45
44
  import { FileStat } from '@theia/filesystem/lib/common/files';
@@ -120,7 +119,22 @@ export class WorkspaceExtImpl implements WorkspaceExt {
120
119
 
121
120
  $onWorkspaceFoldersChanged(event: WorkspaceRootsChangeEvent): void {
122
121
  const newRoots = event.roots || [];
123
- const newFolders = newRoots.map((root, index) => this.toWorkspaceFolder(root, index));
122
+ const oldFoldersByUri = new Map<string, theia.WorkspaceFolder>();
123
+ if (this.folders) {
124
+ for (const folder of this.folders) {
125
+ oldFoldersByUri.set(folder.uri.toString(), folder);
126
+ }
127
+ }
128
+ const newFolders = newRoots.map((root, index) => {
129
+ const existing = oldFoldersByUri.get(root);
130
+ if (existing) {
131
+ // Preserve object identity even if the index changed,
132
+ // since extensions may use folder objects as Map keys.
133
+ Object.assign(existing, { index });
134
+ return existing;
135
+ }
136
+ return this.toWorkspaceFolder(root, index);
137
+ });
124
138
  const delta = this.deltaFolders(this.folders, newFolders);
125
139
 
126
140
  this.folders = newFolders;
@@ -347,7 +361,7 @@ export class WorkspaceExtImpl implements WorkspaceExt {
347
361
  const folderPath = folder.uri.toString();
348
362
 
349
363
  if (resourcePath === folderPath) {
350
- return toWorkspaceFolder(folder);
364
+ return folder;
351
365
  }
352
366
 
353
367
  if (resourcePath.startsWith(folderPath)