@theia/plugin-ext 1.34.1 → 1.35.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 (110) hide show
  1. package/lib/common/plugin-api-rpc-model.d.ts +18 -1
  2. package/lib/common/plugin-api-rpc-model.d.ts.map +1 -1
  3. package/lib/common/plugin-api-rpc-model.js.map +1 -1
  4. package/lib/common/plugin-api-rpc.d.ts +10 -5
  5. package/lib/common/plugin-api-rpc.d.ts.map +1 -1
  6. package/lib/common/plugin-api-rpc.js.map +1 -1
  7. package/lib/common/plugin-protocol.d.ts +30 -0
  8. package/lib/common/plugin-protocol.d.ts.map +1 -1
  9. package/lib/common/plugin-protocol.js.map +1 -1
  10. package/lib/hosted/browser/hosted-plugin.d.ts.map +1 -1
  11. package/lib/hosted/browser/hosted-plugin.js +10 -17
  12. package/lib/hosted/browser/hosted-plugin.js.map +1 -1
  13. package/lib/hosted/node/hosted-plugin-process.js.map +1 -1
  14. package/lib/hosted/node/plugin-activation-events.d.ts +7 -0
  15. package/lib/hosted/node/plugin-activation-events.d.ts.map +1 -0
  16. package/lib/hosted/node/plugin-activation-events.js +96 -0
  17. package/lib/hosted/node/plugin-activation-events.js.map +1 -0
  18. package/lib/hosted/node/plugin-manifest-loader.d.ts +2 -1
  19. package/lib/hosted/node/plugin-manifest-loader.d.ts.map +1 -1
  20. package/lib/hosted/node/plugin-manifest-loader.js +2 -1
  21. package/lib/hosted/node/plugin-manifest-loader.js.map +1 -1
  22. package/lib/hosted/node/scanners/scanner-theia.d.ts.map +1 -1
  23. package/lib/hosted/node/scanners/scanner-theia.js +27 -36
  24. package/lib/hosted/node/scanners/scanner-theia.js.map +1 -1
  25. package/lib/main/browser/data-transfer/data-transfer-type-converters.d.ts +9 -0
  26. package/lib/main/browser/data-transfer/data-transfer-type-converters.d.ts.map +1 -0
  27. package/lib/main/browser/data-transfer/data-transfer-type-converters.js +65 -0
  28. package/lib/main/browser/data-transfer/data-transfer-type-converters.js.map +1 -0
  29. package/lib/main/browser/dialogs/modal-notification.d.ts.map +1 -1
  30. package/lib/main/browser/dialogs/modal-notification.js +4 -2
  31. package/lib/main/browser/dialogs/modal-notification.js.map +1 -1
  32. package/lib/main/browser/languages-main.d.ts +8 -1
  33. package/lib/main/browser/languages-main.d.ts.map +1 -1
  34. package/lib/main/browser/languages-main.js +26 -2
  35. package/lib/main/browser/languages-main.js.map +1 -1
  36. package/lib/main/browser/plugin-ext-frontend-module.d.ts.map +1 -1
  37. package/lib/main/browser/plugin-ext-frontend-module.js +3 -0
  38. package/lib/main/browser/plugin-ext-frontend-module.js.map +1 -1
  39. package/lib/main/browser/text-editors-main.d.ts +2 -2
  40. package/lib/main/browser/text-editors-main.d.ts.map +1 -1
  41. package/lib/main/browser/text-editors-main.js +2 -2
  42. package/lib/main/browser/text-editors-main.js.map +1 -1
  43. package/lib/main/browser/webview/webview-context-keys.d.ts +13 -0
  44. package/lib/main/browser/webview/webview-context-keys.d.ts.map +1 -0
  45. package/lib/main/browser/webview/webview-context-keys.js +64 -0
  46. package/lib/main/browser/webview/webview-context-keys.js.map +1 -0
  47. package/lib/plugin/dialogs.js +2 -2
  48. package/lib/plugin/dialogs.js.map +1 -1
  49. package/lib/plugin/file-system-ext-impl.d.ts +11 -5
  50. package/lib/plugin/file-system-ext-impl.d.ts.map +1 -1
  51. package/lib/plugin/file-system-ext-impl.js +8 -16
  52. package/lib/plugin/file-system-ext-impl.js.map +1 -1
  53. package/lib/plugin/known-commands.d.ts.map +1 -1
  54. package/lib/plugin/known-commands.js +13 -0
  55. package/lib/plugin/known-commands.js.map +1 -1
  56. package/lib/plugin/languages/document-drop-edit.d.ts +16 -0
  57. package/lib/plugin/languages/document-drop-edit.d.ts.map +1 -0
  58. package/lib/plugin/languages/document-drop-edit.js +23 -0
  59. package/lib/plugin/languages/document-drop-edit.js.map +1 -0
  60. package/lib/plugin/languages.d.ts +9 -2
  61. package/lib/plugin/languages.d.ts.map +1 -1
  62. package/lib/plugin/languages.js +27 -1
  63. package/lib/plugin/languages.js.map +1 -1
  64. package/lib/plugin/plugin-context.d.ts.map +1 -1
  65. package/lib/plugin/plugin-context.js +8 -4
  66. package/lib/plugin/plugin-context.js.map +1 -1
  67. package/lib/plugin/preference-registry.d.ts +5 -3
  68. package/lib/plugin/preference-registry.d.ts.map +1 -1
  69. package/lib/plugin/preference-registry.js +31 -42
  70. package/lib/plugin/preference-registry.js.map +1 -1
  71. package/lib/plugin/preference-registry.spec.js +29 -41
  72. package/lib/plugin/preference-registry.spec.js.map +1 -1
  73. package/lib/plugin/text-editors.d.ts +1 -1
  74. package/lib/plugin/text-editors.d.ts.map +1 -1
  75. package/lib/plugin/text-editors.js +2 -2
  76. package/lib/plugin/text-editors.js.map +1 -1
  77. package/lib/plugin/type-converters.d.ts +6 -0
  78. package/lib/plugin/type-converters.d.ts.map +1 -1
  79. package/lib/plugin/type-converters.js +37 -1
  80. package/lib/plugin/type-converters.js.map +1 -1
  81. package/lib/plugin/types-impl.d.ts +5 -0
  82. package/lib/plugin/types-impl.d.ts.map +1 -1
  83. package/lib/plugin/types-impl.js +13 -3
  84. package/lib/plugin/types-impl.js.map +1 -1
  85. package/package.json +27 -27
  86. package/src/common/plugin-api-rpc-model.ts +22 -1
  87. package/src/common/plugin-api-rpc.ts +18 -5
  88. package/src/common/plugin-protocol.ts +32 -4
  89. package/src/hosted/browser/hosted-plugin.ts +9 -16
  90. package/src/hosted/node/hosted-plugin-process.ts +2 -2
  91. package/src/hosted/node/plugin-activation-events.ts +111 -0
  92. package/src/hosted/node/plugin-manifest-loader.ts +4 -3
  93. package/src/hosted/node/scanners/scanner-theia.ts +59 -75
  94. package/src/main/browser/data-transfer/data-transfer-type-converters.ts +70 -0
  95. package/src/main/browser/dialogs/modal-notification.ts +4 -2
  96. package/src/main/browser/languages-main.ts +34 -4
  97. package/src/main/browser/plugin-ext-frontend-module.ts +3 -0
  98. package/src/main/browser/text-editors-main.ts +3 -2
  99. package/src/main/browser/webview/webview-context-keys.ts +49 -0
  100. package/src/plugin/dialogs.ts +2 -2
  101. package/src/plugin/file-system-ext-impl.ts +8 -18
  102. package/src/plugin/known-commands.ts +16 -1
  103. package/src/plugin/languages/document-drop-edit.ts +44 -0
  104. package/src/plugin/languages.ts +41 -3
  105. package/src/plugin/plugin-context.ts +9 -4
  106. package/src/plugin/preference-registry.spec.ts +29 -45
  107. package/src/plugin/preference-registry.ts +33 -45
  108. package/src/plugin/text-editors.ts +2 -2
  109. package/src/plugin/type-converters.ts +37 -0
  110. package/src/plugin/types-impl.ts +11 -0
@@ -0,0 +1,49 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 Ericsson and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
18
+ import { ContextKey, ContextKeyService } from '@theia/core/lib/browser/context-key-service';
19
+ import { ApplicationShell, FocusTracker, Widget } from '@theia/core/lib/browser';
20
+ import { WebviewWidget } from './webview';
21
+
22
+ @injectable()
23
+ export class WebviewContextKeys {
24
+
25
+ /**
26
+ * Context key representing the `viewType` of the active `WebviewWidget`, if any.
27
+ */
28
+ activeWebviewPanelId: ContextKey<string>;
29
+
30
+ @inject(ApplicationShell)
31
+ protected applicationShell: ApplicationShell;
32
+
33
+ @inject(ContextKeyService)
34
+ protected contextKeyService: ContextKeyService;
35
+
36
+ @postConstruct()
37
+ protected postConstruct(): void {
38
+ this.activeWebviewPanelId = this.contextKeyService.createKey('activeWebviewPanelId', '');
39
+ this.applicationShell.onDidChangeCurrentWidget(this.handleDidChangeCurrentWidget, this);
40
+ }
41
+
42
+ protected handleDidChangeCurrentWidget(change: FocusTracker.IChangedArgs<Widget>): void {
43
+ if (change.newValue instanceof WebviewWidget) {
44
+ this.activeWebviewPanelId.set(change.newValue.viewType);
45
+ } else {
46
+ this.activeWebviewPanelId.set('');
47
+ }
48
+ }
49
+ }
@@ -30,8 +30,8 @@ export class DialogsExtImpl {
30
30
  title: options.title,
31
31
  openLabel: options.openLabel,
32
32
  defaultUri: options.defaultUri ? options.defaultUri.path : undefined,
33
- canSelectFiles: options.canSelectFiles ? options.canSelectFiles : true,
34
- canSelectFolders: options.canSelectFolders ? options.canSelectFolders : false,
33
+ canSelectFiles: typeof options.canSelectFiles === 'boolean' ? options.canSelectFiles : true,
34
+ canSelectFolders: typeof options.canSelectFolders === 'boolean' ? options.canSelectFolders : false,
35
35
  canSelectMany: options.canSelectMany,
36
36
  filters: options.filters
37
37
  } as OpenDialogOptionsMain;
@@ -35,16 +35,16 @@ import { PLUGIN_RPC_CONTEXT, FileSystemExt, FileSystemMain, IFileChangeDto } fro
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 { LanguagesExtImpl } from './languages';
39
38
  import { Schemes as Schemas } from '../common/uri-components';
40
39
  import { State, StateMachine, LinkComputer, Edge } from '../common/link-computer';
41
40
  import { commonPrefixLength } from '@theia/core/lib/common/strings';
42
41
  import { CharCode } from '@theia/core/lib/common/char-code';
43
42
  import { BinaryBuffer } from '@theia/core/lib/common/buffer';
43
+ import { Emitter } from '@theia/core/shared/vscode-languageserver-protocol';
44
44
 
45
45
  type IDisposable = vscode.Disposable;
46
46
 
47
- class FsLinkProvider {
47
+ export class FsLinkProvider {
48
48
 
49
49
  private _schemes: string[] = [];
50
50
  private _stateMachine?: StateMachine;
@@ -204,12 +204,14 @@ export class FileSystemExtImpl implements FileSystemExt {
204
204
  private readonly _usedSchemes = new Set<string>();
205
205
  private readonly _watches = new Map<number, IDisposable>();
206
206
 
207
- private _linkProviderRegistration?: IDisposable;
207
+ private readonly onWillRegisterFileSystemProviderEmitter = new Emitter<FsLinkProvider>();
208
+ readonly onWillRegisterFileSystemProvider = this.onWillRegisterFileSystemProviderEmitter.event;
209
+
208
210
  private _handlePool: number = 0;
209
211
 
210
212
  readonly fileSystem: vscode.FileSystem;
211
213
 
212
- constructor(rpc: RPCProtocol, private _extHostLanguageFeatures: LanguagesExtImpl) {
214
+ constructor(rpc: RPCProtocol) {
213
215
  this._proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.FILE_SYSTEM_MAIN);
214
216
  this.fileSystem = new ConsumerFileSystem(this._proxy, this._capabilities);
215
217
 
@@ -218,18 +220,7 @@ export class FileSystemExtImpl implements FileSystemExt {
218
220
  }
219
221
 
220
222
  dispose(): void {
221
- if (this._linkProviderRegistration) {
222
- this._linkProviderRegistration.dispose();
223
- }
224
- }
225
-
226
- private _registerLinkProviderIfNotYetRegistered(): void {
227
- if (!this._linkProviderRegistration) {
228
- this._linkProviderRegistration = this._extHostLanguageFeatures.registerDocumentLinkProvider('*', this._linkProvider, {
229
- id: 'theia.fs-ext-impl',
230
- name: 'fs-ext-impl'
231
- });
232
- }
223
+ this.onWillRegisterFileSystemProviderEmitter.dispose();
233
224
  }
234
225
 
235
226
  registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider, options: { isCaseSensitive?: boolean, isReadonly?: boolean } = {}) {
@@ -238,8 +229,7 @@ export class FileSystemExtImpl implements FileSystemExt {
238
229
  throw new Error(`a provider for the scheme '${scheme}' is already registered`);
239
230
  }
240
231
 
241
- //
242
- this._registerLinkProviderIfNotYetRegistered();
232
+ this.onWillRegisterFileSystemProviderEmitter.fire(this._linkProvider);
243
233
 
244
234
  const handle = this._handlePool++;
245
235
  this._linkProvider.add(scheme);
@@ -14,8 +14,9 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { Range as R, Position as P, Location as L } from '@theia/core/shared/vscode-languageserver-protocol';
18
17
  import * as theia from '@theia/plugin';
18
+ import { Range as R, Position as P, Location as L } from '@theia/core/shared/vscode-languageserver-protocol';
19
+ import { URI as TheiaURI } from '@theia/core/lib/common/uri';
19
20
  import { cloneAndChange } from '../common/objects';
20
21
  import { Position, Range, Location, CallHierarchyItem, TypeHierarchyItem, URI, TextDocumentShowOptions } from './types-impl';
21
22
  import {
@@ -63,6 +64,9 @@ export namespace KnownCommands {
63
64
  }
64
65
  };
65
66
 
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ const identity = (args: any[]) => args;
69
+
66
70
  mappings['editor.action.select.all'] = ['editor.action.select.all', CONVERT_VSCODE_TO_MONACO];
67
71
  mappings['editor.action.toggleHighContrast'] = ['editor.action.toggleHighContrast', CONVERT_VSCODE_TO_MONACO];
68
72
  mappings['editor.action.moveCarretLeftAction'] = ['editor.action.moveCarretLeftAction', CONVERT_VSCODE_TO_MONACO];
@@ -302,6 +306,17 @@ export namespace KnownCommands {
302
306
  mappings['vscode.open'] = ['vscode.open', CONVERT_VSCODE_TO_MONACO];
303
307
  mappings['vscode.diff'] = ['vscode.diff', CONVERT_VSCODE_TO_MONACO];
304
308
 
309
+ // terminal commands
310
+ mappings['workbench.action.terminal.new'] = ['terminal:new', identity];
311
+ mappings['workbench.action.terminal.newWithProfile'] = ['terminal:new:profile', identity];
312
+ mappings['workbench.action.terminal.selectDefaultShell'] = ['terminal:profile:default', identity];
313
+ mappings['workbench.action.terminal.newInActiveWorkspace'] = ['terminal:new:active:workspace', identity];
314
+ mappings['workbench.action.terminal.clear'] = ['terminal:clear', identity];
315
+ mappings['openInTerminal'] = ['terminal:context', createConversionFunction((uri: URI) => new TheiaURI(uri))];
316
+ mappings['workbench.action.terminal.split'] = ['terminal:split', identity];
317
+ mappings['workbench.action.terminal.focusFind'] = ['terminal:find', identity];
318
+ mappings['workbench.action.terminal.hideFind'] = ['terminal:find:cancel', identity];
319
+
305
320
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
306
321
  export function map<T>(id: string, args: any[] | undefined, toDo: (mappedId: string, mappedArgs: any[] | undefined, mappedResult: ConversionFunction | undefined) => T): T {
307
322
  if (mappings[id]) {
@@ -0,0 +1,44 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2018 Red Hat, Inc. and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+ import * as theia from '@theia/plugin';
17
+ import { DataTransferDTO, DocumentDropEdit } from '../../common/plugin-api-rpc-model';
18
+ import { CancellationToken } from '@theia/core/shared/vscode-languageserver-protocol';
19
+ import { Position } from '../../common/plugin-api-rpc';
20
+ import * as Converter from '../type-converters';
21
+ import { DocumentsExtImpl } from '../documents';
22
+ import { URI } from '@theia/core/shared/vscode-uri';
23
+ import { FileSystemExtImpl } from '../file-system-ext-impl';
24
+ import * as os from 'os';
25
+ import * as path from 'path';
26
+
27
+ export class DocumentDropEditAdapter {
28
+ constructor(private readonly provider: theia.DocumentDropEditProvider,
29
+ private readonly documents: DocumentsExtImpl,
30
+ private readonly fileSystem: FileSystemExtImpl) { }
31
+
32
+ async provideDocumentDropEdits(resource: URI, position: Position, dataTransfer: DataTransferDTO, token: CancellationToken): Promise<DocumentDropEdit | undefined> {
33
+ return this.provider.provideDocumentDropEdits(
34
+ this.documents.getDocument(resource),
35
+ Converter.toPosition(position),
36
+ Converter.DataTransfer.toDataTransfer(dataTransfer, itemId => this.resolveFileData(itemId)),
37
+ token) as DocumentDropEdit | undefined;
38
+ }
39
+
40
+ private async resolveFileData(itemId: string): Promise<Uint8Array> {
41
+ const filePath = URI.file(path.resolve(os.tmpdir(), 'theia_upload', itemId));
42
+ return this.fileSystem.fileSystem.readFile(filePath);
43
+ }
44
+ }
@@ -69,7 +69,9 @@ import {
69
69
  InlineValue,
70
70
  InlineValueContext,
71
71
  TypeHierarchyItem,
72
- InlineCompletionContext
72
+ InlineCompletionContext,
73
+ DocumentDropEdit,
74
+ DataTransferDTO
73
75
  } from '../common/plugin-api-rpc-model';
74
76
  import { CompletionAdapter } from './languages/completion';
75
77
  import { Diagnostics } from './languages/diagnostics';
@@ -109,6 +111,9 @@ import { LinkedEditingRangeAdapter } from './languages/linked-editing-range';
109
111
  import { serializeEnterRules, serializeIndentation, serializeRegExp } from './languages-utils';
110
112
  import { InlayHintsAdapter } from './languages/inlay-hints';
111
113
  import { InlineCompletionAdapter, InlineCompletionAdapterBase } from './languages/inline-completion';
114
+ import { DocumentDropEditAdapter } from './languages/document-drop-edit';
115
+ import { IDisposable } from '@theia/monaco-editor-core';
116
+ import { FileSystemExtImpl, FsLinkProvider } from './file-system-ext-impl';
112
117
 
113
118
  type Adapter = CompletionAdapter |
114
119
  SignatureHelpAdapter |
@@ -139,7 +144,8 @@ type Adapter = CompletionAdapter |
139
144
  DocumentSemanticTokensAdapter |
140
145
  LinkedEditingRangeAdapter |
141
146
  TypeHierarchyAdapter |
142
- InlineCompletionAdapter;
147
+ InlineCompletionAdapter |
148
+ DocumentDropEditAdapter;
143
149
 
144
150
  export class LanguagesExtImpl implements LanguagesExt {
145
151
 
@@ -147,15 +153,25 @@ export class LanguagesExtImpl implements LanguagesExt {
147
153
 
148
154
  private readonly diagnostics: Diagnostics;
149
155
 
156
+ private linkProviderRegistration?: IDisposable;
157
+
150
158
  private callId = 0;
151
159
  private adaptersMap = new Map<number, Adapter>();
152
160
 
153
161
  constructor(
154
162
  rpc: RPCProtocol,
155
163
  private readonly documents: DocumentsExtImpl,
156
- private readonly commands: CommandRegistryImpl) {
164
+ private readonly commands: CommandRegistryImpl,
165
+ private readonly filesSystem: FileSystemExtImpl) {
157
166
  this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.LANGUAGES_MAIN);
158
167
  this.diagnostics = new Diagnostics(rpc);
168
+ filesSystem.onWillRegisterFileSystemProvider(linkProvider => this.registerLinkProviderIfNotYetRegistered(linkProvider));
169
+ }
170
+
171
+ dispose(): void {
172
+ if (this.linkProviderRegistration) {
173
+ this.linkProviderRegistration.dispose();
174
+ }
159
175
  }
160
176
 
161
177
  get onDidChangeDiagnostics(): Event<theia.DiagnosticChangeEvent> {
@@ -258,6 +274,15 @@ export class LanguagesExtImpl implements LanguagesExt {
258
274
  return undefined;
259
275
  }
260
276
 
277
+ private registerLinkProviderIfNotYetRegistered(linkProvider: FsLinkProvider): void {
278
+ if (!this.linkProviderRegistration) {
279
+ this.linkProviderRegistration = this.registerDocumentLinkProvider('*', linkProvider, {
280
+ id: 'theia.fs-ext-impl',
281
+ name: 'fs-ext-impl'
282
+ });
283
+ }
284
+ }
285
+
261
286
  // ### Completion begin
262
287
  $provideCompletionItems(handle: number, resource: UriComponents, position: Position,
263
288
  context: CompletionContext, token: theia.CancellationToken): Promise<CompletionResultDto | undefined> {
@@ -463,6 +488,19 @@ export class LanguagesExtImpl implements LanguagesExt {
463
488
  }
464
489
  // ### Document Formatting Edit end
465
490
 
491
+ // ### Drop Edit Provider start
492
+ $provideDocumentDropEdits(handle: number, resource: UriComponents, position: Position,
493
+ dataTransfer: DataTransferDTO, token: theia.CancellationToken): Promise<DocumentDropEdit | undefined> {
494
+ return this.withAdapter(handle, DocumentDropEditAdapter, adapter => adapter.provideDocumentDropEdits(URI.revive(resource), position, dataTransfer, token), undefined);
495
+ }
496
+
497
+ registerDocumentDropEditProvider(selector: theia.DocumentSelector, provider: theia.DocumentDropEditProvider): theia.Disposable {
498
+ const callId = this.addNewAdapter(new DocumentDropEditAdapter(provider, this.documents, this.filesSystem));
499
+ this.proxy.$registerDocumentDropEditProvider(callId, this.transformDocumentSelector(selector));
500
+ return this.createDisposable(callId);
501
+ }
502
+ // ### Drop Edit Provider end
503
+
466
504
  // ### Document Range Formatting Edit begin
467
505
  registerDocumentRangeFormattingEditProvider(selector: theia.DocumentSelector, provider: theia.DocumentRangeFormattingEditProvider,
468
506
  pluginInfo: PluginInfo): theia.Disposable {
@@ -87,6 +87,7 @@ import {
87
87
  DocumentHighlightKind,
88
88
  DocumentHighlight,
89
89
  DocumentLink,
90
+ DocumentDropEdit,
90
91
  CodeLens,
91
92
  CodeActionKind,
92
93
  CodeActionTrigger,
@@ -257,11 +258,11 @@ export function createAPIFactory(
257
258
  const statusBarMessageRegistryExt = new StatusBarMessageRegistryExt(rpc);
258
259
  const terminalExt = rpc.set(MAIN_RPC_CONTEXT.TERMINAL_EXT, new TerminalServiceExtImpl(rpc));
259
260
  const outputChannelRegistryExt = rpc.set(MAIN_RPC_CONTEXT.OUTPUT_CHANNEL_REGISTRY_EXT, new OutputChannelRegistryExtImpl(rpc));
260
- const languagesExt = rpc.set(MAIN_RPC_CONTEXT.LANGUAGES_EXT, new LanguagesExtImpl(rpc, documents, commandRegistry));
261
261
  const treeViewsExt = rpc.set(MAIN_RPC_CONTEXT.TREE_VIEWS_EXT, new TreeViewsExtImpl(rpc, commandRegistry));
262
262
  const tasksExt = rpc.set(MAIN_RPC_CONTEXT.TASKS_EXT, new TasksExtImpl(rpc, terminalExt));
263
263
  const connectionExt = rpc.set(MAIN_RPC_CONTEXT.CONNECTION_EXT, new ConnectionImpl(rpc.getProxy(PLUGIN_RPC_CONTEXT.CONNECTION_MAIN)));
264
- const fileSystemExt = rpc.set(MAIN_RPC_CONTEXT.FILE_SYSTEM_EXT, new FileSystemExtImpl(rpc, languagesExt));
264
+ const fileSystemExt = rpc.set(MAIN_RPC_CONTEXT.FILE_SYSTEM_EXT, new FileSystemExtImpl(rpc));
265
+ const languagesExt = rpc.set(MAIN_RPC_CONTEXT.LANGUAGES_EXT, new LanguagesExtImpl(rpc, documents, commandRegistry, fileSystemExt));
265
266
  const extHostFileSystemEvent = rpc.set(MAIN_RPC_CONTEXT.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpc, editorsAndDocumentsExt));
266
267
  const scmExt = rpc.set(MAIN_RPC_CONTEXT.SCM_EXT, new ScmExtImpl(rpc, commandRegistry));
267
268
  const decorationsExt = rpc.set(MAIN_RPC_CONTEXT.DECORATIONS_EXT, new DecorationsExtImpl(rpc));
@@ -669,8 +670,8 @@ export function createAPIFactory(
669
670
  saveAll(includeUntitled?: boolean): PromiseLike<boolean> {
670
671
  return editors.saveAll(includeUntitled);
671
672
  },
672
- applyEdit(edit: theia.WorkspaceEdit): PromiseLike<boolean> {
673
- return editors.applyWorkspaceEdit(edit);
673
+ applyEdit(edit: theia.WorkspaceEdit, metadata?: theia.WorkspaceEditMetadata): PromiseLike<boolean> {
674
+ return editors.applyWorkspaceEdit(edit, metadata);
674
675
  },
675
676
  registerTextDocumentContentProvider(scheme: string, provider: theia.TextDocumentContentProvider): theia.Disposable {
676
677
  return workspaceExt.registerTextDocumentContentProvider(scheme, provider);
@@ -853,6 +854,9 @@ export function createAPIFactory(
853
854
  ): theia.Disposable {
854
855
  return languagesExt.registerOnTypeFormattingEditProvider(selector, provider, [firstTriggerCharacter].concat(moreTriggerCharacters), pluginToPluginInfo(plugin));
855
856
  },
857
+ registerDocumentDropEditProvider(selector: theia.DocumentSelector, provider: theia.DocumentDropEditProvider) {
858
+ return languagesExt.registerDocumentDropEditProvider(selector, provider);
859
+ },
856
860
  registerDocumentLinkProvider(selector: theia.DocumentSelector, provider: theia.DocumentLinkProvider): theia.Disposable {
857
861
  return languagesExt.registerDocumentLinkProvider(selector, provider, pluginToPluginInfo(plugin));
858
862
  },
@@ -1195,6 +1199,7 @@ export function createAPIFactory(
1195
1199
  DocumentHighlightKind,
1196
1200
  DocumentHighlight,
1197
1201
  DocumentLink,
1202
+ DocumentDropEdit,
1198
1203
  CodeLens,
1199
1204
  CodeActionKind,
1200
1205
  CodeActionTrigger,
@@ -41,36 +41,6 @@ describe('PreferenceRegistryExtImpl:', () => {
41
41
  preferenceRegistryExtImpl = new PreferenceRegistryExtImpl(mockRPC, mockWorkspace);
42
42
  });
43
43
 
44
- it('should parse configuration data without overrides', () => {
45
- const value: Record<string, any> = {
46
- 'my.key1.foo': 'value1',
47
- 'my.key1.bar': 'value2',
48
- };
49
- const result = preferenceRegistryExtImpl['parseConfigurationData'](value);
50
- expect(result.contents.my).to.be.an('object');
51
- expect(result.contents.my.key1).to.be.an('object');
52
-
53
- expect(result.contents.my.key1.foo).to.be.an('string');
54
- expect(result.contents.my.key1.foo).to.equal('value1');
55
-
56
- expect(result.contents.my.key1.bar).to.be.an('string');
57
- expect(result.contents.my.key1.bar).to.equal('value2');
58
- expect(result.keys).deep.equal(['my.key1.foo', 'my.key1.bar']);
59
- });
60
-
61
- it('should parse configuration with overrides', () => {
62
- const value: Record<string, any> = {
63
- 'editor.tabSize': 2,
64
- '[typescript].editor.tabSize': 4,
65
- };
66
- const result = preferenceRegistryExtImpl['parseConfigurationData'](value);
67
- expect(result.contents.editor.tabSize).to.equal(2);
68
- const tsOverride = result.overrides[0];
69
- expect(tsOverride.contents.editor.tabSize).to.equal(4);
70
- expect(tsOverride.identifiers).deep.equal(['typescript']);
71
- expect(tsOverride.keys).deep.equal(['editor.tabSize']);
72
- });
73
-
74
44
  describe('Prototype pollution', () => {
75
45
  it('Ignores key `__proto__`', () => {
76
46
  const value: Record<string, any> = {
@@ -80,16 +50,18 @@ describe('PreferenceRegistryExtImpl:', () => {
80
50
  '__proto__': {},
81
51
  '[typescript].someKey.foo': 'value',
82
52
  '[typescript].__proto__.injectedParsedPrototype': true,
53
+ 'b': { '__proto__.injectedParsedPrototype': true },
54
+ 'c': { '__proto__': { 'injectedParsedPrototype': true } }
83
55
  };
84
- const result = preferenceRegistryExtImpl['parseConfigurationData'](value);
85
- expect(result.contents.my).to.be.an('object');
86
- expect(result.contents.__proto__).to.be.an('undefined');
87
- expect(result.contents.my.key1.foo).to.equal('value1');
88
- expect(result.overrides[0].contents.__proto__).to.be.an('undefined');
56
+ const configuration = preferenceRegistryExtImpl['getConfigurationModel']('test', value);
57
+ const result = configuration['_contents'];
58
+ expect(result.my, 'Safe keys are preserved.').to.be.an('object');
59
+ expect(result.__proto__, 'Keys containing __proto__ are ignored').to.be.an('undefined');
60
+ expect(result.my.key1.foo, 'Safe keys are dendrified.').to.equal('value1');
89
61
  const prototypeObject = Object.prototype as any;
90
- expect(prototypeObject.injectedParsedPrototype).to.be.an('undefined');
62
+ expect(prototypeObject.injectedParsedPrototype, 'Object.prototype is unaffected').to.be.an('undefined');
91
63
  const rawObject = {} as any;
92
- expect(rawObject.injectedParsedPrototype).to.be.an('undefined');
64
+ expect(rawObject.injectedParsedPrototype, 'Instantiated objects are unaffected.').to.be.an('undefined');
93
65
  });
94
66
 
95
67
  it('Ignores key `constructor.prototype`', () => {
@@ -98,17 +70,19 @@ describe('PreferenceRegistryExtImpl:', () => {
98
70
  'a.constructor.prototype.injectedParsedConstructorPrototype': true,
99
71
  'constructor.prototype.injectedParsedConstructorPrototype': true,
100
72
  '[python].some.key.foo': 'value',
101
- '[python].a.constructor.prototype.injectedParsedConstructorPrototype': true
73
+ '[python].a.constructor.prototype.injectedParsedConstructorPrototype': true,
74
+ 'constructor': { 'prototype.injectedParsedConstructorPrototype': true },
75
+ 'b': { 'constructor': { 'prototype': { 'injectedParsedConstructorPrototype': true } } }
102
76
  };
103
- const result = preferenceRegistryExtImpl['parseConfigurationData'](value);
104
- expect(result.contents.my).to.be.an('object');
105
- expect(result.contents.__proto__).to.be.an('undefined');
106
- expect(result.contents.my.key1.foo).to.equal('value1');
77
+ const configuration = preferenceRegistryExtImpl['getConfigurationModel']('test', value);
78
+ const result = configuration['_contents'];
79
+ expect(result.my, 'Safe keys are preserved').to.be.an('object');
80
+ expect(result.__proto__, 'Keys containing __proto__ are ignored').to.be.an('undefined');
81
+ expect(result.my.key1.foo, 'Safe keys are dendrified.').to.equal('value1');
107
82
  const prototypeObject = Object.prototype as any;
108
- expect(prototypeObject.injectedParsedConstructorPrototype).to.be.an('undefined');
109
- expect(result.overrides[0].contents.__proto__).to.be.an('undefined');
83
+ expect(prototypeObject.injectedParsedConstructorPrototype, 'Object.prototype is unaffected').to.be.an('undefined');
110
84
  const rawObject = {} as any;
111
- expect(rawObject.injectedParsedConstructorPrototype).to.be.an('undefined');
85
+ expect(rawObject.injectedParsedConstructorPrototype, 'Instantiated objects are unaffected.').to.be.an('undefined');
112
86
  });
113
87
  });
114
88
 
@@ -250,6 +224,16 @@ describe('PreferenceRegistryExtImpl:', () => {
250
224
  const valuesRetrieved = preferenceRegistryExtImpl.getConfiguration(undefined, { uri: workspaceRoot, languageId: 'python' }).get('editor') as Record<string, unknown>;
251
225
  expect(valuesRetrieved.tabSize).equal(4);
252
226
  });
227
+ it('Allows access to language overrides in bracket form', () => {
228
+ const pythonOverrides = preferenceRegistryExtImpl.getConfiguration().get<Record<string, any>>('[python]');
229
+ expect(pythonOverrides).not.to.be.undefined;
230
+ expect(pythonOverrides?.['editor.renderWhitespace']).equal('all');
231
+ });
232
+ // https://github.com/eclipse-theia/theia/issues/12043
233
+ it('Allows access to preferences without specifying the section', () => {
234
+ const inspection = preferenceRegistryExtImpl.getConfiguration().inspect('editor.fontSize');
235
+ expect(inspection?.defaultValue).equal(14);
236
+ });
253
237
  });
254
238
 
255
239
  describe('Proxy Behavior', () => {
@@ -20,8 +20,8 @@ import { Emitter, Event } from '@theia/core/lib/common/event';
20
20
  import { isOSX, isWindows } from '@theia/core/lib/common/os';
21
21
  import { URI } from '@theia/core/shared/vscode-uri';
22
22
  import { ResourceMap } from '@theia/monaco-editor-core/esm/vs/base/common/map';
23
- import { IConfigurationOverrides, IOverrides } from '@theia/monaco-editor-core/esm/vs/platform/configuration/common/configuration';
24
- import { Configuration, ConfigurationModel } from '@theia/monaco-editor-core/esm/vs/platform/configuration/common/configurationModels';
23
+ import { IConfigurationOverrides } from '@theia/monaco-editor-core/esm/vs/platform/configuration/common/configuration';
24
+ import { Configuration, ConfigurationModel, ConfigurationModelParser } from '@theia/monaco-editor-core/esm/vs/platform/configuration/common/configurationModels';
25
25
  import { Workspace, WorkspaceFolder } from '@theia/monaco-editor-core/esm/vs/platform/workspace/common/workspace';
26
26
  import * as theia from '@theia/plugin';
27
27
  import { v4 } from 'uuid';
@@ -234,12 +234,12 @@ export class PreferenceRegistryExtImpl implements PreferenceRegistryExt {
234
234
  }
235
235
 
236
236
  private parse(data: PreferenceData): Configuration {
237
- const defaultConfiguration = this.getConfigurationModel(data[PreferenceScope.Default]);
238
- const userConfiguration = this.getConfigurationModel(data[PreferenceScope.User]);
239
- const workspaceConfiguration = this.getConfigurationModel(data[PreferenceScope.Workspace]);
237
+ const defaultConfiguration = this.getConfigurationModel('Default', data[PreferenceScope.Default]);
238
+ const userConfiguration = this.getConfigurationModel('User', data[PreferenceScope.User]);
239
+ const workspaceConfiguration = this.getConfigurationModel('Workspace', data[PreferenceScope.Workspace]);
240
240
  const folderConfigurations = new ResourceMap<ConfigurationModel>();
241
241
  Object.keys(data[PreferenceScope.Folder]).forEach(resource => {
242
- folderConfigurations.set(URI.parse(resource), this.getConfigurationModel(data[PreferenceScope.Folder][resource]));
242
+ folderConfigurations.set(URI.parse(resource), this.getConfigurationModel(`Folder: ${resource}`, data[PreferenceScope.Folder][resource]));
243
243
  });
244
244
  return new Configuration(
245
245
  defaultConfiguration,
@@ -252,53 +252,41 @@ export class PreferenceRegistryExtImpl implements PreferenceRegistryExt {
252
252
  );
253
253
  }
254
254
 
255
- private getConfigurationModel(data: { [key: string]: any }): ConfigurationModel {
256
- if (!data) {
257
- return new ConfigurationModel();
258
- }
259
- const configData = this.parseConfigurationData(data);
260
- return new ConfigurationModel(configData.contents, configData.keys, configData.overrides);
255
+ private getConfigurationModel(label: string, data: { [key: string]: any }): ConfigurationModel {
256
+ const parser = new ConfigurationModelParser(label);
257
+ const sanitized = this.sanitize(data);
258
+ parser.parseRaw(sanitized);
259
+ return parser.configurationModel;
261
260
  }
262
261
 
263
- private readonly OVERRIDE_PROPERTY = '^\\[(.*)\\]$';
264
- private readonly OVERRIDE_PROPERTY_PATTERN = new RegExp(this.OVERRIDE_PROPERTY);
265
- private readonly OVERRIDE_KEY_TEST = /^\[([^\]]+)\]\./;
266
-
267
- private parseConfigurationData(data: { [key: string]: any }): Omit<IOverrides, 'identifiers'> & { overrides: IOverrides[] } {
268
- const keys = new Array<string>();
269
- const overrides: Record<string, IOverrides> = Object.create(null);
270
- const contents = Object.keys(data).reduce((result: any, key: string) => {
271
- if (injectionRe.test(key)) {
272
- return result;
273
- }
274
- const parts = key.split('.');
275
- let branch = result;
276
- const isOverride = this.OVERRIDE_KEY_TEST.test(key);
277
- if (!isOverride) {
278
- keys.push(key);
279
- }
280
- for (let i = 0; i < parts.length; i++) {
281
- if (i === 0 && isOverride) {
282
- const identifier = this.OVERRIDE_PROPERTY_PATTERN.exec(parts[i])![1];
283
- if (!overrides[identifier]) {
284
- overrides[identifier] = { keys: [], identifiers: [identifier], contents: Object.create(null) };
262
+ /**
263
+ * Creates a new object and assigns those keys of raw to it that are not likely to cause prototype polution.
264
+ * Also preprocesses override identifiers so that they take the form [identifier]: {...contents}.
265
+ */
266
+ private sanitize<T = unknown>(raw: T): T {
267
+ if (!isObject(raw)) { return raw; }
268
+ const asObject = raw as Record<string, unknown>;
269
+ const sanitized = Object.create(null);
270
+ for (const key of Object.keys(asObject)) {
271
+ if (!injectionRe.test(key)) {
272
+ const override = this.OVERRIDE_KEY_TEST.exec(key);
273
+ if (override) {
274
+ const overrideKey = `[${override[1]}]`;
275
+ const remainder = key.slice(override[0].length);
276
+ if (!isObject(sanitized[overrideKey])) {
277
+ sanitized[overrideKey] = Object.create(null);
285
278
  }
286
- branch = overrides[identifier].contents;
287
- overrides[identifier].keys.push(key.slice(parts[i].length + 1));
288
- } else if (i === parts.length - 1) {
289
- branch[parts[i]] = data[key];
279
+ sanitized[overrideKey][remainder] = this.sanitize(asObject[key]);
290
280
  } else {
291
- if (!branch[parts[i]]) {
292
- branch[parts[i]] = Object.create(null);
293
- }
294
- branch = branch[parts[i]];
281
+ sanitized[key] = this.sanitize(asObject[key]);
295
282
  }
296
283
  }
297
- return result;
298
- }, Object.create(null));
299
- return { contents, keys, overrides: Object.values(overrides) };
284
+ }
285
+ return sanitized;
300
286
  }
301
287
 
288
+ private readonly OVERRIDE_KEY_TEST = /^\[([^\]]+)\]\./;
289
+
302
290
  private toConfigurationChangeEvent(eventData: PreferenceChangeExt[]): theia.ConfigurationChangeEvent {
303
291
  return Object.freeze({
304
292
  affectsConfiguration: (section: string, scope?: theia.ConfigurationScope): boolean => {
@@ -119,9 +119,9 @@ export class TextEditorsExtImpl implements TextEditorsExt {
119
119
  return new TextEditorDecorationType(this.proxy, options);
120
120
  }
121
121
 
122
- applyWorkspaceEdit(edit: theia.WorkspaceEdit): Promise<boolean> {
122
+ applyWorkspaceEdit(edit: theia.WorkspaceEdit, metadata?: theia.WorkspaceEditMetadata): Promise<boolean> {
123
123
  const dto = Converters.fromWorkspaceEdit(edit, this.editorsAndDocuments);
124
- return this.proxy.$tryApplyWorkspaceEdit(dto);
124
+ return this.proxy.$tryApplyWorkspaceEdit(dto, metadata);
125
125
  }
126
126
 
127
127
  saveAll(includeUntitled?: boolean): PromiseLike<boolean> {
@@ -1348,3 +1348,40 @@ export namespace InlayHintKind {
1348
1348
  return kind;
1349
1349
  }
1350
1350
  }
1351
+
1352
+ export namespace DataTransferItem {
1353
+ export function to(mime: string, item: model.DataTransferItemDTO, resolveFileData: (itemId: string) => Promise<Uint8Array>): theia.DataTransferItem {
1354
+ const file = item.fileData;
1355
+ if (file) {
1356
+ return new class extends types.DataTransferItem {
1357
+ override asFile(): theia.DataTransferFile {
1358
+ return {
1359
+ name: file.name,
1360
+ uri: URI.revive(file.uri),
1361
+ data: () => resolveFileData(item.id),
1362
+ };
1363
+ }
1364
+ }('');
1365
+ }
1366
+
1367
+ if (mime === 'text/uri-list' && item.uriListData) {
1368
+ return new types.DataTransferItem(reviveUriList(item.uriListData));
1369
+ }
1370
+
1371
+ return new types.DataTransferItem(item.asString);
1372
+ }
1373
+
1374
+ function reviveUriList(parts: ReadonlyArray<string | UriComponents>): string {
1375
+ return parts.map(part => typeof part === 'string' ? part : URI.revive(part).toString()).join('\r\n');
1376
+ }
1377
+ }
1378
+
1379
+ export namespace DataTransfer {
1380
+ export function toDataTransfer(value: model.DataTransferDTO, resolveFileData: (itemId: string) => Promise<Uint8Array>): theia.DataTransfer {
1381
+ const dataTransfer = new types.DataTransfer();
1382
+ for (const [mimeType, item] of value.items) {
1383
+ dataTransfer.set(mimeType, DataTransferItem.to(mimeType, item, resolveFileData));
1384
+ }
1385
+ return dataTransfer;
1386
+ }
1387
+ }
@@ -1530,6 +1530,17 @@ export class DocumentLink {
1530
1530
  }
1531
1531
  }
1532
1532
 
1533
+ @es5ClassCompat
1534
+ export class DocumentDropEdit {
1535
+ insertText: string | SnippetString;
1536
+
1537
+ additionalEdit?: WorkspaceEdit;
1538
+
1539
+ constructor(insertText: string | SnippetString) {
1540
+ this.insertText = insertText;
1541
+ }
1542
+ }
1543
+
1533
1544
  @es5ClassCompat
1534
1545
  export class CodeLens {
1535
1546