Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { coalesce } from '../../../../base/common/arrays.js';
7
import { CancellationToken } from '../../../../base/common/cancellation.js';
8
import { IReadonlyVSDataTransfer, UriList } from '../../../../base/common/dataTransfer.js';
9
import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js';
10
import { Disposable } from '../../../../base/common/lifecycle.js';
11
import { Mimes } from '../../../../base/common/mime.js';
12
import { Schemas } from '../../../../base/common/network.js';
13
import { relativePath } from '../../../../base/common/resources.js';
14
import { URI } from '../../../../base/common/uri.js';
15
import { localize } from '../../../../nls.js';
16
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
17
import { IPosition } from '../../../common/core/position.js';
18
import { IRange } from '../../../common/core/range.js';
19
import { DocumentDropEditProvider, DocumentDropEditsSession, DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider, DocumentPasteEditsSession, DocumentPasteTriggerKind } from '../../../common/languages.js';
20
import { LanguageFilter } from '../../../common/languageSelector.js';
21
import { ITextModel } from '../../../common/model.js';
22
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
23
24
25
abstract class SimplePasteAndDropProvider implements DocumentDropEditProvider, DocumentPasteEditProvider {
26
27
readonly kind: HierarchicalKind;
28
readonly providedDropEditKinds: HierarchicalKind[];
29
readonly providedPasteEditKinds: HierarchicalKind[];
30
31
abstract readonly dropMimeTypes: readonly string[] | undefined;
32
readonly copyMimeTypes = [];
33
abstract readonly pasteMimeTypes: readonly string[];
34
35
constructor(kind: HierarchicalKind) {
36
this.kind = kind;
37
this.providedDropEditKinds = [this.kind];
38
this.providedPasteEditKinds = [this.kind];
39
}
40
41
async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise<DocumentPasteEditsSession | undefined> {
42
const edit = await this.getEdit(dataTransfer, token);
43
if (!edit) {
44
return undefined;
45
}
46
47
return {
48
edits: [{ insertText: edit.insertText, title: edit.title, kind: edit.kind, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo }],
49
dispose() { },
50
};
51
}
52
53
async provideDocumentDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<DocumentDropEditsSession | undefined> {
54
const edit = await this.getEdit(dataTransfer, token);
55
if (!edit) {
56
return;
57
}
58
return {
59
edits: [{ insertText: edit.insertText, title: edit.title, kind: edit.kind, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo }],
60
dispose() { },
61
};
62
}
63
64
protected abstract getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined>;
65
}
66
67
export class DefaultTextPasteOrDropEditProvider extends SimplePasteAndDropProvider {
68
69
static readonly id = 'text';
70
71
readonly id = DefaultTextPasteOrDropEditProvider.id;
72
readonly dropMimeTypes = [Mimes.text];
73
readonly pasteMimeTypes = [Mimes.text];
74
75
constructor() {
76
super(HierarchicalKind.Empty.append('text', 'plain'));
77
}
78
79
protected async getEdit(dataTransfer: IReadonlyVSDataTransfer, _token: CancellationToken): Promise<DocumentPasteEdit | undefined> {
80
const textEntry = dataTransfer.get(Mimes.text);
81
if (!textEntry) {
82
return;
83
}
84
85
// Suppress if there's also a uriList entry.
86
// Typically the uri-list contains the same text as the text entry so showing both is confusing.
87
if (dataTransfer.has(Mimes.uriList)) {
88
return;
89
}
90
91
const insertText = await textEntry.asString();
92
return {
93
handledMimeType: Mimes.text,
94
title: localize('text.label', "Insert Plain Text"),
95
insertText,
96
kind: this.kind,
97
};
98
}
99
}
100
101
class PathProvider extends SimplePasteAndDropProvider {
102
103
readonly dropMimeTypes = [Mimes.uriList];
104
readonly pasteMimeTypes = [Mimes.uriList];
105
106
constructor() {
107
super(HierarchicalKind.Empty.append('uri', 'path', 'absolute'));
108
}
109
110
protected async getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined> {
111
const entries = await extractUriList(dataTransfer);
112
if (!entries.length || token.isCancellationRequested) {
113
return;
114
}
115
116
let uriCount = 0;
117
const insertText = entries
118
.map(({ uri, originalText }) => {
119
if (uri.scheme === Schemas.file) {
120
return uri.fsPath;
121
} else {
122
uriCount++;
123
return originalText;
124
}
125
})
126
.join(' ');
127
128
let label: string;
129
if (uriCount > 0) {
130
// Dropping at least one generic uri (such as https) so use most generic label
131
label = entries.length > 1
132
? localize('defaultDropProvider.uriList.uris', "Insert Uris")
133
: localize('defaultDropProvider.uriList.uri', "Insert Uri");
134
} else {
135
// All the paths are file paths
136
label = entries.length > 1
137
? localize('defaultDropProvider.uriList.paths', "Insert Paths")
138
: localize('defaultDropProvider.uriList.path', "Insert Path");
139
}
140
141
return {
142
handledMimeType: Mimes.uriList,
143
insertText,
144
title: label,
145
kind: this.kind,
146
};
147
}
148
}
149
150
class RelativePathProvider extends SimplePasteAndDropProvider {
151
152
readonly dropMimeTypes = [Mimes.uriList];
153
readonly pasteMimeTypes = [Mimes.uriList];
154
155
constructor(
156
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService
157
) {
158
super(HierarchicalKind.Empty.append('uri', 'path', 'relative'));
159
}
160
161
protected async getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined> {
162
const entries = await extractUriList(dataTransfer);
163
if (!entries.length || token.isCancellationRequested) {
164
return;
165
}
166
167
const relativeUris = coalesce(entries.map(({ uri }) => {
168
const root = this._workspaceContextService.getWorkspaceFolder(uri);
169
return root ? relativePath(root.uri, uri) : undefined;
170
}));
171
172
if (!relativeUris.length) {
173
return;
174
}
175
176
return {
177
handledMimeType: Mimes.uriList,
178
insertText: relativeUris.join(' '),
179
title: entries.length > 1
180
? localize('defaultDropProvider.uriList.relativePaths', "Insert Relative Paths")
181
: localize('defaultDropProvider.uriList.relativePath', "Insert Relative Path"),
182
kind: this.kind,
183
};
184
}
185
}
186
187
class PasteHtmlProvider implements DocumentPasteEditProvider {
188
189
public readonly kind = new HierarchicalKind('html');
190
public readonly providedPasteEditKinds = [this.kind];
191
192
public readonly copyMimeTypes = [];
193
public readonly pasteMimeTypes = ['text/html'];
194
195
private readonly _yieldTo = [{ mimeType: Mimes.text }];
196
197
async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise<DocumentPasteEditsSession | undefined> {
198
if (context.triggerKind !== DocumentPasteTriggerKind.PasteAs && !context.only?.contains(this.kind)) {
199
return;
200
}
201
202
const entry = dataTransfer.get('text/html');
203
const htmlText = await entry?.asString();
204
if (!htmlText || token.isCancellationRequested) {
205
return;
206
}
207
208
return {
209
dispose() { },
210
edits: [{
211
insertText: htmlText,
212
yieldTo: this._yieldTo,
213
title: localize('pasteHtmlLabel', 'Insert HTML'),
214
kind: this.kind,
215
}],
216
};
217
}
218
}
219
220
async function extractUriList(dataTransfer: IReadonlyVSDataTransfer): Promise<{ readonly uri: URI; readonly originalText: string }[]> {
221
const urlListEntry = dataTransfer.get(Mimes.uriList);
222
if (!urlListEntry) {
223
return [];
224
}
225
226
const strUriList = await urlListEntry.asString();
227
const entries: { readonly uri: URI; readonly originalText: string }[] = [];
228
for (const entry of UriList.parse(strUriList)) {
229
try {
230
entries.push({ uri: URI.parse(entry), originalText: entry });
231
} catch {
232
// noop
233
}
234
}
235
return entries;
236
}
237
238
const genericLanguageSelector: LanguageFilter = { scheme: '*', hasAccessToAllModels: true };
239
240
export class DefaultDropProvidersFeature extends Disposable {
241
constructor(
242
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
243
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
244
) {
245
super();
246
247
this._register(languageFeaturesService.documentDropEditProvider.register(genericLanguageSelector, new DefaultTextPasteOrDropEditProvider()));
248
this._register(languageFeaturesService.documentDropEditProvider.register(genericLanguageSelector, new PathProvider()));
249
this._register(languageFeaturesService.documentDropEditProvider.register(genericLanguageSelector, new RelativePathProvider(workspaceContextService)));
250
}
251
}
252
253
export class DefaultPasteProvidersFeature extends Disposable {
254
constructor(
255
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
256
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
257
) {
258
super();
259
260
this._register(languageFeaturesService.documentPasteEditProvider.register(genericLanguageSelector, new DefaultTextPasteOrDropEditProvider()));
261
this._register(languageFeaturesService.documentPasteEditProvider.register(genericLanguageSelector, new PathProvider()));
262
this._register(languageFeaturesService.documentPasteEditProvider.register(genericLanguageSelector, new RelativePathProvider(workspaceContextService)));
263
this._register(languageFeaturesService.documentPasteEditProvider.register(genericLanguageSelector, new PasteHtmlProvider()));
264
}
265
}
266
267