Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ rust-version = "1.87.0"
exclude = ["/.github", "/img"]

[dependencies]
ansi-to-tui = "7.0.0"
arboard = "3.6.0"
base64 = "0.22.1"
chrono = "0.4.41"
Expand Down
75 changes: 69 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,17 @@ The default key bindings can be overridden. Please refer to [default-keybind.tom
| <kbd>Ctrl-g</kbd> | Toggle ignore case (if searching) | `ignore_case_toggle` |
| <kbd>Ctrl-x</kbd> | Toggle fuzzy match (if searching) | `fuzzy_toggle` |
| <kbd>c/C</kbd> | Copy commit short/full hash | `short_copy` `full_copy` |
| <kbd>d</kbd> | Toggle custom user command view | `user_command_view_toggle_1` |

#### Commit Detail

| Key | Description | Corresponding keybind |
| ----------------------------------- | --------------------------- | ----------------------------- |
| <kbd>Esc</kbd> <kbd>Backspace</kbd> | Close commit details | `close` `cancel` |
| <kbd>Down/Up</kbd> <kbd>j/k</kbd> | Scroll down/up | `navigate_down` `navigate_up` |
| <kbd>g/G</kbd> | Go to top/bottom | `go_to_top` `go_to_bottom` |
| <kbd>c/C</kbd> | Copy commit short/full hash | `short_copy` `full_copy` |
| Key | Description | Corresponding keybind |
| ----------------------------------- | ------------------------------- | ----------------------------- |
| <kbd>Esc</kbd> <kbd>Backspace</kbd> | Close commit details | `close` `cancel` |
| <kbd>Down/Up</kbd> <kbd>j/k</kbd> | Scroll down/up | `navigate_down` `navigate_up` |
| <kbd>g/G</kbd> | Go to top/bottom | `go_to_top` `go_to_bottom` |
| <kbd>c/C</kbd> | Copy commit short/full hash | `short_copy` `full_copy` |
| <kbd>d</kbd> | Toggle custom user command view | `user_command_view_toggle_1` |

#### Refs List

Expand All @@ -215,6 +217,14 @@ The default key bindings can be overridden. Please refer to [default-keybind.tom
| <kbd>Right/Left</kbd> <kbd>l/h</kbd> | Open/Close node | `navigate_right` `navigate_left` |
| <kbd>c</kbd> | Copy ref name | `short_copy` |

#### User Command

| Key | Description | Corresponding keybind |
| ------------------------------------------------ | ------------------ | ------------------------------ |
| <kbd>Esc</kbd> <kbd>Backspace</kbd> <kbd>?</kbd> | Close user command | `close` `cancel` `help_toggle` |
| <kbd>Down/Up</kbd> <kbd>j/k</kbd> | Scroll down/up | `navigate_down` `navigate_up` |
| <kbd>g/G</kbd> | Go to top/bottom | `go_to_top` `go_to_bottom` |

#### Help

| Key | Description | Corresponding keybind |
Expand Down Expand Up @@ -253,6 +263,13 @@ ignore_case = false
# type: boolean
fuzzy = false

[core.user_command]
# The command definition for generating the content displayed in the user command view.
# Multiple commands can be specified in the format commands_{n}.
# For details about user command, see the separate User command section.
# type: object
commands_1 = { name = "git diff", commands = ["git", "--no-pager", "diff", "--color=always", "{{first_parent_hash}}", "{{target_hash}}"]}

[ui.common]
# The type of a cursor to display in the input.
# If `cursor_type = "Native"` is set, the terminal native cursor is used.
Expand Down Expand Up @@ -324,6 +341,50 @@ background = "#00000000"

</details>

### User command

The User command view allows you to display the output (stdout) of your custom external commands.
This allows you to do things like view commit diffs using your favorite tools.

To define a user command, you need to configure the following two settings:
- Keybinding definition. Specify the key to display each user command.
- Config: `keybind.user_command_view_toggle_{n}`
- Command definition. Specify the actual command you want to execute.
- Config: `core.user_command.commands_{n}`

<details>
<summary>Configuration example</summary>

```toml
[keybind]
user_command_view_toggle_1 = ["d"]
user_command_view_toggle_2 = ["shift-d"]

[core.user_command]
commands_1 = { "name" = "git diff", commands = ["git", "--no-pager", "diff", "--color=always", "{{first_parent_hash}}", "{{target_hash}}"] }
commands_2 = { "name" = "xxx", commands = ["xxx", "{{first_parent_hash}}", "{{target_hash}}", "--width", "{{area_width}}", "--height", "{{area_height}}"] }
```

</details>

#### Variables

The following variables can be used in command definitions.
They will be replaced with their respective values command is executed.

- `{{target_hash}}`
- The hash of the selected commit.
- example: `b0ce4cb9c798576af9b4accc9f26ddce5e72063d`
- `{{first_parent_hash}}`
- The hash of the first parent of the selected commit.
- example: `c103d9744df8ebf100773a11345f011152ec5581`
- `{{area_width}}`
- Width of the user command display area (number of cells).
- example: 80
- `{{area_height}}`
- Height of the user command display area (number of cells).
- example: 30

## Compatibility

### Supported terminals
Expand Down Expand Up @@ -370,6 +431,8 @@ Contributions that do not follow these guidelines may not be accepted.
<img src="./img/refs.png" width=600>
<img src="./img/searching.png" width=600>
<img src="./img/applied.png" width=600>
<img src="./img/diff_git.png" width=600>
<img src="./img/diff_difft.png" width=600>

The following repositories are used as these examples:

Expand Down
2 changes: 2 additions & 0 deletions assets/default-keybind.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ search = ["/"]
ignore_case_toggle = ["ctrl-g"]
fuzzy_toggle = ["ctrl-x"]

user_command_view_toggle_1 = ["d"]

# copy part of information, ex: copy the short commit hash not all
short_copy = ["c"]
full_copy = ["shift-c"]
Binary file added img/diff_delta.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/diff_difft.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/diff_git.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
123 changes: 123 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@ pub struct App<'a> {
status_line: StatusLine,

keybind: &'a KeyBind,
core_config: &'a CoreConfig,
ui_config: &'a UiConfig,
color_theme: &'a ColorTheme,
image_protocol: ImageProtocol,
tx: Sender,

numeric_prefix: String,
view_area: Rect,
}

impl<'a> App<'a> {
Expand Down Expand Up @@ -97,11 +100,13 @@ impl<'a> App<'a> {
status_line: StatusLine::None,
view,
keybind,
core_config,
ui_config,
color_theme,
image_protocol,
tx,
numeric_prefix: String::new(),
view_area: Rect::default(),
}
}
}
Expand Down Expand Up @@ -189,6 +194,15 @@ impl App<'_> {
AppEvent::ClearDetail => {
self.clear_detail();
}
AppEvent::OpenUserCommand(n) => {
self.open_user_command(n);
}
AppEvent::CloseUserCommand => {
self.close_user_command();
}
AppEvent::ClearUserCommand => {
self.clear_user_command();
}
AppEvent::OpenRefs => {
self.open_refs();
}
Expand Down Expand Up @@ -238,6 +252,8 @@ impl App<'_> {
let [view_area, status_line_area] =
Layout::vertical([Constraint::Min(0), Constraint::Length(2)]).areas(f.area());

self.update_state(view_area);

self.view.render(f, view_area);
self.render_status_line(f, status_line_area);
}
Expand Down Expand Up @@ -305,6 +321,10 @@ impl App<'_> {
}

impl App<'_> {
fn update_state(&mut self, view_area: Rect) {
self.view_area = view_area;
}

fn open_detail(&mut self) {
if let View::List(ref mut view) = self.view {
let commit_list_state = view.take_list_state();
Expand Down Expand Up @@ -347,6 +367,108 @@ impl App<'_> {
}
}

fn open_user_command(&mut self, user_command_number: usize) {
if let View::List(ref mut view) = self.view {
let commit_list_state = view.take_list_state();
let selected = commit_list_state.selected_commit_hash().clone();
let (commit, _) = self.repository.commit_detail(&selected);
self.view = View::of_user_command_from_list(
commit_list_state,
commit,
user_command_number,
self.view_area,
self.core_config,
self.ui_config,
self.color_theme,
self.image_protocol,
self.tx.clone(),
);
} else if let View::Detail(ref mut view) = self.view {
let commit_list_state = view.take_list_state();
let selected = commit_list_state.selected_commit_hash().clone();
let (commit, _) = self.repository.commit_detail(&selected);
self.view = View::of_user_command_from_detail(
commit_list_state,
commit,
user_command_number,
self.view_area,
self.core_config,
self.ui_config,
self.color_theme,
self.image_protocol,
self.tx.clone(),
);
} else if let View::UserCommand(ref mut view) = self.view {
let commit_list_state = view.take_list_state();
let selected = commit_list_state.selected_commit_hash().clone();
let (commit, _) = self.repository.commit_detail(&selected);
if view.before_view_is_list() {
self.view = View::of_user_command_from_list(
commit_list_state,
commit,
user_command_number,
self.view_area,
self.core_config,
self.ui_config,
self.color_theme,
self.image_protocol,
self.tx.clone(),
);
} else {
self.view = View::of_user_command_from_detail(
commit_list_state,
commit,
user_command_number,
self.view_area,
self.core_config,
self.ui_config,
self.color_theme,
self.image_protocol,
self.tx.clone(),
);
}
}
}

fn close_user_command(&mut self) {
if let View::UserCommand(ref mut view) = self.view {
let commit_list_state = view.take_list_state();
let selected = commit_list_state.selected_commit_hash().clone();
let (commit, changes) = self.repository.commit_detail(&selected);
let refs = self
.repository
.refs(&selected)
.into_iter()
.cloned()
.collect();
if view.before_view_is_list() {
self.view = View::of_list(
commit_list_state,
self.ui_config,
self.color_theme,
self.tx.clone(),
);
} else {
self.view = View::of_detail(
commit_list_state,
commit,
changes,
refs,
self.ui_config,
self.color_theme,
self.image_protocol,
self.tx.clone(),
);
}
}
}

fn clear_user_command(&mut self) {
if let View::UserCommand(ref mut view) = self.view {
view.clear();
}
}

fn open_refs(&mut self) {
if let View::List(ref mut view) = self.view {
let commit_list_state = view.take_list_state();
Expand Down Expand Up @@ -381,6 +503,7 @@ impl App<'_> {
self.image_protocol,
self.tx.clone(),
self.keybind,
self.core_config,
);
}

Expand Down
Loading