mirror of
https://github.com/FliegendeWurst/tmux-thumbs.git
synced 2024-11-09 16:00:35 +00:00
Improve multi selection mode
This commit is contained in:
parent
f461267542
commit
a403638d84
51
README.md
51
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 <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
|
||||
```
|
||||
|
||||
|
16
src/main.rs
16
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,
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
|
86
src/view.rs
86
src/view.rs
@ -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");
|
||||
|
Loading…
Reference in New Issue
Block a user