Allow external copying files into the workspace on markdown drop / paste (#182572)

Allow copying files in the workspace on markdown drop / paste

Fixes #157043

Also:

- Renames the markdown paste settings and makes them no longer experimental
- Makes the copyFiles setting no longer experimental
- Adds a `markdown.copyFiles.overwriteBehavior` which lets you control if/how existing files are overwritten
This commit is contained in:
Matt Bierner
2023-05-15 20:17:52 -07:00
committed by GitHub
parent 97f8af3e08
commit 7a7d45793b
8 changed files with 435 additions and 252 deletions

View File

@@ -6,8 +6,9 @@
import * as vscode from 'vscode';
import { Utils } from 'vscode-uri';
import { Command } from '../commandManager';
import { createUriListSnippet, getParentDocumentUri, imageFileExtensions } from '../languageFeatures/copyFiles/dropIntoEditor';
import { createUriListSnippet, mediaFileExtensions } from '../languageFeatures/copyFiles/shared';
import { coalesce } from '../util/arrays';
import { getParentDocumentUri } from '../util/document';
import { Schemes } from '../util/schemes';
@@ -47,7 +48,7 @@ export class InsertImageFromWorkspace implements Command {
canSelectFolders: false,
canSelectMany: true,
filters: {
[vscode.l10n.t("Images")]: Array.from(imageFileExtensions)
[vscode.l10n.t("Media")]: Array.from(mediaFileExtensions.keys())
},
openLabel: vscode.l10n.t("Insert image"),
title: vscode.l10n.t("Insert image"),
@@ -75,14 +76,14 @@ async function insertLink(activeEditor: vscode.TextEditor, selectedFiles: vscode
await vscode.workspace.applyEdit(edit);
}
function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsImage: boolean) {
function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean) {
const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => {
const selectionText = activeEditor.document.getText(selection);
const snippet = createUriListSnippet(activeEditor.document, selectedFiles, {
insertAsImage: insertAsImage,
insertAsMedia,
placeholderText: selectionText,
placeholderStartIndex: (i + 1) * selectedFiles.length,
separator: insertAsImage ? '\n' : ' ',
separator: insertAsMedia ? '\n' : ' ',
});
return snippet ? new vscode.SnippetTextEdit(selection, snippet.snippet) : undefined;

View File

@@ -6,14 +6,41 @@ import * as path from 'path';
import * as picomatch from 'picomatch';
import * as vscode from 'vscode';
import { Utils } from 'vscode-uri';
import { getParentDocumentUri } from './dropIntoEditor';
import { getParentDocumentUri } from '../../util/document';
type OverwriteBehavior = 'overwrite' | 'nameIncrementally';
interface CopyFileConfiguration {
readonly destination: Record<string, string>;
readonly overwriteBehavior: OverwriteBehavior;
}
function getCopyFileConfiguration(document: vscode.TextDocument): CopyFileConfiguration {
const config = vscode.workspace.getConfiguration('markdown', document);
return {
destination: config.get<Record<string, string>>('copyFiles.destination') ?? {},
overwriteBehavior: readOverwriteBehavior(config),
};
}
function readOverwriteBehavior(config: vscode.WorkspaceConfiguration): OverwriteBehavior {
switch (config.get('copyFiles.overwriteBehavior')) {
case 'overwrite': return 'overwrite';
default: return 'nameIncrementally';
}
}
export class NewFilePathGenerator {
private readonly _usedPaths = new Set<string>();
async getNewFilePath(document: vscode.TextDocument, file: vscode.DataTransferFile, token: vscode.CancellationToken): Promise<vscode.Uri | undefined> {
const desiredPath = getDesiredNewFilePath(document, file);
async getNewFilePath(
document: vscode.TextDocument,
file: vscode.DataTransferFile,
token: vscode.CancellationToken,
): Promise<{ readonly uri: vscode.Uri; readonly overwrite: boolean } | undefined> {
const config = getCopyFileConfiguration(document);
const desiredPath = getDesiredNewFilePath(config, document, file);
const root = Utils.dirname(desiredPath);
const ext = path.extname(file.name);
@@ -29,13 +56,20 @@ export class NewFilePathGenerator {
continue;
}
// Try overwriting if it already exists
if (config.overwriteBehavior === 'overwrite') {
this._usedPaths.add(uri.toString());
return { uri, overwrite: true };
}
// Otherwise we need to check the fs to see if it exists
try {
await vscode.workspace.fs.stat(uri);
} catch {
if (!this._wasPathAlreadyUsed(uri)) {
// Does not exist
this._usedPaths.add(uri.toString());
return uri;
return { uri, overwrite: false };
}
}
}
@@ -46,10 +80,9 @@ export class NewFilePathGenerator {
}
}
function getDesiredNewFilePath(document: vscode.TextDocument, file: vscode.DataTransferFile): vscode.Uri {
function getDesiredNewFilePath(config: CopyFileConfiguration, 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 [rawGlob, rawDest] of Object.entries(config.destination)) {
for (const glob of parseGlob(rawGlob)) {
if (picomatch.isMatch(docUri.path, glob)) {
return resolveCopyDestination(docUri, file.name, rawDest, uri => vscode.workspace.getWorkspaceFolder(uri)?.uri);

View File

@@ -4,18 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { coalesce } from '../../util/arrays';
import { Schemes } from '../../util/schemes';
import { NewFilePathGenerator } from './copyFiles';
import { createUriListSnippet, tryGetUriListSnippet } from './dropIntoEditor';
const supportedImageMimes = new Set([
'image/bmp',
'image/gif',
'image/jpeg',
'image/png',
'image/webp',
]);
import { createEditForMediaFiles, mediaMimes, tryGetUriListSnippet } from './shared';
class PasteEditProvider implements vscode.DocumentPasteEditProvider {
@@ -27,12 +17,12 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
dataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken,
): Promise<vscode.DocumentPasteEdit | undefined> {
const enabled = vscode.workspace.getConfiguration('markdown', document).get('experimental.editor.pasteLinks.enabled', true);
const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.filePaste.enabled', true);
if (!enabled) {
return;
}
const createEdit = await this._makeCreateImagePasteEdit(document, dataTransfer, token);
const createEdit = await this._getMediaFilesEdit(document, dataTransfer, token);
if (createEdit) {
return createEdit;
}
@@ -47,56 +37,23 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
return uriEdit;
}
private async _makeCreateImagePasteEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentPasteEdit | undefined> {
private async _getMediaFilesEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentPasteEdit | undefined> {
if (document.uri.scheme === Schemes.untitled) {
return;
}
interface FileEntry {
readonly uri: vscode.Uri;
readonly newFileContents?: vscode.DataTransferFile;
}
const pathGenerator = new NewFilePathGenerator();
const fileEntries = coalesce(await Promise.all(Array.from(dataTransfer, async ([mime, item]): Promise<FileEntry | undefined> => {
if (!supportedImageMimes.has(mime)) {
return;
}
const file = item?.asFile();
if (!file) {
return;
}
if (file.uri) {
// If the file is already in a workspace, we don't want to create a copy of it
const workspaceFolder = vscode.workspace.getWorkspaceFolder(file.uri);
if (workspaceFolder) {
return { uri: file.uri };
}
}
const uri = await pathGenerator.getNewFilePath(document, file, token);
return uri ? { uri, newFileContents: file } : undefined;
})));
if (!fileEntries.length) {
const copyFilesIntoWorkspace = vscode.workspace.getConfiguration('markdown', document).get<'mediaFiles' | 'never'>('editor.filePaste.copyIntoWorkspace', 'mediaFiles');
if (copyFilesIntoWorkspace === 'never') {
return;
}
const workspaceEdit = new vscode.WorkspaceEdit();
for (const entry of fileEntries) {
if (entry.newFileContents) {
workspaceEdit.createFile(entry.uri, { contents: entry.newFileContents });
}
}
const snippet = createUriListSnippet(document, fileEntries.map(entry => entry.uri));
if (!snippet) {
const edit = await createEditForMediaFiles(document, dataTransfer, token);
if (!edit) {
return;
}
const pasteEdit = new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label);
pasteEdit.additionalEdit = workspaceEdit;
const pasteEdit = new vscode.DocumentPasteEdit(edit.snippet, this._id, edit.label);
pasteEdit.additionalEdit = edit.additionalEdits;
pasteEdit.priority = this._getPriority(dataTransfer);
return pasteEdit;
}
@@ -114,7 +71,7 @@ export function registerPasteSupport(selector: vscode.DocumentSelector,) {
return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteEditProvider(), {
pasteMimeTypes: [
'text/uri-list',
...supportedImageMimes,
...mediaMimes,
]
});
}

View File

@@ -3,186 +3,71 @@
* 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 { createEditForMediaFiles as createEditForMediaFiles, tryGetUriListSnippet } from './shared';
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'
]);
class MarkdownImageDropProvider implements vscode.DocumentDropEditProvider {
private readonly _id = 'insertLink';
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;
}
const filesEdit = await this._getMediaFilesEdit(document, dataTransfer, token);
if (filesEdit) {
return filesEdit;
}
if (token.isCancellationRequested) {
return;
}
return this._getUriListEdit(document, dataTransfer, token);
}
private async _getUriListEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> {
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
if (!snippet) {
return undefined;
}
const edit = new vscode.DocumentDropEdit(snippet.snippet);
edit.id = this._id;
edit.label = snippet.label;
return edit;
}
private async _getMediaFilesEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> {
if (document.uri.scheme === Schemes.untitled) {
return;
}
const copyIntoWorkspace = vscode.workspace.getConfiguration('markdown', document).get<'mediaFiles' | 'never'>('editor.drop.copyIntoWorkspace', 'mediaFiles');
if (copyIntoWorkspace !== 'mediaFiles') {
return;
}
const filesEdit = await createEditForMediaFiles(document, dataTransfer, token);
if (!filesEdit) {
return;
}
const edit = new vscode.DocumentDropEdit(filesEdit.snippet);
edit.id = this._id;
edit.label = filesEdit.label;
edit.additionalEdit = filesEdit.additionalEdits;
return edit;
}
}
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.id = 'insertLink';
edit.label = snippet.label;
return edit;
}
}, {
return vscode.languages.registerDocumentDropEditProvider(selector, new MarkdownImageDropProvider(), {
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;
}

View File

@@ -0,0 +1,234 @@
/*---------------------------------------------------------------------------------------------
* 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';
import { NewFilePathGenerator } from './copyFiles';
import { coalesce } from '../../util/arrays';
import { getDocumentDir } from '../../util/document';
enum MediaKind {
Image,
Video,
}
export const mediaFileExtensions = new Map<string, MediaKind>([
// Images
['bmp', MediaKind.Image],
['gif', MediaKind.Image],
['ico', MediaKind.Image],
['jpe', MediaKind.Image],
['jpeg', MediaKind.Image],
['jpg', MediaKind.Image],
['png', MediaKind.Image],
['psd', MediaKind.Image],
['svg', MediaKind.Image],
['tga', MediaKind.Image],
['tif', MediaKind.Image],
['tiff', MediaKind.Image],
['webp', MediaKind.Image],
// Videos
['ogg', MediaKind.Video],
['mp4', MediaKind.Video],
]);
export const mediaMimes = new Set([
'image/bmp',
'image/gif',
'image/jpeg',
'image/png',
'image/webp',
'video/mp4',
'video/ogg',
]);
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 link or video?
*
* If `undefined`, tries to infer this from the uri.
*/
readonly insertAsMedia?: 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;
}
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 insertAsMedia = typeof options?.insertAsMedia === 'undefined' ? mediaFileExtensions.has(ext) : !!options.insertAsMedia;
const insertAsVideo = mediaFileExtensions.get(ext) === MediaKind.Video;
if (insertAsVideo) {
insertedImageCount++;
snippet.appendText(`<video src="${mdPath}" controls title="`);
snippet.appendPlaceholder('Title');
snippet.appendText('"></video>');
} else {
if (insertAsMedia) {
insertedImageCount++;
} else {
insertedLinkCount++;
}
snippet.appendText(insertAsMedia ? '![' : '[');
const placeholderText = options?.placeholderText ?? (insertAsMedia ? '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 };
}
/**
* Create a new edit from the image files in a data transfer.
*
* This tries copying files outside of the workspace into the workspace.
*/
export async function createEditForMediaFiles(
document: vscode.TextDocument,
dataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken
): Promise<{ snippet: vscode.SnippetString; label: string; additionalEdits: vscode.WorkspaceEdit } | undefined> {
if (document.uri.scheme === Schemes.untitled) {
return;
}
interface FileEntry {
readonly uri: vscode.Uri;
readonly newFile?: { readonly contents: vscode.DataTransferFile; readonly overwrite: boolean };
}
const pathGenerator = new NewFilePathGenerator();
const fileEntries = coalesce(await Promise.all(Array.from(dataTransfer, async ([mime, item]): Promise<FileEntry | undefined> => {
if (!mediaMimes.has(mime)) {
return;
}
const file = item?.asFile();
if (!file) {
return;
}
if (file.uri) {
// If the file is already in a workspace, we don't want to create a copy of it
const workspaceFolder = vscode.workspace.getWorkspaceFolder(file.uri);
if (workspaceFolder) {
return { uri: file.uri };
}
}
const newFile = await pathGenerator.getNewFilePath(document, file, token);
if (!newFile) {
return;
}
return { uri: newFile.uri, newFile: { contents: file, overwrite: newFile.overwrite } };
})));
if (!fileEntries.length) {
return;
}
const workspaceEdit = new vscode.WorkspaceEdit();
for (const entry of fileEntries) {
if (entry.newFile) {
workspaceEdit.createFile(entry.uri, {
contents: entry.newFile.contents,
overwrite: entry.newFile.overwrite,
});
}
}
const snippet = createUriListSnippet(document, fileEntries.map(entry => entry.uri));
if (!snippet) {
return;
}
return {
snippet: snippet.snippet,
label: snippet.label,
additionalEdits: workspaceEdit,
};
}
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);
}

View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* 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 './schemes';
import { Utils } from 'vscode-uri';
export function getDocumentDir(document: vscode.TextDocument): vscode.Uri | undefined {
const docUri = getParentDocumentUri(document);
if (docUri.scheme === Schemes.untitled) {
return vscode.workspace.workspaceFolders?.[0]?.uri;
}
return 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;
}