asyncjob supports sending arbitrary notifications

this is used to send progress reports during work on the job
This commit is contained in:
Stephan Dilly 2021-09-02 13:14:36 +02:00
parent b9e4631ff4
commit 0454e2a1cd
8 changed files with 179 additions and 53 deletions

View file

@ -12,7 +12,10 @@ pub trait AsyncJob: Send + Sync + Clone {
type Notification: Copy + Send + 'static;
/// can run a synchronous time intensive task
fn run(&mut self) -> Self::Notification;
fn run(
&mut self,
sender: Sender<Self::Notification>,
) -> Result<Self::Notification>;
}
/// Abstraction for a FIFO task queue that will only queue up **one** `next` job.
@ -95,7 +98,7 @@ impl<J: 'static + AsyncJob> AsyncSingleJob<J> {
{
let _pending = self.pending.lock()?;
let notification = task.run();
let notification = task.run(self.sender.clone())?;
if let Ok(mut last) = self.last.lock() {
*last = Some(task);
@ -147,7 +150,10 @@ mod test {
impl AsyncJob for TestJob {
type Notification = TestNotificaton;
fn run(&mut self) -> Self::Notification {
fn run(
&mut self,
_sender: Sender<Self::Notification>,
) -> Result<Self::Notification> {
println!("[job] wait");
while !self.finish.load(Ordering::SeqCst) {
@ -165,7 +171,7 @@ mod test {
println!("[job] value: {}", res);
()
Ok(())
}
}

View file

@ -3,51 +3,67 @@
use std::{num::TryFromIntError, string::FromUtf8Error};
use thiserror::Error;
///
#[derive(Error, Debug)]
pub enum Error {
///
#[error("`{0}`")]
Generic(String),
///
#[error("git: no head found")]
NoHead,
///
#[error("git: conflict during rebase")]
RebaseConflict,
///
#[error("git: remote url not found")]
UnknownRemote,
///
#[error("git: inconclusive remotes")]
NoDefaultRemoteFound,
///
#[error("git: work dir error")]
NoWorkDir,
///
#[error("git: uncommitted changes")]
UncommittedChanges,
///
#[error("git: can\u{2019}t run blame on a binary file")]
NoBlameOnBinaryFile,
///
#[error("binary file")]
BinaryFile,
///
#[error("io error:{0}")]
Io(#[from] std::io::Error),
///
#[error("git error:{0}")]
Git(#[from] git2::Error),
///
#[error("utf8 error:{0}")]
Utf8Conversion(#[from] FromUtf8Error),
///
#[error("TryFromInt error:{0}")]
IntConversion(#[from] TryFromIntError),
///
#[error("EasyCast error:{0}")]
EasyCast(#[from] easy_cast::Error),
}
///
pub type Result<T> = std::result::Result<T, Error>;
impl<T> From<std::sync::PoisonError<T>> for Error {

View file

@ -43,7 +43,9 @@ pub use crate::{
blame::{AsyncBlame, BlameParams},
commit_files::{AsyncCommitFiles, CommitFilesParams},
diff::{AsyncDiff, DiffParams, DiffType},
error::{Error, Result},
fetch::{AsyncFetch, FetchRequest},
progress::ProgressPercent,
push::{AsyncPush, PushRequest},
push_tags::{AsyncPushTags, PushTagsRequest},
remote_progress::{RemoteProgress, RemoteProgressState},

View file

@ -4,7 +4,7 @@ use easy_cast::{Conv, ConvFloat};
use std::cmp;
///
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ProgressPercent {
/// percent 0..100
pub progress: u8,

View file

@ -1,5 +1,7 @@
//!
use crossbeam_channel::Sender;
use crate::{
asyncjob::AsyncJob,
error::Result,
@ -52,7 +54,10 @@ impl AsyncRemoteTagsJob {
impl AsyncJob for AsyncRemoteTagsJob {
type Notification = AsyncGitNotification;
fn run(&mut self) -> Self::Notification {
fn run(
&mut self,
_sender: Sender<Self::Notification>,
) -> Result<Self::Notification> {
if let Ok(mut state) = self.state.lock() {
*state = state.take().map(|state| match state {
JobState::Request(basic_credential) => {
@ -73,6 +78,6 @@ impl AsyncJob for AsyncRemoteTagsJob {
});
}
AsyncGitNotification::RemoteTags
Ok(AsyncGitNotification::RemoteTags)
}
}

View file

@ -9,13 +9,13 @@ use crate::{
self, common_nav, style::SharedTheme, AsyncSyntaxJob,
ParagraphState, ScrollPos, StatefulParagraph,
},
AsyncAppNotification, AsyncNotification,
AsyncAppNotification, AsyncNotification, SyntaxHighlightProgress,
};
use anyhow::Result;
use asyncgit::{
asyncjob::AsyncSingleJob,
sync::{self, TreeFile},
CWD,
ProgressPercent, CWD,
};
use crossbeam_channel::Sender;
use crossterm::event::Event;
@ -33,6 +33,7 @@ use tui::{
pub struct SyntaxTextComponent {
current_file: Option<(String, Either<ui::SyntaxText, String>)>,
async_highlighting: AsyncSingleJob<AsyncSyntaxJob>,
syntax_progress: Option<ProgressPercent>,
key_config: SharedKeyConfig,
paragraph_state: Cell<ParagraphState>,
focused: bool,
@ -48,6 +49,7 @@ impl SyntaxTextComponent {
) -> Self {
Self {
async_highlighting: AsyncSingleJob::new(sender.clone()),
syntax_progress: None,
current_file: None,
paragraph_state: Cell::new(ParagraphState::default()),
focused: false,
@ -58,19 +60,27 @@ impl SyntaxTextComponent {
///
pub fn update(&mut self, ev: AsyncNotification) {
if matches!(
ev,
AsyncNotification::App(
AsyncAppNotification::SyntaxHighlighting
)
) {
if let Some(job) = self.async_highlighting.take_last() {
if let Some((path, content)) =
self.current_file.as_mut()
{
if let Some(syntax) = job.result() {
if syntax.path() == Path::new(path) {
*content = Either::Left(syntax);
if let AsyncNotification::App(
AsyncAppNotification::SyntaxHighlighting(progress),
) = ev
{
match progress {
SyntaxHighlightProgress::Progress(progress) => {
self.syntax_progress = Some(progress);
}
SyntaxHighlightProgress::Done => {
self.syntax_progress = None;
if let Some(job) =
self.async_highlighting.take_last()
{
if let Some((path, content)) =
self.current_file.as_mut()
{
if let Some(syntax) = job.result() {
if syntax.path() == Path::new(path) {
*content = Either::Left(syntax);
}
}
}
}
}
@ -101,6 +111,8 @@ impl SyntaxTextComponent {
match sync::tree_file_content(CWD, item) {
Ok(content) => {
let content = tabs_to_spaces(content);
self.syntax_progress =
Some(ProgressPercent::empty());
self.async_highlighting.spawn(
AsyncSyntaxJob::new(
content.clone(),
@ -185,16 +197,22 @@ impl DrawableComponent for SyntaxTextComponent {
},
);
let title = format!(
"{}{}",
self.current_file
.as_ref()
.map(|(name, _)| name.clone())
.unwrap_or_default(),
self.syntax_progress
.map(|p| format!(" ({}%)", p.progress))
.unwrap_or_default()
);
let content = StatefulParagraph::new(text)
.wrap(Wrap { trim: false })
.block(
Block::default()
.title(
self.current_file
.as_ref()
.map(|(name, _)| name.clone())
.unwrap_or_default(),
)
.title(title)
.borders(Borders::ALL)
.border_style(self.theme.title(self.focused())),
);

View file

@ -76,10 +76,16 @@ pub enum QueueEvent {
InputEvent(InputEvent),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SyntaxHighlightProgress {
Progress(asyncgit::ProgressPercent),
Done,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AsyncAppNotification {
///
SyntaxHighlighting,
SyntaxHighlighting(SyntaxHighlightProgress),
}
#[derive(Clone, Copy, Debug, PartialEq)]

View file

@ -1,4 +1,5 @@
use asyncgit::asyncjob::AsyncJob;
use asyncgit::{asyncjob::AsyncJob, ProgressPercent};
use crossbeam_channel::Sender;
use lazy_static::lazy_static;
use scopetime::scope_time;
use std::{
@ -6,6 +7,7 @@ use std::{
ops::Range,
path::{Path, PathBuf},
sync::{Arc, Mutex},
time::{Duration, Instant},
};
use syntect::{
highlighting::{
@ -16,7 +18,7 @@ use syntect::{
};
use tui::text::{Span, Spans};
use crate::AsyncAppNotification;
use crate::{AsyncAppNotification, SyntaxHighlightProgress};
struct SyntaxLine {
items: Vec<(Style, usize, Range<usize>)>,
@ -34,12 +36,47 @@ lazy_static! {
static ref THEME_SET: ThemeSet = ThemeSet::load_defaults();
}
pub struct AsyncProgressBuffer {
current: usize,
total: usize,
last_send: Option<Instant>,
min_interval: Duration,
}
impl AsyncProgressBuffer {
pub const fn new(total: usize, min_interval: Duration) -> Self {
Self {
current: 0,
total,
last_send: None,
min_interval,
}
}
pub fn send_progress(&mut self) -> ProgressPercent {
self.last_send = Some(Instant::now());
ProgressPercent::new(self.current, self.total)
}
pub fn update(&mut self, current: usize) -> bool {
self.current = current;
self.last_send.map_or(true, |last_send| {
last_send.elapsed() > self.min_interval
})
}
}
impl SyntaxText {
pub fn new(text: String, file_path: &Path) -> Self {
pub fn new(
text: String,
file_path: &Path,
sender: &Sender<AsyncAppNotification>,
) -> asyncgit::Result<Self> {
scope_time!("syntax_highlighting");
log::debug!("syntax: {:?}", file_path);
let mut state = {
scope_time!("syntax_highlighting.0");
let syntax = file_path
.extension()
.and_then(OsStr::to_str)
@ -66,27 +103,53 @@ impl SyntaxText {
let mut highlight_state =
HighlightState::new(&highlighter, ScopeStack::new());
for (number, line) in text.lines().enumerate() {
let ops = state.parse_line(line, &SYNTAX_SET);
let iter = RangedHighlightIterator::new(
&mut highlight_state,
&ops[..],
line,
&highlighter,
);
{
let total_count = text.lines().count();
syntax_lines.push(SyntaxLine {
items: iter
.map(|(style, _, range)| (style, number, range))
.collect(),
});
let mut buffer = AsyncProgressBuffer::new(
total_count,
Duration::from_millis(200),
);
sender.send(AsyncAppNotification::SyntaxHighlighting(
SyntaxHighlightProgress::Progress(
buffer.send_progress(),
),
))?;
for (number, line) in text.lines().enumerate() {
let ops = state.parse_line(line, &SYNTAX_SET);
let iter = RangedHighlightIterator::new(
&mut highlight_state,
&ops[..],
line,
&highlighter,
);
syntax_lines.push(SyntaxLine {
items: iter
.map(|(style, _, range)| {
(style, number, range)
})
.collect(),
});
if buffer.update(number) {
sender.send(
AsyncAppNotification::SyntaxHighlighting(
SyntaxHighlightProgress::Progress(
buffer.send_progress(),
),
),
)?;
}
}
}
Self {
Ok(Self {
text,
lines: syntax_lines,
path: file_path.into(),
}
})
}
///
@ -179,18 +242,28 @@ impl AsyncSyntaxJob {
impl AsyncJob for AsyncSyntaxJob {
type Notification = AsyncAppNotification;
fn run(&mut self) -> Self::Notification {
if let Ok(mut state) = self.state.lock() {
*state = state.take().map(|state| match state {
fn run(
&mut self,
sender: Sender<Self::Notification>,
) -> asyncgit::Result<Self::Notification> {
let mut state_mutex = self.state.lock()?;
if let Some(state) = state_mutex.take() {
*state_mutex = Some(match state {
JobState::Request((content, path)) => {
let syntax =
SyntaxText::new(content, Path::new(&path));
let syntax = SyntaxText::new(
content,
Path::new(&path),
&sender,
)?;
JobState::Response(syntax)
}
JobState::Response(res) => JobState::Response(res),
});
}
AsyncAppNotification::SyntaxHighlighting
Ok(AsyncAppNotification::SyntaxHighlighting(
SyntaxHighlightProgress::Done,
))
}
}