mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 17:08:21 +00:00
Add multiple selection in diff view (#231)
* Add multiple selection in diff view * Copy selection to clipboard in diff view * Show error message if copy to clipboard fails - Draw `msg` after `inspect_commit_popup` to make sure the error message is visible - Move `try_or_popup!` to `utils`
This commit is contained in:
parent
c8ae3b8eae
commit
bc23270951
12 changed files with 327 additions and 80 deletions
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
|
|
@ -27,6 +27,11 @@ jobs:
|
|||
profile: minimal
|
||||
components: clippy
|
||||
|
||||
- name: Install dependencies for clipboard access
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get -qq install libxcb-shape0-dev libxcb-xfixes0-dev
|
||||
|
||||
- name: Build Debug
|
||||
run: |
|
||||
rustc --version
|
||||
|
|
@ -54,6 +59,10 @@ jobs:
|
|||
profile: minimal
|
||||
target: x86_64-unknown-linux-musl
|
||||
|
||||
- name: Install dependencies for clipboard access
|
||||
run: |
|
||||
sudo apt-get -qq install libxcb-shape0-dev libxcb-xfixes0-dev
|
||||
|
||||
- name: Setup MUSL
|
||||
run: |
|
||||
sudo apt-get -qq install musl-tools
|
||||
|
|
|
|||
86
Cargo.lock
generated
86
Cargo.lock
generated
|
|
@ -120,6 +120,12 @@ dependencies = [
|
|||
"constant_time_eq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.2.0"
|
||||
|
|
@ -175,6 +181,28 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"x11-clipboard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
|
|
@ -410,6 +438,7 @@ dependencies = [
|
|||
"bytesize",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clipboard",
|
||||
"crossbeam-channel",
|
||||
"crossterm",
|
||||
"dirs",
|
||||
|
|
@ -576,6 +605,15 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.8"
|
||||
|
|
@ -722,6 +760,35 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||
dependencies = [
|
||||
"malloc_buf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-foundation"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
|
||||
dependencies = [
|
||||
"block",
|
||||
"objc",
|
||||
"objc_id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_id"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
|
||||
dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.20.0"
|
||||
|
|
@ -1283,3 +1350,22 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "x11-clipboard"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea"
|
||||
dependencies = [
|
||||
"xcb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcb"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ serde = "1.0"
|
|||
anyhow = "1.0.32"
|
||||
unicode-width = "0.1"
|
||||
textwrap = "0.12"
|
||||
clipboard = "0.5"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
pprof = { version = "0.3", features = ["flamegraph"], optional = true }
|
||||
|
|
|
|||
|
|
@ -515,8 +515,8 @@ impl App {
|
|||
self.stashmsg_popup.draw(f, size)?;
|
||||
self.reset.draw(f, size)?;
|
||||
self.help.draw(f, size)?;
|
||||
self.msg.draw(f, size)?;
|
||||
self.inspect_commit_popup.draw(f, size)?;
|
||||
self.msg.draw(f, size)?;
|
||||
self.external_editor_popup.draw(f, size)?;
|
||||
self.tag_commit_popup.draw(f, size)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
components::{CommandInfo, Component},
|
||||
keys,
|
||||
queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem},
|
||||
strings,
|
||||
strings, try_or_popup,
|
||||
ui::style::SharedTheme,
|
||||
};
|
||||
use anyhow::Result;
|
||||
|
|
@ -17,22 +17,6 @@ use std::path::Path;
|
|||
use strings::commands;
|
||||
use tui::{backend::Backend, layout::Rect, Frame};
|
||||
|
||||
/// macro to simplify running code that might return Err.
|
||||
/// It will show a popup in that case
|
||||
#[macro_export]
|
||||
macro_rules! try_or_popup {
|
||||
($self:ident, $msg:literal, $e:expr) => {
|
||||
if let Err(err) = $e {
|
||||
$self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(format!(
|
||||
"{}\n{}",
|
||||
$msg, err
|
||||
)),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
///
|
||||
pub struct ChangesComponent {
|
||||
title: String,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
use super::{CommandBlocking, DrawableComponent, ScrollType};
|
||||
use super::{
|
||||
CommandBlocking, Direction, DrawableComponent, ScrollType,
|
||||
};
|
||||
use crate::{
|
||||
components::{CommandInfo, Component},
|
||||
keys,
|
||||
queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem},
|
||||
strings::{self, commands},
|
||||
try_or_popup,
|
||||
ui::{calc_scroll_top, style::SharedTheme},
|
||||
};
|
||||
use asyncgit::{hash, sync, DiffLine, DiffLineType, FileDiff, CWD};
|
||||
use bytesize::ByteSize;
|
||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
||||
use crossterm::event::Event;
|
||||
use std::{borrow::Cow, cell::Cell, cmp, path::Path};
|
||||
use tui::{
|
||||
|
|
@ -18,7 +22,7 @@ use tui::{
|
|||
Frame,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Current {
|
||||
|
|
@ -27,23 +31,91 @@ struct Current {
|
|||
hash: u64,
|
||||
}
|
||||
|
||||
///
|
||||
#[derive(Clone, Copy)]
|
||||
enum Selection {
|
||||
Single(usize),
|
||||
Multiple(usize, usize),
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
fn get_start(&self) -> usize {
|
||||
match self {
|
||||
Self::Single(start) | Self::Multiple(start, _) => *start,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_end(&self) -> usize {
|
||||
match self {
|
||||
Self::Single(end) | Self::Multiple(_, end) => *end,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_top(&self) -> usize {
|
||||
match self {
|
||||
Self::Single(start) => *start,
|
||||
Self::Multiple(start, end) => cmp::min(*start, *end),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bottom(&self) -> usize {
|
||||
match self {
|
||||
Self::Single(start) => *start,
|
||||
Self::Multiple(start, end) => cmp::max(*start, *end),
|
||||
}
|
||||
}
|
||||
|
||||
fn modify(&mut self, direction: Direction, max: usize) {
|
||||
let start = self.get_start();
|
||||
let old_end = self.get_end();
|
||||
|
||||
*self = match direction {
|
||||
Direction::Up => {
|
||||
Self::Multiple(start, old_end.saturating_sub(1))
|
||||
}
|
||||
|
||||
Direction::Down => {
|
||||
Self::Multiple(start, cmp::min(old_end + 1, max))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn contains(&self, index: usize) -> bool {
|
||||
match self {
|
||||
Self::Single(start) => index == *start,
|
||||
Self::Multiple(start, end) => {
|
||||
if start <= end {
|
||||
*start <= index && index <= *end
|
||||
} else {
|
||||
*end <= index && index <= *start
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub struct DiffComponent {
|
||||
diff: Option<FileDiff>,
|
||||
pending: bool,
|
||||
selection: usize,
|
||||
selection: Selection,
|
||||
selected_hunk: Option<usize>,
|
||||
current_size: Cell<(u16, u16)>,
|
||||
focused: bool,
|
||||
current: Current,
|
||||
scroll_top: Cell<usize>,
|
||||
queue: Option<Queue>,
|
||||
queue: Queue,
|
||||
theme: SharedTheme,
|
||||
is_immutable: bool,
|
||||
}
|
||||
|
||||
impl DiffComponent {
|
||||
///
|
||||
pub fn new(queue: Option<Queue>, theme: SharedTheme) -> Self {
|
||||
pub fn new(
|
||||
queue: Queue,
|
||||
theme: SharedTheme,
|
||||
is_immutable: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
focused: false,
|
||||
queue,
|
||||
|
|
@ -52,9 +124,10 @@ impl DiffComponent {
|
|||
selected_hunk: None,
|
||||
diff: None,
|
||||
current_size: Cell::new((0, 0)),
|
||||
selection: 0,
|
||||
selection: Selection::Single(0),
|
||||
scroll_top: Cell::new(0),
|
||||
theme,
|
||||
is_immutable,
|
||||
}
|
||||
}
|
||||
///
|
||||
|
|
@ -73,7 +146,7 @@ impl DiffComponent {
|
|||
self.current = Current::default();
|
||||
self.diff = None;
|
||||
self.scroll_top.set(0);
|
||||
self.selection = 0;
|
||||
self.selection = Selection::Single(0);
|
||||
self.selected_hunk = None;
|
||||
self.pending = pending;
|
||||
|
||||
|
|
@ -97,12 +170,14 @@ impl DiffComponent {
|
|||
hash,
|
||||
};
|
||||
|
||||
self.selected_hunk =
|
||||
Self::find_selected_hunk(&diff, self.selection)?;
|
||||
self.selected_hunk = Self::find_selected_hunk(
|
||||
&diff,
|
||||
self.selection.get_start(),
|
||||
)?;
|
||||
|
||||
self.diff = Some(diff);
|
||||
self.scroll_top.set(0);
|
||||
self.selection = 0;
|
||||
self.selection = Selection::Single(0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -113,37 +188,97 @@ impl DiffComponent {
|
|||
move_type: ScrollType,
|
||||
) -> Result<()> {
|
||||
if let Some(diff) = &self.diff {
|
||||
let old = self.selection;
|
||||
|
||||
let max = diff.lines.saturating_sub(1) as usize;
|
||||
|
||||
self.selection = match move_type {
|
||||
ScrollType::Down => old.saturating_add(1),
|
||||
ScrollType::Up => old.saturating_sub(1),
|
||||
let new_start = match move_type {
|
||||
ScrollType::Down => {
|
||||
self.selection.get_bottom().saturating_add(1)
|
||||
}
|
||||
ScrollType::Up => {
|
||||
self.selection.get_top().saturating_sub(1)
|
||||
}
|
||||
ScrollType::Home => 0,
|
||||
ScrollType::End => max,
|
||||
ScrollType::PageDown => {
|
||||
self.selection.saturating_add(
|
||||
self.selection.get_bottom().saturating_add(
|
||||
self.current_size.get().1.saturating_sub(1)
|
||||
as usize,
|
||||
)
|
||||
}
|
||||
ScrollType::PageUp => {
|
||||
self.selection.get_top().saturating_sub(
|
||||
self.current_size.get().1.saturating_sub(1)
|
||||
as usize,
|
||||
)
|
||||
}
|
||||
ScrollType::PageUp => self.selection.saturating_sub(
|
||||
self.current_size.get().1.saturating_sub(1)
|
||||
as usize,
|
||||
),
|
||||
};
|
||||
|
||||
self.selection = cmp::min(max, self.selection);
|
||||
self.selection =
|
||||
Selection::Single(cmp::min(max, new_start));
|
||||
|
||||
if old != self.selection {
|
||||
self.selected_hunk =
|
||||
Self::find_selected_hunk(diff, self.selection)?;
|
||||
}
|
||||
self.selected_hunk =
|
||||
Self::find_selected_hunk(diff, new_start)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn modify_selection(
|
||||
&mut self,
|
||||
direction: Direction,
|
||||
) -> Result<()> {
|
||||
if let Some(diff) = &self.diff {
|
||||
let max = diff.lines.saturating_sub(1) as usize;
|
||||
|
||||
self.selection.modify(direction, max);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_string(string: String) -> Result<()> {
|
||||
let mut ctx: ClipboardContext = ClipboardProvider::new()
|
||||
.map_err(|_| {
|
||||
anyhow!("failed to get access to clipboard")
|
||||
})?;
|
||||
ctx.set_contents(string).map_err(|_| {
|
||||
anyhow!("failed to set clipboard contents")
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_selection(&self) -> Result<()> {
|
||||
if let Some(diff) = &self.diff {
|
||||
let lines_to_copy: Vec<&str> = diff
|
||||
.hunks
|
||||
.iter()
|
||||
.flat_map(|hunk| hunk.lines.iter())
|
||||
.enumerate()
|
||||
.filter_map(|(i, line)| {
|
||||
if self.selection.contains(i) {
|
||||
Some(
|
||||
line.content
|
||||
.trim_matches(|c| {
|
||||
c == '\n' || c == '\r'
|
||||
})
|
||||
.as_ref(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
try_or_popup!(
|
||||
self,
|
||||
"copy to clipboard error:",
|
||||
Self::copy_string(lines_to_copy.join("\n"))
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_selected_hunk(
|
||||
diff: &FileDiff,
|
||||
line_selected: usize,
|
||||
|
|
@ -210,8 +345,6 @@ impl DiffComponent {
|
|||
Text::Raw(Cow::from(")")),
|
||||
]);
|
||||
} else {
|
||||
let selection = self.selection;
|
||||
|
||||
let min = self.scroll_top.get();
|
||||
let max = min + height as usize;
|
||||
|
||||
|
|
@ -242,7 +375,8 @@ impl DiffComponent {
|
|||
&mut res,
|
||||
width,
|
||||
line,
|
||||
selection == line_cursor,
|
||||
self.selection
|
||||
.contains(line_cursor),
|
||||
hunk_selected,
|
||||
i == hunk_len as usize - 1,
|
||||
&self.theme,
|
||||
|
|
@ -369,7 +503,6 @@ impl DiffComponent {
|
|||
fn queue_update(&mut self) {
|
||||
self.queue
|
||||
.as_ref()
|
||||
.expect("try using queue in immutable diff")
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::Update(NeedsUpdate::ALL));
|
||||
}
|
||||
|
|
@ -379,40 +512,28 @@ impl DiffComponent {
|
|||
if let Some(hunk) = self.selected_hunk {
|
||||
let hash = diff.hunks[hunk].header_hash;
|
||||
|
||||
self.queue
|
||||
.as_ref()
|
||||
.expect("try using queue in immutable diff")
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::ConfirmAction(
|
||||
Action::ResetHunk(
|
||||
self.current.path.clone(),
|
||||
hash,
|
||||
),
|
||||
));
|
||||
self.queue.as_ref().borrow_mut().push_back(
|
||||
InternalEvent::ConfirmAction(Action::ResetHunk(
|
||||
self.current.path.clone(),
|
||||
hash,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_untracked(&self) -> Result<()> {
|
||||
self.queue
|
||||
.as_ref()
|
||||
.expect("try using queue in immutable diff")
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::ConfirmAction(Action::Reset(
|
||||
ResetItem {
|
||||
path: self.current.path.clone(),
|
||||
is_folder: false,
|
||||
},
|
||||
)));
|
||||
self.queue.as_ref().borrow_mut().push_back(
|
||||
InternalEvent::ConfirmAction(Action::Reset(ResetItem {
|
||||
path: self.current.path.clone(),
|
||||
is_folder: false,
|
||||
})),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_immutable(&self) -> bool {
|
||||
self.queue.is_none()
|
||||
}
|
||||
|
||||
const fn is_stage(&self) -> bool {
|
||||
self.current.is_stage
|
||||
}
|
||||
|
|
@ -432,7 +553,7 @@ impl DrawableComponent for DiffComponent {
|
|||
self.scroll_top.set(calc_scroll_top(
|
||||
self.scroll_top.get(),
|
||||
self.current_size.get().1 as usize,
|
||||
self.selection,
|
||||
self.selection.get_end(),
|
||||
));
|
||||
|
||||
let title =
|
||||
|
|
@ -474,6 +595,12 @@ impl Component for DiffComponent {
|
|||
self.focused,
|
||||
));
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
commands::COPY,
|
||||
true,
|
||||
self.focused,
|
||||
));
|
||||
|
||||
out.push(
|
||||
CommandInfo::new(
|
||||
commands::DIFF_HOME_END,
|
||||
|
|
@ -483,7 +610,7 @@ impl Component for DiffComponent {
|
|||
.hidden(),
|
||||
);
|
||||
|
||||
if !self.is_immutable() {
|
||||
if !self.is_immutable {
|
||||
out.push(CommandInfo::new(
|
||||
commands::DIFF_HUNK_REMOVE,
|
||||
self.selected_hunk.is_some(),
|
||||
|
|
@ -512,11 +639,19 @@ impl Component for DiffComponent {
|
|||
self.move_selection(ScrollType::Down)?;
|
||||
Ok(true)
|
||||
}
|
||||
keys::SHIFT_DOWN | keys::END => {
|
||||
keys::SHIFT_DOWN => {
|
||||
self.modify_selection(Direction::Down)?;
|
||||
Ok(true)
|
||||
}
|
||||
keys::SHIFT_UP => {
|
||||
self.modify_selection(Direction::Up)?;
|
||||
Ok(true)
|
||||
}
|
||||
keys::END => {
|
||||
self.move_selection(ScrollType::End)?;
|
||||
Ok(true)
|
||||
}
|
||||
keys::HOME | keys::SHIFT_UP => {
|
||||
keys::HOME => {
|
||||
self.move_selection(ScrollType::Home)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
|
@ -532,7 +667,7 @@ impl Component for DiffComponent {
|
|||
self.move_selection(ScrollType::PageDown)?;
|
||||
Ok(true)
|
||||
}
|
||||
keys::ENTER if !self.is_immutable() => {
|
||||
keys::ENTER if !self.is_immutable => {
|
||||
if self.current.is_stage {
|
||||
self.unstage_hunk()?;
|
||||
} else {
|
||||
|
|
@ -541,8 +676,7 @@ impl Component for DiffComponent {
|
|||
Ok(true)
|
||||
}
|
||||
keys::DIFF_RESET_HUNK
|
||||
if !self.is_immutable()
|
||||
&& !self.is_stage() =>
|
||||
if !self.is_immutable && !self.is_stage() =>
|
||||
{
|
||||
if let Some(diff) = &self.diff {
|
||||
if diff.untracked {
|
||||
|
|
@ -553,6 +687,10 @@ impl Component for DiffComponent {
|
|||
}
|
||||
Ok(true)
|
||||
}
|
||||
keys::COPY => {
|
||||
self.copy_selection()?;
|
||||
Ok(true)
|
||||
}
|
||||
_ => Ok(false),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ impl InspectCommitComponent {
|
|||
sender,
|
||||
theme.clone(),
|
||||
),
|
||||
diff: DiffComponent::new(None, theme),
|
||||
diff: DiffComponent::new(queue.clone(), theme, true),
|
||||
commit_id: None,
|
||||
tags: None,
|
||||
git_diff: AsyncDiff::new(sender.clone()),
|
||||
|
|
|
|||
|
|
@ -105,6 +105,12 @@ pub enum ScrollType {
|
|||
PageDown,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
///
|
||||
#[derive(PartialEq)]
|
||||
pub enum CommandBlocking {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,22 @@ pub mod filetree;
|
|||
pub mod logitems;
|
||||
pub mod statustree;
|
||||
|
||||
/// macro to simplify running code that might return Err.
|
||||
/// It will show a popup in that case
|
||||
#[macro_export]
|
||||
macro_rules! try_or_popup {
|
||||
($self:ident, $msg:literal, $e:expr) => {
|
||||
if let Err(err) = $e {
|
||||
$self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(format!(
|
||||
"{}\n{}",
|
||||
$msg, err
|
||||
)),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// helper func to convert unix time since epoch to formated time string in local timezone
|
||||
pub fn time_to_string(secs: i64, short: bool) -> String {
|
||||
let time = DateTime::<Local>::from(DateTime::<Utc>::from_utc(
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ pub const SHIFT_UP: KeyEvent =
|
|||
pub const SHIFT_DOWN: KeyEvent =
|
||||
with_mod(KeyCode::Down, KeyModifiers::SHIFT);
|
||||
pub const ENTER: KeyEvent = no_mod(KeyCode::Enter);
|
||||
pub const COPY: KeyEvent = no_mod(KeyCode::Char('y'));
|
||||
pub const EDIT_FILE: KeyEvent = no_mod(KeyCode::Char('e'));
|
||||
pub const STATUS_STAGE_FILE: KeyEvent = no_mod(KeyCode::Enter);
|
||||
pub const STATUS_STAGE_ALL: KeyEvent = no_mod(KeyCode::Char('a'));
|
||||
|
|
|
|||
|
|
@ -105,6 +105,12 @@ pub mod commands {
|
|||
CMD_GROUP_GENERAL,
|
||||
);
|
||||
///
|
||||
pub static COPY: CommandText = CommandText::new(
|
||||
"Copy [y]",
|
||||
"copy selected lines to clipboard",
|
||||
CMD_GROUP_DIFF,
|
||||
);
|
||||
///
|
||||
pub static DIFF_HOME_END: CommandText = CommandText::new(
|
||||
"Jump up/down [home,end,\u{2191} up,\u{2193} down]",
|
||||
"scroll to top or bottom of diff",
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ impl Status {
|
|||
queue.clone(),
|
||||
theme.clone(),
|
||||
),
|
||||
diff: DiffComponent::new(Some(queue.clone()), theme),
|
||||
diff: DiffComponent::new(queue.clone(), theme, false),
|
||||
git_diff: AsyncDiff::new(sender.clone()),
|
||||
git_status_workdir: AsyncStatus::new(sender.clone()),
|
||||
git_status_stage: AsyncStatus::new(sender.clone()),
|
||||
|
|
|
|||
Loading…
Reference in a new issue