mirror of
https://github.com/extrawurst/gitui.git
synced 2024-11-22 19:29:14 +03:00
add syntax highlighting for blame view (#745)
This commit is contained in:
parent
ab95b98ef8
commit
af9da95178
@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
### Changes
|
||||||
|
- add syntax highlighting for blame view [[@tdtrung17693](https://github.com/tdtrung17693)] ([#745](https://github.com/extrawurst/gitui/issues/745))
|
||||||
|
|
||||||
** multiline text editor **
|
** multiline text editor **
|
||||||
|
|
||||||
|
@ -399,7 +399,6 @@ impl App {
|
|||||||
self.status_tab.update_git(ev)?;
|
self.status_tab.update_git(ev)?;
|
||||||
self.stashing_tab.update_git(ev)?;
|
self.stashing_tab.update_git(ev)?;
|
||||||
self.revlog.update_git(ev)?;
|
self.revlog.update_git(ev)?;
|
||||||
self.blame_file_popup.update_git(ev)?;
|
|
||||||
self.file_revlog_popup.update_git(ev)?;
|
self.file_revlog_popup.update_git(ev)?;
|
||||||
self.inspect_commit_popup.update_git(ev)?;
|
self.inspect_commit_popup.update_git(ev)?;
|
||||||
self.compare_commits_popup.update_git(ev)?;
|
self.compare_commits_popup.update_git(ev)?;
|
||||||
@ -411,6 +410,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.files_tab.update_async(ev)?;
|
self.files_tab.update_async(ev)?;
|
||||||
|
self.blame_file_popup.update_async(ev)?;
|
||||||
self.revision_files_popup.update(ev)?;
|
self.revision_files_popup.update(ev)?;
|
||||||
self.tags_popup.update(ev);
|
self.tags_popup.update(ev);
|
||||||
|
|
||||||
|
@ -10,28 +10,73 @@ use crate::{
|
|||||||
queue::{InternalEvent, Queue, StackablePopupOpen},
|
queue::{InternalEvent, Queue, StackablePopupOpen},
|
||||||
string_utils::tabs_to_spaces,
|
string_utils::tabs_to_spaces,
|
||||||
strings,
|
strings,
|
||||||
ui::{self, style::SharedTheme},
|
ui::{self, style::SharedTheme, AsyncSyntaxJob, SyntaxText},
|
||||||
|
AsyncAppNotification, AsyncNotification, SyntaxHighlightProgress,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use asyncgit::{
|
use asyncgit::{
|
||||||
sync::{BlameHunk, CommitId, FileBlame},
|
asyncjob::AsyncSingleJob,
|
||||||
|
sync::{BlameHunk, CommitId, FileBlame, RepoPathRef},
|
||||||
AsyncBlame, AsyncGitNotification, BlameParams,
|
AsyncBlame, AsyncGitNotification, BlameParams,
|
||||||
};
|
};
|
||||||
|
use crossbeam_channel::Sender;
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Constraint, Rect},
|
layout::{Constraint, Rect},
|
||||||
symbols::line::VERTICAL,
|
symbols::line::VERTICAL,
|
||||||
text::Span,
|
text::{Span, Text},
|
||||||
widgets::{Block, Borders, Cell, Clear, Row, Table, TableState},
|
widgets::{Block, Borders, Cell, Clear, Row, Table, TableState},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
use std::{convert::TryInto, path::Path};
|
||||||
|
|
||||||
static NO_COMMIT_ID: &str = "0000000";
|
static NO_COMMIT_ID: &str = "0000000";
|
||||||
static NO_AUTHOR: &str = "<no author>";
|
static NO_AUTHOR: &str = "<no author>";
|
||||||
static MIN_AUTHOR_WIDTH: usize = 3;
|
static MIN_AUTHOR_WIDTH: usize = 3;
|
||||||
static MAX_AUTHOR_WIDTH: usize = 20;
|
static MAX_AUTHOR_WIDTH: usize = 20;
|
||||||
|
|
||||||
|
struct SyntaxFileBlame {
|
||||||
|
pub file_blame: FileBlame,
|
||||||
|
pub styled_text: Option<SyntaxText>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyntaxFileBlame {
|
||||||
|
fn path(&self) -> &str {
|
||||||
|
&self.file_blame.path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit_id(&self) -> &CommitId {
|
||||||
|
&self.file_blame.commit_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lines(&self) -> &Vec<(Option<BlameHunk>, String)> {
|
||||||
|
&self.file_blame.lines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BlameProcess {
|
||||||
|
GettingBlame(AsyncBlame),
|
||||||
|
SyntaxHighlighting {
|
||||||
|
unstyled_file_blame: SyntaxFileBlame,
|
||||||
|
job: AsyncSingleJob<AsyncSyntaxJob>,
|
||||||
|
},
|
||||||
|
Result(SyntaxFileBlame),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlameProcess {
|
||||||
|
fn result(&self) -> Option<&SyntaxFileBlame> {
|
||||||
|
match self {
|
||||||
|
Self::GettingBlame(_) => None,
|
||||||
|
Self::SyntaxHighlighting {
|
||||||
|
unstyled_file_blame,
|
||||||
|
..
|
||||||
|
} => Some(unstyled_file_blame),
|
||||||
|
Self::Result(ref file_blame) => Some(file_blame),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BlameFileOpen {
|
pub struct BlameFileOpen {
|
||||||
pub file_path: String,
|
pub file_path: String,
|
||||||
@ -43,14 +88,16 @@ pub struct BlameFileComponent {
|
|||||||
title: String,
|
title: String,
|
||||||
theme: SharedTheme,
|
theme: SharedTheme,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
async_blame: AsyncBlame,
|
|
||||||
visible: bool,
|
visible: bool,
|
||||||
open_request: Option<BlameFileOpen>,
|
open_request: Option<BlameFileOpen>,
|
||||||
params: Option<BlameParams>,
|
params: Option<BlameParams>,
|
||||||
file_blame: Option<FileBlame>,
|
|
||||||
table_state: std::cell::Cell<TableState>,
|
table_state: std::cell::Cell<TableState>,
|
||||||
key_config: SharedKeyConfig,
|
key_config: SharedKeyConfig,
|
||||||
current_height: std::cell::Cell<usize>,
|
current_height: std::cell::Cell<usize>,
|
||||||
|
blame: BlameProcess,
|
||||||
|
app_sender: Sender<AsyncAppNotification>,
|
||||||
|
git_sender: Sender<AsyncGitNotification>,
|
||||||
|
repo: RepoPathRef,
|
||||||
}
|
}
|
||||||
impl DrawableComponent for BlameFileComponent {
|
impl DrawableComponent for BlameFileComponent {
|
||||||
fn draw<B: Backend>(
|
fn draw<B: Backend>(
|
||||||
@ -80,6 +127,18 @@ impl DrawableComponent for BlameFileComponent {
|
|||||||
];
|
];
|
||||||
|
|
||||||
let number_of_rows = rows.len();
|
let number_of_rows = rows.len();
|
||||||
|
let syntax_highlight_progress = match self.blame {
|
||||||
|
BlameProcess::SyntaxHighlighting {
|
||||||
|
ref job, ..
|
||||||
|
} => job
|
||||||
|
.progress()
|
||||||
|
.map(|p| format!(" ({}%)", p.progress))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
BlameProcess::GettingBlame(_)
|
||||||
|
| BlameProcess::Result(_) => String::new(),
|
||||||
|
};
|
||||||
|
let title_with_highlight_progress =
|
||||||
|
format!("{title}{syntax_highlight_progress}");
|
||||||
|
|
||||||
let table = Table::new(rows)
|
let table = Table::new(rows)
|
||||||
.widths(&constraints)
|
.widths(&constraints)
|
||||||
@ -89,7 +148,7 @@ impl DrawableComponent for BlameFileComponent {
|
|||||||
Block::default()
|
Block::default()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.title(Span::styled(
|
.title(Span::styled(
|
||||||
title,
|
title_with_highlight_progress,
|
||||||
self.theme.title(true),
|
self.theme.title(true),
|
||||||
))
|
))
|
||||||
.border_style(self.theme.block(true)),
|
.border_style(self.theme.block(true)),
|
||||||
@ -139,6 +198,7 @@ impl Component for BlameFileComponent {
|
|||||||
out: &mut Vec<CommandInfo>,
|
out: &mut Vec<CommandInfo>,
|
||||||
force_all: bool,
|
force_all: bool,
|
||||||
) -> CommandBlocking {
|
) -> CommandBlocking {
|
||||||
|
let file_blame = self.blame.result();
|
||||||
if self.is_visible() || force_all {
|
if self.is_visible() || force_all {
|
||||||
out.push(
|
out.push(
|
||||||
CommandInfo::new(
|
CommandInfo::new(
|
||||||
@ -152,7 +212,7 @@ impl Component for BlameFileComponent {
|
|||||||
CommandInfo::new(
|
CommandInfo::new(
|
||||||
strings::commands::scroll(&self.key_config),
|
strings::commands::scroll(&self.key_config),
|
||||||
true,
|
true,
|
||||||
self.file_blame.is_some(),
|
file_blame.is_some(),
|
||||||
)
|
)
|
||||||
.order(1),
|
.order(1),
|
||||||
);
|
);
|
||||||
@ -162,7 +222,7 @@ impl Component for BlameFileComponent {
|
|||||||
&self.key_config,
|
&self.key_config,
|
||||||
),
|
),
|
||||||
true,
|
true,
|
||||||
self.file_blame.is_some(),
|
file_blame.is_some(),
|
||||||
)
|
)
|
||||||
.order(1),
|
.order(1),
|
||||||
);
|
);
|
||||||
@ -172,7 +232,7 @@ impl Component for BlameFileComponent {
|
|||||||
&self.key_config,
|
&self.key_config,
|
||||||
),
|
),
|
||||||
true,
|
true,
|
||||||
self.file_blame.is_some(),
|
file_blame.is_some(),
|
||||||
)
|
)
|
||||||
.order(1),
|
.order(1),
|
||||||
);
|
);
|
||||||
@ -275,18 +335,20 @@ impl BlameFileComponent {
|
|||||||
Self {
|
Self {
|
||||||
title: String::from(title),
|
title: String::from(title),
|
||||||
theme: env.theme.clone(),
|
theme: env.theme.clone(),
|
||||||
async_blame: AsyncBlame::new(
|
|
||||||
env.repo.borrow().clone(),
|
|
||||||
&env.sender_git,
|
|
||||||
),
|
|
||||||
queue: env.queue.clone(),
|
queue: env.queue.clone(),
|
||||||
visible: false,
|
visible: false,
|
||||||
params: None,
|
params: None,
|
||||||
file_blame: None,
|
|
||||||
open_request: None,
|
open_request: None,
|
||||||
table_state: std::cell::Cell::new(TableState::default()),
|
table_state: std::cell::Cell::new(TableState::default()),
|
||||||
key_config: env.key_config.clone(),
|
key_config: env.key_config.clone(),
|
||||||
current_height: std::cell::Cell::new(0),
|
current_height: std::cell::Cell::new(0),
|
||||||
|
app_sender: env.sender_app.clone(),
|
||||||
|
git_sender: env.sender_git.clone(),
|
||||||
|
blame: BlameProcess::GettingBlame(AsyncBlame::new(
|
||||||
|
env.repo.borrow().clone(),
|
||||||
|
&env.sender_git,
|
||||||
|
)),
|
||||||
|
repo: env.repo.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,10 +376,12 @@ impl BlameFileComponent {
|
|||||||
file_path: open.file_path,
|
file_path: open.file_path,
|
||||||
commit_id: open.commit_id,
|
commit_id: open.commit_id,
|
||||||
});
|
});
|
||||||
self.file_blame = None;
|
self.blame = BlameProcess::GettingBlame(AsyncBlame::new(
|
||||||
|
self.repo.borrow().clone(),
|
||||||
|
&self.git_sender,
|
||||||
|
));
|
||||||
self.table_state.get_mut().select(Some(0));
|
self.table_state.get_mut().select(Some(0));
|
||||||
self.visible = true;
|
self.visible = true;
|
||||||
|
|
||||||
self.update()?;
|
self.update()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -325,11 +389,22 @@ impl BlameFileComponent {
|
|||||||
|
|
||||||
///
|
///
|
||||||
pub fn any_work_pending(&self) -> bool {
|
pub fn any_work_pending(&self) -> bool {
|
||||||
self.async_blame.is_pending()
|
!matches!(self.blame, BlameProcess::Result(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
pub fn update_async(
|
||||||
pub fn update_git(
|
&mut self,
|
||||||
|
ev: AsyncNotification,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let AsyncNotification::Git(ev) = ev {
|
||||||
|
return self.update_git(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_syntax(ev);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_git(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: AsyncGitNotification,
|
event: AsyncGitNotification,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@ -342,33 +417,87 @@ impl BlameFileComponent {
|
|||||||
|
|
||||||
fn update(&mut self) -> Result<()> {
|
fn update(&mut self) -> Result<()> {
|
||||||
if self.is_visible() {
|
if self.is_visible() {
|
||||||
if let Some(params) = &self.params {
|
match self.blame {
|
||||||
if let Some((
|
BlameProcess::Result(_)
|
||||||
previous_blame_params,
|
| BlameProcess::SyntaxHighlighting { .. } => {}
|
||||||
last_file_blame,
|
BlameProcess::GettingBlame(ref mut async_blame) => {
|
||||||
)) = self.async_blame.last()?
|
if let Some(params) = &self.params {
|
||||||
{
|
if let Some((
|
||||||
if previous_blame_params == *params {
|
previous_blame_params,
|
||||||
self.file_blame = Some(last_file_blame);
|
last_file_blame,
|
||||||
self.set_open_selection();
|
)) = async_blame.last()?
|
||||||
|
{
|
||||||
|
if previous_blame_params == *params {
|
||||||
|
self.blame = BlameProcess::SyntaxHighlighting {
|
||||||
|
unstyled_file_blame: SyntaxFileBlame {
|
||||||
|
file_blame: last_file_blame,
|
||||||
|
styled_text: None,
|
||||||
|
},
|
||||||
|
job: AsyncSingleJob::new(
|
||||||
|
self.app_sender.clone(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.set_open_selection();
|
||||||
|
self.highlight_blame_lines();
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async_blame.request(params.clone())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.async_blame.request(params.clone())?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_syntax(&mut self, ev: AsyncNotification) {
|
||||||
|
let BlameProcess::SyntaxHighlighting {
|
||||||
|
ref unstyled_file_blame,
|
||||||
|
ref job,
|
||||||
|
} = self.blame
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let AsyncNotification::App(
|
||||||
|
AsyncAppNotification::SyntaxHighlighting(progress),
|
||||||
|
) = ev
|
||||||
|
{
|
||||||
|
match progress {
|
||||||
|
SyntaxHighlightProgress::Done => {
|
||||||
|
if let Some(job) = job.take_last() {
|
||||||
|
if let Some(syntax) = job.result() {
|
||||||
|
if syntax.path()
|
||||||
|
== Path::new(
|
||||||
|
unstyled_file_blame.path(),
|
||||||
|
) {
|
||||||
|
self.blame = BlameProcess::Result(
|
||||||
|
SyntaxFileBlame {
|
||||||
|
file_blame:
|
||||||
|
unstyled_file_blame
|
||||||
|
.file_blame
|
||||||
|
.clone(),
|
||||||
|
styled_text: Some(syntax),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SyntaxHighlightProgress::Progress => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
fn get_title(&self) -> String {
|
fn get_title(&self) -> String {
|
||||||
match (
|
match (
|
||||||
self.any_work_pending(),
|
self.any_work_pending(),
|
||||||
self.params.as_ref(),
|
self.params.as_ref(),
|
||||||
self.file_blame.as_ref(),
|
self.blame.result(),
|
||||||
) {
|
) {
|
||||||
(true, Some(params), _) => {
|
(true, Some(params), _) => {
|
||||||
format!(
|
format!(
|
||||||
@ -381,7 +510,7 @@ impl BlameFileComponent {
|
|||||||
"{} -- {} -- {}",
|
"{} -- {} -- {}",
|
||||||
self.title,
|
self.title,
|
||||||
params.file_path,
|
params.file_path,
|
||||||
file_blame.commit_id.get_short_string()
|
file_blame.commit_id().get_short_string()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
(false, Some(params), None) => {
|
(false, Some(params), None) => {
|
||||||
@ -396,11 +525,16 @@ impl BlameFileComponent {
|
|||||||
|
|
||||||
///
|
///
|
||||||
fn get_rows(&self, width: usize) -> Vec<Row> {
|
fn get_rows(&self, width: usize) -> Vec<Row> {
|
||||||
self.file_blame
|
let file_blame = self.blame.result();
|
||||||
.as_ref()
|
|
||||||
.map_or_else(Vec::new, |file_blame| {
|
file_blame
|
||||||
|
.map(|file_blame| {
|
||||||
|
let styled_text: Option<Text<'_>> = file_blame
|
||||||
|
.styled_text
|
||||||
|
.as_ref()
|
||||||
|
.map(std::convert::Into::into);
|
||||||
file_blame
|
file_blame
|
||||||
.lines
|
.lines()
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, (blame_hunk, line))| {
|
.map(|(i, (blame_hunk, line))| {
|
||||||
@ -409,26 +543,55 @@ impl BlameFileComponent {
|
|||||||
i,
|
i,
|
||||||
(blame_hunk.as_ref(), line.as_ref()),
|
(blame_hunk.as_ref(), line.as_ref()),
|
||||||
file_blame,
|
file_blame,
|
||||||
|
&styled_text,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_line_blame(
|
fn highlight_blame_lines(&mut self) {
|
||||||
&self,
|
let BlameProcess::SyntaxHighlighting {
|
||||||
|
ref unstyled_file_blame,
|
||||||
|
ref mut job,
|
||||||
|
} = self.blame
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(params) = &self.params else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let raw_lines = unstyled_file_blame
|
||||||
|
.lines()
|
||||||
|
.iter()
|
||||||
|
.map(|l| l.1.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let text = tabs_to_spaces(raw_lines.join("\n"));
|
||||||
|
|
||||||
|
job.spawn(AsyncSyntaxJob::new(
|
||||||
|
text,
|
||||||
|
params.file_path.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_line_blame<'a>(
|
||||||
|
&'a self,
|
||||||
width: usize,
|
width: usize,
|
||||||
line_number: usize,
|
line_number: usize,
|
||||||
hunk_and_line: (Option<&BlameHunk>, &str),
|
hunk_and_line: (Option<&BlameHunk>, &str),
|
||||||
file_blame: &FileBlame,
|
file_blame: &'a SyntaxFileBlame,
|
||||||
) -> Row {
|
styled_text: &Option<Text<'a>>,
|
||||||
|
) -> Row<'a> {
|
||||||
let (hunk_for_line, line) = hunk_and_line;
|
let (hunk_for_line, line) = hunk_and_line;
|
||||||
|
|
||||||
let show_metadata = if line_number == 0 {
|
let show_metadata = if line_number == 0 {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let hunk_for_previous_line =
|
let hunk_for_previous_line =
|
||||||
&file_blame.lines[line_number - 1];
|
&file_blame.lines()[line_number - 1];
|
||||||
|
|
||||||
match (hunk_for_previous_line, hunk_for_line) {
|
match (hunk_for_previous_line, hunk_for_line) {
|
||||||
((Some(previous), _), Some(current)) => {
|
((Some(previous), _), Some(current)) => {
|
||||||
@ -445,16 +608,26 @@ impl BlameFileComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let line_number_width = self.get_line_number_width();
|
let line_number_width = self.get_line_number_width();
|
||||||
|
|
||||||
|
let text_cell = styled_text.as_ref().map_or_else(
|
||||||
|
|| {
|
||||||
|
Cell::from(tabs_to_spaces(String::from(line)))
|
||||||
|
.style(self.theme.text(true, false))
|
||||||
|
},
|
||||||
|
|styled_text| {
|
||||||
|
let styled_text =
|
||||||
|
styled_text.lines[line_number].clone();
|
||||||
|
Cell::from(styled_text)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
cells.push(
|
cells.push(
|
||||||
Cell::from(format!(
|
Cell::from(format!(
|
||||||
"{line_number:>line_number_width$}{VERTICAL}",
|
"{line_number:>line_number_width$}{VERTICAL}",
|
||||||
))
|
))
|
||||||
.style(self.theme.text(true, false)),
|
.style(self.theme.text(true, false)),
|
||||||
);
|
);
|
||||||
cells.push(
|
cells.push(text_cell);
|
||||||
Cell::from(tabs_to_spaces(String::from(line)))
|
|
||||||
.style(self.theme.text(true, false)),
|
|
||||||
);
|
|
||||||
|
|
||||||
Row::new(cells)
|
Row::new(cells)
|
||||||
}
|
}
|
||||||
@ -478,12 +651,11 @@ impl BlameFileComponent {
|
|||||||
utils::time_to_string(hunk.time, true)
|
utils::time_to_string(hunk.time, true)
|
||||||
});
|
});
|
||||||
|
|
||||||
let is_blamed_commit = self
|
let file_blame = self.blame.result();
|
||||||
.file_blame
|
let is_blamed_commit = file_blame
|
||||||
.as_ref()
|
|
||||||
.and_then(|file_blame| {
|
.and_then(|file_blame| {
|
||||||
blame_hunk.map(|hunk| {
|
blame_hunk.map(|hunk| {
|
||||||
file_blame.commit_id == hunk.commit_id
|
file_blame.commit_id() == &hunk.commit_id
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
@ -498,9 +670,9 @@ impl BlameFileComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_max_line_number(&self) -> usize {
|
fn get_max_line_number(&self) -> usize {
|
||||||
self.file_blame
|
self.blame
|
||||||
.as_ref()
|
.result()
|
||||||
.map_or(0, |file_blame| file_blame.lines.len() - 1)
|
.map_or(0, |file_blame| file_blame.lines().len() - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_line_number_width(&self) -> usize {
|
fn get_line_number_width(&self) -> usize {
|
||||||
@ -551,7 +723,7 @@ impl BlameFileComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_selection(&self) -> Option<usize> {
|
fn get_selection(&self) -> Option<usize> {
|
||||||
self.file_blame.as_ref().and_then(|_| {
|
self.blame.result().as_ref().and_then(|_| {
|
||||||
let table_state = self.table_state.take();
|
let table_state = self.table_state.take();
|
||||||
|
|
||||||
let selection = table_state.selected();
|
let selection = table_state.selected();
|
||||||
@ -563,12 +735,12 @@ impl BlameFileComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn selected_commit(&self) -> Option<CommitId> {
|
fn selected_commit(&self) -> Option<CommitId> {
|
||||||
self.file_blame.as_ref().and_then(|file_blame| {
|
self.blame.result().as_ref().and_then(|file_blame| {
|
||||||
let table_state = self.table_state.take();
|
let table_state = self.table_state.take();
|
||||||
|
|
||||||
let commit_id =
|
let commit_id =
|
||||||
table_state.selected().and_then(|selected| {
|
table_state.selected().and_then(|selected| {
|
||||||
file_blame.lines[selected]
|
file_blame.lines()[selected]
|
||||||
.0
|
.0
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|hunk| hunk.commit_id)
|
.map(|hunk| hunk.commit_id)
|
||||||
|
Loading…
Reference in New Issue
Block a user