Improve multi selection mode

This commit is contained in:
Ferran Basora 2021-06-19 10:50:19 +00:00
parent f461267542
commit a403638d84
4 changed files with 174 additions and 50 deletions

View File

@ -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 <kbd>Space</kbd>. Then, choose the matches with highlighted hints or
<kbd>Enter</kbd> (moving with cursors) and then <kbd>Space</kbd> 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 <kbd>Space</kbd> to finalize the selection.
## Tmux compatibility
@ -402,6 +449,8 @@ OPTIONS:
-x, --regexp <regexp>... Use this regexp as extra pattern to match
--select-bg-color <select_background_color> Sets the background color for selection [default: black]
--select-fg-color <select_foreground_color> Sets the foreground color for selection [default: blue]
--multi-bg-color <multi_background_color> Sets the background color for a multi selected item [default: black]
--multi-fg-color <multi_foreground_color> Sets the foreground color for a multi selected item [default: cyan]
-t, --target <target> Stores the hint in the specified path
```

View File

@ -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,

View File

@ -60,6 +60,7 @@ pub struct Swapper<'a> {
dir: String,
command: String,
upcase_command: String,
multi_command: String,
osc52: bool,
active_pane_id: Option<String>,
active_pane_height: Option<i32>,
@ -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::<Vec<&str>>()
.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,
);

View File

@ -19,15 +19,18 @@ pub struct View<'a> {
matches: Vec<state::Match<'a>>,
select_foreground_color: Box<dyn color::Color>,
select_background_color: Box<dyn color::Color>,
multi_foreground_color: Box<dyn color::Color>,
multi_background_color: Box<dyn color::Color>,
foreground_color: Box<dyn color::Color>,
background_color: Box<dyn color::Color>,
hint_background_color: Box<dyn color::Color>,
hint_foreground_color: Box<dyn color::Color>,
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<dyn color::Color>,
select_background_color: Box<dyn color::Color>,
multi_foreground_color: Box<dyn color::Color>,
multi_background_color: Box<dyn color::Color>,
foreground_color: Box<dyn color::Color>,
background_color: Box<dyn color::Color>,
hint_foreground_color: Box<dyn color::Color>,
@ -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");