Skip to content

Commit 3a67bc1

Browse files
authored
Expand tilde in Coder settings paths (#667)
Closes #417
1 parent 6d57292 commit 3a67bc1

File tree

3 files changed

+83
-10
lines changed

3 files changed

+83
-10
lines changed

CHANGELOG.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22

33
## Unreleased
44

5+
### Added
6+
7+
- Support for paths that begin with a tilde (`~`).
8+
59
### Fixed
610

711
- Fixed race condition when multiple VS Code windows download the Coder CLI binary simultaneously.
812
Other windows now wait and display real-time progress instead of attempting concurrent downloads,
913
preventing corruption and failures.
14+
- Remove duplicate "Cancel" buttons on the workspace update dialog.
1015

1116
### Changed
1217

@@ -15,9 +20,9 @@
1520

1621
## [v1.11.4](https://github.com/coder/vscode-coder/releases/tag/v1.11.4) 2025-11-20
1722

18-
### Fixed
23+
### Added
1924

20-
- Add support for `google.antigravity-remote-openssh` Remote SSH extension.
25+
- Support for the `google.antigravity-remote-openssh` Remote SSH extension.
2126

2227
### Changed
2328

@@ -55,7 +60,7 @@
5560

5661
### Changed
5762

58-
- Always enable verbose (`-v`) flag when a log directory is configured (`coder.proxyLogDir`).
63+
- Always enable verbose (`-v`) flag when a log directory is configured (`coder.proxyLogDirectory`).
5964
- Automatically start a workspace without prompting if it is explicitly opened but not running.
6065

6166
### Added
@@ -134,7 +139,7 @@
134139

135140
### Added
136141

137-
- Coder extension sidebar now displays available app statuses, and let's
142+
- Coder extension sidebar now displays available app statuses, and lets
138143
the user click them to drop into a session with a running AI Agent.
139144

140145
## [v1.7.1](https://github.com/coder/vscode-coder/releases/tag/v1.7.1) (2025-04-14)

src/util.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,14 @@ export function toSafeHost(rawUrl: string): string {
119119
}
120120

121121
/**
122-
* Expand a path with ${userHome} in the input string
123-
* @param input string
124-
* @returns string
122+
* Expand a path if it starts with tilde (~) or contains ${userHome}.
125123
*/
126124
export function expandPath(input: string): string {
127125
const userHome = os.homedir();
128-
return input.replace(/\${userHome}/g, userHome);
126+
if (input.startsWith("~")) {
127+
input = userHome + input.substring("~".length);
128+
}
129+
return input.replaceAll("${userHome}", userHome);
129130
}
130131

131132
/**
@@ -145,5 +146,6 @@ export function countSubstring(needle: string, haystack: string): number {
145146
}
146147

147148
export function escapeCommandArg(arg: string): string {
148-
return `"${arg.replace(/"/g, '\\"')}"`;
149+
const escapedString = arg.replaceAll('"', String.raw`\"`);
150+
return `"${escapedString}"`;
149151
}

test/unit/util.test.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
import os from "node:os";
12
import { describe, it, expect } from "vitest";
23

3-
import { countSubstring, parseRemoteAuthority, toSafeHost } from "@/util";
4+
import {
5+
countSubstring,
6+
escapeCommandArg,
7+
expandPath,
8+
parseRemoteAuthority,
9+
toSafeHost,
10+
} from "@/util";
411

512
it("ignore unrelated authorities", () => {
613
const tests = [
@@ -124,3 +131,62 @@ describe("countSubstring", () => {
124131
expect(countSubstring("aa", "aaaaaa")).toBe(3);
125132
});
126133
});
134+
135+
describe("escapeCommandArg", () => {
136+
it("wraps simple string in quotes", () => {
137+
expect(escapeCommandArg("hello")).toBe('"hello"');
138+
});
139+
140+
it("handles empty string", () => {
141+
expect(escapeCommandArg("")).toBe('""');
142+
});
143+
144+
it("escapes double quotes", () => {
145+
expect(escapeCommandArg('say "hello"')).toBe(String.raw`"say \"hello\""`);
146+
});
147+
148+
it("preserves backslashes", () => {
149+
expect(escapeCommandArg(String.raw`path\to\file`)).toBe(
150+
String.raw`"path\to\file"`,
151+
);
152+
});
153+
154+
it("handles string with spaces", () => {
155+
expect(escapeCommandArg("hello world")).toBe('"hello world"');
156+
});
157+
});
158+
159+
describe("expandPath", () => {
160+
const home = os.homedir();
161+
162+
it("expands tilde at start of path", () => {
163+
expect(expandPath("~/foo/bar")).toBe(`${home}/foo/bar`);
164+
});
165+
166+
it("expands standalone tilde", () => {
167+
expect(expandPath("~")).toBe(home);
168+
});
169+
170+
it("does not expand tilde in middle of path", () => {
171+
expect(expandPath("/foo/~/bar")).toBe("/foo/~/bar");
172+
});
173+
174+
it("expands ${userHome} variable", () => {
175+
expect(expandPath("${userHome}/foo")).toBe(`${home}/foo`);
176+
});
177+
178+
it("expands multiple ${userHome} variables", () => {
179+
expect(expandPath("${userHome}/foo/${userHome}/bar")).toBe(
180+
`${home}/foo/${home}/bar`,
181+
);
182+
});
183+
184+
it("leaves paths without tilde or variable unchanged", () => {
185+
expect(expandPath("/absolute/path")).toBe("/absolute/path");
186+
expect(expandPath("relative/path")).toBe("relative/path");
187+
});
188+
189+
it("expands both tilde and ${userHome}", () => {
190+
expect(expandPath("~/${userHome}/foo")).toBe(`${home}/${home}/foo`);
191+
});
192+
});

0 commit comments

Comments
 (0)