Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/agentHost/common/commandLineHelpers.ts
13394 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
6
import { extUriBiasedIgnorePathCase } from '../../../base/common/resources.js';
7
import { URI } from '../../../base/common/uri.js';
8
9
/**
10
* Result of {@link extractCdPrefix}: the directory the `cd` jumps to and the
11
* remaining command after the chain operator.
12
*/
13
export interface IExtractedCdPrefix {
14
readonly directory: string;
15
readonly command: string;
16
}
17
18
/**
19
* Extracts a `cd <dir> &&` (or PowerShell equivalent) prefix from a command
20
* line, returning the directory and remaining command. Does not check whether
21
* the directory matches anything — callers do that comparison themselves.
22
*
23
* Recognized forms:
24
* - bash: `cd <dir> && <suffix>`
25
* - powershell: `cd <dir> && <suffix>`, `cd <dir>; <suffix>`
26
* `cd /d <dir> && <suffix>`, `cd /d <dir>; <suffix>`
27
* `Set-Location <dir> && <suffix>`, `Set-Location <dir>; <suffix>`
28
* `Set-Location -Path <dir> && <suffix>`, `Set-Location -Path <dir>; <suffix>`
29
*
30
* Surrounding double quotes around `<dir>` are stripped.
31
*/
32
export function extractCdPrefix(commandLine: string, isPowerShell: boolean): IExtractedCdPrefix | undefined {
33
const cdPrefixMatch = commandLine.match(
34
isPowerShell
35
? /^(?:cd(?: \/d)?|Set-Location(?: -Path)?) (?<dir>"[^"]*"|[^\s]+) ?(?:&&|;)\s+(?<suffix>.+)$/i
36
: /^cd (?<dir>"[^"]*"|[^\s]+) &&\s+(?<suffix>.+)$/
37
);
38
const cdDir = cdPrefixMatch?.groups?.dir;
39
const cdSuffix = cdPrefixMatch?.groups?.suffix;
40
if (cdDir && cdSuffix) {
41
let cdDirPath = cdDir;
42
if (cdDirPath.startsWith('"') && cdDirPath.endsWith('"')) {
43
cdDirPath = cdDirPath.slice(1, -1);
44
}
45
return { directory: cdDirPath, command: cdSuffix };
46
}
47
return undefined;
48
}
49
50
/**
51
* If `toolName` is a shell tool (`bash` or `powershell`) and
52
* `parameters.command` starts with a `cd <workingDirectory> && …` (or
53
* PowerShell equivalent) prefix, mutate `parameters.command` to drop the
54
* prefix and return `true`. Returns `false` otherwise.
55
*
56
* Path comparison normalizes trailing slashes and is case-insensitive on
57
* Windows.
58
*/
59
export function stripRedundantCdPrefix(
60
toolName: string,
61
parameters: Record<string, unknown> | undefined,
62
workingDirectory: URI | undefined,
63
): boolean {
64
if (!workingDirectory || !parameters) {
65
return false;
66
}
67
const isBash = toolName === 'bash';
68
const isPowerShell = toolName === 'powershell';
69
if (!isBash && !isPowerShell) {
70
return false;
71
}
72
const command = parameters.command;
73
if (typeof command !== 'string') {
74
return false;
75
}
76
const extracted = extractCdPrefix(command, isPowerShell);
77
if (!extracted) {
78
return false;
79
}
80
if (!sameDirectory(extracted.directory, workingDirectory)) {
81
return false;
82
}
83
parameters.command = extracted.command;
84
return true;
85
}
86
87
/**
88
* Compares an extracted `cd <dir>` argument (a raw filesystem path string,
89
* possibly using either `/` or `\` separators) to a working-directory URI.
90
* Normalizes separators by routing the extracted string through `URI.file`,
91
* which converts to the platform-native `fsPath` shape, so that e.g.
92
* `cd C:/repo` matches a working directory of `C:\repo` on Windows.
93
*
94
* Path comparison uses {@link extUriBiasedIgnorePathCase}, which is
95
* case-insensitive on Windows / macOS.
96
*/
97
function sameDirectory(extractedDir: string, workingDirectory: URI): boolean {
98
if (!extractedDir) {
99
return false;
100
}
101
// Strip trailing path separators (either flavor) so e.g. `/repo/project/`
102
// matches `/repo/project`. Without this, URI.file would preserve the
103
// trailing slash and the URIs would not compare equal. We do this for
104
// both sides because the working directory may also end in a separator.
105
const trim = (p: string) => p.replace(/[\\/]+$/, '');
106
const trimmedExtracted = trim(extractedDir);
107
const trimmedWd = trim(workingDirectory.fsPath);
108
if (!trimmedExtracted || !trimmedWd) {
109
return false;
110
}
111
let extractedUri: URI;
112
let wdUri: URI;
113
try {
114
extractedUri = URI.file(trimmedExtracted);
115
wdUri = URI.file(trimmedWd);
116
} catch {
117
return false;
118
}
119
return extUriBiasedIgnorePathCase.isEqual(extractedUri, wdUri);
120
}
121
122
123