mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-21 00:59:03 +01:00
Add drop/paste resource for css (#221612)
* New drop and paste providers that create a url function snippet * added url pasting feature * added url pasting feature * added url pasting feature * Target just dropping/pasting resources for now * Move files * Remove unused strings * Removing more unused logic for now * Remove tsconfig change * Remove doc file * Capitalize * Remove old proposal names --------- Co-authored-by: Meghan Kulkarni <kulkarni.meg@gmail.com> Co-authored-by: Martin Aeschlimann <martinae@microsoft.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { getDocumentDir, Mimes, Schemes } from './shared';
|
||||
import { UriList } from './uriList';
|
||||
|
||||
class DropOrPasteResourceProvider implements vscode.DocumentDropEditProvider, vscode.DocumentPasteEditProvider {
|
||||
readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('css', 'url');
|
||||
|
||||
async provideDocumentDropEdits(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<vscode.DocumentDropEdit | undefined> {
|
||||
const uriList = await this.getUriList(dataTransfer);
|
||||
if (!uriList.entries.length || token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snippet = await this.createUriListSnippet(uriList);
|
||||
if (!snippet || token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
kind: this.kind,
|
||||
title: snippet.label,
|
||||
insertText: snippet.snippet.value,
|
||||
yieldTo: this.pasteAsCssUrlByDefault(document, position) ? [] : [vscode.DocumentDropOrPasteEditKind.Empty.append('uri')]
|
||||
};
|
||||
}
|
||||
|
||||
async provideDocumentPasteEdits(
|
||||
document: vscode.TextDocument,
|
||||
ranges: readonly vscode.Range[],
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
_context: vscode.DocumentPasteEditContext,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.DocumentPasteEdit[] | undefined> {
|
||||
const uriList = await this.getUriList(dataTransfer);
|
||||
if (!uriList.entries.length || token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snippet = await this.createUriListSnippet(uriList);
|
||||
if (!snippet || token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
return [{
|
||||
kind: this.kind,
|
||||
title: snippet.label,
|
||||
insertText: snippet.snippet.value,
|
||||
yieldTo: this.pasteAsCssUrlByDefault(document, ranges[0].start) ? [] : [vscode.DocumentDropOrPasteEditKind.Empty.append('uri')]
|
||||
}];
|
||||
}
|
||||
|
||||
private async getUriList(dataTransfer: vscode.DataTransfer): Promise<UriList> {
|
||||
const urlList = await dataTransfer.get(Mimes.uriList)?.asString();
|
||||
if (urlList) {
|
||||
return UriList.from(urlList);
|
||||
}
|
||||
|
||||
// Find file entries
|
||||
const uris: vscode.Uri[] = [];
|
||||
for (const [_, entry] of dataTransfer) {
|
||||
const file = entry.asFile();
|
||||
if (file?.uri) {
|
||||
uris.push(file.uri);
|
||||
}
|
||||
}
|
||||
|
||||
return new UriList(uris.map(uri => ({ uri, str: uri.toString(true) })));
|
||||
}
|
||||
|
||||
private async createUriListSnippet(uriList: UriList): Promise<{ readonly snippet: vscode.SnippetString; readonly label: string } | undefined> {
|
||||
if (!uriList.entries.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snippet = new vscode.SnippetString();
|
||||
for (let i = 0; i < uriList.entries.length; i++) {
|
||||
const uri = uriList.entries[i];
|
||||
const relativePath = getRelativePath(uri.uri);
|
||||
const urlText = relativePath ?? uri.str;
|
||||
|
||||
snippet.appendText(`url(${urlText})`);
|
||||
if (i !== uriList.entries.length - 1) {
|
||||
snippet.appendText(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
snippet,
|
||||
label: uriList.entries.length > 1
|
||||
? vscode.l10n.t('Insert url() Functions')
|
||||
: vscode.l10n.t('Insert url() Function')
|
||||
};
|
||||
}
|
||||
|
||||
private pasteAsCssUrlByDefault(document: vscode.TextDocument, position: vscode.Position): boolean {
|
||||
const regex = /url\(.+?\)/gi;
|
||||
for (const match of Array.from(document.lineAt(position.line).text.matchAll(regex))) {
|
||||
if (position.character > match.index && position.character < match.index + match[0].length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function getRelativePath(file: vscode.Uri): string | undefined {
|
||||
const dir = getDocumentDir(file);
|
||||
if (dir && dir.scheme === file.scheme && dir.authority === file.authority) {
|
||||
if (file.scheme === Schemes.file) {
|
||||
// On windows, we must use the native `path.relative` to generate the relative path
|
||||
// so that drive-letters are resolved cast insensitively. However we then want to
|
||||
// convert back to a posix path to insert in to the document
|
||||
const relativePath = path.relative(dir.fsPath, file.fsPath);
|
||||
return path.posix.normalize(relativePath.split(path.sep).join(path.posix.sep));
|
||||
}
|
||||
|
||||
return path.posix.relative(dir.path, file.path);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function registerDropOrPasteResourceSupport(selector: vscode.DocumentSelector): vscode.Disposable {
|
||||
const provider = new DropOrPasteResourceProvider();
|
||||
|
||||
return vscode.Disposable.from(
|
||||
vscode.languages.registerDocumentDropEditProvider(selector, provider, {
|
||||
providedDropEditKinds: [provider.kind],
|
||||
dropMimeTypes: [
|
||||
Mimes.uriList,
|
||||
'files'
|
||||
]
|
||||
}),
|
||||
vscode.languages.registerDocumentPasteEditProvider(selector, provider, {
|
||||
providedPasteEditKinds: [provider.kind],
|
||||
pasteMimeTypes: [
|
||||
Mimes.uriList,
|
||||
'files'
|
||||
]
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Utils } from 'vscode-uri';
|
||||
|
||||
export const Schemes = Object.freeze({
|
||||
file: 'file',
|
||||
notebookCell: 'vscode-notebook-cell',
|
||||
untitled: 'untitled',
|
||||
});
|
||||
|
||||
export const Mimes = Object.freeze({
|
||||
plain: 'text/plain',
|
||||
uriList: 'text/uri-list',
|
||||
});
|
||||
|
||||
|
||||
export function getDocumentDir(uri: vscode.Uri): vscode.Uri | undefined {
|
||||
const docUri = getParentDocumentUri(uri);
|
||||
if (docUri.scheme === Schemes.untitled) {
|
||||
return vscode.workspace.workspaceFolders?.[0]?.uri;
|
||||
}
|
||||
return Utils.dirname(docUri);
|
||||
}
|
||||
|
||||
function getParentDocumentUri(uri: vscode.Uri): vscode.Uri {
|
||||
if (uri.scheme === Schemes.notebookCell) {
|
||||
// is notebook documents necessary?
|
||||
for (const notebook of vscode.workspace.notebookDocuments) {
|
||||
for (const cell of notebook.getCells()) {
|
||||
if (cell.document.uri.toString() === uri.toString()) {
|
||||
return notebook.uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
function splitUriList(str: string): string[] {
|
||||
return str.split('\r\n');
|
||||
}
|
||||
|
||||
function parseUriList(str: string): string[] {
|
||||
return splitUriList(str)
|
||||
.filter(value => !value.startsWith('#')) // Remove comments
|
||||
.map(value => value.trim());
|
||||
}
|
||||
|
||||
export class UriList {
|
||||
|
||||
static from(str: string): UriList {
|
||||
return new UriList(coalesce(parseUriList(str).map(line => {
|
||||
try {
|
||||
return { uri: vscode.Uri.parse(line), str: line };
|
||||
} catch {
|
||||
// Uri parse failure
|
||||
return undefined;
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly entries: ReadonlyArray<{ readonly uri: vscode.Uri; readonly str: string }>
|
||||
) { }
|
||||
}
|
||||
|
||||
function coalesce<T>(array: ReadonlyArray<T | undefined | null>): T[] {
|
||||
return <T[]>array.filter(e => !!e);
|
||||
}
|
||||
Reference in New Issue
Block a user