Skip to content

Commit 4c8fec6

Browse files
committed
bug: space in folder and file name
1 parent 910e9ba commit 4c8fec6

File tree

4 files changed

+74
-30
lines changed

4 files changed

+74
-30
lines changed

src/core/mentions/__tests__/index.test.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ Detailed commit message with multiple lines
145145
expect(result).toContain(`Error fetching commit info: ${errorMessage}`)
146146
})
147147

148-
it("should parse file paths with spaces", async () => {
148+
it("should parse file paths with escaped spaces", async () => {
149149
// Mock the file content fetching
150150
const fileContent = "This is the content of the file with spaces in its name"
151151

@@ -155,8 +155,8 @@ Detailed commit message with multiple lines
155155
jest.spyOn(fs, "readFile").mockResolvedValue(fileContent)
156156
jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => true, isDirectory: () => false } as any)
157157

158-
// Test with a file path containing spaces
159-
const filePath = "/path/with spaces/my file.txt"
158+
// Test with a file path containing escaped spaces
159+
const filePath = "/path/with\\ spaces/my\\ file.txt"
160160

161161
// First, verify that the regex pattern correctly matches the entire path
162162
// Import the regex pattern directly to test it
@@ -168,14 +168,14 @@ Detailed commit message with multiple lines
168168
// Now test the full parseMentions function
169169
const result = await parseMentions(`Check out this file @${filePath}`, mockCwd, mockUrlContentFetcher)
170170

171-
// Verify the file path with spaces was correctly parsed
171+
// Verify the file path with escaped spaces was correctly parsed
172+
// The spaces should be unescaped when displayed
172173
expect(result).toContain(`'path/with spaces/my file.txt' (see below for file content)`)
173174
expect(result).toContain(`<file_content path="path/with spaces/my file.txt">`)
174175
})
175176

176-
it("should parse folder paths with spaces", async () => {
177+
it("should parse folder paths with escaped spaces", async () => {
177178
// Mock the folder content fetching
178-
const folderContent = "├── file1.txt\n├── file2.txt\n└── subfolder/"
179179

180180
// Mock the getFileOrFolderContent function (which is called internally by parseMentions)
181181
// This is done by mocking the fs.readdir and fs.stat that would be called inside getFileOrFolderContent
@@ -187,13 +187,48 @@ Detailed commit message with multiple lines
187187
])
188188
jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => false, isDirectory: () => true } as any)
189189

190-
const folderPath = "/folder with spaces/"
190+
// Test with a folder path containing escaped spaces
191+
const folderPath = "/folder\\ with\\ spaces/"
192+
193+
// First, verify that the regex pattern correctly matches the entire path
194+
const { mentionRegexGlobal } = require("../../../shared/context-mentions")
195+
const mentionMatch = `@${folderPath}`.match(mentionRegexGlobal)
196+
expect(mentionMatch).not.toBeNull()
197+
expect(mentionMatch![0]).toBe(`@${folderPath}`)
198+
191199
const result = await parseMentions(`Check out this folder @${folderPath}`, mockCwd, mockUrlContentFetcher)
192200

193-
// Verify the folder path with spaces was correctly parsed
201+
// Verify the folder path with escaped spaces was correctly parsed
202+
// The spaces should be unescaped when displayed
194203
expect(result).toContain(`'folder with spaces/' (see below for folder content)`)
195204
expect(result).toContain(`<folder_content path="folder with spaces/">`)
196205
})
206+
207+
it("should parse nested paths with multiple escaped spaces", async () => {
208+
// Mock the file content fetching
209+
const fileContent = "This is the content of the file with multiple spaces in its path"
210+
211+
// Mock the getFileOrFolderContent function
212+
const fs = require("fs/promises")
213+
jest.spyOn(fs, "readFile").mockResolvedValue(fileContent)
214+
jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => true, isDirectory: () => false } as any)
215+
216+
// Test with a deeply nested path containing multiple escaped spaces
217+
const filePath = "/root\\ dir/my\\ documents/project\\ files/important\\ notes/final\\ draft\\ v2.txt"
218+
219+
// Verify the regex pattern correctly matches the entire path
220+
const { mentionRegexGlobal } = require("../../../shared/context-mentions")
221+
const mentionMatch = `@${filePath}`.match(mentionRegexGlobal)
222+
expect(mentionMatch).not.toBeNull()
223+
expect(mentionMatch![0]).toBe(`@${filePath}`)
224+
225+
// Test the full parseMentions function
226+
const result = await parseMentions(`Check out this file @${filePath}`, mockCwd, mockUrlContentFetcher)
227+
228+
// Verify the complex path was correctly parsed with all spaces unescaped
229+
expect(result).toContain(`'root dir/my documents/project files/important notes/final draft v2.txt' (see below for file content)`)
230+
expect(result).toContain(`<file_content path="root dir/my documents/project files/important notes/final draft v2.txt">`)
231+
})
197232
})
198233

199234
describe("openMention", () => {

src/core/mentions/index.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,24 @@ export async function parseMentions(
4747
): Promise<string> {
4848
const mentions: Set<string> = new Set()
4949
let parsedText = text.replace(mentionRegexGlobal, (match, mention) => {
50-
mentions.add(mention)
51-
if (mention.startsWith("http")) {
52-
return `'${mention}' (see below for site content)`
53-
} else if (mention.startsWith("/")) {
54-
const mentionPath = mention.slice(1)
50+
// Unescape spaces in the mention (convert "\\s" to " ")
51+
const unescapedMention = mention.replace(/\\\\\s/g, " ")
52+
mentions.add(unescapedMention)
53+
54+
if (unescapedMention.startsWith("http")) {
55+
return `'${unescapedMention}' (see below for site content)`
56+
} else if (unescapedMention.startsWith("/")) {
57+
const mentionPath = unescapedMention.slice(1)
5558
return mentionPath.endsWith("/")
5659
? `'${mentionPath}' (see below for folder content)`
5760
: `'${mentionPath}' (see below for file content)`
58-
} else if (mention === "problems") {
61+
} else if (unescapedMention === "problems") {
5962
return `Workspace Problems (see below for diagnostics)`
60-
} else if (mention === "git-changes") {
63+
} else if (unescapedMention === "git-changes") {
6164
return `Working directory changes (see below for details)`
62-
} else if (/^[a-f0-9]{7,40}$/.test(mention)) {
63-
return `Git commit '${mention}' (see below for commit info)`
64-
} else if (mention === "terminal") {
65+
} else if (/^[a-f0-9]{7,40}$/.test(unescapedMention)) {
66+
return `Git commit '${unescapedMention}' (see below for commit info)`
67+
} else if (unescapedMention === "terminal") {
6568
return `Terminal Output (see below for output)`
6669
}
6770
return match

src/shared/context-mentions.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ Mention regex:
1818
- `|`: Logical OR.
1919
- `\w+:\/\/`:
2020
- **Protocol (`\w+://`)**: Matches URLs that start with a word character sequence followed by '://', such as 'http://', 'https://', 'ftp://', etc.
21-
- `[^\r\n]*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))`:
22-
- **Character Pattern**: Matches any characters except line breaks.
21+
- `[^\s\r\n]*?(?:\\\\\s[^\s\r\n]*?)*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))`:
22+
- **Character Pattern**: Matches any characters except whitespace and line breaks.
23+
- **Escaped Spaces**: The `(?:\\\\\s[^\s\r\n]*?)*?` part allows for escaped spaces (like `\\s`) in the path.
2324
- **Followed by a lookahead**: Ensures the match ends at the end of the line, before another @ symbol, or before punctuation followed by whitespace or end of line.
24-
- **This allows spaces within file paths while properly handling path boundaries**.
25+
- **This handles paths with escaped spaces (e.g., `my\\ folder/my\\ file.txt`)**.
2526
- **Non-Greedy (`*?`)**: Ensures the smallest possible match.
2627
- `|`: Logical OR.
2728
- `problems\b`:
@@ -40,8 +41,8 @@ Mention regex:
4041
4142
- **Summary**:
4243
- The regex effectively matches:
43-
- Mentions that are file or folder paths starting with '/' and can contain spaces within the path (e.g., 'my folder/my file.txt').
44-
The regex now properly handles paths with multiple spaces and ensures the entire path is captured.
44+
- Mentions that are file or folder paths starting with '/' and can contain escaped spaces within the path (e.g., 'my\\ folder/my\\ file.txt').
45+
The regex properly handles paths with escaped spaces and ensures the entire path is captured.
4546
- URLs that start with a protocol (like 'http://') followed by any non-whitespace characters (including query parameters).
4647
- The exact word 'problems'.
4748
- The exact word 'git-changes'.
@@ -53,7 +54,7 @@ Mention regex:
5354
5455
*/
5556
export const mentionRegex =
56-
/@((?:\/|\w+:\/\/)[^\r\n]*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/
57+
/@((?:\/|\w+:\/\/)[^\s\r\n]*?(?:\\\\\s[^\s\r\n]*?)*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/
5758
export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g")
5859

5960
export interface MentionSuggestion {

webview-ui/src/utils/__tests__/context-mentions.test.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -373,13 +373,18 @@ describe("shouldShowContextMenu", () => {
373373
expect(shouldShowContextMenu("@problems", 9)).toBe(true)
374374
})
375375

376-
it("should return true for file paths with spaces", () => {
377-
// Test with a file path containing spaces
378-
expect(shouldShowContextMenu("@/path/to/my file.txt", 20)).toBe(true)
376+
it("should return true for file paths with escaped spaces", () => {
377+
// Test with a file path containing escaped spaces
378+
expect(shouldShowContextMenu("@/path/to/my\\ file.txt", 20)).toBe(true)
379379
})
380380

381-
it("should return true for folder paths with spaces", () => {
382-
// Test with a folder path containing spaces
383-
expect(shouldShowContextMenu("@/path/to/my folder/", 20)).toBe(true)
381+
it("should return true for folder paths with escaped spaces", () => {
382+
// Test with a folder path containing escaped spaces
383+
expect(shouldShowContextMenu("@/path/to/my\\ folder/", 20)).toBe(true)
384+
})
385+
386+
it("should return true for nested paths with multiple escaped spaces", () => {
387+
// Test with a deeply nested path containing multiple escaped spaces
388+
expect(shouldShowContextMenu("@/root\\ dir/my\\ documents/project\\ files/file.txt", 50)).toBe(true)
384389
})
385390
})

0 commit comments

Comments
 (0)