Skip to content

Commit 585f4b4

Browse files
committed
Better fix
1 parent 5146186 commit 585f4b4

File tree

1 file changed

+31
-37
lines changed

1 file changed

+31
-37
lines changed

crates/shell/src/completion.rs

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -188,31 +188,13 @@ fn is_executable(entry: &fs::DirEntry) -> bool {
188188
}
189189

190190
fn resolve_dir_path(dir_path: &str) -> PathBuf {
191-
// On Windows, we need to distinguish between:
192-
// 1. System paths like "C:\Users\..." where backslashes are path separators
193-
// 2. User-typed paths like "some\ dir/" where backslashes are escape characters
194-
//
195-
// Windows absolute paths start with a drive letter (C:\) or UNC path (\\server\share)
196-
let is_windows_absolute = cfg!(windows)
197-
&& (dir_path.len() >= 3 && dir_path.chars().nth(1) == Some(':') || // C:\...
198-
dir_path.starts_with("\\\\")); // UNC path
199-
200-
// Only unescape user-typed relative paths to handle escaped spaces
201-
// Don't unescape Windows absolute paths - their backslashes are path separators
202-
let unescaped = if is_windows_absolute {
203-
dir_path.to_string()
204-
} else {
205-
unescape_for_completion(dir_path)
206-
};
191+
// Unescape the directory path to handle spaces and other special characters
192+
let unescaped = unescape_for_completion(dir_path);
207193

208-
if dir_path.starts_with('/') || is_windows_absolute {
194+
if dir_path.starts_with('/') {
209195
PathBuf::from(unescaped)
210196
} else if let Some(stripped) = dir_path.strip_prefix('~') {
211-
let unescaped_stripped = if is_windows_absolute {
212-
stripped.to_string()
213-
} else {
214-
unescape_for_completion(stripped)
215-
};
197+
let unescaped_stripped = unescape_for_completion(stripped);
216198
dirs::home_dir()
217199
.map(|h| {
218200
h.join(
@@ -336,8 +318,14 @@ mod tests {
336318
use super::*;
337319
use rustyline::history::DefaultHistory;
338320
use std::fs;
321+
use std::path::Path;
339322
use tempfile::TempDir;
340323

324+
// Helper function to convert a path to a shell-escaped string
325+
fn path_to_escaped_string(path: &Path) -> String {
326+
escape_for_shell(&path.display().to_string())
327+
}
328+
341329
#[tokio::test]
342330
async fn test_complete_hidden_files_when_starting_with_dot() {
343331
let temp_dir = TempDir::new().unwrap();
@@ -352,7 +340,8 @@ mod tests {
352340
// Test completion with "." prefix
353341
let completer = ShellCompleter::new(HashSet::new());
354342
let history = DefaultHistory::new();
355-
let line = format!("cat {}/.gi", temp_path.display());
343+
let escaped_path = path_to_escaped_string(temp_path);
344+
let line = format!("cat {}/.gi", escaped_path);
356345
let pos = line.len();
357346
let (_start, matches) = completer
358347
.complete(&line, pos, &Context::new(&history))
@@ -379,7 +368,8 @@ mod tests {
379368
// Test completion without "." prefix
380369
let completer = ShellCompleter::new(HashSet::new());
381370
let history = DefaultHistory::new();
382-
let line = format!("cat {}/", temp_path.display());
371+
let escaped_path = path_to_escaped_string(temp_path);
372+
let line = format!("cat {}/", escaped_path);
383373
let pos = line.len();
384374
let (_start, matches) = completer
385375
.complete(&line, pos, &Context::new(&history))
@@ -404,7 +394,8 @@ mod tests {
404394
// Test completion with ".gith" prefix
405395
let completer = ShellCompleter::new(HashSet::new());
406396
let history = DefaultHistory::new();
407-
let line = format!("cd {}/.gith", temp_path.display());
397+
let escaped_path = path_to_escaped_string(temp_path);
398+
let line = format!("cd {}/.gith", escaped_path);
408399
let pos = line.len();
409400
let (_start, matches) = completer
410401
.complete(&line, pos, &Context::new(&history))
@@ -428,7 +419,8 @@ mod tests {
428419
// Test completion with just "." prefix
429420
let completer = ShellCompleter::new(HashSet::new());
430421
let history = DefaultHistory::new();
431-
let line = format!("ls {}/.", temp_path.display());
422+
let escaped_path = path_to_escaped_string(temp_path);
423+
let line = format!("ls {}/.", escaped_path);
432424
let pos = line.len();
433425
let (_start, matches) = completer
434426
.complete(&line, pos, &Context::new(&history))
@@ -453,41 +445,42 @@ mod tests {
453445

454446
let completer = ShellCompleter::new(HashSet::new());
455447
let history = DefaultHistory::new();
448+
let escaped_path = path_to_escaped_string(temp_path);
456449

457450
// Test 1: completion of "s" should suggest both files
458-
let line = format!("cat {}/s", temp_path.display());
451+
let line = format!("cat {}/s", escaped_path);
459452
let pos = line.len();
460453
let (_start, matches) = completer
461454
.complete(&line, pos, &Context::new(&history))
462455
.unwrap();
463456
assert_eq!(matches.len(), 2);
464457

465458
// Test 2: completion of "some\ fi" (escaped space) should complete to full path
466-
let line = format!("cat {}/some\\ fi", temp_path.display());
459+
let line = format!("cat {}/some\\ fi", escaped_path);
467460
let pos = line.len();
468461
let (_start, matches) = completer
469462
.complete(&line, pos, &Context::new(&history))
470463
.unwrap();
471464
assert_eq!(matches.len(), 1);
472465
assert_eq!(
473466
matches[0].replacement,
474-
format!("{}/some\\ file.txt", temp_path.display())
467+
format!("{}/some\\ file.txt", escaped_path)
475468
);
476469

477470
// Test 3: completion of "some\ fa" (escaped space) should complete to full path
478-
let line = format!("cat {}/some\\ fa", temp_path.display());
471+
let line = format!("cat {}/some\\ fa", escaped_path);
479472
let pos = line.len();
480473
let (_start, matches) = completer
481474
.complete(&line, pos, &Context::new(&history))
482475
.unwrap();
483476
assert_eq!(matches.len(), 1);
484477
assert_eq!(
485478
matches[0].replacement,
486-
format!("{}/some\\ fact.txt", temp_path.display())
479+
format!("{}/some\\ fact.txt", escaped_path)
487480
);
488481

489482
// Test 4: completion of "some\ fx" (escaped space) should return no matches
490-
let line = format!("cat {}/some\\ fx", temp_path.display());
483+
let line = format!("cat {}/some\\ fx", escaped_path);
491484
let pos = line.len();
492485
let (_start, matches) = completer
493486
.complete(&line, pos, &Context::new(&history))
@@ -507,37 +500,38 @@ mod tests {
507500

508501
let completer = ShellCompleter::new(HashSet::new());
509502
let history = DefaultHistory::new();
503+
let escaped_path = path_to_escaped_string(temp_path);
510504

511505
// Test 1: completion of "some\ d" should suggest the directory
512-
let line = format!("cd {}/some\\ d", temp_path.display());
506+
let line = format!("cd {}/some\\ d", escaped_path);
513507
let pos = line.len();
514508
let (_start, matches) = completer
515509
.complete(&line, pos, &Context::new(&history))
516510
.unwrap();
517511
assert_eq!(matches.len(), 1);
518512
assert_eq!(
519513
matches[0].replacement,
520-
format!("{}/some\\ dir/", temp_path.display())
514+
format!("{}/some\\ dir/", escaped_path)
521515
);
522516

523517
// Test 2: completion of "some\ dir/f" should suggest both files
524-
let line = format!("cat {}/some\\ dir/f", temp_path.display());
518+
let line = format!("cat {}/some\\ dir/f", escaped_path);
525519
let pos = line.len();
526520
let (_start, matches) = completer
527521
.complete(&line, pos, &Context::new(&history))
528522
.unwrap();
529523
assert_eq!(matches.len(), 2);
530524

531525
// Test 3: completion of "some\ dir/file1" should complete to file1.txt
532-
let line = format!("cat {}/some\\ dir/file1", temp_path.display());
526+
let line = format!("cat {}/some\\ dir/file1", escaped_path);
533527
let pos = line.len();
534528
let (_start, matches) = completer
535529
.complete(&line, pos, &Context::new(&history))
536530
.unwrap();
537531
assert_eq!(matches.len(), 1);
538532
assert_eq!(
539533
matches[0].replacement,
540-
format!("{}/some\\ dir/file1.txt", temp_path.display())
534+
format!("{}/some\\ dir/file1.txt", escaped_path)
541535
);
542536
}
543537
}

0 commit comments

Comments
 (0)