Repository: microsoft/inshellisense
Pull request #227 (2024-04-12 19:05:58)

fix: support paths containing spaces or quoted paths
Handles cases where escaped spaces or quotes are used in pathname for suggestions in commands like `cd`


Patch:
diff --git a/src/runtime/model.ts b/src/runtime/model.ts
index 896ab4b..c0e9a10 100644
--- a/src/runtime/model.ts
+++ b/src/runtime/model.ts
@@ -8,6 +8,7 @@ export type Suggestion = {
   icon: string;
   priority: number;
   insertValue?: string;
+  pathy?: boolean;
 };
 
 export type SuggestionBlob = {
diff --git a/src/runtime/parser.ts b/src/runtime/parser.ts
index 663aff3..652d9e0 100644
--- a/src/runtime/parser.ts
+++ b/src/runtime/parser.ts
@@ -8,6 +8,7 @@ export type CommandToken = {
   isPersistent?: boolean;
   isPath?: boolean;
   isPathComplete?: boolean;
+  isQuoted?: boolean;
 };
 
 const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/;
@@ -41,9 +42,10 @@ const lex = (command: string): CommandToken[] => {
       readingQuotedString = false;
       const complete = idx + 1 < command.length && spaceRegex.test(command[idx + 1]);
       tokens.push({
-        token: command.slice(readingIdx, idx + 1),
+        token: command.slice(readingIdx + 1, idx),
         complete,
         isOption: false,
+        isQuoted: true,
       });
     } else if ((readingFlag && spaceRegex.test(char)) || char === "=") {
       readingFlag = false;
@@ -52,7 +54,7 @@ const lex = (command: string): CommandToken[] => {
         complete: true,
         isOption: true,
       });
-    } else if (readingCmd && spaceRegex.test(char)) {
+    } else if (readingCmd && spaceRegex.test(char) && command.at(idx - 1) !== "\\") {
       readingCmd = false;
       tokens.push({
         token: command.slice(readingIdx, idx),
@@ -64,11 +66,20 @@ const lex = (command: string): CommandToken[] => {
 
   const reading = readingQuotedString || readingFlag || readingCmd;
   if (reading) {
-    tokens.push({
-      token: command.slice(readingIdx),
-      complete: false,
-      isOption: readingFlag,
-    });
+    if (readingQuotedString) {
+      tokens.push({
+        token: command.slice(readingIdx + 1),
+        complete: false,
+        isOption: false,
+        isQuoted: true,
+      });
+    } else {
+      tokens.push({
+        token: command.slice(readingIdx),
+        complete: false,
+        isOption: readingFlag,
+      });
+    }
   }
 
   return tokens;
diff --git a/src/runtime/suggestion.ts b/src/runtime/suggestion.ts
index 265d2bf..b90e862 100644
--- a/src/runtime/suggestion.ts
+++ b/src/runtime/suggestion.ts
@@ -7,6 +7,7 @@ import { CommandToken } from "./parser.js";
 import { runGenerator } from "./generator.js";
 import { runTemplates } from "./template.js";
 import { Suggestion, SuggestionBlob } from "./model.js";
+import log from "../utils/log.js";
 
 enum SuggestionIcons {
   File = "📄",
@@ -50,6 +51,10 @@ const getLong = (suggestion: Fig.SingleOrArray<string>): string => {
   return suggestion instanceof Array ? suggestion.reduce((p, c) => (p.length > c.length ? p : c)) : suggestion;
 };
 
+const getPathy = (type: Fig.SuggestionType | undefined): boolean => {
+  return type === "file" || type === "folder";
+};
+
 const toSuggestion = (suggestion: Fig.Suggestion, name?: string, type?: Fig.SuggestionType): Suggestion | undefined => {
   if (suggestion.name == null) return;
   return {
@@ -59,6 +64,7 @@ const toSuggestion = (suggestion: Fig.Suggestion, name?: string, type?: Fig.Sugg
     allNames: suggestion.name instanceof Array ? suggestion.name : [suggestion.name],
     priority: suggestion.priority ?? 50,
     insertValue: suggestion.insertValue,
+    pathy: getPathy(suggestion.type),
   };
 };
 
@@ -85,6 +91,7 @@ function filter<T extends Fig.BaseSuggestion & { name?: Fig.SingleOrArray<string
                   allNames: s.name,
                   priority: s.priority ?? 50,
                   insertValue: s.insertValue,
+                  pathy: getPathy(s.type),
                 }
               : undefined;
           }
@@ -96,6 +103,7 @@ function filter<T extends Fig.BaseSuggestion & { name?: Fig.SingleOrArray<string
                 allNames: [s.name],
                 priority: s.priority ?? 50,
                 insertValue: s.insertValue,
+                pathy: getPathy(s.type),
               }
             : undefined;
         })
@@ -114,6 +122,7 @@ function filter<T extends Fig.BaseSuggestion & { name?: Fig.SingleOrArray<string
                   allNames: s.name,
                   insertValue: s.insertValue,
                   priority: s.priority ?? 50,
+                  pathy: getPathy(s.type),
                 }
               : undefined;
           }
@@ -125,6 +134,7 @@ function filter<T extends Fig.BaseSuggestion & { name?: Fig.SingleOrArray<string
                 allNames: [s.name],
                 insertValue: s.insertValue,
                 priority: s.priority ?? 50,
+                pathy: getPathy(s.type),
               }
             : undefined;
         })
@@ -190,6 +200,17 @@ const optionSuggestions = (
   return filter<Fig.Option>(validOptions ?? [], filterStrategy, partialCmd, "option");
 };
 
+const getEscapedPath = (value?: string): string | undefined => {
+  return value?.replaceAll(" ", "\\ ");
+};
+
+function adjustPathSuggestions(suggestions: Suggestion[], partialToken?: CommandToken): Suggestion[] {
+  if (partialToken == null || partialToken.isQuoted) return suggestions;
+  return suggestions.map((s) =>
+    s.pathy ? { ...s, insertValue: getEscapedPath(s.insertValue), name: s.insertValue == null ? getEscapedPath(s.name)! : s.name } : s,
+  );
+}
+
 const removeAcceptedSuggestions = (suggestions: Suggestion[], acceptedTokens: CommandToken[]): Suggestion[] => {
   const seen = new Set<string>(acceptedTokens.map((t) => t.token));
   return suggestions.filter((s) => s.allNames.every((n) => !seen.has(n)));
@@ -219,6 +240,7 @@ export const getSubcommandDrivenRecommendation = async (
   acceptedTokens: CommandToken[],
   cwd: string,
 ): Promise<SuggestionBlob | undefined> => {
+  log.debug({ msg: "suggestion point", subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd });
   if (argsDepleted && argsFromSubcommand) {
     return;
   }
@@ -246,7 +268,10 @@ export const getSubcommandDrivenRecommendation = async (
     suggestions: removeDuplicateSuggestion(
       removeEmptySuggestion(
         removeAcceptedSuggestions(
-          suggestions.sort((a, b) => b.priority - a.priority),
+          adjustPathSuggestions(
+            suggestions.sort((a, b) => b.priority - a.priority),
+            partialToken,
+          ),
           acceptedTokens,
         ),
       ),
@@ -285,7 +310,10 @@ export const getArgDrivenRecommendation = async (
     suggestions: removeDuplicateSuggestion(
       removeEmptySuggestion(
         removeAcceptedSuggestions(
-          suggestions.sort((a, b) => b.priority - a.priority),
+          adjustPathSuggestions(
+            suggestions.sort((a, b) => b.priority - a.priority),
+            partialToken,
+          ),
           acceptedTokens,
         ),
       ),
diff --git a/src/runtime/utils.ts b/src/runtime/utils.ts
index 599fe5a..eee7642 100644
--- a/src/runtime/utils.ts
+++ b/src/runtime/utils.ts
@@ -42,7 +42,8 @@ export const resolveCwd = async (
   shell: Shell,
 ): Promise<{ cwd: string; pathy: boolean; complete: boolean }> => {
   if (cmdToken == null) return { cwd, pathy: false, complete: false };
-  const { token } = cmdToken;
+  const { token: rawToken, isQuoted } = cmdToken;
+  const token = !isQuoted ? rawToken.replaceAll("\\ ", " ") : rawToken;
   const sep = getPathSeperator(shell);
   if (!token.includes(sep)) return { cwd, pathy: false, complete: false };
   const resolvedCwd = path.isAbsolute(token) ? token : path.join(cwd, token);


Test patch:
diff --git a/src/tests/runtime/__snapshots__/alias.test.ts.snap b/src/tests/runtime/__snapshots__/alias.test.ts.snap
index 46aa0f5..5234b1a 100644
--- a/src/tests/runtime/__snapshots__/alias.test.ts.snap
+++ b/src/tests/runtime/__snapshots__/alias.test.ts.snap
@@ -20,12 +20,14 @@ exports[`aliasExpand expand on bash aliases 2`] = `
   {
     "complete": true,
     "isOption": false,
-    "token": "'lo'",
+    "isQuoted": true,
+    "token": "lo",
   },
   {
     "complete": true,
     "isOption": false,
-    "token": "'la'",
+    "isQuoted": true,
+    "token": "la",
   },
 ]
 `;
@@ -80,12 +82,14 @@ exports[`aliasExpand expand on zsh aliases 2`] = `
   {
     "complete": true,
     "isOption": false,
-    "token": "'lo'",
+    "isQuoted": true,
+    "token": "lo",
   },
   {
     "complete": true,
     "isOption": false,
-    "token": "'la'",
+    "isQuoted": true,
+    "token": "la",
   },
 ]
 `;
diff --git a/src/tests/runtime/__snapshots__/parser.test.ts.snap b/src/tests/runtime/__snapshots__/parser.test.ts.snap
index e205ba5..fe6499f 100644
--- a/src/tests/runtime/__snapshots__/parser.test.ts.snap
+++ b/src/tests/runtime/__snapshots__/parser.test.ts.snap
@@ -20,7 +20,8 @@ exports[`parseCommand cmd "value'  1`] = `
   {
     "complete": false,
     "isOption": false,
-    "token": ""value' ",
+    "isQuoted": true,
+    "token": "value' ",
   },
 ]
 `;
@@ -35,7 +36,8 @@ exports[`parseCommand cmd "value'\\"\\""  1`] = `
   {
     "complete": true,
     "isOption": false,
-    "token": ""value'\\"\\""",
+    "isQuoted": true,
+    "token": "value'\\"\\"",
   },
 ]
 `;
@@ -50,7 +52,8 @@ exports[`parseCommand cmd 'value'  1`] = `
   {
     "complete": true,
     "isOption": false,
-    "token": "'value'",
+    "isQuoted": true,
+    "token": "value",
   },
 ]
 `;
@@ -90,7 +93,8 @@ exports[`parseCommand cmd --flag="value"  1`] = `
   {
     "complete": true,
     "isOption": false,
-    "token": ""value"",
+    "isQuoted": true,
+    "token": "value",
   },
 ]
 `;
@@ -110,7 +114,8 @@ exports[`parseCommand cmd --flag='value'  1`] = `
   {
     "complete": true,
     "isOption": false,
-    "token": "'value'",
+    "isQuoted": true,
+    "token": "value",
   },
 ]
 `;
@@ -165,7 +170,8 @@ exports[`parseCommand cmd -f 'value'  1`] = `
   {
     "complete": true,
     "isOption": false,
-    "token": "'value'",
+    "isQuoted": true,
+    "token": "value",
   },
 ]
 `;
@@ -250,7 +256,8 @@ exports[`parseCommand cmd -f="value"  1`] = `
   {
     "complete": true,
     "isOption": false,
-    "token": ""value"",
+    "isQuoted": true,
+    "token": "value",
   },
 ]
 `;
@@ -270,7 +277,8 @@ exports[`parseCommand cmd -f='val 1`] = `
   {
     "complete": false,
     "isOption": false,
-    "token": "'val",
+    "isQuoted": true,
+    "token": "val",
   },
 ]
 `;
@@ -305,6 +313,21 @@ exports[`parseCommand cmd 1`] = `
 ]
 `;
 
+exports[`parseCommand cmd dir\\ 1/dir\\ 2/item1 1`] = `
+[
+  {
+    "complete": true,
+    "isOption": false,
+    "token": "cmd",
+  },
+  {
+    "complete": false,
+    "isOption": false,
+    "token": "dir\\ 1/dir\\ 2/item1",
+  },
+]
+`;
+
 exports[`parseCommand cmd value  1`] = `
 [
   {
diff --git a/src/tests/runtime/__snapshots__/runtime.test.ts.snap b/src/tests/runtime/__snapshots__/runtime.test.ts.snap
index a5ef57f..ac22bab 100644
--- a/src/tests/runtime/__snapshots__/runtime.test.ts.snap
+++ b/src/tests/runtime/__snapshots__/runtime.test.ts.snap
@@ -12,6 +12,7 @@ exports[`parseCommand alreadyUsedOption 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--bug-report",
+      "pathy": false,
       "priority": 50,
     },
   ],
@@ -32,6 +33,7 @@ exports[`parseCommand command 1`] = `
       "icon": "📦",
       "insertValue": undefined,
       "name": "stage",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -42,6 +44,7 @@ exports[`parseCommand command 1`] = `
       "icon": "📦",
       "insertValue": undefined,
       "name": "status",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -52,6 +55,7 @@ exports[`parseCommand command 1`] = `
       "icon": "📦",
       "insertValue": undefined,
       "name": "stash",
+      "pathy": false,
       "priority": 50,
     },
   ],
@@ -70,6 +74,7 @@ exports[`parseCommand completePrefixFilter 1`] = `
       "icon": "📦",
       "insertValue": undefined,
       "name": "status",
+      "pathy": false,
       "priority": 50,
     },
   ],
@@ -88,6 +93,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--artifact-server-path",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -98,6 +104,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--artifact-server-port",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -108,6 +115,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--container-architecture",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -118,6 +126,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--container-daemon-socket",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -129,6 +138,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--directory",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -140,6 +150,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--dryrun",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -150,6 +161,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--env-file",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -160,6 +172,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--github-instance",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -170,6 +183,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--insecure-secrets",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -180,6 +194,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--json",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -190,6 +205,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--no-recurse",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -200,6 +216,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--no-skip-checkout",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -211,6 +228,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--quiet",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -221,6 +239,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--secret-file",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -232,6 +251,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--verbose",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -243,6 +263,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--workflows",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -254,6 +275,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--help",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -264,6 +286,7 @@ exports[`parseCommand completedOptionWithArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--no-descriptions",
+      "pathy": false,
       "priority": 50,
     },
   ],
@@ -289,6 +312,7 @@ exports[`parseCommand exclusiveOnOption 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--nobreak",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -299,6 +323,7 @@ exports[`parseCommand exclusiveOnOption 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--nocolor",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -309,6 +334,7 @@ exports[`parseCommand exclusiveOnOption 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--nofilename",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -319,6 +345,7 @@ exports[`parseCommand exclusiveOnOption 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--nofollow",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -329,6 +356,7 @@ exports[`parseCommand exclusiveOnOption 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--nogroup",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -339,6 +367,7 @@ exports[`parseCommand exclusiveOnOption 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--noheading",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -349,6 +378,7 @@ exports[`parseCommand exclusiveOnOption 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--nommap",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -359,6 +389,7 @@ exports[`parseCommand exclusiveOnOption 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--nomultiline",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -370,6 +401,7 @@ exports[`parseCommand exclusiveOnOption 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--norecurse",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -380,6 +412,7 @@ exports[`parseCommand exclusiveOnOption 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--nonumbers",
+      "pathy": false,
       "priority": 50,
     },
   ],
@@ -398,6 +431,7 @@ exports[`parseCommand fullyTypedSuggestion 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "-W",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -408,6 +442,7 @@ exports[`parseCommand fullyTypedSuggestion 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "-w",
+      "pathy": false,
       "priority": 50,
     },
   ],
@@ -426,6 +461,7 @@ exports[`parseCommand generatorUsingPartialInput 1`] = `
       "icon": "📀",
       "insertValue": "Microsoft.Azure.WebJobs.Core",
       "name": "Microsoft.Azure.WebJobs.Core",
+      "pathy": false,
       "priority": 60,
     },
   ],
@@ -444,6 +480,7 @@ exports[`parseCommand loadSpec 1`] = `
       "icon": "📦",
       "insertValue": undefined,
       "name": "add-tags-to-certificate",
+      "pathy": false,
       "priority": 50,
     },
   ],
@@ -462,6 +499,7 @@ exports[`parseCommand noArgsArgumentGiven 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--analyzer-output",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -472,6 +510,7 @@ exports[`parseCommand noArgsArgumentGiven 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "--analyze",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -482,6 +521,7 @@ exports[`parseCommand noArgsArgumentGiven 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "-arcmt-migrate-emit-errors",
+      "pathy": false,
       "priority": 50,
     },
   ],
@@ -508,6 +548,7 @@ exports[`parseCommand optionsSuggestedAfterVariadicArg 1`] = `
       "icon": "📄",
       "insertValue": undefined,
       "name": "package-lock.json",
+      "pathy": true,
       "priority": 55,
     },
     {
@@ -518,6 +559,7 @@ exports[`parseCommand optionsSuggestedAfterVariadicArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "-L",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -528,6 +570,7 @@ exports[`parseCommand optionsSuggestedAfterVariadicArg 1`] = `
       "icon": "🔗",
       "insertValue": undefined,
       "name": "-l",
+      "pathy": false,
       "priority": 50,
     },
   ],
@@ -546,6 +589,7 @@ exports[`parseCommand partialPrefixFilter 1`] = `
       "icon": "📦",
       "insertValue": undefined,
       "name": "stage",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -556,6 +600,7 @@ exports[`parseCommand partialPrefixFilter 1`] = `
       "icon": "📦",
       "insertValue": undefined,
       "name": "status",
+      "pathy": false,
       "priority": 50,
     },
     {
@@ -566,6 +611,7 @@ exports[`parseCommand partialPrefixFilter 1`] = `
       "icon": "📦",
       "insertValue": undefined,
       "name": "stash",
+      "pathy": false,
       "priority": 50,
     },
   ],
@@ -584,6 +630,7 @@ exports[`parseCommand provideFileSuggestion 1`] = `
       "icon": "📄",
       "insertValue": undefined,
       "name": "README.md",
+      "pathy": true,
       "priority": 55,
     },
   ],
@@ -602,6 +649,7 @@ exports[`parseCommand provideFolderSuggestion 1`] = `
       "icon": "📁",
       "insertValue": undefined,
       "name": "src",
+      "pathy": true,
       "priority": 55,
     },
   ],
@@ -629,6 +677,7 @@ exports[`parseCommand providedSuggestion 1`] = `
       "icon": "📀",
       "insertValue": undefined,
       "name": "zsh",
+      "pathy": false,
       "priority": 50,
     },
   ],
diff --git a/src/tests/runtime/parser.test.ts b/src/tests/runtime/parser.test.ts
index 3c42408..ecab703 100644
--- a/src/tests/runtime/parser.test.ts
+++ b/src/tests/runtime/parser.test.ts
@@ -25,6 +25,7 @@ const testData = [
   { command: `cmd "value'\\"\\"" ` },
   { command: `cmd1 | cmd2 ` },
   { command: `cmd1 -` },
+  { command: `cmd dir\\ 1/dir\\ 2/item1` },
 ];
 
 describe(`parseCommand`, () => {