diff --git a/README.md b/README.md
index e6d583f..ba5b9a5 100644
--- a/README.md
+++ b/README.md
@@ -91,12 +91,15 @@ NOTE: for changes to take effect, you'll need to source again your `.tmux.conf`
* [@thumbs-regexp-N](#thumbs-regexp-N)
* [@thumbs-command](#thumbs-command)
* [@thumbs-upcase-command](#thumbs-upcase-command)
+* [@thumbs-multi-command](#thumbs-multi-command)
* [@thumbs-bg-color](#thumbs-bg-color)
* [@thumbs-fg-color](#thumbs-fg-color)
* [@thumbs-hint-bg-color](#thumbs-hint-bg-color)
* [@thumbs-hint-fg-color](#thumbs-hint-fg-color)
* [@thumbs-select-fg-color](#thumbs-select-fg-color)
* [@thumbs-select-bg-color](#thumbs-select-bg-color)
+* [@thumbs-multi-fg-color](#thumbs-multi-fg-color)
+* [@thumbs-multi-bg-color](#thumbs-multi-bg-color)
* [@thumbs-contrast](#thumbs-contrast)
* [@thumbs-osc52](#thumbs-osc52)
@@ -198,6 +201,18 @@ For example:
set -g @thumbs-upcase-command 'echo -n {} | pbcopy'
```
+### @thumbs-multi-command
+
+`default: 'tmux set-buffer -- {} && tmux paste-buffer && tmux send-keys ' ' && tmux display-message \"Copied multiple items!\"'`
+
+Choose which command execute when you select multiple items. `tmux-thumbs` will replace `{}` with the picked hint for each one.
+
+For example:
+
+```
+set -g @thumbs-multi-command 'echo -n {}'
+```
+
### @thumbs-bg-color
`default: black`
@@ -270,6 +285,30 @@ For example:
set -g @thumbs-select-bg-color red
```
+### @thumbs-multi-fg-color
+
+`default: yellow`
+
+Sets the foreground color for multi selected item
+
+For example:
+
+```
+set -g @thumbs-multi-fg-color green
+```
+
+### @thumbs-select-bg-color
+
+`default: black`
+
+Sets the background color for multi selected item
+
+For example:
+
+```
+set -g @thumbs-select-bg-color red
+```
+
### @thumbs-contrast
`default: 0`
@@ -341,7 +380,15 @@ This is the list of available alphabets:
- **Arrow navigation:** You can use the arrows to move around between all matched items.
- **Auto paste:** If your last typed hint character is uppercase, you are going to pick and paste the desired hint.
-- **Multi selection:** If you run thumb with multi selection mode you will be able to choose multiple hints pressing the desired letter and `Space` to finalize the selection.
+
+### Multi selection
+
+If you want to enable the capability to choose multiple matches, you have to
+press Space. Then, choose the matches with highlighted hints or
+Enter (moving with cursors) and then Space again to
+output all of them.
+
+If you run standalone `thumbs` with multi selection mode (-m) you will be able to choose multiple hints pressing the desired letter and Space to finalize the selection.
## Tmux compatibility
@@ -402,6 +449,8 @@ OPTIONS:
-x, --regexp ... Use this regexp as extra pattern to match
--select-bg-color Sets the background color for selection [default: black]
--select-fg-color Sets the foreground color for selection [default: blue]
+ --multi-bg-color Sets the background color for a multi selected item [default: black]
+ --multi-fg-color Sets the foreground color for a multi selected item [default: cyan]
-t, --target Stores the hint in the specified path
```
diff --git a/src/main.rs b/src/main.rs
index a810286..49ded04 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -66,6 +66,18 @@ fn app_args<'a>() -> clap::ArgMatches<'a> {
.long("hint-bg-color")
.default_value("black"),
)
+ .arg(
+ Arg::with_name("multi_foreground_color")
+ .help("Sets the foreground color for a multi selected item")
+ .long("multi-fg-color")
+ .default_value("yellow"),
+ )
+ .arg(
+ Arg::with_name("multi_background_color")
+ .help("Sets the background color for a multi selected item")
+ .long("multi-bg-color")
+ .default_value("black"),
+ )
.arg(
Arg::with_name("select_foreground_color")
.help("Sets the foreground color for selection")
@@ -149,6 +161,8 @@ fn main() {
let hint_background_color = colors::get_color(args.value_of("hint_background_color").unwrap());
let select_foreground_color = colors::get_color(args.value_of("select_foreground_color").unwrap());
let select_background_color = colors::get_color(args.value_of("select_background_color").unwrap());
+ let multi_foreground_color = colors::get_color(args.value_of("multi_foreground_color").unwrap());
+ let multi_background_color = colors::get_color(args.value_of("multi_background_color").unwrap());
let stdin = io::stdin();
let mut handle = stdin.lock();
@@ -170,6 +184,8 @@ fn main() {
position,
select_foreground_color,
select_background_color,
+ multi_foreground_color,
+ multi_background_color,
foreground_color,
background_color,
hint_foreground_color,
diff --git a/src/swapper.rs b/src/swapper.rs
index aff33e1..cce19e0 100644
--- a/src/swapper.rs
+++ b/src/swapper.rs
@@ -60,6 +60,7 @@ pub struct Swapper<'a> {
dir: String,
command: String,
upcase_command: String,
+ multi_command: String,
osc52: bool,
active_pane_id: Option,
active_pane_height: Option,
@@ -76,6 +77,7 @@ impl<'a> Swapper<'a> {
dir: String,
command: String,
upcase_command: String,
+ multi_command: String,
osc52: bool,
) -> Swapper {
let since_the_epoch = SystemTime::now()
@@ -88,6 +90,7 @@ impl<'a> Swapper<'a> {
dir,
command,
upcase_command,
+ multi_command,
osc52,
active_pane_id: None,
active_pane_height: None,
@@ -306,7 +309,24 @@ impl<'a> Swapper<'a> {
pub fn execute_command(&mut self) {
let content = self.content.clone().unwrap();
- let mut splitter = content.splitn(2, ':');
+ let items: Vec<&str> = content.split('\n').collect();
+
+ if items.len() > 1 {
+ let text = items
+ .iter()
+ .map(|item| item.splitn(2, ':').last().unwrap())
+ .collect::>()
+ .join(" ");
+
+ self.execute_final_command(&text, &self.multi_command.clone());
+
+ return;
+ }
+
+ // Only one item
+ let item: &str = items.first().unwrap();
+
+ let mut splitter = item.splitn(2, ':');
if let Some(upcase) = splitter.next() {
if let Some(text) = splitter.next() {
@@ -367,21 +387,26 @@ impl<'a> Swapper<'a> {
// Ideally user commands would just use "${THUMB}" to begin with rather than having any
// sort of ad-hoc string splicing here at all, and then they could specify the quoting they
// want, but that would break backwards compatibility.
- let final_command = str::replace(execute_command.as_str(), "{}", "${THUMB}");
- let retrieve_command = vec![
- "bash",
- "-c",
- "THUMB=\"$1\"; eval \"$2\"",
- "--",
- text.trim_end(),
- final_command.as_str(),
- ];
- let params = retrieve_command.iter().map(|arg| arg.to_string()).collect();
-
- self.executor.execute(params);
+ self.execute_final_command(text.trim_end(), &execute_command);
}
}
}
+
+ pub fn execute_final_command(&mut self, text: &str, execute_command: &str) {
+ let final_command = str::replace(execute_command, "{}", "${THUMB}");
+ let retrieve_command = vec![
+ "bash",
+ "-c",
+ "THUMB=\"$1\"; eval \"$2\"",
+ "--",
+ text,
+ final_command.as_str(),
+ ];
+
+ let params = retrieve_command.iter().map(|arg| arg.to_string()).collect();
+
+ self.executor.execute(params);
+ }
}
#[cfg(test)]
@@ -422,6 +447,7 @@ mod tests {
"".to_string(),
"".to_string(),
"".to_string(),
+ "".to_string(),
false,
);
@@ -444,6 +470,7 @@ mod tests {
"".to_string(),
"".to_string(),
"".to_string(),
+ "".to_string(),
false,
);
@@ -463,11 +490,13 @@ mod tests {
let user_command = "echo \"{}\"".to_string();
let upcase_command = "open \"{}\"".to_string();
+ let multi_command = "open \"{}\"".to_string();
let mut swapper = Swapper::new(
Box::new(&mut executor),
"".to_string(),
user_command,
upcase_command,
+ multi_command,
false,
);
@@ -509,15 +538,21 @@ fn app_args<'a>() -> clap::ArgMatches<'a> {
)
.arg(
Arg::with_name("command")
- .help("Pick command")
+ .help("Command to execute after choose a hint")
.long("command")
- .default_value("tmux set-buffer -- {} && tmux display-message \"Copied {}\""),
+ .default_value("tmux set-buffer -- \"{}\" && tmux display-message \"Copied {}\""),
)
.arg(
Arg::with_name("upcase_command")
- .help("Upcase command")
+ .help("Command to execute after choose a hint, in upcase")
.long("upcase-command")
- .default_value("tmux set-buffer -- {} && tmux paste-buffer && tmux display-message \"Copied {}\""),
+ .default_value("tmux set-buffer -- \"{}\" && tmux paste-buffer && tmux display-message \"Copied {}\""),
+ )
+ .arg(
+ Arg::with_name("multi_command")
+ .help("Command to execute after choose multiple hints")
+ .long("multi-command")
+ .default_value("tmux set-buffer -- \"{}\" && tmux paste-buffer && tmux display-message \"Multi copied {}\""),
)
.arg(
Arg::with_name("osc52")
@@ -533,6 +568,7 @@ fn main() -> std::io::Result<()> {
let dir = args.value_of("dir").unwrap();
let command = args.value_of("command").unwrap();
let upcase_command = args.value_of("upcase_command").unwrap();
+ let multi_command = args.value_of("multi_command").unwrap();
let osc52 = args.is_present("osc52");
if dir.is_empty() {
@@ -545,6 +581,7 @@ fn main() -> std::io::Result<()> {
dir.to_string(),
command.to_string(),
upcase_command.to_string(),
+ multi_command.to_string(),
osc52,
);
diff --git a/src/view.rs b/src/view.rs
index 2bd483f..eea57fb 100644
--- a/src/view.rs
+++ b/src/view.rs
@@ -19,15 +19,18 @@ pub struct View<'a> {
matches: Vec>,
select_foreground_color: Box,
select_background_color: Box,
+ multi_foreground_color: Box,
+ multi_background_color: Box,
foreground_color: Box,
background_color: Box,
hint_background_color: Box,
hint_foreground_color: Box,
+ chosen: Vec<(String, bool)>,
}
enum CaptureEvent {
Exit,
- Hint(Vec<(String, bool)>),
+ Hint,
}
impl<'a> View<'a> {
@@ -40,6 +43,8 @@ impl<'a> View<'a> {
position: &'a str,
select_foreground_color: Box,
select_background_color: Box,
+ multi_foreground_color: Box,
+ multi_background_color: Box,
foreground_color: Box,
background_color: Box,
hint_foreground_color: Box,
@@ -57,10 +62,13 @@ impl<'a> View<'a> {
matches,
select_foreground_color,
select_background_color,
+ multi_foreground_color,
+ multi_background_color,
foreground_color,
background_color,
hint_foreground_color,
hint_background_color,
+ chosen: vec![],
}
}
@@ -98,12 +106,18 @@ impl<'a> View<'a> {
let selected = self.matches.get(self.skip);
for mat in self.matches.iter() {
- let selected_color = if selected == Some(mat) {
+ let chosen_hint = self.chosen.iter().any(|(hint, _)| hint == mat.text);
+
+ let selected_color = if chosen_hint {
+ &self.multi_foreground_color
+ } else if selected == Some(mat) {
&self.select_foreground_color
} else {
&self.foreground_color
};
- let selected_background_color = if selected == Some(mat) {
+ let selected_background_color = if chosen_hint {
+ &self.multi_background_color
+ } else if selected == Some(mat) {
&self.select_background_color
} else {
&self.background_color
@@ -157,7 +171,6 @@ impl<'a> View<'a> {
return CaptureEvent::Exit;
}
- let mut chosen = vec![];
let mut typed_hint: String = "".to_owned();
let longest_hint = self
.matches
@@ -195,43 +208,49 @@ impl<'a> View<'a> {
self.next();
}
Key::Char(ch) => {
- if ch == '\n' {
- match self.matches.iter().enumerate().find(|&h| h.0 == self.skip) {
+ match ch {
+ '\n' => match self.matches.iter().enumerate().find(|&h| h.0 == self.skip) {
Some(hm) => {
- chosen.push((hm.1.text.to_string(), false));
+ self.chosen.push((hm.1.text.to_string(), false));
if !self.multi {
- return CaptureEvent::Hint(chosen);
+ return CaptureEvent::Hint;
}
}
_ => panic!("Match not found?"),
- }
- }
-
- if ch == ' ' && self.multi {
- return CaptureEvent::Hint(chosen);
- }
-
- let key = ch.to_string();
- let lower_key = key.to_lowercase();
-
- typed_hint.push_str(lower_key.as_str());
-
- let selection = self.matches.iter().find(|mat| mat.hint == Some(typed_hint.clone()));
-
- match selection {
- Some(mat) => {
- chosen.push((mat.text.to_string(), key != lower_key));
-
+ },
+ ' ' => {
if self.multi {
- typed_hint.clear();
+ // Finalize the multi selection
+ return CaptureEvent::Hint;
} else {
- return CaptureEvent::Hint(chosen);
+ // Enable the multi selection
+ self.multi = true;
}
}
- None => {
- if !self.multi && typed_hint.len() >= longest_hint.len() {
- break;
+ key => {
+ let key = key.to_string();
+ let lower_key = key.to_lowercase();
+
+ typed_hint.push_str(lower_key.as_str());
+
+ let selection = self.matches.iter().find(|mat| mat.hint == Some(typed_hint.clone()));
+
+ match selection {
+ Some(mat) => {
+ self.chosen.push((mat.text.to_string(), key != lower_key));
+
+ if self.multi {
+ typed_hint.clear();
+ } else {
+ return CaptureEvent::Hint;
+ }
+ }
+ None => {
+ if !self.multi && typed_hint.len() >= longest_hint.len() {
+ break;
+ }
+ }
}
}
}
@@ -265,7 +284,7 @@ impl<'a> View<'a> {
let hints = match self.listen(&mut stdin, &mut stdout) {
CaptureEvent::Exit => vec![],
- CaptureEvent::Hint(chosen) => chosen,
+ CaptureEvent::Hint => self.chosen.clone(),
};
write!(stdout, "{}", cursor::Show).unwrap();
@@ -296,10 +315,13 @@ mod tests {
matches: vec![],
select_foreground_color: colors::get_color("default"),
select_background_color: colors::get_color("default"),
+ multi_foreground_color: colors::get_color("default"),
+ multi_background_color: colors::get_color("default"),
foreground_color: colors::get_color("default"),
background_color: colors::get_color("default"),
hint_background_color: colors::get_color("default"),
hint_foreground_color: colors::get_color("default"),
+ chosen: vec![],
};
let result = view.make_hint_text("a");