diff --git a/docs/config.md b/docs/config.md index fd6eae52a..f068a4cf9 100644 --- a/docs/config.md +++ b/docs/config.md @@ -201,7 +201,7 @@ Obviously, you would only set one line, don't copy them all in! The `ui.diff-editor` setting affects the tool used for editing diffs (e.g. `jj split`, `jj amend -i`). The default is `meld`. -`jj` replaces the following arguments: +`jj` makes the following substitutions: - `$left` and `$right` are replaced with the paths to the left and right directories to diff respectively. @@ -270,7 +270,7 @@ merge-tools.vimdiff.program = "vim" merge-tools.vimdiff.merge-tool-edits-conflict-markers = true # See below for an explanation ``` -`jj` replaces the following arguments with the appropriate file names: +`jj` makes the following substitutions: - `$output` (REQUIRED) is replaced with the name of the file that the merge tool should output. `jj` will read this file after the merge tool exits. diff --git a/src/merge_tools.rs b/src/merge_tools.rs index 6bd0e566d..79a4aec79 100644 --- a/src/merge_tools.rs +++ b/src/merge_tools.rs @@ -33,6 +33,7 @@ use jujutsu_lib::settings::UserSettings; use jujutsu_lib::store::Store; use jujutsu_lib::tree::Tree; use jujutsu_lib::working_copy::{CheckoutError, SnapshotError, TreeState}; +use regex::{Captures, Regex}; use thiserror::Error; use crate::config::CommandNameAndArgs; @@ -285,16 +286,19 @@ fn interpolate_variables>( args: &[String], variables: &HashMap<&str, V>, ) -> Vec { + // Not interested in $UPPER_CASE_VARIABLES + let re = Regex::new(r"\$([a-z0-9_]+)\b").unwrap(); args.iter() .map(|arg| { - // TODO: Match all instances of `\$\w+` pattern and replace them - // so that portions of args can be replaced, and so that file paths - // that include the '$' character are processed correctly. - if let Some(subst) = arg.strip_prefix('$').and_then(|p| variables.get(p)) { - subst.as_ref().to_owned() - } else { - arg.clone() - } + re.replace_all(arg, |caps: &Captures| { + let name = &caps[1]; + if let Some(subst) = variables.get(name) { + subst.as_ref().to_owned() + } else { + caps[0].to_owned() + } + }) + .into_owned() }) .collect() } @@ -392,8 +396,6 @@ struct MergeTool { /// Arguments to pass to the program when resolving 3-way conflicts. /// `$left`, `$right`, `$base`, and `$output` are replaced with /// paths to the corresponding files. - /// TODO: Currently, the entire argument has to match one of these 4 - /// strings to be substituted. pub merge_args: Vec, /// If false (default), the `$output` file starts out empty and is accepted /// as a full conflict resolution as-is by `jj` after the merge tool is @@ -771,4 +773,45 @@ mod tests { // Invalid type assert!(get(r#"ui.merge-editor.k = 0"#).is_err()); } + + #[test] + fn test_interpolate_variables() { + let patterns = maplit::hashmap! { + "left" => "LEFT", + "right" => "RIGHT", + "left_right" => "$left $right", + }; + + assert_eq!( + interpolate_variables( + &["$left", "$1", "$right", "$2"].map(ToOwned::to_owned), + &patterns + ), + ["LEFT", "$1", "RIGHT", "$2"], + ); + + // Option-like + assert_eq!( + interpolate_variables(&["-o$left$right".to_owned()], &patterns), + ["-oLEFTRIGHT"], + ); + + // Sexp-like + assert_eq!( + interpolate_variables(&["($unknown $left $right)".to_owned()], &patterns), + ["($unknown LEFT RIGHT)"], + ); + + // Not a word "$left" + assert_eq!( + interpolate_variables(&["$lefty".to_owned()], &patterns), + ["$lefty"], + ); + + // Patterns in pattern: not expanded recursively + assert_eq!( + interpolate_variables(&["$left_right".to_owned()], &patterns), + ["$left $right"], + ); + } }