-
Notifications
You must be signed in to change notification settings - Fork 138
feat: add interactive picker #468
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
948ad2d
b84c9d9
714d227
1a64c11
11b04d5
e223740
0cc2f30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| /nix/store/5jr06kaid73ilblgxp0njd5k35nxynnx-pre-commit-config.json |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,19 @@ Configuration is also loaded from: | |
| - Windows: `%USERPROFILE%\AppData\Roaming\gitu\config.toml` | ||
|
|
||
| , refer to the [default configuration](src/default_config.toml). | ||
|
|
||
| #### Picker Style Customization | ||
|
|
||
| You can customize the appearance of the interactive picker by adding the following to your config: | ||
|
|
||
| ```toml | ||
| [style.picker] | ||
| prompt = { fg = "cyan" } # Prompt text color | ||
| info = { mods = "DIM" } # Status line style (e.g., "3/10 matches") | ||
| selection_line = { mods = "BOLD" } # Selected item style | ||
| matched = { fg = "yellow", mods = "BOLD" } # Fuzzy-matched characters highlight | ||
| ``` | ||
|
|
||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this doesn't belong in the README, it's a bit too specific. There's documentation about configuration inside of default_config.toml. |
||
| ### Installing Gitu | ||
| Follow the install instructions: [Installing Gitu](docs/installing.md)\ | ||
| Or install from your package manager: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| use std::{collections::BTreeMap, path::PathBuf}; | ||
|
|
||
| use crate::{Bindings, Res, error::Error, menu::Menu, ops::Op}; | ||
| use crate::{Bindings, Res, error::Error, key_parser, menu::Menu, ops::Op}; | ||
| use crossterm::event::{KeyCode, KeyModifiers}; | ||
| use etcetera::{BaseStrategy, choose_base_strategy}; | ||
| use figment::{ | ||
| Figment, | ||
|
|
@@ -15,6 +16,27 @@ pub struct Config { | |
| pub general: GeneralConfig, | ||
| pub style: StyleConfig, | ||
| pub bindings: Bindings, | ||
| pub picker_bindings: PickerBindings, | ||
| } | ||
|
|
||
| #[derive(Default, Deserialize)] | ||
| pub(crate) struct PickerBindingsConfig { | ||
| #[serde(default)] | ||
| pub next: Vec<String>, | ||
| #[serde(default)] | ||
| pub previous: Vec<String>, | ||
| #[serde(default)] | ||
| pub done: Vec<String>, | ||
| #[serde(default)] | ||
| pub cancel: Vec<String>, | ||
| } | ||
|
|
||
| #[derive(Default, Deserialize)] | ||
| pub(crate) struct BindingsConfig { | ||
| #[serde(flatten)] | ||
| pub menus: BTreeMap<Menu, BTreeMap<Op, Vec<String>>>, | ||
| #[serde(default)] | ||
| pub picker: PickerBindingsConfig, | ||
| } | ||
|
|
||
| #[derive(Default, Deserialize)] | ||
|
|
@@ -23,7 +45,7 @@ pub struct Config { | |
| pub(crate) struct FigmentConfig { | ||
| pub general: GeneralConfig, | ||
| pub style: StyleConfig, | ||
| pub bindings: BTreeMap<Menu, BTreeMap<Op, Vec<String>>>, | ||
| pub bindings: BindingsConfig, | ||
| } | ||
|
|
||
| #[derive(Default, Debug, Deserialize)] | ||
|
|
@@ -78,6 +100,9 @@ pub struct StyleConfig { | |
| #[serde(default)] | ||
| pub syntax_highlight: SyntaxHighlightConfig, | ||
|
|
||
| #[serde(default)] | ||
| pub picker: PickerStyleConfig, | ||
|
|
||
| pub cursor: SymbolStyleConfigEntry, | ||
| pub selection_bar: SymbolStyleConfigEntry, | ||
| pub selection_line: StyleConfigEntry, | ||
|
|
@@ -170,6 +195,18 @@ pub struct SyntaxHighlightConfig { | |
| pub variable_parameter: StyleConfigEntry, | ||
| } | ||
|
|
||
| #[derive(Default, Debug, Deserialize)] | ||
| pub struct PickerStyleConfig { | ||
| #[serde(default)] | ||
| pub prompt: StyleConfigEntry, | ||
| #[serde(default)] | ||
| pub info: StyleConfigEntry, | ||
| #[serde(default)] | ||
| pub selection_line: StyleConfigEntry, | ||
| #[serde(default)] | ||
| pub matched: StyleConfigEntry, | ||
| } | ||
|
|
||
| #[derive(Default, Debug, Deserialize)] | ||
| pub struct StyleConfigEntry { | ||
| #[serde(default)] | ||
|
|
@@ -216,6 +253,55 @@ impl From<&SymbolStyleConfigEntry> for Style { | |
| } | ||
| } | ||
|
|
||
| pub struct PickerBindings { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realize this was indeed not as smooth to add, due to how the existing bindings always relate to a menu. |
||
| pub next: Vec<Vec<(KeyModifiers, KeyCode)>>, | ||
| pub previous: Vec<Vec<(KeyModifiers, KeyCode)>>, | ||
| pub done: Vec<Vec<(KeyModifiers, KeyCode)>>, | ||
| pub cancel: Vec<Vec<(KeyModifiers, KeyCode)>>, | ||
| } | ||
|
|
||
| impl TryFrom<PickerBindingsConfig> for PickerBindings { | ||
| type Error = crate::error::Error; | ||
|
|
||
| fn try_from(config: PickerBindingsConfig) -> Result<Self, Self::Error> { | ||
| let mut bad_bindings = Vec::new(); | ||
|
|
||
| let next = parse_picker_keys(&config.next, "picker.next", &mut bad_bindings); | ||
| let previous = parse_picker_keys(&config.previous, "picker.previous", &mut bad_bindings); | ||
| let done = parse_picker_keys(&config.done, "picker.done", &mut bad_bindings); | ||
| let cancel = parse_picker_keys(&config.cancel, "picker.cancel", &mut bad_bindings); | ||
|
|
||
| if !bad_bindings.is_empty() { | ||
| return Err(Error::Bindings { bad_key_bindings: bad_bindings }); | ||
| } | ||
|
|
||
| Ok(Self { | ||
| next, | ||
| previous, | ||
| done, | ||
| cancel, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| fn parse_picker_keys( | ||
| raw_keys: &[String], | ||
| action_name: &str, | ||
| bad_bindings: &mut Vec<String>, | ||
| ) -> Vec<Vec<(KeyModifiers, KeyCode)>> { | ||
| raw_keys | ||
| .iter() | ||
| .filter_map(|keys| { | ||
| if let Ok(("", parsed)) = key_parser::parse_config_keys(keys) { | ||
| Some(parsed) | ||
| } else { | ||
| bad_bindings.push(format!("- {} = {}", action_name, keys)); | ||
| None | ||
| } | ||
| }) | ||
| .collect() | ||
| } | ||
|
|
||
| pub fn init_config(path: Option<PathBuf>) -> Res<Config> { | ||
| let config_path = path.unwrap_or_else(config_path); | ||
|
|
||
|
|
@@ -228,19 +314,21 @@ pub fn init_config(path: Option<PathBuf>) -> Res<Config> { | |
| let FigmentConfig { | ||
| general, | ||
| style, | ||
| bindings: raw_bindings, | ||
| bindings: bindings_config, | ||
| } = Figment::new() | ||
| .merge(Toml::string(DEFAULT_CONFIG)) | ||
| .merge(Toml::file(config_path)) | ||
| .extract() | ||
| .map_err(Box::new) | ||
| .map_err(Error::Config)?; | ||
| let bindings = Bindings::try_from(raw_bindings)?; | ||
| let bindings = Bindings::try_from(bindings_config.menus)?; | ||
| let picker_bindings = PickerBindings::try_from(bindings_config.picker)?; | ||
|
|
||
| Ok(Config { | ||
| general, | ||
| style, | ||
| bindings, | ||
| picker_bindings, | ||
| }) | ||
| } | ||
|
|
||
|
|
@@ -256,7 +344,7 @@ pub(crate) fn init_test_config() -> Res<Config> { | |
| let FigmentConfig { | ||
| mut general, | ||
| style, | ||
| bindings: raw_bindings, | ||
| bindings: bindings_config, | ||
| } = Figment::new() | ||
| .merge(Toml::string(DEFAULT_CONFIG)) | ||
| .extract() | ||
|
|
@@ -269,7 +357,8 @@ pub(crate) fn init_test_config() -> Res<Config> { | |
| Ok(Config { | ||
| general, | ||
| style, | ||
| bindings: Bindings::try_from(raw_bindings).unwrap(), | ||
| bindings: Bindings::try_from(bindings_config.menus).unwrap(), | ||
| picker_bindings: PickerBindings::try_from(bindings_config.picker).unwrap(), | ||
| }) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -83,6 +83,11 @@ syntax_highlight.type_builtin = { fg = "yellow" } | |||||||||
| syntax_highlight.variable_builtin = {} | ||||||||||
| syntax_highlight.variable_parameter = {} | ||||||||||
|
|
||||||||||
| picker.prompt = { fg = "cyan" } | ||||||||||
| picker.info = { mods = "DIM" } | ||||||||||
| picker.selection_line = { mods = "BOLD" } | ||||||||||
| picker.matched = { fg = "yellow", mods = "BOLD" } | ||||||||||
|
|
||||||||||
| cursor = { symbol = "▌", fg = "blue" } | ||||||||||
| selection_bar = { symbol = "▌", fg = "blue", mods = "DIM" } | ||||||||||
| selection_line = { mods = "BOLD" } | ||||||||||
|
|
@@ -227,3 +232,8 @@ stash_menu.stash_pop = ["p"] | |||||||||
| stash_menu.stash_apply = ["a"] | ||||||||||
| stash_menu.stash_drop = ["k"] | ||||||||||
| stash_menu.quit = ["q", "esc"] | ||||||||||
|
|
||||||||||
| picker.next = ["down", "ctrl+n"] | ||||||||||
| picker.previous = ["up", "ctrl+p"] | ||||||||||
|
Comment on lines
+236
to
+237
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the tab bindings would be like this.
Suggested change
|
||||||||||
| picker.done = ["enter"] | ||||||||||
| picker.cancel = ["esc", "ctrl+c"] | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leftover?