1use std::{io::stdout, path::Path};
2
3use crate::*;
4use crossterm::{clipboard::CopyToClipboard, execute, terminal};
5use git2::Oid;
6use log::*;
7
8pub struct Cli {
9 renderer: BlameRenderer,
10 commit_ids: Vec<Oid>,
11}
12
13impl Cli {
14 pub fn new(path: &Path) -> anyhow::Result<Self> {
15 Ok(Self {
16 renderer: BlameRenderer::new(path)?,
17 commit_ids: vec![],
18 })
19 }
20
21 pub fn run(&mut self) -> anyhow::Result<()> {
22 terminal::enable_raw_mode()?;
23 let result = self.run_core();
24 if let Err(e) = terminal::disable_raw_mode() {
25 warn!("Failed to disable raw mode, ignored: {e}");
26 }
27 result
28 }
29
30 fn run_core(&mut self) -> anyhow::Result<()> {
31 let renderer = &mut self.renderer;
32 let size = terminal::size()?;
33 renderer.set_size((size.0, size.1 - 1));
34 renderer.read()?;
35 let mut out = stdout();
36 let prompt = String::new();
37 loop {
38 renderer.render(&mut out)?;
39
40 let command = Command::read(renderer.rendered_rows(), &prompt)?;
41 match command {
42 Command::PrevDiff => renderer.move_to_prev_diff(),
43 Command::NextDiff => renderer.move_to_next_diff(),
44 Command::PageUp => renderer.move_to_prev_page(),
45 Command::PageDown => renderer.move_to_next_page(),
46 Command::FirstLine => renderer.move_to_first_line(),
47 Command::LastLine => renderer.move_to_last_line(),
48 Command::LineNumber(number) => renderer.set_current_number(number),
49 Command::Older => {
50 let id = renderer.newest_commit_id();
51 renderer.set_newest_commit_id_to_older()?;
52 self.commit_ids.push(id);
53 }
54 Command::Newer => {
55 if let Some(id) = self.commit_ids.pop() {
56 renderer.set_newest_commit_id(id)?;
57 }
58 }
59 Command::Copy => execute!(
60 out,
61 CopyToClipboard::to_clipboard_from(renderer.current_commit_id().to_string())
62 )?,
63 Command::Resize(columns, rows) => renderer.set_size((columns, rows - 1)),
64 Command::Quit => break,
65 }
66 }
67 Ok(())
68 }
69}