1use std::{
2 io::stdout,
3 path::{Path, PathBuf},
4};
5
6use crossterm::{clipboard::CopyToClipboard, cursor, execute, style, terminal};
7use git2::Oid;
8
9use crate::*;
10
11#[derive(Debug, Default)]
12pub struct Cli {
25 path: PathBuf,
26}
27
28impl Cli {
29 pub fn new(path: &Path) -> Self {
30 Self {
31 path: path.to_path_buf(),
32 }
33 }
34
35 pub fn run(&mut self) -> anyhow::Result<()> {
37 let mut renderer = BlameRenderer::new(&self.path)?;
38 let size = terminal::size()?;
39 renderer.set_view_size((size.0, size.1 - 1));
40 renderer.read()?;
41
42 let mut history: Vec<Oid> = vec![];
43 let mut last_search: Option<String> = None;
44 let mut out = stdout();
45 let key_map = CommandKeyMap::new();
46 let mut prompt: CommandPrompt = CommandPrompt::None;
47 let mut terminal_raw_mode = TerminalRawModeScope::new(true)?;
48 loop {
49 renderer.render(&mut out)?;
50 let command_rows = renderer.rendered_rows();
51
52 let command = Command::read(command_rows, &key_map, &prompt)?;
53 prompt = CommandPrompt::None;
54 match command {
55 Command::PrevDiff => renderer.move_to_prev_diff(),
56 Command::NextDiff => renderer.move_to_next_diff(),
57 Command::PrevPage => renderer.move_to_prev_page(),
58 Command::NextPage => renderer.move_to_next_page(),
59 Command::FirstLine => renderer.move_to_first_line(),
60 Command::LastLine => renderer.move_to_last_line(),
61 Command::LineNumber(number) => renderer.set_current_line_number(number),
62 Command::Search(search) => {
63 renderer.search(&search, false);
64 last_search = Some(search);
65 }
66 Command::SearchPrev | Command::SearchNext => {
67 if let Some(search) = last_search.as_ref() {
68 renderer.search(search, command == Command::SearchPrev);
69 }
70 }
71 Command::Older => {
72 execute!(
73 out,
74 terminal::Clear(terminal::ClearType::All),
75 cursor::MoveTo(0, 0),
76 style::Print("Working...")
77 )?;
78 let path_before = renderer.path().to_path_buf();
79 let old_commit_id = renderer.commit_id();
80 if let Err(error) = renderer.set_commit_id_to_older_than_current_line() {
81 prompt = CommandPrompt::Err { error };
82 renderer.invalidate_render();
84 continue;
85 }
86 history.push(old_commit_id);
87 if path_before != renderer.path() {
88 prompt = CommandPrompt::Message {
89 message: format!("Path changed to {}", renderer.path().display()),
90 };
91 }
92 }
93 Command::Newer => {
94 if let Some(commit_id) = history.pop() {
95 renderer.set_commit_id(commit_id)?;
96 }
97 }
98 Command::Copy => {
99 execute!(
100 out,
101 CopyToClipboard::to_clipboard_from(
102 renderer.current_line_commit_id().to_string()
103 )
104 )?;
105 prompt = CommandPrompt::Message {
106 message: "Copied to clipboard".to_string(),
107 };
108 }
109 Command::ShowCommit | Command::ShowDiff => {
110 let mut terminal_raw_mode = TerminalRawModeScope::new(false)?;
111 renderer.show_current_line_commit(command == Command::ShowDiff)?;
112 terminal_raw_mode.reset();
113 Command::wait_for_any_key("Press any key to continue...")?;
114 }
115 Command::Help => {
116 execute!(
117 out,
118 terminal::Clear(terminal::ClearType::All),
119 cursor::MoveTo(0, 0),
120 )?;
121 renderer.invalidate_render();
122 let mut terminal_raw_mode = TerminalRawModeScope::new(false)?;
123 key_map.print_help();
124 println!();
125 terminal_raw_mode.reset();
126 Command::wait_for_any_key("Press any key to continue...")?;
127 }
128 Command::Repaint => renderer.invalidate_render(),
129 Command::Resize(columns, rows) => renderer.set_view_size((columns, rows - 1)),
130 Command::Quit => break,
131 }
132 }
133
134 terminal_raw_mode.reset();
135 Ok(())
136 }
137}