@theia/plugin-ext 1.70.0-next.7 → 1.70.0-next.71

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 (164) hide show
  1. package/lib/common/plugin-api-rpc-model.d.ts +6 -1
  2. package/lib/common/plugin-api-rpc-model.d.ts.map +1 -1
  3. package/lib/common/plugin-api-rpc-model.js +2 -1
  4. package/lib/common/plugin-api-rpc-model.js.map +1 -1
  5. package/lib/common/plugin-api-rpc.d.ts +25 -2
  6. package/lib/common/plugin-api-rpc.d.ts.map +1 -1
  7. package/lib/common/plugin-api-rpc.js.map +1 -1
  8. package/lib/common/plugin-protocol.d.ts +8 -0
  9. package/lib/common/plugin-protocol.d.ts.map +1 -1
  10. package/lib/common/plugin-protocol.js.map +1 -1
  11. package/lib/hosted/browser/hosted-plugin.d.ts +2 -0
  12. package/lib/hosted/browser/hosted-plugin.d.ts.map +1 -1
  13. package/lib/hosted/browser/hosted-plugin.js +7 -0
  14. package/lib/hosted/browser/hosted-plugin.js.map +1 -1
  15. package/lib/hosted/common/hosted-plugin.d.ts +3 -0
  16. package/lib/hosted/common/hosted-plugin.d.ts.map +1 -1
  17. package/lib/hosted/common/hosted-plugin.js +13 -0
  18. package/lib/hosted/common/hosted-plugin.js.map +1 -1
  19. package/lib/hosted/common/hosted-plugin.spec.d.ts +2 -0
  20. package/lib/hosted/common/hosted-plugin.spec.d.ts.map +1 -0
  21. package/lib/hosted/common/hosted-plugin.spec.js +308 -0
  22. package/lib/hosted/common/hosted-plugin.spec.js.map +1 -0
  23. package/lib/hosted/node/hosted-plugin-process.d.ts +2 -0
  24. package/lib/hosted/node/hosted-plugin-process.d.ts.map +1 -1
  25. package/lib/hosted/node/hosted-plugin-process.js +8 -0
  26. package/lib/hosted/node/hosted-plugin-process.js.map +1 -1
  27. package/lib/hosted/node/plugin-host-navigator-override.d.ts +12 -0
  28. package/lib/hosted/node/plugin-host-navigator-override.d.ts.map +1 -0
  29. package/lib/hosted/node/plugin-host-navigator-override.js +37 -0
  30. package/lib/hosted/node/plugin-host-navigator-override.js.map +1 -0
  31. package/lib/hosted/node/plugin-host-navigator-override.spec.d.ts +2 -0
  32. package/lib/hosted/node/plugin-host-navigator-override.spec.d.ts.map +1 -0
  33. package/lib/hosted/node/plugin-host-navigator-override.spec.js +49 -0
  34. package/lib/hosted/node/plugin-host-navigator-override.spec.js.map +1 -0
  35. package/lib/hosted/node/plugin-host.js +3 -0
  36. package/lib/hosted/node/plugin-host.js.map +1 -1
  37. package/lib/hosted/node/scanners/scanner-theia.d.ts +1 -0
  38. package/lib/hosted/node/scanners/scanner-theia.d.ts.map +1 -1
  39. package/lib/hosted/node/scanners/scanner-theia.js +7 -0
  40. package/lib/hosted/node/scanners/scanner-theia.js.map +1 -1
  41. package/lib/hosted/node/scanners/scanner-theia.spec.d.ts +2 -0
  42. package/lib/hosted/node/scanners/scanner-theia.spec.d.ts.map +1 -0
  43. package/lib/hosted/node/scanners/scanner-theia.spec.js +93 -0
  44. package/lib/hosted/node/scanners/scanner-theia.spec.js.map +1 -0
  45. package/lib/main/browser/languages-main.d.ts +2 -2
  46. package/lib/main/browser/languages-main.d.ts.map +1 -1
  47. package/lib/main/browser/languages-main.js +14 -10
  48. package/lib/main/browser/languages-main.js.map +1 -1
  49. package/lib/main/browser/menus/menus-contribution-handler.d.ts.map +1 -1
  50. package/lib/main/browser/menus/menus-contribution-handler.js +2 -0
  51. package/lib/main/browser/menus/menus-contribution-handler.js.map +1 -1
  52. package/lib/main/browser/menus/plugin-menu-command-adapter.d.ts.map +1 -1
  53. package/lib/main/browser/menus/plugin-menu-command-adapter.js +3 -0
  54. package/lib/main/browser/menus/plugin-menu-command-adapter.js.map +1 -1
  55. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts +3 -2
  56. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts.map +1 -1
  57. package/lib/main/browser/menus/vscode-theia-menu-mappings.js +10 -2
  58. package/lib/main/browser/menus/vscode-theia-menu-mappings.js.map +1 -1
  59. package/lib/main/browser/plugin-contribution-handler.d.ts.map +1 -1
  60. package/lib/main/browser/plugin-contribution-handler.js +6 -1
  61. package/lib/main/browser/plugin-contribution-handler.js.map +1 -1
  62. package/lib/main/browser/plugin-ext-frontend-module.d.ts.map +1 -1
  63. package/lib/main/browser/plugin-ext-frontend-module.js +26 -0
  64. package/lib/main/browser/plugin-ext-frontend-module.js.map +1 -1
  65. package/lib/main/browser/plugin-ext-widget.d.ts +1 -1
  66. package/lib/main/browser/plugin-ext-widget.d.ts.map +1 -1
  67. package/lib/main/browser/plugin-ext-widget.js +11 -4
  68. package/lib/main/browser/plugin-ext-widget.js.map +1 -1
  69. package/lib/main/browser/scm-main.d.ts +4 -2
  70. package/lib/main/browser/scm-main.d.ts.map +1 -1
  71. package/lib/main/browser/scm-main.js +8 -4
  72. package/lib/main/browser/scm-main.js.map +1 -1
  73. package/lib/main/browser/text-editor-main.d.ts +2 -0
  74. package/lib/main/browser/text-editor-main.d.ts.map +1 -1
  75. package/lib/main/browser/text-editor-main.js +15 -4
  76. package/lib/main/browser/text-editor-main.js.map +1 -1
  77. package/lib/main/browser/text-editors-main.d.ts +4 -0
  78. package/lib/main/browser/text-editors-main.d.ts.map +1 -1
  79. package/lib/main/browser/text-editors-main.js +121 -0
  80. package/lib/main/browser/text-editors-main.js.map +1 -1
  81. package/lib/main/browser/view/plugin-view-registry.d.ts.map +1 -1
  82. package/lib/main/browser/view/plugin-view-registry.js +16 -0
  83. package/lib/main/browser/view/plugin-view-registry.js.map +1 -1
  84. package/lib/main/browser/webview/webview.js +1 -1
  85. package/lib/main/browser/webview/webview.js.map +1 -1
  86. package/lib/main/common/plugin-host-environment-preferences.d.ts +14 -0
  87. package/lib/main/common/plugin-host-environment-preferences.d.ts.map +1 -0
  88. package/lib/main/common/plugin-host-environment-preferences.js +43 -0
  89. package/lib/main/common/plugin-host-environment-preferences.js.map +1 -0
  90. package/lib/main/node/plugin-deployer-impl.d.ts.map +1 -1
  91. package/lib/main/node/plugin-deployer-impl.js +5 -0
  92. package/lib/main/node/plugin-deployer-impl.js.map +1 -1
  93. package/lib/main/node/plugin-ext-backend-module.d.ts.map +1 -1
  94. package/lib/main/node/plugin-ext-backend-module.js +5 -0
  95. package/lib/main/node/plugin-ext-backend-module.js.map +1 -1
  96. package/lib/main/node/plugin-host-navigator-state-initializer.d.ts +17 -0
  97. package/lib/main/node/plugin-host-navigator-state-initializer.d.ts.map +1 -0
  98. package/lib/main/node/plugin-host-navigator-state-initializer.js +53 -0
  99. package/lib/main/node/plugin-host-navigator-state-initializer.js.map +1 -0
  100. package/lib/plugin/command-registry.d.ts.map +1 -1
  101. package/lib/plugin/command-registry.js +11 -0
  102. package/lib/plugin/command-registry.js.map +1 -1
  103. package/lib/plugin/env.d.ts +5 -0
  104. package/lib/plugin/env.d.ts.map +1 -1
  105. package/lib/plugin/env.js +7 -0
  106. package/lib/plugin/env.js.map +1 -1
  107. package/lib/plugin/file-system-event-service-ext-impl.d.ts +1 -1
  108. package/lib/plugin/file-system-event-service-ext-impl.d.ts.map +1 -1
  109. package/lib/plugin/file-system-ext-impl.d.ts +1 -1
  110. package/lib/plugin/file-system-ext-impl.d.ts.map +1 -1
  111. package/lib/plugin/file-system-ext-impl.js +5 -1
  112. package/lib/plugin/file-system-ext-impl.js.map +1 -1
  113. package/lib/plugin/plugin-context.d.ts.map +1 -1
  114. package/lib/plugin/plugin-context.js +3 -2
  115. package/lib/plugin/plugin-context.js.map +1 -1
  116. package/lib/plugin/scm.d.ts +2 -1
  117. package/lib/plugin/scm.d.ts.map +1 -1
  118. package/lib/plugin/scm.js +20 -3
  119. package/lib/plugin/scm.js.map +1 -1
  120. package/lib/plugin/text-editors.d.ts +2 -2
  121. package/lib/plugin/text-editors.d.ts.map +1 -1
  122. package/lib/plugin/text-editors.js.map +1 -1
  123. package/lib/plugin/types-impl.d.ts +2 -1
  124. package/lib/plugin/types-impl.d.ts.map +1 -1
  125. package/lib/plugin/types-impl.js.map +1 -1
  126. package/package.json +30 -30
  127. package/src/common/plugin-api-rpc-model.ts +10 -1
  128. package/src/common/plugin-api-rpc.ts +21 -2
  129. package/src/common/plugin-protocol.ts +9 -1
  130. package/src/hosted/browser/hosted-plugin.ts +6 -0
  131. package/src/hosted/common/hosted-plugin.spec.ts +377 -0
  132. package/src/hosted/common/hosted-plugin.ts +18 -1
  133. package/src/hosted/node/hosted-plugin-process.ts +7 -0
  134. package/src/hosted/node/plugin-host-navigator-override.spec.ts +53 -0
  135. package/src/hosted/node/plugin-host-navigator-override.ts +34 -0
  136. package/src/hosted/node/plugin-host.ts +4 -0
  137. package/src/hosted/node/scanners/scanner-theia.spec.ts +112 -0
  138. package/src/hosted/node/scanners/scanner-theia.ts +8 -0
  139. package/src/main/browser/languages-main.ts +38 -52
  140. package/src/main/browser/menus/menus-contribution-handler.ts +2 -0
  141. package/src/main/browser/menus/plugin-menu-command-adapter.ts +3 -0
  142. package/src/main/browser/menus/vscode-theia-menu-mappings.ts +12 -2
  143. package/src/main/browser/plugin-contribution-handler.ts +6 -1
  144. package/src/main/browser/plugin-ext-frontend-module.ts +31 -1
  145. package/src/main/browser/plugin-ext-widget.tsx +14 -4
  146. package/src/main/browser/scm-main.ts +13 -6
  147. package/src/main/browser/style/plugin-sidebar.css +13 -0
  148. package/src/main/browser/text-editor-main.ts +14 -2
  149. package/src/main/browser/text-editors-main.ts +132 -0
  150. package/src/main/browser/view/plugin-view-registry.ts +18 -1
  151. package/src/main/browser/webview/pre/main.js +9 -9
  152. package/src/main/browser/webview/webview.ts +1 -1
  153. package/src/main/common/plugin-host-environment-preferences.ts +48 -0
  154. package/src/main/node/plugin-deployer-impl.ts +5 -0
  155. package/src/main/node/plugin-ext-backend-module.ts +5 -1
  156. package/src/main/node/plugin-host-navigator-state-initializer.ts +47 -0
  157. package/src/plugin/command-registry.ts +12 -1
  158. package/src/plugin/env.ts +8 -0
  159. package/src/plugin/file-system-event-service-ext-impl.ts +1 -1
  160. package/src/plugin/file-system-ext-impl.ts +1 -2
  161. package/src/plugin/plugin-context.ts +3 -2
  162. package/src/plugin/scm.ts +26 -4
  163. package/src/plugin/text-editors.ts +2 -2
  164. package/src/plugin/types-impl.ts +2 -1
@@ -120,6 +120,8 @@ export class PluginScmProvider implements ScmProvider {
120
120
 
121
121
  private features: SourceControlProviderFeatures = {};
122
122
 
123
+ get providerContextValue(): string | undefined { return this.features.contextValue; }
124
+
123
125
  get handle(): number { return this._handle; }
124
126
  get label(): string { return this._label; }
125
127
  get rootUri(): string { return this._rootUri ? this._rootUri.toString() : ''; }
@@ -164,7 +166,8 @@ export class PluginScmProvider implements ScmProvider {
164
166
  private readonly _contextValue: string,
165
167
  private readonly _label: string,
166
168
  private readonly _rootUri: vscodeURI | undefined,
167
- private disposables: DisposableCollection
169
+ private disposables: DisposableCollection,
170
+ readonly parentHandle?: number
168
171
  ) { }
169
172
 
170
173
  updateSourceControl(features: SourceControlProviderFeatures): void {
@@ -330,17 +333,21 @@ export class ScmMainImpl implements ScmMain {
330
333
  this.disposables.dispose();
331
334
  }
332
335
 
333
- async $registerSourceControl(handle: number, id: string, label: string, rootUri: UriComponents | undefined): Promise<void> {
334
- const provider = new PluginScmProvider(this.proxy, this.colors, this.sharedStyle, handle, id, label, rootUri ? vscodeURI.revive(rootUri) : undefined, this.disposables);
336
+ async $registerSourceControl(handle: number, id: string, label: string, rootUri: UriComponents | undefined, parentHandle?: number): Promise<void> {
337
+ const provider = new PluginScmProvider(
338
+ this.proxy, this.colors, this.sharedStyle, handle, id, label,
339
+ rootUri ? vscodeURI.revive(rootUri) : undefined, this.disposables, parentHandle
340
+ );
341
+ const parentRepo = parentHandle !== undefined ? this.repositories.get(parentHandle) : undefined;
335
342
  const repository = this.scmService.registerScmProvider(provider, {
336
343
  input: {
337
344
  validator: async value => {
338
345
  const result = await this.proxy.$validateInput(handle, value, value.length);
339
346
  return result && { message: result[0], type: result[1] };
340
347
  }
341
- }
342
- }
343
- );
348
+ },
349
+ parentRootUri: parentRepo?.provider.rootUri
350
+ });
344
351
  this.repositories.set(handle, repository);
345
352
 
346
353
  const disposables = new DisposableCollection(
@@ -71,3 +71,16 @@
71
71
  .plugins-tab-icon::before {
72
72
  content: "\f0fe";
73
73
  }
74
+
75
+ .theia-plugins .pluginHeaderContainer.pluginDisabledByTrust {
76
+ opacity: 0.6;
77
+ }
78
+
79
+ .theia-plugins .pluginRestrictedBadge {
80
+ font-size: var(--theia-ui-font-size0);
81
+ color: var(--theia-statusBarItem-prominentForeground);
82
+ background-color: var(--theia-statusBarItem-prominentBackground);
83
+ border-radius: 2px;
84
+ padding: calc(var(--theia-ui-padding) / 3 * 2);
85
+ white-space: nowrap;
86
+ }
@@ -95,8 +95,16 @@ export class TextEditorMain implements Disposable {
95
95
  this.toDisposeOnEditor.push(this.editor.onSelectionChanged(_ => {
96
96
  this.updateProperties();
97
97
  }));
98
- this.toDisposeOnEditor.push(monacoEditor.onDidChangeModel(() => {
99
- this.setEditor(undefined);
98
+ this.toDisposeOnEditor.push(monacoEditor.onDidChangeModel(e => {
99
+ // Ignore visibility-related model changes (null ↔ model) triggered by
100
+ // the setModel(null) workaround in MonacoEditor.handleVisibilityChanged().
101
+ // cf. https://github.com/eclipse-theia/theia/issues/14880
102
+ // Only react to genuine model swaps where both old and new models exist.
103
+ // In practice, this condition is never met because MonacoEditor instances
104
+ // are created with a fixed document and never swap to a different model.
105
+ if (e.oldModelUrl && e.newModelUrl) {
106
+ this.setEditor(undefined);
107
+ }
100
108
  }));
101
109
  this.toDisposeOnEditor.push(monacoEditor.onDidChangeCursorSelection(e => {
102
110
  this.updateProperties(e.source);
@@ -139,6 +147,10 @@ export class TextEditorMain implements Disposable {
139
147
  return this.editor.diffInformation;
140
148
  }
141
149
 
150
+ getDiffEditor(): MonacoDiffEditor | undefined {
151
+ return this.editor instanceof MonacoDiffEditor ? this.editor : undefined;
152
+ }
153
+
142
154
  setSelections(selections: Selection[]): void {
143
155
  if (this.editor) {
144
156
  this.editor.getControl().setSelections(selections);
@@ -49,6 +49,11 @@ import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/stan
49
49
  import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService';
50
50
  import { type ILineChange } from '@theia/monaco-editor-core/esm/vs/editor/common/diff/legacyLinesDiffComputer';
51
51
  import { ArrayUtils, URI } from '@theia/core';
52
+ import { DiffUris } from '@theia/core/lib/browser/diff-uris';
53
+ import { TextEditorChangeKind } from '../../plugin/types-impl';
54
+ import { Change } from '@theia/scm/lib/browser/dirty-diff/diff-computer';
55
+ import { DirtyDiffUpdate } from '@theia/scm/lib/browser/dirty-diff/dirty-diff-decorator';
56
+ import { ScmDecorationsService } from '@theia/scm/lib/browser/decorations/scm-decorations-service';
52
57
  import { toNotebookWorspaceEdit } from './notebooks/notebooks-main';
53
58
  import { interfaces } from '@theia/core/shared/inversify';
54
59
  import { NotebookService } from '@theia/notebook/lib/browser';
@@ -62,6 +67,7 @@ export class TextEditorsMainImpl implements TextEditorsMain, Disposable {
62
67
 
63
68
  private readonly bulkEditService: MonacoBulkEditService;
64
69
  private readonly notebookService: NotebookService;
70
+ private readonly scmDecorationsService: ScmDecorationsService;
65
71
 
66
72
  constructor(
67
73
  private readonly editorsAndDocuments: EditorsAndDocumentsMain,
@@ -73,10 +79,12 @@ export class TextEditorsMainImpl implements TextEditorsMain, Disposable {
73
79
 
74
80
  this.bulkEditService = container.get(MonacoBulkEditService);
75
81
  this.notebookService = container.get(NotebookService);
82
+ this.scmDecorationsService = container.get(ScmDecorationsService);
76
83
 
77
84
  this.toDispose.push(editorsAndDocuments);
78
85
  this.toDispose.push(editorsAndDocuments.onTextEditorAdd(editors => editors.forEach(this.onTextEditorAdd, this)));
79
86
  this.toDispose.push(editorsAndDocuments.onTextEditorRemove(editors => editors.forEach(this.onTextEditorRemove, this)));
87
+ this.toDispose.push(this.scmDecorationsService.onDirtyDiffUpdate(update => this.onDirtyDiffUpdate(update)));
80
88
  }
81
89
 
82
90
  dispose(): void {
@@ -93,6 +101,130 @@ export class TextEditorsMainImpl implements TextEditorsMain, Disposable {
93
101
  );
94
102
  this.editorsToDispose.set(id, toDispose);
95
103
  this.toDispose.push(toDispose);
104
+
105
+ const diffEditor = editor.getDiffEditor();
106
+ if (diffEditor) {
107
+ const pushDiffEditorInfo = () => this.pushDiffEditorDiffInformation(id, editor);
108
+ toDispose.push(diffEditor.diffEditor.onDidUpdateDiff(pushDiffEditorInfo));
109
+ pushDiffEditorInfo();
110
+ }
111
+ }
112
+
113
+ private onDirtyDiffUpdate(update: DirtyDiffUpdate): void {
114
+ const editorUri = update.editor.uri.toString();
115
+ const editorIds = this.findEditorIdsByUri(editorUri, true);
116
+ if (editorIds.length === 0) {
117
+ return;
118
+ }
119
+
120
+ const originalUri = update.previousRevisionUri?.toComponents();
121
+
122
+ const changes = update.changes.map(change => {
123
+ let kind: TextEditorChangeKind;
124
+ if (Change.isAddition(change)) {
125
+ kind = TextEditorChangeKind.Addition;
126
+ } else if (Change.isRemoval(change)) {
127
+ kind = TextEditorChangeKind.Deletion;
128
+ } else {
129
+ kind = TextEditorChangeKind.Modification;
130
+ }
131
+ return {
132
+ original: {
133
+ startLineNumber: change.previousRange.start + 1,
134
+ endLineNumberExclusive: change.previousRange.end + 1
135
+ },
136
+ modified: {
137
+ startLineNumber: change.currentRange.start + 1,
138
+ endLineNumberExclusive: change.currentRange.end + 1
139
+ },
140
+ kind
141
+ };
142
+ });
143
+
144
+ // Push diff information to all editors with this URI (regular editors and diff editor modified sides).
145
+ for (const editorId of editorIds) {
146
+ const editor = this.editorsAndDocuments.getEditor(editorId);
147
+ if (!editor) {
148
+ continue;
149
+ }
150
+ const model = editor.getModel();
151
+ const modifiedUri = URI.fromComponents(model.uri).toComponents();
152
+ this.proxy.$acceptEditorDiffInformation(editorId, [{
153
+ documentVersion: model.getVersionId(),
154
+ original: originalUri,
155
+ modified: modifiedUri,
156
+ changes,
157
+ isStale: false
158
+ }]);
159
+ }
160
+ }
161
+
162
+ private pushDiffEditorDiffInformation(id: string, editor: TextEditorMain): void {
163
+ const diffEditor = editor.getDiffEditor();
164
+ if (!diffEditor) {
165
+ return;
166
+ }
167
+
168
+ let originalUri: UriComponents | undefined;
169
+ let modifiedUri: UriComponents;
170
+
171
+ try {
172
+ const [left, right] = DiffUris.decode(diffEditor.uri);
173
+ originalUri = left.toComponents();
174
+ modifiedUri = right.toComponents();
175
+ } catch {
176
+ // Not a valid DiffUri; fall back to model URIs
177
+ originalUri = new URI(diffEditor.originalModel.uri).toComponents();
178
+ modifiedUri = new URI(diffEditor.modifiedModel.uri).toComponents();
179
+ }
180
+
181
+ const lineChanges = diffEditor.diffInformation;
182
+
183
+ const changes = lineChanges.map(change => {
184
+ let kind: TextEditorChangeKind;
185
+ if (change.originalEndLineNumber === 0) {
186
+ kind = TextEditorChangeKind.Addition;
187
+ } else if (change.modifiedEndLineNumber === 0) {
188
+ kind = TextEditorChangeKind.Deletion;
189
+ } else {
190
+ kind = TextEditorChangeKind.Modification;
191
+ }
192
+
193
+ // ILineChange uses 1-based lines where 0 means empty range.
194
+ // TextEditorLineRange uses 1-based startLineNumber and endLineNumberExclusive.
195
+ const toLineRange = (start: number, end: number) => end === 0
196
+ ? { startLineNumber: start + 1, endLineNumberExclusive: start + 1 }
197
+ : { startLineNumber: start, endLineNumberExclusive: end + 1 };
198
+
199
+ return {
200
+ original: toLineRange(change.originalStartLineNumber, change.originalEndLineNumber),
201
+ modified: toLineRange(change.modifiedStartLineNumber, change.modifiedEndLineNumber),
202
+ kind
203
+ };
204
+ });
205
+
206
+ const model = editor.getModel();
207
+ this.proxy.$acceptEditorDiffInformation(id, [{
208
+ documentVersion: model.getVersionId(),
209
+ original: originalUri,
210
+ modified: modifiedUri,
211
+ changes,
212
+ isStale: false
213
+ }]);
214
+ }
215
+
216
+ private findEditorIdsByUri(uri: string, excludeDiffEditors = false): string[] {
217
+ const ids: string[] = [];
218
+ for (const id of this.editorsToDispose.keys()) {
219
+ const editor = this.editorsAndDocuments.getEditor(id);
220
+ if (editor && editor.getModel().uri.toString() === uri) {
221
+ if (excludeDiffEditors && editor.getDiffEditor()) {
222
+ continue;
223
+ }
224
+ ids.push(id);
225
+ }
226
+ }
227
+ return ids;
96
228
  }
97
229
 
98
230
  private onTextEditorRemove(id: string): void {
@@ -24,7 +24,9 @@ import { ViewContainer, View, ViewWelcome, PluginViewType } from '../../../commo
24
24
  import { PluginSharedStyle } from '../plugin-shared-style';
25
25
  import { DebugWidget } from '@theia/debug/lib/browser/view/debug-widget';
26
26
  import { PluginViewWidget, PluginViewWidgetIdentifier } from './plugin-view-widget';
27
- import { SCM_VIEW_CONTAINER_ID, ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
27
+ import { SCM_VIEW_CONTAINER_ID, SCM_WIDGET_FACTORY_ID, ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
28
+ import { ScmWidget } from '@theia/scm/lib/browser/scm-widget';
29
+ import { ScmTreeWidget } from '@theia/scm/lib/browser/scm-tree-widget';
28
30
  import { EXPLORER_VIEW_CONTAINER_ID, FileNavigatorWidget, FILE_NAVIGATOR_ID } from '@theia/navigator/lib/browser';
29
31
  import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
30
32
  import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
@@ -187,6 +189,17 @@ export class PluginViewRegistry implements FrontendApplicationContribution {
187
189
  }));
188
190
  disposable.push(event.widget.onDidDispose(() => disposable.dispose()));
189
191
  }
192
+ if (event.widget instanceof ScmWidget) {
193
+ const disposable = new DisposableCollection();
194
+ disposable.push(this.registerViewWelcome({
195
+ view: 'scm',
196
+ content: nls.localizeByDefault('None of the registered source control providers work in Restricted Mode.')
197
+ + `\n[${nls.localizeByDefault('Manage Workspace Trust')}](command:workspace:manageTrust)`,
198
+ when: '!isWorkspaceTrusted',
199
+ order: 0
200
+ }));
201
+ disposable.push(event.widget.onDidDispose(() => disposable.dispose()));
202
+ }
190
203
  });
191
204
  this.contextKeyService.onDidChange(e => {
192
205
  for (const [, view] of this.views.values()) {
@@ -561,6 +574,10 @@ export class PluginViewRegistry implements FrontendApplicationContribution {
561
574
  switch (viewId) {
562
575
  case 'explorer':
563
576
  return this.widgetManager.getWidget<TreeViewWelcomeWidget>(FILE_NAVIGATOR_ID);
577
+ case 'scm': {
578
+ const scmWidget = await this.widgetManager.getWidget<ScmWidget>(SCM_WIDGET_FACTORY_ID);
579
+ return scmWidget?.resourceWidget as ScmTreeWidget | undefined;
580
+ }
564
581
  default:
565
582
  return this.widgetManager.getWidget<TreeViewWelcomeWidget>(PLUGIN_VIEW_DATA_FACTORY_ID, { id: viewId });
566
583
  }
@@ -154,29 +154,29 @@ globalThis.acquireVsCodeApi = (function() {
154
154
 
155
155
  let state = ${state ? `JSON.parse(${JSON.stringify(state)})` : undefined};
156
156
 
157
- const forwardConsoleLog = (level, msg, args) => {
157
+ const forwardConsoleLog = (level, msg, ...args) => {
158
158
  let message, optionalParams;
159
159
  try {
160
160
  if (msg) {
161
161
  message = JSON.stringify(msg) ?? null;
162
162
  }
163
- if (args) {
163
+ if (args.length > 0) {
164
164
  optionalParams = JSON.stringify(args) ?? null;
165
165
  }
166
166
  } catch (e) {
167
167
  // Log non serializable objects inside of view
168
- originalConsole[level](msg, args);
168
+ originalConsole[level](msg, ...args);
169
169
  return;
170
170
  }
171
171
  originalPostMessage({ command: 'onconsole', data: { level, message, optionalParams } }, targetOrigin);
172
172
  };
173
173
 
174
- console.log = (message, args) => forwardConsoleLog('log', message, args);
175
- console.info = (message, args) => forwardConsoleLog('info', message, args);
176
- console.warn = (message, args) => forwardConsoleLog('warn', message, args);
177
- console.error = (message, args) => forwardConsoleLog('error', message, args);
178
- console.debug = (message, args) => forwardConsoleLog('debug', message, args);
179
- console.trace = (message, args) => forwardConsoleLog('trace', message, args);
174
+ console.log = (message, ...args) => forwardConsoleLog('log', message, ...args);
175
+ console.info = (message, ...args) => forwardConsoleLog('info', message, ...args);
176
+ console.warn = (message, ...args) => forwardConsoleLog('warn', message, ...args);
177
+ console.error = (message, ...args) => forwardConsoleLog('error', message, ...args);
178
+ console.debug = (message, ...args) => forwardConsoleLog('debug', message, ...args);
179
+ console.trace = (message, ...args) => forwardConsoleLog('trace', message, ...args);
180
180
 
181
181
  return () => {
182
182
  if (acquired) {
@@ -475,7 +475,7 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget, Extract
475
475
  protected forwardConsoleLog(log: WebviewConsoleLog): void {
476
476
  const message = `[webview: ${this.identifier.id}] ${log.message ? JSON.parse(log.message) : undefined}`;
477
477
  if (log.optionalParams !== undefined) {
478
- console[log.level](message, JSON.parse(log.optionalParams));
478
+ console[log.level](message, ...JSON.parse(log.optionalParams));
479
479
  } else {
480
480
  console[log.level](message);
481
481
  }
@@ -0,0 +1,48 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 STMicroelectronics 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 { interfaces } from '@theia/core/shared/inversify';
18
+ import { PreferenceContribution, PreferenceSchema } from '@theia/core/lib/common/preferences';
19
+ import { nls } from '@theia/core/lib/common/nls';
20
+
21
+ export const SUPPORT_NODE_GLOBAL_NAVIGATOR_PREF = 'extensions.supportNodeGlobalNavigator';
22
+
23
+ export const PluginHostEnvironmentPreferenceSchema: PreferenceSchema = {
24
+ properties: {
25
+ [SUPPORT_NODE_GLOBAL_NAVIGATOR_PREF]: {
26
+ type: 'boolean',
27
+ description: nls.localize('theia/plugin-ext/supportNodeGlobalNavigator',
28
+ 'If enabled, the global navigator object in the extension host will be defined as provided by Node.js. '
29
+ + 'Extensions may use the presence of the navigator object as a hint that code is running in a browser. '
30
+ + 'Disabling this (the default) undefines the navigator in the extension host to preserve this assumption.'),
31
+ default: false,
32
+ }
33
+ }
34
+ };
35
+
36
+ /**
37
+ * Mutable state holder for the navigator preference, injected as a constant value
38
+ * so it can be safely resolved in connection-scoped containers (no async dependencies).
39
+ */
40
+ export const PluginHostNavigatorState = Symbol('PluginHostNavigatorState');
41
+ export interface PluginHostNavigatorState {
42
+ supportNodeGlobalNavigator: boolean;
43
+ }
44
+
45
+ export function bindPluginHostEnvironmentPreferences(bind: interfaces.Bind): void {
46
+ bind(PreferenceContribution).toConstantValue({ schema: PluginHostEnvironmentPreferenceSchema });
47
+ bind(PluginHostNavigatorState).toConstantValue({ supportNodeGlobalNavigator: false });
48
+ }
@@ -181,6 +181,7 @@ export class PluginDeployerImpl implements PluginDeployer {
181
181
  const hasBeenVisited = (id: string) => visited.has(id) || (visited.add(id), false);
182
182
  const pluginsToDeploy = new Map<PluginIdentifiers.VersionedId, PluginDeployerEntry>();
183
183
  const unversionedIdsHandled = new Map<PluginIdentifiers.UnversionedId, string[]>();
184
+ const errors: Error[] = [];
184
185
 
185
186
  const queue: UnresolvedPluginEntry[] = [...plugins];
186
187
  while (queue.length) {
@@ -215,6 +216,7 @@ export class PluginDeployerImpl implements PluginDeployer {
215
216
  }
216
217
  } catch (e) {
217
218
  console.error(`Failed to resolve plugins from '${entry.id}'`, e);
219
+ errors.push(e instanceof Error ? e : new Error(String(e)));
218
220
  }
219
221
  }));
220
222
  queue.length = 0;
@@ -229,6 +231,9 @@ export class PluginDeployerImpl implements PluginDeployer {
229
231
  }
230
232
  }
231
233
  }
234
+ if (pluginsToDeploy.size === 0 && errors.length > 0) {
235
+ throw errors[0];
236
+ }
232
237
  return [...pluginsToDeploy.values()];
233
238
  }
234
239
 
@@ -47,6 +47,8 @@ import { RemoteCliContribution } from '@theia/core/lib/node/remote/remote-cli-co
47
47
  import { PluginRemoteCopyContribution } from './plugin-remote-copy-contribution';
48
48
  import { RemoteCopyContribution } from '@theia/core/lib/node/remote/remote-copy-contribution';
49
49
  import { bindWebviewPreferences } from '../common/webview-preferences';
50
+ import { bindPluginHostEnvironmentPreferences } from '../common/plugin-host-environment-preferences';
51
+ import { PluginHostNavigatorStateInitializer } from './plugin-host-navigator-state-initializer';
50
52
 
51
53
  export function bindMainBackend(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void {
52
54
  bind(PluginApiContribution).toSelf().inSingletonScope();
@@ -104,5 +106,7 @@ export function bindMainBackend(bind: interfaces.Bind, unbind: interfaces.Unbind
104
106
 
105
107
  rebind(LocalizationServerImpl).to(PluginLocalizationServer).inSingletonScope();
106
108
  bindWebviewPreferences(bind);
107
-
109
+ bindPluginHostEnvironmentPreferences(bind);
110
+ bind(PluginHostNavigatorStateInitializer).toSelf().inSingletonScope();
111
+ bind(BackendApplicationContribution).toService(PluginHostNavigatorStateInitializer);
108
112
  }
@@ -0,0 +1,47 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 STMicroelectronics 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 { inject, injectable } from '@theia/core/shared/inversify';
18
+ import { PreferenceService } from '@theia/core/lib/common/preferences';
19
+ import { BackendApplicationContribution } from '@theia/core/lib/node';
20
+ import { PluginHostNavigatorState, SUPPORT_NODE_GLOBAL_NAVIGATOR_PREF } from '../common/plugin-host-environment-preferences';
21
+
22
+ /**
23
+ * Keeps {@link PluginHostNavigatorState} in sync with the navigator preference.
24
+ *
25
+ * `PreferenceService` has async dependencies and cannot be injected in
26
+ * connection-scoped containers. This contribution runs in the root container
27
+ * and mutates the shared state object by reference so that
28
+ * `HostedPluginProcess` (connection-scoped) always sees the latest value.
29
+ */
30
+ @injectable()
31
+ export class PluginHostNavigatorStateInitializer implements BackendApplicationContribution {
32
+
33
+ @inject(PreferenceService)
34
+ protected readonly preferences: PreferenceService;
35
+
36
+ @inject(PluginHostNavigatorState)
37
+ protected readonly state: PluginHostNavigatorState;
38
+
39
+ onStart(): void {
40
+ this.state.supportNodeGlobalNavigator = this.preferences.get<boolean>(SUPPORT_NODE_GLOBAL_NAVIGATOR_PREF, false);
41
+ this.preferences.onPreferenceChanged(e => {
42
+ if (e.preferenceName === SUPPORT_NODE_GLOBAL_NAVIGATOR_PREF) {
43
+ this.state.supportNodeGlobalNavigator = this.preferences.get<boolean>(SUPPORT_NODE_GLOBAL_NAVIGATOR_PREF, false);
44
+ }
45
+ });
46
+ }
47
+ }
@@ -22,10 +22,11 @@ import * as theia from '@theia/plugin';
22
22
  import * as model from '../common/plugin-api-rpc-model';
23
23
  import { CommandRegistryExt, PLUGIN_RPC_CONTEXT as Ext, CommandRegistryMain } from '../common/plugin-api-rpc';
24
24
  import { RPCProtocol } from '../common/rpc-protocol';
25
- import { Disposable } from './types-impl';
25
+ import { Disposable, URI } from './types-impl';
26
26
  import { DisposableCollection } from '@theia/core';
27
27
  import { KnownCommands } from './known-commands';
28
28
  import { ArgumentProcessor } from '../common/commands';
29
+ import { isUriComponents } from './type-converters';
29
30
 
30
31
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
32
  export type Handler = <T>(...args: any[]) => T | PromiseLike<T | undefined>;
@@ -42,6 +43,16 @@ export class CommandRegistryImpl implements CommandRegistryExt {
42
43
  this.proxy = rpc.getProxy(Ext.COMMAND_REGISTRY_MAIN);
43
44
  this.argumentProcessors = [];
44
45
  this.commandsConverter = new CommandsConverter(this);
46
+ this.registerArgumentProcessor({
47
+ processArgument: arg => {
48
+ // Revive URI-like plain objects that lost their class identity during RPC/JSON serialization
49
+ // (e.g. command arguments from markdown links parsed by CommandOpenHandler)
50
+ if (isUriComponents(arg) && !(arg instanceof URI)) {
51
+ return URI.revive(arg);
52
+ }
53
+ return arg;
54
+ }
55
+ });
45
56
  }
46
57
 
47
58
  get converter(): CommandsConverter {
package/src/plugin/env.ts CHANGED
@@ -108,6 +108,14 @@ export abstract class EnvExtImpl {
108
108
 
109
109
  abstract get isNewAppInstall(): boolean;
110
110
 
111
+ /**
112
+ * @stubbed
113
+ * Portable mode detection is not yet communicated to the plugin host.
114
+ */
115
+ get isAppPortable(): boolean {
116
+ return false;
117
+ }
118
+
111
119
  get appHost(): string {
112
120
  return this.host;
113
121
  }
@@ -30,7 +30,7 @@
30
30
 
31
31
  import { Emitter, WaitUntilEvent, AsyncEmitter, WaitUntilData } from '@theia/core/lib/common/event';
32
32
  import { IRelativePattern, parse } from '@theia/core/lib/common/glob';
33
- import { UriComponents } from '@theia/core/shared/vscode-uri';
33
+ import { UriComponents } from '../common/uri-components';
34
34
  import { Disposable, URI, WorkspaceEdit } from './types-impl';
35
35
  import { EditorsAndDocumentsExtImpl as ExtHostDocumentsAndEditors } from './editors-and-documents';
36
36
  import type * as vscode from '@theia/plugin';
@@ -28,14 +28,13 @@
28
28
  /* eslint-disable @typescript-eslint/tslint/config */
29
29
  /* eslint-disable @typescript-eslint/no-explicit-any */
30
30
 
31
- import { UriComponents } from '@theia/core/shared/vscode-uri';
31
+ import { Schemes as Schemas, UriComponents } from '../common/uri-components';
32
32
  import { FileChangeType, FileSystemError, URI } from './types-impl';
33
33
  import { RPCProtocol } from '../common/rpc-protocol';
34
34
  import { PLUGIN_RPC_CONTEXT, FileSystemExt, FileSystemMain, IFileChangeDto } from '../common/plugin-api-rpc';
35
35
  import * as vscode from '@theia/plugin';
36
36
  import * as files from '@theia/filesystem/lib/common/files';
37
37
  import * as typeConverter from './type-converters';
38
- import { Schemes as Schemas } from '../common/uri-components';
39
38
  import { State, StateMachine, LinkComputer, Edge } from '../common/link-computer';
40
39
  import { commonPrefixLength } from '@theia/core/lib/common/strings';
41
40
  import { CharCode } from '@theia/core/lib/common/char-code';
@@ -930,6 +930,7 @@ export function createAPIFactory(
930
930
  get appHost(): string { return envExt.appHost; },
931
931
  get language(): string { return envExt.language; },
932
932
  get isNewAppInstall(): boolean { return envExt.isNewAppInstall; },
933
+ get isAppPortable(): boolean { return envExt.isAppPortable; },
933
934
  get isTelemetryEnabled(): boolean { return telemetryExt.isTelemetryEnabled; },
934
935
  get onDidChangeTelemetryEnabled(): theia.Event<boolean> {
935
936
  return telemetryExt.onDidChangeTelemetryEnabled;
@@ -1285,8 +1286,8 @@ export function createAPIFactory(
1285
1286
  throw new Error('Input box not found!');
1286
1287
  }
1287
1288
  },
1288
- createSourceControl(id: string, label: string, rootUri?: URI): theia.SourceControl {
1289
- return createAPIObject(scmExt.createSourceControl(plugin, id, label, rootUri));
1289
+ createSourceControl(id: string, label: string, rootUri?: URI, iconPath?: theia.IconPath, parent?: theia.SourceControl): theia.SourceControl {
1290
+ return createAPIObject(scmExt.createSourceControl(plugin, id, label, rootUri, iconPath, parent));
1290
1291
  }
1291
1292
  };
1292
1293
 
package/src/plugin/scm.ts CHANGED
@@ -676,12 +676,23 @@ class SourceControlImpl implements theia.SourceControl {
676
676
  private readonly onDidChangeSelectionEmitter = new Emitter<boolean>();
677
677
  readonly onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
678
678
 
679
+ private _contextValue: string | undefined = undefined;
680
+
681
+ get contextValue(): string | undefined {
682
+ return this._contextValue;
683
+ }
684
+
685
+ set contextValue(contextValue: string | undefined) {
686
+ this._contextValue = contextValue;
687
+ this.proxy.$updateSourceControl(this.handle, { contextValue });
688
+ }
689
+
679
690
  private readonly onDidDisposeEmitter = new Emitter<void>();
680
691
  readonly onDidDispose = this.onDidDisposeEmitter.event;
681
692
 
682
693
  readonly onDidDisposeParent: Event<void>;
683
694
 
684
- private handle: number = SourceControlImpl.handlePool++;
695
+ readonly handle: number = SourceControlImpl.handlePool++;
685
696
 
686
697
  constructor(
687
698
  private plugin: Plugin,
@@ -694,7 +705,7 @@ class SourceControlImpl implements theia.SourceControl {
694
705
  _parent?: SourceControlImpl
695
706
  ) {
696
707
  this.inputBox = new ScmInputBoxImpl(plugin, this.proxy, this.handle);
697
- this.proxy.$registerSourceControl(this.handle, _id, _label, _rootUri);
708
+ this.proxy.$registerSourceControl(this.handle, _id, _label, _rootUri, _parent?.handle);
698
709
  this.onDidDisposeParent = _parent ? _parent.onDidDispose : Event.None;
699
710
  }
700
711
 
@@ -824,9 +835,11 @@ export class ScmExtImpl implements ScmExt {
824
835
  });
825
836
  }
826
837
 
827
- createSourceControl(extension: Plugin, id: string, label: string, rootUri: theia.Uri | undefined): theia.SourceControl {
838
+ createSourceControl(extension: Plugin, id: string, label: string, rootUri: theia.Uri | undefined,
839
+ iconPath?: theia.IconPath, parent?: theia.SourceControl): theia.SourceControl {
828
840
  const handle = ScmExtImpl.handlePool++;
829
- const sourceControl = new SourceControlImpl(extension, this.proxy, this.commands, id, label, rootUri);
841
+ const parentImpl = parent ? this.findSourceControlImpl(parent) : undefined;
842
+ const sourceControl = new SourceControlImpl(extension, this.proxy, this.commands, id, label, rootUri, iconPath, parentImpl);
830
843
  this.sourceControls.set(handle, sourceControl);
831
844
 
832
845
  const sourceControls = this.sourceControlsByExtension.get(extension.model.id) || [];
@@ -836,6 +849,15 @@ export class ScmExtImpl implements ScmExt {
836
849
  return sourceControl;
837
850
  }
838
851
 
852
+ private findSourceControlImpl(apiObject: theia.SourceControl): SourceControlImpl | undefined {
853
+ for (const impl of this.sourceControls.values()) {
854
+ if (impl.id === apiObject.id && impl.rootUri?.toString() === apiObject.rootUri?.toString()) {
855
+ return impl;
856
+ }
857
+ }
858
+ return undefined;
859
+ }
860
+
839
861
  getLastInputBox(extension: Plugin): ScmInputBoxImpl | undefined {
840
862
  const sourceControls = this.sourceControlsByExtension.get(extension.model.id);
841
863
  const sourceControl = sourceControls && sourceControls[sourceControls.length - 1];