Files
vscode/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropIntoEditor.ts
Matt Bierner 3273501a5a Address drop UX feedback (#180343)
- Reduce margin on button
- Change icon to insert
- Try to align styling to match other editor widgets
- Use capital casing on entries
2023-04-19 19:05:56 +02:00

189 lines
5.6 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* 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',
]);
const videoFileExtensions = new Set<string>([
'ogg',
'mp4'
]);
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);
if (!snippet) {
return undefined;
}
const edit = new vscode.DocumentDropEdit(snippet.snippet);
edit.label = snippet.label;
return edit;
}
}, {
id: 'insertLink',
dropMimeTypes: [
'text/uri-list'
]
});
}
export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<{ snippet: vscode.SnippetString; label: string } | 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(/\r?\n/g)) {
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): { snippet: vscode.SnippetString; label: string } | undefined {
if (!uris.length) {
return undefined;
}
const dir = getDocumentDir(document);
const snippet = new vscode.SnippetString();
let insertedLinkCount = 0;
let insertedImageCount = 0;
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;
const insertAsVideo = videoFileExtensions.has(ext);
if (insertAsVideo) {
insertedImageCount++;
snippet.appendText(`<video src="${mdPath}" controls title="`);
snippet.appendPlaceholder('Title');
snippet.appendText('"></video>');
} else {
if (insertAsImage) {
insertedImageCount++;
} else {
insertedLinkCount++;
}
snippet.appendText(insertAsImage ? '![' : '[');
const placeholderText = options?.placeholderText ?? (insertAsImage ? 'Alt text' : 'label');
const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : undefined;
snippet.appendPlaceholder(placeholderText, placeholderIndex);
snippet.appendText(`](${mdPath})`);
}
if (i < uris.length - 1 && uris.length > 1) {
snippet.appendText(options?.separator ?? ' ');
}
});
let label: string;
if (insertedImageCount > 0 && insertedLinkCount > 0) {
label = vscode.l10n.t('Insert Markdown Images and Links');
} else if (insertedImageCount > 0) {
label = insertedImageCount > 1
? vscode.l10n.t('Insert Markdown Images')
: vscode.l10n.t('Insert Markdown Image');
} else {
label = insertedLinkCount > 1
? vscode.l10n.t('Insert Markdown Links')
: vscode.l10n.t('Insert Markdown Link');
}
return { snippet, label };
}
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;
}