Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts
3294 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
import * as path from 'path';
6
import * as vscode from 'vscode';
7
import { Utils } from 'vscode-uri';
8
9
type OverwriteBehavior = 'overwrite' | 'nameIncrementally';
10
11
export interface CopyFileConfiguration {
12
readonly destination: Record<string, string>;
13
readonly overwriteBehavior: OverwriteBehavior;
14
}
15
16
export function getCopyFileConfiguration(document: vscode.TextDocument): CopyFileConfiguration {
17
const config = vscode.workspace.getConfiguration('markdown', document);
18
return {
19
destination: config.get<Record<string, string>>('copyFiles.destination') ?? {},
20
overwriteBehavior: readOverwriteBehavior(config),
21
};
22
}
23
24
function readOverwriteBehavior(config: vscode.WorkspaceConfiguration): OverwriteBehavior {
25
switch (config.get('copyFiles.overwriteBehavior')) {
26
case 'overwrite': return 'overwrite';
27
default: return 'nameIncrementally';
28
}
29
}
30
31
export function parseGlob(rawGlob: string): Iterable<string> {
32
if (rawGlob.startsWith('/')) {
33
// Anchor to workspace folders
34
return (vscode.workspace.workspaceFolders ?? []).map(folder => vscode.Uri.joinPath(folder.uri, rawGlob).path);
35
}
36
37
// Relative path, so implicitly track on ** to match everything
38
if (!rawGlob.startsWith('**')) {
39
return ['**/' + rawGlob];
40
}
41
42
return [rawGlob];
43
}
44
45
type GetWorkspaceFolder = (documentUri: vscode.Uri) => vscode.Uri | undefined;
46
47
export function resolveCopyDestination(documentUri: vscode.Uri, fileName: string, dest: string, getWorkspaceFolder: GetWorkspaceFolder): vscode.Uri {
48
const resolvedDest = resolveCopyDestinationSetting(documentUri, fileName, dest, getWorkspaceFolder);
49
50
if (resolvedDest.startsWith('/')) {
51
// Absolute path
52
return Utils.resolvePath(documentUri, resolvedDest);
53
}
54
55
// Relative to document
56
const dirName = Utils.dirname(documentUri);
57
return Utils.resolvePath(dirName, resolvedDest);
58
}
59
60
61
function resolveCopyDestinationSetting(documentUri: vscode.Uri, fileName: string, dest: string, getWorkspaceFolder: GetWorkspaceFolder): string {
62
let outDest = dest.trim();
63
if (!outDest) {
64
outDest = '${fileName}';
65
}
66
67
// Destination that start with `/` implicitly means go to workspace root
68
if (outDest.startsWith('/')) {
69
outDest = '${documentWorkspaceFolder}/' + outDest.slice(1);
70
}
71
72
// Destination that ends with `/` implicitly needs a fileName
73
if (outDest.endsWith('/')) {
74
outDest += '${fileName}';
75
}
76
77
const documentDirName = Utils.dirname(documentUri);
78
const documentBaseName = Utils.basename(documentUri);
79
const documentExtName = Utils.extname(documentUri);
80
81
const workspaceFolder = getWorkspaceFolder(documentUri);
82
83
const vars = new Map<string, string>([
84
// Document
85
['documentDirName', documentDirName.path], // Absolute parent directory path of the Markdown document, e.g. `/Users/me/myProject/docs`.
86
['documentRelativeDirName', workspaceFolder ? path.posix.relative(workspaceFolder.path, documentDirName.path) : documentDirName.path], // Relative parent directory path of the Markdown document, e.g. `docs`. This is the same as `${documentDirName}` if the file is not part of a workspace.
87
['documentFileName', documentBaseName], // The full filename of the Markdown document, e.g. `README.md`.
88
['documentBaseName', documentBaseName.slice(0, documentBaseName.length - documentExtName.length)], // The basename of the Markdown document, e.g. `README`.
89
['documentExtName', documentExtName.replace('.', '')], // The extension of the Markdown document, e.g. `md`.
90
['documentFilePath', documentUri.path], // Absolute path of the Markdown document, e.g. `/Users/me/myProject/docs/README.md`.
91
['documentRelativeFilePath', workspaceFolder ? path.posix.relative(workspaceFolder.path, documentUri.path) : documentUri.path], // Relative path of the Markdown document, e.g. `docs/README.md`. This is the same as `${documentFilePath}` if the file is not part of a workspace.
92
93
// Workspace
94
['documentWorkspaceFolder', ((workspaceFolder ?? documentDirName).path)], // The workspace folder for the Markdown document, e.g. `/Users/me/myProject`. This is the same as `${documentDirName}` if the file is not part of a workspace.
95
96
// File
97
['fileName', fileName], // The file name of the dropped file, e.g. `image.png`.
98
['fileExtName', path.extname(fileName).replace('.', '')], // The extension of the dropped file, e.g. `png`.
99
['unixTime', Date.now().toString()], // The current Unix timestamp in milliseconds.
100
['isoTime', new Date().toISOString()], // The current time in ISO 8601 format, e.g. '2025-06-06T08:40:32.123Z'.
101
]);
102
103
return outDest.replaceAll(/(?<escape>\\\$)|(?<!\\)\$\{(?<name>\w+)(?:\/(?<pattern>(?:\\\/|[^\}\/])+)\/(?<replacement>(?:\\\/|[^\}\/])*)\/)?\}/g, (match, _escape, name, pattern, replacement, _offset, _str, groups) => {
104
if (groups?.['escape']) {
105
return '$';
106
}
107
108
const entry = vars.get(name);
109
if (typeof entry !== 'string') {
110
return match;
111
}
112
113
if (pattern && replacement) {
114
try {
115
return entry.replace(new RegExp(replaceTransformEscapes(pattern)), replaceTransformEscapes(replacement));
116
} catch (e) {
117
console.log(`Error applying 'resolveCopyDestinationSetting' transform: ${pattern} -> ${replacement}`);
118
}
119
}
120
121
return entry;
122
});
123
}
124
125
function replaceTransformEscapes(str: string): string {
126
return str.replaceAll(/\\\//g, '/');
127
}
128
129