From e21e2f779ba794527b7de2d1fe2b185db97dcd1f Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Thu, 4 Dec 2025 16:31:05 +0300 Subject: [PATCH 1/2] Add support for tilde (~) path expansion --- CHANGELOG.md | 13 +++++++++---- src/util.ts | 12 +++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b1745b7..a7ebd676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,16 @@ ## Unreleased +### Added + +- Support for paths that begin with a tilde (`~`). + ### Fixed - Fixed race condition when multiple VS Code windows download the Coder CLI binary simultaneously. Other windows now wait and display real-time progress instead of attempting concurrent downloads, preventing corruption and failures. +- Remove duplicate "Cancel" buttons on the workspace update dialog. ### Changed @@ -15,9 +20,9 @@ ## [v1.11.4](https://github.com/coder/vscode-coder/releases/tag/v1.11.4) 2025-11-20 -### Fixed +### Added -- Add support for `google.antigravity-remote-openssh` Remote SSH extension. +- Support for the `google.antigravity-remote-openssh` Remote SSH extension. ### Changed @@ -55,7 +60,7 @@ ### Changed -- Always enable verbose (`-v`) flag when a log directory is configured (`coder.proxyLogDir`). +- Always enable verbose (`-v`) flag when a log directory is configured (`coder.proxyLogDirectory`). - Automatically start a workspace without prompting if it is explicitly opened but not running. ### Added @@ -134,7 +139,7 @@ ### Added -- Coder extension sidebar now displays available app statuses, and let's +- Coder extension sidebar now displays available app statuses, and lets the user click them to drop into a session with a running AI Agent. ## [v1.7.1](https://github.com/coder/vscode-coder/releases/tag/v1.7.1) (2025-04-14) diff --git a/src/util.ts b/src/util.ts index e7c5c24c..21785cf6 100644 --- a/src/util.ts +++ b/src/util.ts @@ -119,13 +119,14 @@ export function toSafeHost(rawUrl: string): string { } /** - * Expand a path with ${userHome} in the input string - * @param input string - * @returns string + * Expand a path if it starts with tilde (~) or contains ${userHome}. */ export function expandPath(input: string): string { const userHome = os.homedir(); - return input.replace(/\${userHome}/g, userHome); + if (input.startsWith("~")) { + input = userHome + input.substring("~".length); + } + return input.replaceAll("${userHome}", userHome); } /** @@ -145,5 +146,6 @@ export function countSubstring(needle: string, haystack: string): number { } export function escapeCommandArg(arg: string): string { - return `"${arg.replace(/"/g, '\\"')}"`; + const escapedString = arg.replaceAll('"', String.raw`\"`); + return `"${escapedString}"`; } From 0332613c82edf0750b97d4b7fc02bfc103531a09 Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Thu, 4 Dec 2025 16:41:42 +0300 Subject: [PATCH 2/2] Add tests --- test/unit/util.test.ts | 68 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/test/unit/util.test.ts b/test/unit/util.test.ts index d508f41c..a5d6eb7a 100644 --- a/test/unit/util.test.ts +++ b/test/unit/util.test.ts @@ -1,6 +1,13 @@ +import os from "node:os"; import { describe, it, expect } from "vitest"; -import { countSubstring, parseRemoteAuthority, toSafeHost } from "@/util"; +import { + countSubstring, + escapeCommandArg, + expandPath, + parseRemoteAuthority, + toSafeHost, +} from "@/util"; it("ignore unrelated authorities", () => { const tests = [ @@ -124,3 +131,62 @@ describe("countSubstring", () => { expect(countSubstring("aa", "aaaaaa")).toBe(3); }); }); + +describe("escapeCommandArg", () => { + it("wraps simple string in quotes", () => { + expect(escapeCommandArg("hello")).toBe('"hello"'); + }); + + it("handles empty string", () => { + expect(escapeCommandArg("")).toBe('""'); + }); + + it("escapes double quotes", () => { + expect(escapeCommandArg('say "hello"')).toBe(String.raw`"say \"hello\""`); + }); + + it("preserves backslashes", () => { + expect(escapeCommandArg(String.raw`path\to\file`)).toBe( + String.raw`"path\to\file"`, + ); + }); + + it("handles string with spaces", () => { + expect(escapeCommandArg("hello world")).toBe('"hello world"'); + }); +}); + +describe("expandPath", () => { + const home = os.homedir(); + + it("expands tilde at start of path", () => { + expect(expandPath("~/foo/bar")).toBe(`${home}/foo/bar`); + }); + + it("expands standalone tilde", () => { + expect(expandPath("~")).toBe(home); + }); + + it("does not expand tilde in middle of path", () => { + expect(expandPath("/foo/~/bar")).toBe("/foo/~/bar"); + }); + + it("expands ${userHome} variable", () => { + expect(expandPath("${userHome}/foo")).toBe(`${home}/foo`); + }); + + it("expands multiple ${userHome} variables", () => { + expect(expandPath("${userHome}/foo/${userHome}/bar")).toBe( + `${home}/foo/${home}/bar`, + ); + }); + + it("leaves paths without tilde or variable unchanged", () => { + expect(expandPath("/absolute/path")).toBe("/absolute/path"); + expect(expandPath("relative/path")).toBe("relative/path"); + }); + + it("expands both tilde and ${userHome}", () => { + expect(expandPath("~/${userHome}/foo")).toBe(`${home}/${home}/foo`); + }); +});