From 9c1869abc947e81d89284e9f36e9b92be211c998 Mon Sep 17 00:00:00 2001 From: Kunwar Vivek Singh Date: Fri, 18 Apr 2025 22:35:50 +0530 Subject: [PATCH 1/4] bug: space in folder and file name --- src/core/mentions/__tests__/index.test.ts | 40 +++++++++++++++++++ src/shared/context-mentions.ts | 33 +++++++-------- .../utils/__tests__/context-mentions.test.ts | 10 +++++ 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/core/mentions/__tests__/index.test.ts b/src/core/mentions/__tests__/index.test.ts index a85fe1f0a88..c0b4c9b468b 100644 --- a/src/core/mentions/__tests__/index.test.ts +++ b/src/core/mentions/__tests__/index.test.ts @@ -144,6 +144,46 @@ Detailed commit message with multiple lines expect(result).toContain(``) expect(result).toContain(`Error fetching commit info: ${errorMessage}`) }) + + it("should parse file paths with spaces", async () => { + // Mock the file content fetching + const fileContent = "This is the content of the file with spaces in its name" + + // Mock the getFileOrFolderContent function (which is called internally by parseMentions) + // This is done by mocking the fs.readFile that would be called inside getFileOrFolderContent + const fs = require("fs/promises") + jest.spyOn(fs, "readFile").mockResolvedValue(fileContent) + jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => true, isDirectory: () => false } as any) + + const filePath = "/path/with spaces/my file.txt" + const result = await parseMentions(`Check out this file @${filePath}`, mockCwd, mockUrlContentFetcher) + + // Verify the file path with spaces was correctly parsed + expect(result).toContain(`'path/with spaces/my file.txt' (see below for file content)`) + expect(result).toContain(``) + }) + + it("should parse folder paths with spaces", async () => { + // Mock the folder content fetching + const folderContent = "├── file1.txt\n├── file2.txt\n└── subfolder/" + + // Mock the getFileOrFolderContent function (which is called internally by parseMentions) + // This is done by mocking the fs.readdir and fs.stat that would be called inside getFileOrFolderContent + const fs = require("fs/promises") + jest.spyOn(fs, "readdir").mockResolvedValue([ + { name: "file1.txt", isFile: () => true, isDirectory: () => false }, + { name: "file2.txt", isFile: () => true, isDirectory: () => false }, + { name: "subfolder", isFile: () => false, isDirectory: () => true } + ]) + jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => false, isDirectory: () => true } as any) + + const folderPath = "/folder with spaces/" + const result = await parseMentions(`Check out this folder @${folderPath}`, mockCwd, mockUrlContentFetcher) + + // Verify the folder path with spaces was correctly parsed + expect(result).toContain(`'folder with spaces/' (see below for folder content)`) + expect(result).toContain(``) + }) }) describe("openMention", () => { diff --git a/src/shared/context-mentions.ts b/src/shared/context-mentions.ts index 915114ab932..781b67680c5 100644 --- a/src/shared/context-mentions.ts +++ b/src/shared/context-mentions.ts @@ -1,28 +1,29 @@ /* Mention regex: -- **Purpose**: - - To identify and highlight specific mentions in text that start with '@'. +- **Purpose**: + - To identify and highlight specific mentions in text that start with '@'. - These mentions can be file paths, URLs, or the exact word 'problems'. - Ensures that trailing punctuation marks (like commas, periods, etc.) are not included in the match, allowing punctuation to follow the mention without being part of it. - **Regex Breakdown**: - - `/@`: + - `/@`: - **@**: The mention must start with the '@' symbol. - - - `((?:\/|\w+:\/\/)[^\s]+?|problems\b|git-changes\b)`: + + - `((?:\/|\w+:\/\/)(?:[^\s]|\s(?=[^\s]))+?|problems\b|git-changes\b)`: - **Capturing Group (`(...)`)**: Captures the part of the string that matches one of the specified patterns. - - `(?:\/|\w+:\/\/)`: + - `(?:\/|\w+:\/\/)`: - **Non-Capturing Group (`(?:...)`)**: Groups the alternatives without capturing them for back-referencing. - - `\/`: + - `\/`: - **Slash (`/`)**: Indicates that the mention is a file or folder path starting with a '/'. - `|`: Logical OR. - - `\w+:\/\/`: + - `\w+:\/\/`: - **Protocol (`\w+://`)**: Matches URLs that start with a word character sequence followed by '://', such as 'http://', 'https://', 'ftp://', etc. - - `[^\s]+?`: - - **Non-Whitespace Characters (`[^\s]+`)**: Matches one or more characters that are not whitespace. + - `(?:[^\s]|\s(?=[^\s]))+?`: + - **Character Pattern**: Matches either a non-whitespace character OR a whitespace character that is followed by a non-whitespace character. + - **This allows spaces within file paths while preventing trailing spaces**. - **Non-Greedy (`+?`)**: Ensures the smallest possible match, preventing the inclusion of trailing punctuation. - `|`: Logical OR. - - `problems\b`: + - `problems\b`: - **Exact Word ('problems')**: Matches the exact word 'problems'. - **Word Boundary (`\b`)**: Ensures that 'problems' is matched as a whole word and not as part of another word (e.g., 'problematic'). - `|`: Logical OR. @@ -31,14 +32,14 @@ Mention regex: - **Word Boundary (`\b`)**: Ensures that 'terminal' is matched as a whole word and not as part of another word (e.g., 'terminals'). - `(?=[.,;:!?]?(?=[\s\r\n]|$))`: - **Positive Lookahead (`(?=...)`)**: Ensures that the match is followed by specific patterns without including them in the match. - - `[.,;:!?]?`: + - `[.,;:!?]?`: - **Optional Punctuation (`[.,;:!?]?`)**: Matches zero or one of the specified punctuation marks. - - `(?=[\s\r\n]|$)`: + - `(?=[\s\r\n]|$)`: - **Nested Positive Lookahead (`(?=[\s\r\n]|$)`)**: Ensures that the punctuation (if present) is followed by a whitespace character, a line break, or the end of the string. - + - **Summary**: - The regex effectively matches: - - Mentions that are file or folder paths starting with '/' and containing any non-whitespace characters (including periods within the path). + - Mentions that are file or folder paths starting with '/' and can contain spaces within the path (e.g., 'my folder/my file.txt'). - URLs that start with a protocol (like 'http://') followed by any non-whitespace characters (including query parameters). - The exact word 'problems'. - The exact word 'git-changes'. @@ -50,7 +51,7 @@ Mention regex: */ export const mentionRegex = - /@((?:\/|\w+:\/\/)[^\s]+?|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/ + /@((?:\/|\w+:\/\/)(?:[^\s]|\s(?=[^\s]))+?|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/ export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g") export interface MentionSuggestion { diff --git a/webview-ui/src/utils/__tests__/context-mentions.test.ts b/webview-ui/src/utils/__tests__/context-mentions.test.ts index 1e69964a94f..b89eaf9b242 100644 --- a/webview-ui/src/utils/__tests__/context-mentions.test.ts +++ b/webview-ui/src/utils/__tests__/context-mentions.test.ts @@ -372,4 +372,14 @@ describe("shouldShowContextMenu", () => { // Position cursor at the end to test the full word expect(shouldShowContextMenu("@problems", 9)).toBe(true) }) + + it("should return true for file paths with spaces", () => { + // Test with a file path containing spaces + expect(shouldShowContextMenu("@/path/to/my file.txt", 20)).toBe(true) + }) + + it("should return true for folder paths with spaces", () => { + // Test with a folder path containing spaces + expect(shouldShowContextMenu("@/path/to/my folder/", 20)).toBe(true) + }) }) From 910e9ba7f023dbc30885b3aff267cdcc35d4e1b1 Mon Sep 17 00:00:00 2001 From: Kunwar Vivek Singh Date: Fri, 18 Apr 2025 23:53:01 +0530 Subject: [PATCH 2/4] bug: space in folder and file name --- src/core/mentions/__tests__/index.test.ts | 10 ++++++++++ src/shared/context-mentions.ts | 14 ++++++++------ webview-ui/src/utils/context-mentions.ts | 5 +++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/core/mentions/__tests__/index.test.ts b/src/core/mentions/__tests__/index.test.ts index c0b4c9b468b..4949ebd7f44 100644 --- a/src/core/mentions/__tests__/index.test.ts +++ b/src/core/mentions/__tests__/index.test.ts @@ -155,7 +155,17 @@ Detailed commit message with multiple lines jest.spyOn(fs, "readFile").mockResolvedValue(fileContent) jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => true, isDirectory: () => false } as any) + // Test with a file path containing spaces const filePath = "/path/with spaces/my file.txt" + + // First, verify that the regex pattern correctly matches the entire path + // Import the regex pattern directly to test it + const { mentionRegexGlobal } = require("../../../shared/context-mentions") + const mentionMatch = `@${filePath}`.match(mentionRegexGlobal) + expect(mentionMatch).not.toBeNull() + expect(mentionMatch![0]).toBe(`@${filePath}`) + + // Now test the full parseMentions function const result = await parseMentions(`Check out this file @${filePath}`, mockCwd, mockUrlContentFetcher) // Verify the file path with spaces was correctly parsed diff --git a/src/shared/context-mentions.ts b/src/shared/context-mentions.ts index 781b67680c5..7d0cde22156 100644 --- a/src/shared/context-mentions.ts +++ b/src/shared/context-mentions.ts @@ -9,7 +9,7 @@ Mention regex: - `/@`: - **@**: The mention must start with the '@' symbol. - - `((?:\/|\w+:\/\/)(?:[^\s]|\s(?=[^\s]))+?|problems\b|git-changes\b)`: + - `((?:\/|\w+:\/\/)[^\r\n]*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))|problems\b|git-changes\b)`: - **Capturing Group (`(...)`)**: Captures the part of the string that matches one of the specified patterns. - `(?:\/|\w+:\/\/)`: - **Non-Capturing Group (`(?:...)`)**: Groups the alternatives without capturing them for back-referencing. @@ -18,10 +18,11 @@ Mention regex: - `|`: Logical OR. - `\w+:\/\/`: - **Protocol (`\w+://`)**: Matches URLs that start with a word character sequence followed by '://', such as 'http://', 'https://', 'ftp://', etc. - - `(?:[^\s]|\s(?=[^\s]))+?`: - - **Character Pattern**: Matches either a non-whitespace character OR a whitespace character that is followed by a non-whitespace character. - - **This allows spaces within file paths while preventing trailing spaces**. - - **Non-Greedy (`+?`)**: Ensures the smallest possible match, preventing the inclusion of trailing punctuation. + - `[^\r\n]*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))`: + - **Character Pattern**: Matches any characters except line breaks. + - **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. + - **This allows spaces within file paths while properly handling path boundaries**. + - **Non-Greedy (`*?`)**: Ensures the smallest possible match. - `|`: Logical OR. - `problems\b`: - **Exact Word ('problems')**: Matches the exact word 'problems'. @@ -40,6 +41,7 @@ Mention regex: - **Summary**: - The regex effectively matches: - Mentions that are file or folder paths starting with '/' and can contain spaces within the path (e.g., 'my folder/my file.txt'). + The regex now properly handles paths with multiple spaces and ensures the entire path is captured. - URLs that start with a protocol (like 'http://') followed by any non-whitespace characters (including query parameters). - The exact word 'problems'. - The exact word 'git-changes'. @@ -51,7 +53,7 @@ Mention regex: */ export const mentionRegex = - /@((?:\/|\w+:\/\/)(?:[^\s]|\s(?=[^\s]))+?|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/ + /@((?:\/|\w+:\/\/)[^\r\n]*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/ export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g") export interface MentionSuggestion { diff --git a/webview-ui/src/utils/context-mentions.ts b/webview-ui/src/utils/context-mentions.ts index 12478864f64..ebf38dc6fe2 100644 --- a/webview-ui/src/utils/context-mentions.ts +++ b/webview-ui/src/utils/context-mentions.ts @@ -280,8 +280,9 @@ export function shouldShowContextMenu(text: string, position: number): boolean { const textAfterAt = beforeCursor.slice(atIndex + 1) - // Check if there's any whitespace after the '@' - if (/\s/.test(textAfterAt)) return false + // Check if there's any whitespace immediately after the '@' + // This only checks the first character after @ to allow for paths with spaces + if (textAfterAt.startsWith(" ")) return false // Don't show the menu if it's clearly a URL if (textAfterAt.toLowerCase().startsWith("http")) { From 4c8fec68364912a175f499a83bbe76129d38777f Mon Sep 17 00:00:00 2001 From: Kunwar Vivek Singh Date: Sun, 20 Apr 2025 09:45:36 +0530 Subject: [PATCH 3/4] bug: space in folder and file name --- src/core/mentions/__tests__/index.test.ts | 51 ++++++++++++++++--- src/core/mentions/index.ts | 23 +++++---- src/shared/context-mentions.ts | 13 ++--- .../utils/__tests__/context-mentions.test.ts | 17 ++++--- 4 files changed, 74 insertions(+), 30 deletions(-) diff --git a/src/core/mentions/__tests__/index.test.ts b/src/core/mentions/__tests__/index.test.ts index 4949ebd7f44..edae2db4999 100644 --- a/src/core/mentions/__tests__/index.test.ts +++ b/src/core/mentions/__tests__/index.test.ts @@ -145,7 +145,7 @@ Detailed commit message with multiple lines expect(result).toContain(`Error fetching commit info: ${errorMessage}`) }) - it("should parse file paths with spaces", async () => { + it("should parse file paths with escaped spaces", async () => { // Mock the file content fetching const fileContent = "This is the content of the file with spaces in its name" @@ -155,8 +155,8 @@ Detailed commit message with multiple lines jest.spyOn(fs, "readFile").mockResolvedValue(fileContent) jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => true, isDirectory: () => false } as any) - // Test with a file path containing spaces - const filePath = "/path/with spaces/my file.txt" + // Test with a file path containing escaped spaces + const filePath = "/path/with\\ spaces/my\\ file.txt" // First, verify that the regex pattern correctly matches the entire path // Import the regex pattern directly to test it @@ -168,14 +168,14 @@ Detailed commit message with multiple lines // Now test the full parseMentions function const result = await parseMentions(`Check out this file @${filePath}`, mockCwd, mockUrlContentFetcher) - // Verify the file path with spaces was correctly parsed + // Verify the file path with escaped spaces was correctly parsed + // The spaces should be unescaped when displayed expect(result).toContain(`'path/with spaces/my file.txt' (see below for file content)`) expect(result).toContain(``) }) - it("should parse folder paths with spaces", async () => { + it("should parse folder paths with escaped spaces", async () => { // Mock the folder content fetching - const folderContent = "├── file1.txt\n├── file2.txt\n└── subfolder/" // Mock the getFileOrFolderContent function (which is called internally by parseMentions) // 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 ]) jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => false, isDirectory: () => true } as any) - const folderPath = "/folder with spaces/" + // Test with a folder path containing escaped spaces + const folderPath = "/folder\\ with\\ spaces/" + + // First, verify that the regex pattern correctly matches the entire path + const { mentionRegexGlobal } = require("../../../shared/context-mentions") + const mentionMatch = `@${folderPath}`.match(mentionRegexGlobal) + expect(mentionMatch).not.toBeNull() + expect(mentionMatch![0]).toBe(`@${folderPath}`) + const result = await parseMentions(`Check out this folder @${folderPath}`, mockCwd, mockUrlContentFetcher) - // Verify the folder path with spaces was correctly parsed + // Verify the folder path with escaped spaces was correctly parsed + // The spaces should be unescaped when displayed expect(result).toContain(`'folder with spaces/' (see below for folder content)`) expect(result).toContain(``) }) + + it("should parse nested paths with multiple escaped spaces", async () => { + // Mock the file content fetching + const fileContent = "This is the content of the file with multiple spaces in its path" + + // Mock the getFileOrFolderContent function + const fs = require("fs/promises") + jest.spyOn(fs, "readFile").mockResolvedValue(fileContent) + jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => true, isDirectory: () => false } as any) + + // Test with a deeply nested path containing multiple escaped spaces + const filePath = "/root\\ dir/my\\ documents/project\\ files/important\\ notes/final\\ draft\\ v2.txt" + + // Verify the regex pattern correctly matches the entire path + const { mentionRegexGlobal } = require("../../../shared/context-mentions") + const mentionMatch = `@${filePath}`.match(mentionRegexGlobal) + expect(mentionMatch).not.toBeNull() + expect(mentionMatch![0]).toBe(`@${filePath}`) + + // Test the full parseMentions function + const result = await parseMentions(`Check out this file @${filePath}`, mockCwd, mockUrlContentFetcher) + + // Verify the complex path was correctly parsed with all spaces unescaped + expect(result).toContain(`'root dir/my documents/project files/important notes/final draft v2.txt' (see below for file content)`) + expect(result).toContain(``) + }) }) describe("openMention", () => { diff --git a/src/core/mentions/index.ts b/src/core/mentions/index.ts index 592ff8fe87e..bf206ffb716 100644 --- a/src/core/mentions/index.ts +++ b/src/core/mentions/index.ts @@ -47,21 +47,24 @@ export async function parseMentions( ): Promise { const mentions: Set = new Set() let parsedText = text.replace(mentionRegexGlobal, (match, mention) => { - mentions.add(mention) - if (mention.startsWith("http")) { - return `'${mention}' (see below for site content)` - } else if (mention.startsWith("/")) { - const mentionPath = mention.slice(1) + // Unescape spaces in the mention (convert "\\s" to " ") + const unescapedMention = mention.replace(/\\\\\s/g, " ") + mentions.add(unescapedMention) + + if (unescapedMention.startsWith("http")) { + return `'${unescapedMention}' (see below for site content)` + } else if (unescapedMention.startsWith("/")) { + const mentionPath = unescapedMention.slice(1) return mentionPath.endsWith("/") ? `'${mentionPath}' (see below for folder content)` : `'${mentionPath}' (see below for file content)` - } else if (mention === "problems") { + } else if (unescapedMention === "problems") { return `Workspace Problems (see below for diagnostics)` - } else if (mention === "git-changes") { + } else if (unescapedMention === "git-changes") { return `Working directory changes (see below for details)` - } else if (/^[a-f0-9]{7,40}$/.test(mention)) { - return `Git commit '${mention}' (see below for commit info)` - } else if (mention === "terminal") { + } else if (/^[a-f0-9]{7,40}$/.test(unescapedMention)) { + return `Git commit '${unescapedMention}' (see below for commit info)` + } else if (unescapedMention === "terminal") { return `Terminal Output (see below for output)` } return match diff --git a/src/shared/context-mentions.ts b/src/shared/context-mentions.ts index 7d0cde22156..f4a96714cc9 100644 --- a/src/shared/context-mentions.ts +++ b/src/shared/context-mentions.ts @@ -18,10 +18,11 @@ Mention regex: - `|`: Logical OR. - `\w+:\/\/`: - **Protocol (`\w+://`)**: Matches URLs that start with a word character sequence followed by '://', such as 'http://', 'https://', 'ftp://', etc. - - `[^\r\n]*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))`: - - **Character Pattern**: Matches any characters except line breaks. + - `[^\s\r\n]*?(?:\\\\\s[^\s\r\n]*?)*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))`: + - **Character Pattern**: Matches any characters except whitespace and line breaks. + - **Escaped Spaces**: The `(?:\\\\\s[^\s\r\n]*?)*?` part allows for escaped spaces (like `\\s`) in the path. - **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. - - **This allows spaces within file paths while properly handling path boundaries**. + - **This handles paths with escaped spaces (e.g., `my\\ folder/my\\ file.txt`)**. - **Non-Greedy (`*?`)**: Ensures the smallest possible match. - `|`: Logical OR. - `problems\b`: @@ -40,8 +41,8 @@ Mention regex: - **Summary**: - The regex effectively matches: - - Mentions that are file or folder paths starting with '/' and can contain spaces within the path (e.g., 'my folder/my file.txt'). - The regex now properly handles paths with multiple spaces and ensures the entire path is captured. + - Mentions that are file or folder paths starting with '/' and can contain escaped spaces within the path (e.g., 'my\\ folder/my\\ file.txt'). + The regex properly handles paths with escaped spaces and ensures the entire path is captured. - URLs that start with a protocol (like 'http://') followed by any non-whitespace characters (including query parameters). - The exact word 'problems'. - The exact word 'git-changes'. @@ -53,7 +54,7 @@ Mention regex: */ export const mentionRegex = - /@((?:\/|\w+:\/\/)[^\r\n]*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/ + /@((?:\/|\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]|$))/ export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g") export interface MentionSuggestion { diff --git a/webview-ui/src/utils/__tests__/context-mentions.test.ts b/webview-ui/src/utils/__tests__/context-mentions.test.ts index b89eaf9b242..9375427ab58 100644 --- a/webview-ui/src/utils/__tests__/context-mentions.test.ts +++ b/webview-ui/src/utils/__tests__/context-mentions.test.ts @@ -373,13 +373,18 @@ describe("shouldShowContextMenu", () => { expect(shouldShowContextMenu("@problems", 9)).toBe(true) }) - it("should return true for file paths with spaces", () => { - // Test with a file path containing spaces - expect(shouldShowContextMenu("@/path/to/my file.txt", 20)).toBe(true) + it("should return true for file paths with escaped spaces", () => { + // Test with a file path containing escaped spaces + expect(shouldShowContextMenu("@/path/to/my\\ file.txt", 20)).toBe(true) }) - it("should return true for folder paths with spaces", () => { - // Test with a folder path containing spaces - expect(shouldShowContextMenu("@/path/to/my folder/", 20)).toBe(true) + it("should return true for folder paths with escaped spaces", () => { + // Test with a folder path containing escaped spaces + expect(shouldShowContextMenu("@/path/to/my\\ folder/", 20)).toBe(true) + }) + + it("should return true for nested paths with multiple escaped spaces", () => { + // Test with a deeply nested path containing multiple escaped spaces + expect(shouldShowContextMenu("@/root\\ dir/my\\ documents/project\\ files/file.txt", 50)).toBe(true) }) }) From 589c1cb980c466a9c074d9b09d74e0341d3e6627 Mon Sep 17 00:00:00 2001 From: Kunwar Vivek Singh Date: Sun, 20 Apr 2025 10:01:49 +0530 Subject: [PATCH 4/4] bug: space in folder and file name #2361 --- src/core/mentions/__tests__/index.test.ts | 6 +++--- src/core/mentions/index.ts | 4 ++-- src/shared/context-mentions.ts | 10 +++++----- .../src/utils/__tests__/context-mentions.test.ts | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/core/mentions/__tests__/index.test.ts b/src/core/mentions/__tests__/index.test.ts index edae2db4999..5df4b87b775 100644 --- a/src/core/mentions/__tests__/index.test.ts +++ b/src/core/mentions/__tests__/index.test.ts @@ -156,7 +156,7 @@ Detailed commit message with multiple lines jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => true, isDirectory: () => false } as any) // Test with a file path containing escaped spaces - const filePath = "/path/with\\ spaces/my\\ file.txt" + const filePath = "/path/with\ spaces/my\ file.txt" // First, verify that the regex pattern correctly matches the entire path // Import the regex pattern directly to test it @@ -188,7 +188,7 @@ Detailed commit message with multiple lines jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => false, isDirectory: () => true } as any) // Test with a folder path containing escaped spaces - const folderPath = "/folder\\ with\\ spaces/" + const folderPath = "/folder\ with\ spaces/" // First, verify that the regex pattern correctly matches the entire path const { mentionRegexGlobal } = require("../../../shared/context-mentions") @@ -214,7 +214,7 @@ Detailed commit message with multiple lines jest.spyOn(fs, "stat").mockResolvedValue({ isFile: () => true, isDirectory: () => false } as any) // Test with a deeply nested path containing multiple escaped spaces - const filePath = "/root\\ dir/my\\ documents/project\\ files/important\\ notes/final\\ draft\\ v2.txt" + const filePath = "/root\ dir/my\ documents/project\ files/important\ notes/final\ draft\ v2.txt" // Verify the regex pattern correctly matches the entire path const { mentionRegexGlobal } = require("../../../shared/context-mentions") diff --git a/src/core/mentions/index.ts b/src/core/mentions/index.ts index bf206ffb716..e3feee7c46c 100644 --- a/src/core/mentions/index.ts +++ b/src/core/mentions/index.ts @@ -47,8 +47,8 @@ export async function parseMentions( ): Promise { const mentions: Set = new Set() let parsedText = text.replace(mentionRegexGlobal, (match, mention) => { - // Unescape spaces in the mention (convert "\\s" to " ") - const unescapedMention = mention.replace(/\\\\\s/g, " ") + // Unescape spaces in the mention (convert "\s" to " ") + const unescapedMention = mention.replace(/\\\s/g, " ") mentions.add(unescapedMention) if (unescapedMention.startsWith("http")) { diff --git a/src/shared/context-mentions.ts b/src/shared/context-mentions.ts index f4a96714cc9..52a30548ed0 100644 --- a/src/shared/context-mentions.ts +++ b/src/shared/context-mentions.ts @@ -18,11 +18,11 @@ Mention regex: - `|`: Logical OR. - `\w+:\/\/`: - **Protocol (`\w+://`)**: Matches URLs that start with a word character sequence followed by '://', such as 'http://', 'https://', 'ftp://', etc. - - `[^\s\r\n]*?(?:\\\\\s[^\s\r\n]*?)*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))`: + - `[^\s\r\n]*?(?:\\[\s][^\s\r\n]*?)*?(?=\s*$|\s+@|[.,;:!?](?=[\s\r\n]|$))`: - **Character Pattern**: Matches any characters except whitespace and line breaks. - - **Escaped Spaces**: The `(?:\\\\\s[^\s\r\n]*?)*?` part allows for escaped spaces (like `\\s`) in the path. + - **Escaped Spaces**: The `(?:\\[\s][^\s\r\n]*?)*?` part allows for escaped spaces (like `\s`) in the path. - **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. - - **This handles paths with escaped spaces (e.g., `my\\ folder/my\\ file.txt`)**. + - **This handles paths with escaped spaces (e.g., `my\ folder/my\ file.txt`)**. - **Non-Greedy (`*?`)**: Ensures the smallest possible match. - `|`: Logical OR. - `problems\b`: @@ -41,7 +41,7 @@ Mention regex: - **Summary**: - The regex effectively matches: - - Mentions that are file or folder paths starting with '/' and can contain escaped spaces within the path (e.g., 'my\\ folder/my\\ file.txt'). + - Mentions that are file or folder paths starting with '/' and can contain escaped spaces within the path (e.g., 'my\ folder/my\ file.txt'). The regex properly handles paths with escaped spaces and ensures the entire path is captured. - URLs that start with a protocol (like 'http://') followed by any non-whitespace characters (including query parameters). - The exact word 'problems'. @@ -54,7 +54,7 @@ Mention regex: */ export const mentionRegex = - /@((?:\/|\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]|$))/ + /@((?:\/|\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]|$))/ export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g") export interface MentionSuggestion { diff --git a/webview-ui/src/utils/__tests__/context-mentions.test.ts b/webview-ui/src/utils/__tests__/context-mentions.test.ts index 9375427ab58..7f3c501b7ca 100644 --- a/webview-ui/src/utils/__tests__/context-mentions.test.ts +++ b/webview-ui/src/utils/__tests__/context-mentions.test.ts @@ -375,16 +375,16 @@ describe("shouldShowContextMenu", () => { it("should return true for file paths with escaped spaces", () => { // Test with a file path containing escaped spaces - expect(shouldShowContextMenu("@/path/to/my\\ file.txt", 20)).toBe(true) + expect(shouldShowContextMenu("@/path/to/my\ file.txt", 20)).toBe(true) }) it("should return true for folder paths with escaped spaces", () => { // Test with a folder path containing escaped spaces - expect(shouldShowContextMenu("@/path/to/my\\ folder/", 20)).toBe(true) + expect(shouldShowContextMenu("@/path/to/my\ folder/", 20)).toBe(true) }) it("should return true for nested paths with multiple escaped spaces", () => { // Test with a deeply nested path containing multiple escaped spaces - expect(shouldShowContextMenu("@/root\\ dir/my\\ documents/project\\ files/file.txt", 50)).toBe(true) + expect(shouldShowContextMenu("@/root\ dir/my\ documents/project\ files/file.txt", 50)).toBe(true) }) })