mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-20 16:49:06 +01:00
Revert 1d0a9f9e63 (#169578)
Resubmits the original PR with test fixes for windows
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 picomatch from 'picomatch';
|
||||
import * as vscode from 'vscode';
|
||||
import { Utils } from 'vscode-uri';
|
||||
import { getParentDocumentUri } from './dropIntoEditor';
|
||||
|
||||
|
||||
export async function getNewFileName(document: vscode.TextDocument, file: vscode.DataTransferFile): Promise<vscode.Uri> {
|
||||
const desiredPath = getDesiredNewFilePath(document, file);
|
||||
|
||||
const root = Utils.dirname(desiredPath);
|
||||
const ext = path.extname(file.name);
|
||||
const baseName = path.basename(file.name, ext);
|
||||
for (let i = 0; ; ++i) {
|
||||
const name = i === 0 ? baseName : `${baseName}-${i}`;
|
||||
const uri = vscode.Uri.joinPath(root, `${name}${ext}`);
|
||||
try {
|
||||
await vscode.workspace.fs.stat(uri);
|
||||
} catch {
|
||||
// Does not exist
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDesiredNewFilePath(document: vscode.TextDocument, file: vscode.DataTransferFile): vscode.Uri {
|
||||
const docUri = getParentDocumentUri(document);
|
||||
const config = vscode.workspace.getConfiguration('markdown').get<Record<string, string>>('experimental.copyFiles.destination') ?? {};
|
||||
for (const [rawGlob, rawDest] of Object.entries(config)) {
|
||||
for (const glob of parseGlob(rawGlob)) {
|
||||
if (picomatch.isMatch(docUri.path, glob)) {
|
||||
return resolveCopyDestination(docUri, file.name, rawDest, uri => vscode.workspace.getWorkspaceFolder(uri)?.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default to next to current file
|
||||
return vscode.Uri.joinPath(Utils.dirname(docUri), file.name);
|
||||
}
|
||||
|
||||
function parseGlob(rawGlob: string): Iterable<string> {
|
||||
if (rawGlob.startsWith('/')) {
|
||||
// Anchor to workspace folders
|
||||
return (vscode.workspace.workspaceFolders ?? []).map(folder => vscode.Uri.joinPath(folder.uri, rawGlob).path);
|
||||
}
|
||||
|
||||
// Relative path, so implicitly track on ** to match everything
|
||||
if (!rawGlob.startsWith('**')) {
|
||||
return ['**/' + rawGlob];
|
||||
}
|
||||
|
||||
return [rawGlob];
|
||||
}
|
||||
|
||||
type GetWorkspaceFolder = (documentUri: vscode.Uri) => vscode.Uri | undefined;
|
||||
|
||||
export function resolveCopyDestination(documentUri: vscode.Uri, fileName: string, dest: string, getWorkspaceFolder: GetWorkspaceFolder): vscode.Uri {
|
||||
const resolvedDest = resolveCopyDestinationSetting(documentUri, fileName, dest, getWorkspaceFolder);
|
||||
|
||||
if (resolvedDest.startsWith('/')) {
|
||||
// Absolute path
|
||||
return Utils.resolvePath(documentUri, resolvedDest);
|
||||
}
|
||||
|
||||
// Relative to document
|
||||
const dirName = Utils.dirname(documentUri);
|
||||
return Utils.resolvePath(dirName, resolvedDest);
|
||||
}
|
||||
|
||||
|
||||
function resolveCopyDestinationSetting(documentUri: vscode.Uri, fileName: string, dest: string, getWorkspaceFolder: GetWorkspaceFolder): string {
|
||||
let outDest = dest;
|
||||
|
||||
// Destination that start with `/` implicitly means go to workspace root
|
||||
if (outDest.startsWith('/')) {
|
||||
outDest = '${documentWorkspaceFolder}/' + outDest.slice(1);
|
||||
}
|
||||
|
||||
// Destination that ends with `/` implicitly needs a fileName
|
||||
if (outDest.endsWith('/')) {
|
||||
outDest += '${fileName}';
|
||||
}
|
||||
|
||||
const documentDirName = Utils.dirname(documentUri);
|
||||
const documentBaseName = Utils.basename(documentUri);
|
||||
const documentExtName = Utils.extname(documentUri);
|
||||
|
||||
const workspaceFolder = getWorkspaceFolder(documentUri);
|
||||
|
||||
const vars = new Map<string, string>([
|
||||
['documentDirName', documentDirName.fsPath], // Parent directory path
|
||||
['documentFileName', documentBaseName], // Full filename: file.md
|
||||
['documentBaseName', documentBaseName.slice(0, documentBaseName.length - documentExtName.length)], // Just the name: file
|
||||
['documentExtName', documentExtName.replace('.', '')], // Just the file ext: md
|
||||
|
||||
// Workspace
|
||||
['documentWorkspaceFolder', (workspaceFolder ?? documentDirName).fsPath],
|
||||
|
||||
// File
|
||||
['fileName', fileName],// Full file name
|
||||
]);
|
||||
|
||||
return outDest.replaceAll(/\$\{(\w+)(?:\/([^\}]+?)\/([^\}]+?)\/)?\}/g, (_, name, pattern, replacement) => {
|
||||
const entry = vars.get(name);
|
||||
if (!entry) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (pattern && replacement) {
|
||||
return entry.replace(new RegExp(pattern), replacement);
|
||||
}
|
||||
|
||||
return entry;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Schemes } from '../../util/schemes';
|
||||
import { getNewFileName } from './copyFiles';
|
||||
import { createUriListSnippet, tryGetUriListSnippet } from './dropIntoEditor';
|
||||
|
||||
const supportedImageMimes = new Set([
|
||||
'image/png',
|
||||
'image/jpg',
|
||||
]);
|
||||
|
||||
class PasteEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
async provideDocumentPasteEdits(
|
||||
document: vscode.TextDocument,
|
||||
_ranges: readonly vscode.Range[],
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<vscode.DocumentPasteEdit | undefined> {
|
||||
const enabled = vscode.workspace.getConfiguration('markdown', document).get('experimental.editor.pasteLinks.enabled', true);
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.uri.scheme === Schemes.notebookCell) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const imageMime of supportedImageMimes) {
|
||||
const file = dataTransfer.get(imageMime)?.asFile();
|
||||
if (file) {
|
||||
const edit = await this._makeCreateImagePasteEdit(document, file, token);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (edit) {
|
||||
return edit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
|
||||
return snippet ? new vscode.DocumentPasteEdit(snippet) : undefined;
|
||||
}
|
||||
|
||||
private async _makeCreateImagePasteEdit(document: vscode.TextDocument, file: vscode.DataTransferFile, token: vscode.CancellationToken): Promise<vscode.DocumentPasteEdit | undefined> {
|
||||
if (file.uri) {
|
||||
// If file is already in workspace, we don't want to create a copy of it
|
||||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(file.uri);
|
||||
if (workspaceFolder) {
|
||||
const snippet = createUriListSnippet(document, [file.uri]);
|
||||
return snippet ? new vscode.DocumentPasteEdit(snippet) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const uri = await getNewFileName(document, file);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snippet = createUriListSnippet(document, [uri]);
|
||||
if (!snippet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note that there is currently no way to undo the file creation :/
|
||||
const workspaceEdit = new vscode.WorkspaceEdit();
|
||||
workspaceEdit.createFile(uri, { contents: await file.data() });
|
||||
|
||||
const pasteEdit = new vscode.DocumentPasteEdit(snippet);
|
||||
pasteEdit.additionalEdit = workspaceEdit;
|
||||
return pasteEdit;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerPasteSupport(selector: vscode.DocumentSelector,) {
|
||||
return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteEditProvider(), {
|
||||
pasteMimeTypes: [
|
||||
'text/uri-list',
|
||||
...supportedImageMimes,
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as URI from 'vscode-uri';
|
||||
import { Schemes } from '../../util/schemes';
|
||||
|
||||
export const imageFileExtensions = new Set<string>([
|
||||
'bmp',
|
||||
'gif',
|
||||
'ico',
|
||||
'jpe',
|
||||
'jpeg',
|
||||
'jpg',
|
||||
'png',
|
||||
'psd',
|
||||
'svg',
|
||||
'tga',
|
||||
'tif',
|
||||
'tiff',
|
||||
'webp',
|
||||
]);
|
||||
|
||||
export function registerDropIntoEditorSupport(selector: vscode.DocumentSelector) {
|
||||
return vscode.languages.registerDocumentDropEditProvider(selector, new class implements vscode.DocumentDropEditProvider {
|
||||
async provideDocumentDropEdits(document: vscode.TextDocument, _position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> {
|
||||
const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true);
|
||||
if (!enabled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
|
||||
return snippet ? new vscode.DocumentDropEdit(snippet) : undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.SnippetString | undefined> {
|
||||
const urlList = await dataTransfer.get('text/uri-list')?.asString();
|
||||
if (!urlList || token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const uris: vscode.Uri[] = [];
|
||||
for (const resource of urlList.split('\n')) {
|
||||
try {
|
||||
uris.push(vscode.Uri.parse(resource));
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
return createUriListSnippet(document, uris);
|
||||
}
|
||||
|
||||
interface UriListSnippetOptions {
|
||||
readonly placeholderText?: string;
|
||||
|
||||
readonly placeholderStartIndex?: number;
|
||||
|
||||
/**
|
||||
* Should the snippet be for an image?
|
||||
*
|
||||
* If `undefined`, tries to infer this from the uri.
|
||||
*/
|
||||
readonly insertAsImage?: boolean;
|
||||
|
||||
readonly separator?: string;
|
||||
}
|
||||
|
||||
export function createUriListSnippet(document: vscode.TextDocument, uris: readonly vscode.Uri[], options?: UriListSnippetOptions): vscode.SnippetString | undefined {
|
||||
if (!uris.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const dir = getDocumentDir(document);
|
||||
|
||||
const snippet = new vscode.SnippetString();
|
||||
uris.forEach((uri, i) => {
|
||||
const mdPath = getMdPath(dir, uri);
|
||||
|
||||
const ext = URI.Utils.extname(uri).toLowerCase().replace('.', '');
|
||||
const insertAsImage = typeof options?.insertAsImage === 'undefined' ? imageFileExtensions.has(ext) : !!options.insertAsImage;
|
||||
|
||||
snippet.appendText(insertAsImage ? '`);
|
||||
|
||||
if (i < uris.length - 1 && uris.length > 1) {
|
||||
snippet.appendText(options?.separator ?? ' ');
|
||||
}
|
||||
});
|
||||
return snippet;
|
||||
}
|
||||
|
||||
function getMdPath(dir: vscode.Uri | undefined, file: vscode.Uri) {
|
||||
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 encodeURI(path.posix.normalize(relativePath.split(path.sep).join(path.posix.sep)));
|
||||
}
|
||||
|
||||
return encodeURI(path.posix.relative(dir.path, file.path));
|
||||
}
|
||||
|
||||
return file.toString(false);
|
||||
}
|
||||
|
||||
function getDocumentDir(document: vscode.TextDocument): vscode.Uri | undefined {
|
||||
const docUri = getParentDocumentUri(document);
|
||||
if (docUri.scheme === Schemes.untitled) {
|
||||
return vscode.workspace.workspaceFolders?.[0]?.uri;
|
||||
}
|
||||
return URI.Utils.dirname(docUri);
|
||||
}
|
||||
|
||||
export function getParentDocumentUri(document: vscode.TextDocument): vscode.Uri {
|
||||
if (document.uri.scheme === Schemes.notebookCell) {
|
||||
for (const notebook of vscode.workspace.notebookDocuments) {
|
||||
for (const cell of notebook.getCells()) {
|
||||
if (cell.document === document) {
|
||||
return notebook.uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return document.uri;
|
||||
}
|
||||
Reference in New Issue
Block a user