-
Notifications
You must be signed in to change notification settings - Fork 402
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
5 changed files
with
278 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,273 @@ | ||
use std::convert::{TryFrom, TryInto}; | ||
|
||
use lazy_static::lazy_static; | ||
use regex::Regex; | ||
|
||
use crate::delta::{State, StateMachine}; | ||
use crate::handlers::hunk_header; | ||
use crate::paint::BgShouldFill; | ||
use crate::utils; | ||
|
||
struct GrepOutputConfig { | ||
render_context_header_as_hunk_header: bool, | ||
highlight_hits: bool, | ||
} | ||
|
||
impl<'a> StateMachine<'a> { | ||
/// If this is a line of git grep output then render it accordingly. If this | ||
/// is the first grep line, then set the syntax-highlighter language. | ||
pub fn handle_grep_line(&mut self) -> std::io::Result<bool> { | ||
let mut output_config = GrepOutputConfig { | ||
render_context_header_as_hunk_header: true, | ||
highlight_hits: true, | ||
}; | ||
if let Some((longs, shorts)) = utils::parent_command_options() { | ||
if shorts.contains("-W") || longs.contains("--function-context") { | ||
// --function-context is in effect: i.e. the entire function is | ||
// being displayed. In that case we don't render the first line | ||
// as a header, since the second line is the true next line, and | ||
// it will be more readable to have these displayed normally. We | ||
// highlight hits, since these will be surrounded by non-hits. | ||
output_config = GrepOutputConfig { | ||
render_context_header_as_hunk_header: false, | ||
highlight_hits: true, | ||
} | ||
} else if shorts.contains("-p") || longs.contains("--show-function") { | ||
// --show-function is in effect, i.e. the function header is | ||
// being displayed, along with hits within the function. | ||
// Therefore we render the first line as a header, but we do not | ||
// highlight hits, since all other lines are hits. | ||
output_config = GrepOutputConfig { | ||
render_context_header_as_hunk_header: true, | ||
highlight_hits: false, | ||
} | ||
} | ||
}; | ||
|
||
// TODO: It should be possible to eliminate some of the .clone()s and | ||
// .to_owned()s. | ||
let mut handled_line = false; | ||
self.painter.emit()?; | ||
let (_previous_file, repeat_grep_line, try_parse) = match &self.state { | ||
State::Grep(file, repeat_grep_line) => { | ||
(Some(file.as_str()), repeat_grep_line.clone(), true) | ||
} | ||
State::Unknown => (None, None, true), | ||
_ => (None, None, false), | ||
}; | ||
if try_parse { | ||
if let Some(grep) = parse_git_grep_line(&self.line) { | ||
// let is_repeat = previous_file == Some(grep.file); | ||
|
||
// Emit syntax-highlighted code | ||
if matches!(self.state, State::Unknown) { | ||
if let Some(lang) = self.config.default_language.as_ref() { | ||
self.painter.set_syntax(Some(lang)); | ||
self.painter.set_highlighter(); | ||
} | ||
} | ||
self.state = State::Grep(grep.file.to_owned(), repeat_grep_line); | ||
|
||
match ( | ||
&grep.line_type, | ||
output_config.render_context_header_as_hunk_header, | ||
) { | ||
// Emit context header line | ||
(LineType::ContextHeader, true) => hunk_header::write_hunk_header( | ||
grep.code, | ||
&[(grep.line_number.unwrap_or(0), 0)], | ||
&mut self.painter, | ||
&self.line, | ||
grep.file, | ||
self.config, | ||
)?, | ||
_ => { | ||
// Emit file & line-number | ||
let grep_line = format!( | ||
"{:<35}│ ", | ||
format!( | ||
"{}:{}", | ||
grep.file, | ||
grep.line_number | ||
.map(|n| format!("{}", n)) | ||
.unwrap_or_else(|| "".into()) | ||
) | ||
); | ||
let style = self.config.file_style; | ||
write!(self.painter.writer, "{}", style.paint(grep_line))?; | ||
|
||
// Emit code line | ||
self.painter.syntax_highlight_and_paint_line( | ||
&format!("{}\n", grep.code), | ||
if matches!(&grep.line_type, LineType::Hit) | ||
&& output_config.highlight_hits | ||
{ | ||
self.config.plus_style | ||
} else { | ||
self.config.zero_style | ||
}, | ||
self.state.clone(), | ||
BgShouldFill::default(), | ||
) | ||
} | ||
} | ||
handled_line = true | ||
} | ||
} | ||
Ok(handled_line) | ||
} | ||
} | ||
|
||
#[derive(Debug, PartialEq)] | ||
pub struct GrepLine<'a> { | ||
pub file: &'a str, | ||
pub line_number: Option<usize>, | ||
pub line_type: LineType, | ||
pub code: &'a str, | ||
} | ||
|
||
#[derive(Debug, PartialEq)] | ||
pub enum LineType { | ||
ContextHeader, | ||
Hit, | ||
NoHit, | ||
} | ||
|
||
// See tests for example grep lines | ||
lazy_static! { | ||
static ref GREP_LINE_REGEX: Regex = Regex::new( | ||
r"(?x) | ||
^ | ||
(.+?) # 1. file name (non-greedy: stop at line-type marker) | ||
(?: | ||
[-=:]([0-9]+) # 2. optional line number | ||
)? | ||
([-=:]) # 3. line-type marker | ||
(.*) # 4. code (i.e. line contents) | ||
$ | ||
" | ||
) | ||
.unwrap(); | ||
} | ||
|
||
pub fn parse_git_grep_line(line: &str) -> Option<GrepLine> { | ||
let caps = GREP_LINE_REGEX.captures(line)?; | ||
let file = caps.get(1).unwrap().as_str(); | ||
let line_number = caps.get(2).map(|m| m.as_str().parse().ok()).flatten(); | ||
let line_type = caps.get(3).map(|m| m.as_str()).try_into().ok()?; | ||
let code = caps.get(4).unwrap().as_str(); | ||
|
||
Some(GrepLine { | ||
file, | ||
line_number, | ||
line_type, | ||
code, | ||
}) | ||
} | ||
|
||
impl TryFrom<Option<&str>> for LineType { | ||
type Error = (); | ||
fn try_from(from: Option<&str>) -> Result<Self, Self::Error> { | ||
match from { | ||
Some(marker) if marker == "=" => Ok(LineType::ContextHeader), | ||
Some(marker) if marker == ":" => Ok(LineType::Hit), | ||
Some(marker) if marker == "-" => Ok(LineType::NoHit), | ||
_ => Err(()), | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::handlers::grep::{parse_git_grep_line, GrepLine, LineType}; | ||
|
||
#[test] | ||
fn test_parse_grep_line() { | ||
// git grep MinusPlus | ||
assert_eq!( | ||
parse_git_grep_line("src/config.rs:use crate::minusplus::MinusPlus;"), | ||
Some(GrepLine { | ||
file: "src/config.rs", | ||
line_number: None, | ||
line_type: LineType::Hit, | ||
code: "use crate::minusplus::MinusPlus;", | ||
}) | ||
); | ||
|
||
// git grep -n MinusPlus [with line numbers] | ||
assert_eq!( | ||
parse_git_grep_line("src/config.rs:21:use crate::minusplus::MinusPlus;"), | ||
Some(GrepLine { | ||
file: "src/config.rs", | ||
line_number: Some(21), | ||
line_type: LineType::Hit, | ||
code: "use crate::minusplus::MinusPlus;", | ||
}) | ||
); | ||
|
||
// git grep -W MinusPlus [with function context] | ||
assert_eq!( | ||
parse_git_grep_line("src/config.rs=pub struct Config {"), // hit | ||
Some(GrepLine { | ||
file: "src/config.rs", | ||
line_number: None, | ||
line_type: LineType::ContextHeader, | ||
code: "pub struct Config {", | ||
}) | ||
); | ||
assert_eq!( | ||
parse_git_grep_line("src/config.rs- pub available_terminal_width: usize,"), | ||
Some(GrepLine { | ||
file: "src/config.rs", | ||
line_number: None, | ||
line_type: LineType::NoHit, | ||
code: " pub available_terminal_width: usize,", | ||
}) | ||
); | ||
assert_eq!( | ||
parse_git_grep_line( | ||
"src/config.rs: pub line_numbers_style_minusplus: MinusPlus<Style>," | ||
), | ||
Some(GrepLine { | ||
file: "src/config.rs", | ||
line_number: None, | ||
line_type: LineType::Hit, | ||
code: " pub line_numbers_style_minusplus: MinusPlus<Style>,", | ||
}) | ||
); | ||
|
||
// git grep -n -W MinusPlus [with line numbers and function context] | ||
assert_eq!( | ||
parse_git_grep_line("src/config.rs=57=pub struct Config {"), | ||
Some(GrepLine { | ||
file: "src/config.rs", | ||
line_number: Some(57), | ||
line_type: LineType::ContextHeader, | ||
code: "pub struct Config {", | ||
}) | ||
); | ||
assert_eq!( | ||
parse_git_grep_line("src/config.rs-58- pub available_terminal_width: usize,"), | ||
Some(GrepLine { | ||
file: "src/config.rs", | ||
line_number: Some(58), | ||
line_type: LineType::NoHit, | ||
code: " pub available_terminal_width: usize,", | ||
}) | ||
); | ||
assert_eq!( | ||
parse_git_grep_line( | ||
"src/config.rs:95: pub line_numbers_style_minusplus: MinusPlus<Style>," | ||
), | ||
Some(GrepLine { | ||
file: "src/config.rs", | ||
line_number: Some(95), | ||
line_type: LineType::Hit, | ||
code: " pub line_numbers_style_minusplus: MinusPlus<Style>,", | ||
}) | ||
); | ||
|
||
// git grep -h MinusPlus [no file names: TODO: handle this?] | ||
//use crate::minusplus::MinusPlus; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters