fix left arrow closing popup (#1121)

* fix left arrow closing popup
* fix pageup/down being borked
* adhere to global diff options
* fix build for rust 1.50
* show revision count in header
* allow blaming any specific file revision
* show blame command
* allow opening history from blame
This commit is contained in:
Stephan Dilly 2022-01-31 20:56:59 +01:00 committed by GitHub
parent 750b45a6c4
commit 4f3ecfcd7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 181 additions and 101 deletions

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
error::Result, error::Result,
hash, hash,
sync::{self, FileBlame, RepoPath}, sync::{self, CommitId, FileBlame, RepoPath},
AsyncGitNotification, AsyncGitNotification,
}; };
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
@ -18,6 +18,8 @@ use std::{
pub struct BlameParams { pub struct BlameParams {
/// path to the file to blame /// path to the file to blame
pub file_path: String, pub file_path: String,
/// blame at a specific revision
pub commit_id: Option<CommitId>,
} }
struct Request<R, A>(R, Option<A>); struct Request<R, A>(R, Option<A>);
@ -145,8 +147,11 @@ impl AsyncBlame {
arc_current: &Arc<Mutex<Request<u64, FileBlame>>>, arc_current: &Arc<Mutex<Request<u64, FileBlame>>>,
hash: u64, hash: u64,
) -> Result<bool> { ) -> Result<bool> {
let file_blame = let file_blame = sync::blame::blame_file(
sync::blame::blame_file(repo_path, &params.file_path)?; repo_path,
&params.file_path,
params.commit_id,
)?;
let mut notify = false; let mut notify = false;
{ {

View file

@ -175,11 +175,13 @@ impl AsyncDiff {
repo_path, repo_path,
id, id,
params.path.clone(), params.path.clone(),
Some(params.options),
)?, )?,
DiffType::Commits(ids) => sync::diff::get_diff_commits( DiffType::Commits(ids) => sync::diff::get_diff_commits(
repo_path, repo_path,
ids, ids,
params.path.clone(), params.path.clone(),
Some(params.options),
)?, )?,
}; };

View file

@ -59,7 +59,7 @@ impl AsyncLog {
} }
/// ///
pub fn count(&mut self) -> Result<usize> { pub fn count(&self) -> Result<usize> {
Ok(self.current.lock()?.len()) Ok(self.current.lock()?.len())
} }

View file

@ -5,6 +5,7 @@ use crate::{
error::{Error, Result}, error::{Error, Result},
sync::{get_commits_info, repository::repo}, sync::{get_commits_info, repository::repo},
}; };
use git2::BlameOptions;
use scopetime::scope_time; use scopetime::scope_time;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
@ -56,12 +57,17 @@ fn fixup_windows_path(path: &str) -> String {
pub fn blame_file( pub fn blame_file(
repo_path: &RepoPath, repo_path: &RepoPath,
file_path: &str, file_path: &str,
commit_id: Option<CommitId>,
) -> Result<FileBlame> { ) -> Result<FileBlame> {
scope_time!("blame_file"); scope_time!("blame_file");
let repo = repo(repo_path)?; let repo = repo(repo_path)?;
let commit_id = utils::get_head_repo(&repo)?; let commit_id = if let Some(commit_id) = commit_id {
commit_id
} else {
utils::get_head_repo(&repo)?
};
let spec = format!( let spec = format!(
"{}:{}", "{}:{}",
@ -76,7 +82,11 @@ pub fn blame_file(
return Err(Error::NoBlameOnBinaryFile); return Err(Error::NoBlameOnBinaryFile);
} }
let blame = repo.blame_file(Path::new(file_path), None)?; let mut opts = BlameOptions::new();
opts.newest_commit(commit_id.into());
let blame =
repo.blame_file(Path::new(file_path), Some(&mut opts))?;
let reader = BufReader::new(blob.content()); let reader = BufReader::new(blob.content());
@ -160,7 +170,10 @@ mod tests {
let repo_path: &RepoPath = let repo_path: &RepoPath =
&root.as_os_str().to_str().unwrap().into(); &root.as_os_str().to_str().unwrap().into();
assert!(matches!(blame_file(&repo_path, "foo"), Err(_))); assert!(matches!(
blame_file(&repo_path, "foo", None),
Err(_)
));
File::create(&root.join(file_path))? File::create(&root.join(file_path))?
.write_all(b"line 1\n")?; .write_all(b"line 1\n")?;
@ -168,7 +181,7 @@ mod tests {
stage_add_file(repo_path, file_path)?; stage_add_file(repo_path, file_path)?;
commit(repo_path, "first commit")?; commit(repo_path, "first commit")?;
let blame = blame_file(&repo_path, "foo")?; let blame = blame_file(&repo_path, "foo", None)?;
assert!(matches!( assert!(matches!(
blame.lines.as_slice(), blame.lines.as_slice(),
@ -192,7 +205,7 @@ mod tests {
stage_add_file(repo_path, file_path)?; stage_add_file(repo_path, file_path)?;
commit(repo_path, "second commit")?; commit(repo_path, "second commit")?;
let blame = blame_file(&repo_path, "foo")?; let blame = blame_file(&repo_path, "foo", None)?;
assert!(matches!( assert!(matches!(
blame.lines.as_slice(), blame.lines.as_slice(),
@ -219,14 +232,14 @@ mod tests {
file.write(b"line 3\n")?; file.write(b"line 3\n")?;
let blame = blame_file(&repo_path, "foo")?; let blame = blame_file(&repo_path, "foo", None)?;
assert_eq!(blame.lines.len(), 2); assert_eq!(blame.lines.len(), 2);
stage_add_file(repo_path, file_path)?; stage_add_file(repo_path, file_path)?;
commit(repo_path, "third commit")?; commit(repo_path, "third commit")?;
let blame = blame_file(&repo_path, "foo")?; let blame = blame_file(&repo_path, "foo", None)?;
assert_eq!(blame.lines.len(), 3); assert_eq!(blame.lines.len(), 3);
@ -251,6 +264,6 @@ mod tests {
stage_add_file(repo_path, file_path).unwrap(); stage_add_file(repo_path, file_path).unwrap();
commit(repo_path, "first commit").unwrap(); commit(repo_path, "first commit").unwrap();
assert!(blame_file(&repo_path, "bar\\foo").is_ok()); assert!(blame_file(&repo_path, "bar\\foo", None).is_ok());
} }
} }

View file

@ -1,10 +1,12 @@
//! Functions for getting infos about files in commits //! Functions for getting infos about files in commits
use super::{stash::is_stash_commit, CommitId, RepoPath}; use super::{
diff::DiffOptions, stash::is_stash_commit, CommitId, RepoPath,
};
use crate::{ use crate::{
error::Result, sync::repository::repo, StatusItem, StatusItemType, error::Result, sync::repository::repo, StatusItem, StatusItemType,
}; };
use git2::{Diff, DiffOptions, Repository}; use git2::{Diff, Repository};
use scopetime::scope_time; use scopetime::scope_time;
use std::cmp::Ordering; use std::cmp::Ordering;
@ -19,9 +21,9 @@ pub fn get_commit_files(
let repo = repo(repo_path)?; let repo = repo(repo_path)?;
let diff = if let Some(other) = other { let diff = if let Some(other) = other {
get_compare_commits_diff(&repo, (id, other), None)? get_compare_commits_diff(&repo, (id, other), None, None)?
} else { } else {
get_commit_diff(repo_path, &repo, id, None)? get_commit_diff(repo_path, &repo, id, None, None)?
}; };
let res = diff let res = diff
@ -49,6 +51,7 @@ pub fn get_compare_commits_diff(
repo: &Repository, repo: &Repository,
ids: (CommitId, CommitId), ids: (CommitId, CommitId),
pathspec: Option<String>, pathspec: Option<String>,
options: Option<DiffOptions>,
) -> Result<Diff<'_>> { ) -> Result<Diff<'_>> {
// scope_time!("get_compare_commits_diff"); // scope_time!("get_compare_commits_diff");
@ -67,7 +70,12 @@ pub fn get_compare_commits_diff(
let trees = (commits.0.tree()?, commits.1.tree()?); let trees = (commits.0.tree()?, commits.1.tree()?);
let mut opts = DiffOptions::new(); let mut opts = git2::DiffOptions::new();
if let Some(options) = options {
opts.context_lines(options.context);
opts.ignore_whitespace(options.ignore_whitespace);
opts.interhunk_lines(options.interhunk_lines);
}
if let Some(p) = &pathspec { if let Some(p) = &pathspec {
opts.pathspec(p.clone()); opts.pathspec(p.clone());
} }
@ -88,6 +96,7 @@ pub fn get_commit_diff<'a>(
repo: &'a Repository, repo: &'a Repository,
id: CommitId, id: CommitId,
pathspec: Option<String>, pathspec: Option<String>,
options: Option<DiffOptions>,
) -> Result<Diff<'a>> { ) -> Result<Diff<'a>> {
// scope_time!("get_commit_diff"); // scope_time!("get_commit_diff");
@ -102,7 +111,12 @@ pub fn get_commit_diff<'a>(
None None
}; };
let mut opts = DiffOptions::new(); let mut opts = git2::DiffOptions::new();
if let Some(options) = options {
opts.context_lines(options.context);
opts.ignore_whitespace(options.ignore_whitespace);
opts.interhunk_lines(options.interhunk_lines);
}
if let Some(p) = &pathspec { if let Some(p) = &pathspec {
opts.pathspec(p.clone()); opts.pathspec(p.clone());
} }
@ -121,6 +135,7 @@ pub fn get_commit_diff<'a>(
repo, repo,
CommitId::new(untracked_commit), CommitId::new(untracked_commit),
pathspec, pathspec,
options,
)?; )?;
diff.merge(&untracked_diff)?; diff.merge(&untracked_diff)?;

View file

@ -214,12 +214,14 @@ pub fn get_diff_commit(
repo_path: &RepoPath, repo_path: &RepoPath,
id: CommitId, id: CommitId,
p: String, p: String,
options: Option<DiffOptions>,
) -> Result<FileDiff> { ) -> Result<FileDiff> {
scope_time!("get_diff_commit"); scope_time!("get_diff_commit");
let repo = repo(repo_path)?; let repo = repo(repo_path)?;
let work_dir = work_dir(&repo)?; let work_dir = work_dir(&repo)?;
let diff = get_commit_diff(repo_path, &repo, id, Some(p))?; let diff =
get_commit_diff(repo_path, &repo, id, Some(p), options)?;
raw_diff_to_file_diff(&diff, work_dir) raw_diff_to_file_diff(&diff, work_dir)
} }
@ -229,13 +231,18 @@ pub fn get_diff_commits(
repo_path: &RepoPath, repo_path: &RepoPath,
ids: (CommitId, CommitId), ids: (CommitId, CommitId),
p: String, p: String,
options: Option<DiffOptions>,
) -> Result<FileDiff> { ) -> Result<FileDiff> {
scope_time!("get_diff_commits"); scope_time!("get_diff_commits");
let repo = repo(repo_path)?; let repo = repo(repo_path)?;
let work_dir = work_dir(&repo)?; let work_dir = work_dir(&repo)?;
let diff = let diff = get_compare_commits_diff(
get_compare_commits_diff(&repo, (ids.0, ids.1), Some(p))?; &repo,
(ids.0, ids.1),
Some(p),
options,
)?;
raw_diff_to_file_diff(&diff, work_dir) raw_diff_to_file_diff(&diff, work_dir)
} }
@ -649,7 +656,8 @@ mod tests {
let id = commit(repo_path, "").unwrap(); let id = commit(repo_path, "").unwrap();
let diff = let diff =
get_diff_commit(repo_path, id, String::new()).unwrap(); get_diff_commit(repo_path, id, String::new(), None)
.unwrap();
dbg!(&diff); dbg!(&diff);
assert_eq!(diff.sizes, (1, 2)); assert_eq!(diff.sizes, (1, 2));

View file

@ -49,6 +49,7 @@ pub fn diff_contains_file(
repo, repo,
*commit_id, *commit_id,
Some(file_path.clone()), Some(file_path.clone()),
None,
)?; )?;
let contains_file = diff.deltas().len() > 0; let contains_file = diff.deltas().len() > 0;

View file

@ -129,6 +129,7 @@ impl App {
sender, sender,
theme.clone(), theme.clone(),
key_config.clone(), key_config.clone(),
options.clone(),
), ),
revision_files_popup: RevisionFilesPopup::new( revision_files_popup: RevisionFilesPopup::new(
repo.clone(), repo.clone(),
@ -714,8 +715,8 @@ impl App {
InternalEvent::TagCommit(id) => { InternalEvent::TagCommit(id) => {
self.tag_commit_popup.open(id)?; self.tag_commit_popup.open(id)?;
} }
InternalEvent::BlameFile(path) => { InternalEvent::BlameFile(path, commit_id) => {
self.blame_file_popup.open(&path)?; self.blame_file_popup.open(&path, commit_id)?;
flags flags
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
} }

View file

@ -33,7 +33,7 @@ pub struct BlameFileComponent {
queue: Queue, queue: Queue,
async_blame: AsyncBlame, async_blame: AsyncBlame,
visible: bool, visible: bool,
file_path: Option<String>, params: Option<BlameParams>,
file_blame: Option<FileBlame>, file_blame: Option<FileBlame>,
table_state: std::cell::Cell<TableState>, table_state: std::cell::Cell<TableState>,
key_config: SharedKeyConfig, key_config: SharedKeyConfig,
@ -175,6 +175,16 @@ impl Component for BlameFileComponent {
) )
.order(1), .order(1),
); );
out.push(
CommandInfo::new(
strings::commands::open_file_history(
&self.key_config,
),
true,
self.file_blame.is_some(),
)
.order(1),
);
} }
visibility_blocking(self) visibility_blocking(self)
@ -205,19 +215,23 @@ impl Component for BlameFileComponent {
} else if key == self.key_config.keys.page_up { } else if key == self.key_config.keys.page_up {
self.move_selection(ScrollType::PageUp); self.move_selection(ScrollType::PageUp);
} else if key == self.key_config.keys.focus_right { } else if key == self.key_config.keys.focus_right {
self.hide(); if let Some(id) = self.selected_commit() {
self.hide();
return self.selected_commit().map_or( self.queue.push(
Ok(EventState::NotConsumed), InternalEvent::InspectCommit(id, None),
|id| { );
self.queue.push( }
InternalEvent::InspectCommit( } else if key == self.key_config.keys.file_history {
id, None, if let Some(filepath) = self
), .params
); .as_ref()
Ok(EventState::Consumed) .map(|p| p.file_path.clone())
}, {
); self.hide();
self.queue.push(
InternalEvent::OpenFileRevlog(filepath),
);
}
} }
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
@ -261,7 +275,7 @@ impl BlameFileComponent {
), ),
queue: queue.clone(), queue: queue.clone(),
visible: false, visible: false,
file_path: None, params: None,
file_blame: None, file_blame: None,
table_state: std::cell::Cell::new(TableState::default()), table_state: std::cell::Cell::new(TableState::default()),
key_config, key_config,
@ -270,8 +284,15 @@ impl BlameFileComponent {
} }
/// ///
pub fn open(&mut self, file_path: &str) -> Result<()> { pub fn open(
self.file_path = Some(file_path.into()); &mut self,
file_path: &str,
commit_id: Option<CommitId>,
) -> Result<()> {
self.params = Some(BlameParams {
file_path: file_path.into(),
commit_id,
});
self.file_blame = None; self.file_blame = None;
self.table_state.get_mut().select(Some(0)); self.table_state.get_mut().select(Some(0));
self.show()?; self.show()?;
@ -300,24 +321,20 @@ impl BlameFileComponent {
fn update(&mut self) -> Result<()> { fn update(&mut self) -> Result<()> {
if self.is_visible() { if self.is_visible() {
if let Some(file_path) = &self.file_path { if let Some(params) = &self.params {
let blame_params = BlameParams {
file_path: file_path.into(),
};
if let Some(( if let Some((
previous_blame_params, previous_blame_params,
last_file_blame, last_file_blame,
)) = self.async_blame.last()? )) = self.async_blame.last()?
{ {
if previous_blame_params == blame_params { if previous_blame_params == *params {
self.file_blame = Some(last_file_blame); self.file_blame = Some(last_file_blame);
return Ok(()); return Ok(());
} }
} }
self.async_blame.request(blame_params)?; self.async_blame.request(params.clone())?;
} }
} }
@ -328,27 +345,27 @@ impl BlameFileComponent {
fn get_title(&self) -> String { fn get_title(&self) -> String {
match ( match (
self.any_work_pending(), self.any_work_pending(),
self.file_path.as_ref(), self.params.as_ref(),
self.file_blame.as_ref(), self.file_blame.as_ref(),
) { ) {
(true, Some(file_path), _) => { (true, Some(params), _) => {
format!( format!(
"{} -- {} -- <calculating.. (who is to blame?)>", "{} -- {} -- <calculating.. (who is to blame?)>",
self.title, file_path self.title, params.file_path
) )
} }
(false, Some(file_path), Some(file_blame)) => { (false, Some(params), Some(file_blame)) => {
format!( format!(
"{} -- {} -- {}", "{} -- {} -- {}",
self.title, self.title,
file_path, params.file_path,
file_blame.commit_id.get_short_string() file_blame.commit_id.get_short_string()
) )
} }
(false, Some(file_path), None) => { (false, Some(params), None) => {
format!( format!(
"{} -- {} -- <no blame available>", "{} -- {} -- <no blame available>",
self.title, file_path self.title, params.file_path
) )
} }
_ => format!("{} -- <no blame available>", self.title), _ => format!("{} -- <no blame available>", self.title),

View file

@ -1,5 +1,5 @@
use super::utils::logitems::ItemBatch;
use super::visibility_blocking; use super::visibility_blocking;
use super::{utils::logitems::ItemBatch, SharedOptions};
use crate::{ use crate::{
components::{ components::{
event_pump, CommandBlocking, CommandInfo, Component, event_pump, CommandBlocking, CommandInfo, Component,
@ -13,8 +13,7 @@ use crate::{
use anyhow::Result; use anyhow::Result;
use asyncgit::{ use asyncgit::{
sync::{ sync::{
diff::DiffOptions, diff_contains_file, get_commits_info, diff_contains_file, get_commits_info, CommitId, RepoPathRef,
CommitId, RepoPathRef,
}, },
AsyncDiff, AsyncGitNotification, AsyncLog, DiffParams, DiffType, AsyncDiff, AsyncGitNotification, AsyncLog, DiffParams, DiffType,
FetchStatus, FetchStatus,
@ -47,6 +46,7 @@ pub struct FileRevlogComponent {
items: ItemBatch, items: ItemBatch,
count_total: usize, count_total: usize,
key_config: SharedKeyConfig, key_config: SharedKeyConfig,
options: SharedOptions,
current_width: std::cell::Cell<usize>, current_width: std::cell::Cell<usize>,
current_height: std::cell::Cell<usize>, current_height: std::cell::Cell<usize>,
} }
@ -59,6 +59,7 @@ impl FileRevlogComponent {
sender: &Sender<AsyncGitNotification>, sender: &Sender<AsyncGitNotification>,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig, key_config: SharedKeyConfig,
options: SharedOptions,
) -> Self { ) -> Self {
Self { Self {
theme: theme.clone(), theme: theme.clone(),
@ -85,6 +86,7 @@ impl FileRevlogComponent {
key_config, key_config,
current_width: std::cell::Cell::new(0), current_width: std::cell::Cell::new(0),
current_height: std::cell::Cell::new(0), current_height: std::cell::Cell::new(0),
options,
} }
} }
@ -169,7 +171,7 @@ impl FileRevlogComponent {
let diff_params = DiffParams { let diff_params = DiffParams {
path: file_path.clone(), path: file_path.clone(),
diff_type: DiffType::Commit(commit_id), diff_type: DiffType::Commit(commit_id),
options: DiffOptions::default(), options: self.options.borrow().diff,
}; };
if let Some((params, last)) = if let Some((params, last)) =
@ -243,10 +245,20 @@ impl FileRevlogComponent {
} }
fn get_title(&self) -> String { fn get_title(&self) -> String {
let selected = {
let table = self.table_state.take();
let res = table.selected().unwrap_or_default();
self.table_state.set(table);
res
};
let revisions = self.get_max_selection();
self.file_path.as_ref().map_or( self.file_path.as_ref().map_or(
"<no history available>".into(), "<no history available>".into(),
|file_path| { |file_path| {
strings::file_log_title(&self.key_config, file_path) strings::file_log_title(
file_path, selected, revisions,
)
}, },
) )
} }
@ -282,8 +294,8 @@ impl FileRevlogComponent {
.collect() .collect()
} }
fn get_max_selection(&mut self) -> usize { fn get_max_selection(&self) -> usize {
self.git_log.as_mut().map_or(0, |log| { self.git_log.as_ref().map_or(0, |log| {
log.count().unwrap_or(0).saturating_sub(1) log.count().unwrap_or(0).saturating_sub(1)
}) })
} }
@ -293,6 +305,7 @@ impl FileRevlogComponent {
let old_selection = table_state.selected().unwrap_or(0); let old_selection = table_state.selected().unwrap_or(0);
let max_selection = self.get_max_selection(); let max_selection = self.get_max_selection();
let height_in_items = self.current_height.get() / 2;
let new_selection = match scroll_type { let new_selection = match scroll_type {
ScrollType::Up => old_selection.saturating_sub(1), ScrollType::Up => old_selection.saturating_sub(1),
@ -301,13 +314,10 @@ impl FileRevlogComponent {
} }
ScrollType::Home => 0, ScrollType::Home => 0,
ScrollType::End => max_selection, ScrollType::End => max_selection,
ScrollType::PageUp => old_selection.saturating_sub( ScrollType::PageUp => old_selection
self.current_height.get().saturating_sub(2), .saturating_sub(height_in_items.saturating_sub(2)),
),
ScrollType::PageDown => old_selection ScrollType::PageDown => old_selection
.saturating_add( .saturating_add(height_in_items.saturating_sub(2))
self.current_height.get().saturating_sub(2),
)
.min(max_selection), .min(max_selection),
}; };
@ -418,53 +428,46 @@ impl Component for FileRevlogComponent {
if let Event::Key(key) = event { if let Event::Key(key) = event {
if key == self.key_config.keys.exit_popup { if key == self.key_config.keys.exit_popup {
self.hide(); self.hide();
return Ok(EventState::Consumed);
} else if key == self.key_config.keys.focus_right } else if key == self.key_config.keys.focus_right
&& self.can_focus_diff() && self.can_focus_diff()
{ {
self.diff.focus(true); self.diff.focus(true);
return Ok(EventState::Consumed);
} else if key == self.key_config.keys.focus_left { } else if key == self.key_config.keys.focus_left {
if self.diff.focused() { if self.diff.focused() {
self.diff.focus(false); self.diff.focus(false);
} else {
self.hide();
} }
return Ok(EventState::Consumed);
} else if key == self.key_config.keys.enter { } else if key == self.key_config.keys.enter {
self.hide(); if let Some(id) = self.selected_commit() {
self.hide();
return self.selected_commit().map_or( self.queue.push(
Ok(EventState::NotConsumed), InternalEvent::InspectCommit(id, None),
|id| { );
self.queue.push( };
InternalEvent::InspectCommit( } else if key == self.key_config.keys.blame {
id, None, if let Some(file) = self.file_path.clone() {
), self.hide();
); self.queue.push(InternalEvent::BlameFile(
Ok(EventState::Consumed) file,
}, self.selected_commit(),
); ));
}
} else if key == self.key_config.keys.move_up { } else if key == self.key_config.keys.move_up {
self.move_selection(ScrollType::Up) self.move_selection(ScrollType::Up);
} else if key == self.key_config.keys.move_down { } else if key == self.key_config.keys.move_down {
self.move_selection(ScrollType::Down) self.move_selection(ScrollType::Down);
} else if key == self.key_config.keys.shift_up } else if key == self.key_config.keys.shift_up
|| key == self.key_config.keys.home || key == self.key_config.keys.home
{ {
self.move_selection(ScrollType::Home) self.move_selection(ScrollType::Home);
} else if key == self.key_config.keys.shift_down } else if key == self.key_config.keys.shift_down
|| key == self.key_config.keys.end || key == self.key_config.keys.end
{ {
self.move_selection(ScrollType::End) self.move_selection(ScrollType::End);
} else if key == self.key_config.keys.page_up { } else if key == self.key_config.keys.page_up {
self.move_selection(ScrollType::PageUp) self.move_selection(ScrollType::PageUp);
} else if key == self.key_config.keys.page_down { } else if key == self.key_config.keys.page_down {
self.move_selection(ScrollType::PageDown) self.move_selection(ScrollType::PageDown);
} else { }
false
};
} }
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
@ -497,6 +500,14 @@ impl Component for FileRevlogComponent {
) )
.order(1), .order(1),
); );
out.push(
CommandInfo::new(
strings::commands::blame_file(&self.key_config),
true,
self.selected_commit().is_some(),
)
.order(1),
);
out.push(CommandInfo::new( out.push(CommandInfo::new(
strings::commands::diff_focus_right(&self.key_config), strings::commands::diff_focus_right(&self.key_config),

View file

@ -133,7 +133,8 @@ impl RevisionFilesComponent {
fn blame(&self) -> bool { fn blame(&self) -> bool {
self.selected_file_path().map_or(false, |path| { self.selected_file_path().map_or(false, |path| {
self.queue.push(InternalEvent::BlameFile(path)); self.queue
.push(InternalEvent::BlameFile(path, self.revision));
true true
}) })

View file

@ -418,8 +418,10 @@ impl Component for StatusTreeComponent {
return if e == self.key_config.keys.blame { return if e == self.key_config.keys.blame {
match (&self.queue, self.selection_file()) { match (&self.queue, self.selection_file()) {
(Some(queue), Some(status_item)) => { (Some(queue), Some(status_item)) => {
//TODO: use correct revision here
queue.push(InternalEvent::BlameFile( queue.push(InternalEvent::BlameFile(
status_item.path, status_item.path,
None,
)); ));
Ok(EventState::Consumed) Ok(EventState::Consumed)

View file

@ -79,7 +79,7 @@ pub enum InternalEvent {
/// ///
Tags, Tags,
/// ///
BlameFile(String), BlameFile(String, Option<CommitId>),
/// ///
OpenFileRevlog(String), OpenFileRevlog(String),
/// ///

View file

@ -270,10 +270,14 @@ pub fn log_title(_key_config: &SharedKeyConfig) -> String {
"Commit".to_string() "Commit".to_string()
} }
pub fn file_log_title( pub fn file_log_title(
_key_config: &SharedKeyConfig,
file_path: &str, file_path: &str,
selected: usize,
revisions: usize,
) -> String { ) -> String {
format!("Commits for file {}", file_path) format!(
"Revisions of '{}' ({}/{})",
file_path, selected, revisions
)
} }
pub fn blame_title(_key_config: &SharedKeyConfig) -> String { pub fn blame_title(_key_config: &SharedKeyConfig) -> String {
"Blame".to_string() "Blame".to_string()
@ -1057,7 +1061,7 @@ pub mod commands {
key_config.get_hint(key_config.keys.blame), key_config.get_hint(key_config.keys.blame),
), ),
"open blame view of selected file", "open blame view of selected file",
CMD_GROUP_LOG, CMD_GROUP_GENERAL,
) )
} }
pub fn open_file_history( pub fn open_file_history(