From c0f34d45319f1f3e9d0db21b591a452eacfca21a Mon Sep 17 00:00:00 2001 From: sigoden Date: Fri, 13 Dec 2024 07:49:12 +0800 Subject: [PATCH] fix: `.file` path args do not need double backslash on Windows (#1053) --- src/repl/mod.rs | 132 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 105 insertions(+), 27 deletions(-) diff --git a/src/repl/mod.rs b/src/repl/mod.rs index a8b172ef..cdca146e 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -23,11 +23,6 @@ use reedline::{ use reedline::{MenuBuilder, Signal}; use std::{env, process}; -lazy_static::lazy_static! { - static ref SPLIT_FILES_TEXT_ARGS_RE: Regex = - Regex::new(r"(?m) (-- |--\n|--\r\n|--\r|--$)").unwrap(); -} - const MENU_NAME: &str = "completion_menu"; lazy_static::lazy_static! { @@ -406,8 +401,7 @@ impl Repl { }, ".file" => match args { Some(args) => { - let (files, text) = split_files_text(args); - let files = shell_words::split(files).with_context(|| "Invalid args")?; + let (files, text) = split_files_text(args, cfg!(windows)); let input = Input::from_files_with_spinner( &self.config, text, @@ -708,19 +702,72 @@ fn split_args(args: Option<&str>) -> Option<(&str, Option<&str>)> { }) } -fn split_files_text(args: &str) -> (&str, &str) { - match SPLIT_FILES_TEXT_ARGS_RE.find(args).ok().flatten() { - Some(mat) => { - let files = &args[0..mat.start()]; - let text = if mat.end() < args.len() { - &args[mat.end()..] - } else { - "" - }; - (files, text) +fn split_files_text(line: &str, is_win: bool) -> (Vec, &str) { + let mut words = Vec::new(); + let mut word = String::new(); + let mut unbalance: Option = None; + let mut prev_char: Option = None; + let mut text_starts_at = None; + let unquote_word = |word: &str| { + if ((word.starts_with('"') && word.ends_with('"')) + || (word.starts_with('\'') && word.ends_with('\''))) + && word.len() >= 2 + { + word[1..word.len() - 1].to_string() + } else { + word.to_string() + } + }; + + for (i, char) in line.chars().enumerate() { + match unbalance { + Some(ub_char) if ub_char == char => { + word.push(char); + unbalance = None; + } + Some(_) => { + word.push(char); + } + None => match char { + ' ' | '\t' | '\r' | '\n' => { + if let Some('\\') = prev_char.filter(|_| !is_win) { + word.push(char); + } else if !word.is_empty() { + if word == "--" { + word.clear(); + text_starts_at = Some(i); + break; + } + words.push(unquote_word(&word)); + word.clear(); + } + } + '\'' | '"' => { + word.push(char); + unbalance = Some(char); + } + '\\' => { + if is_win || prev_char.map(|c| c == '\\').unwrap_or_default() { + word.push(char); + } + } + _ => { + word.push(char); + } + }, } - None => (args, ""), + prev_char = Some(char); } + + if !word.is_empty() && word != "--" { + words.push(unquote_word(&word)); + } + let text = match text_starts_at { + Some(start) => line[start..].trim(), + None => "", + }; + + (words, text) } #[cfg(test)] @@ -748,20 +795,51 @@ mod tests { #[test] fn test_split_files_text() { - assert_eq!(split_files_text("file.txt"), ("file.txt", "")); - assert_eq!(split_files_text("file.txt --"), ("file.txt", "")); - assert_eq!(split_files_text("file.txt -- hello"), ("file.txt", "hello")); assert_eq!( - split_files_text("file.txt --\nhello"), - ("file.txt", "hello") + split_files_text("file.txt", false), + (vec!["file.txt".into()], "") + ); + assert_eq!( + split_files_text("file.txt --", false), + (vec!["file.txt".into()], "") + ); + assert_eq!( + split_files_text("file.txt -- hello", false), + (vec!["file.txt".into()], "hello") + ); + assert_eq!( + split_files_text("file.txt --\nhello", false), + (vec!["file.txt".into()], "hello") + ); + assert_eq!( + split_files_text("file.txt --\r\nhello", false), + (vec!["file.txt".into()], "hello") + ); + assert_eq!( + split_files_text("file.txt --\rhello", false), + (vec!["file.txt".into()], "hello") ); assert_eq!( - split_files_text("file.txt --\r\nhello"), - ("file.txt", "hello") + split_files_text(r#"file1.txt 'file2.txt' "file3.txt""#, false), + ( + vec!["file1.txt".into(), "file2.txt".into(), "file3.txt".into()], + "" + ) + ); + assert_eq!( + split_files_text(r#"./file1.txt 'file1 - Copy.txt' file\ 2.txt"#, false), + ( + vec![ + "./file1.txt".into(), + "file1 - Copy.txt".into(), + "file 2.txt".into() + ], + "" + ) ); assert_eq!( - split_files_text("file.txt --\rhello"), - ("file.txt", "hello") + split_files_text(r#".\file.txt C:\dir\file.txt"#, true), + (vec![".\\file.txt".into(), "C:\\dir\\file.txt".into()], "") ); } }