summaryrefslogtreecommitdiffstats
path: root/src/handlers/ripgrep_json.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/handlers/ripgrep_json.rs')
-rw-r--r--src/handlers/ripgrep_json.rs210
1 files changed, 210 insertions, 0 deletions
diff --git a/src/handlers/ripgrep_json.rs b/src/handlers/ripgrep_json.rs
new file mode 100644
index 00000000..1a278a14
--- /dev/null
+++ b/src/handlers/ripgrep_json.rs
@@ -0,0 +1,210 @@
+// See https://github.com/BurntSushi/ripgrep
+// This module implements handling of `rg --json` output. It is called by the
+// handler in handlers/grep.rs. Normal rg output (i.e. without --json) is
+// handled by the same code paths as `git grep` etc output, in handlers/grep.rs.
+use std::borrow::Cow;
+
+use crate::handlers::grep;
+use serde::Deserialize;
+use serde_json::Value;
+
+pub fn parse_line(line: &str) -> Option<grep::GrepLine> {
+ let ripgrep_line: Option<RipGrepLine> = serde_json::from_str(line).ok();
+ match ripgrep_line {
+ Some(ripgrep_line) => {
+ // A real line of rg --json output, i.e. either of type "match" or
+ // "context".
+ let mut code = ripgrep_line.data.lines.text;
+ if code.ends_with('\n') {
+ code.truncate(code.len() - 1);
+ if code.ends_with('\r') {
+ code.truncate(code.len() - 1);
+ }
+ }
+ Some(grep::GrepLine {
+ line_type: ripgrep_line._type,
+ line_number: ripgrep_line.data.line_number,
+ path: Cow::from(ripgrep_line.data.path.text),
+ code: Cow::from(code),
+ submatches: Some(
+ ripgrep_line
+ .data
+ .submatches
+ .iter()
+ .map(|m| (m.start, m.end))
+ .collect(),
+ ),
+ })
+ }
+ None => {
+ let value: Value = serde_json::from_str(line).ok()?;
+ match &value["type"] {
+ Value::String(s) if s == "begin" || s == "end" || s == "summary" => {
+ Some(grep::GrepLine {
+ // ripgrep --json also emits these metadata lines at
+ // file boundaries. We emit nothing but signal that the
+ // line has been handled.
+ line_type: grep::LineType::Ignore,
+ line_number: None,
+ path: "".into(),
+ code: "".into(),
+ submatches: None,
+ })
+ }
+ _ => {
+ // Failed to interpret the line as ripgrep output; allow
+ // another delta handler to try.
+ None
+ }
+ }
+ }
+ }
+}
+
+// {
+// "type": "match",
+// "data": {
+// "path": {
+// "text": "src/cli.rs"
+// },
+// "lines": {
+// "text": " fn from_clap_and_git_config(\n"
+// },
+// "line_number": null,
+// "absolute_offset": 35837,
+// "submatches": [
+// {
+// "match": {
+// "text": "fn"
+// },
+// "start": 4,
+// "end": 6
+// }
+// ]
+// }
+// }
+
+#[derive(Deserialize, PartialEq, Debug)]
+struct RipGrepLine {
+ #[serde(rename(deserialize = "type"))]
+ _type: grep::LineType,
+ data: RipGrepLineData,
+}
+
+#[derive(Deserialize, PartialEq, Debug)]
+struct RipGrepLineData {
+ path: RipGrepLineText,
+ lines: RipGrepLineText,
+ line_number: Option<usize>,
+ absolute_offset: usize,
+ submatches: Vec<RipGrepLineSubmatch>,
+}
+
+#[derive(Deserialize, PartialEq, Debug)]
+struct RipGrepLineText {
+ text: String,
+}
+
+#[derive(Deserialize, PartialEq, Debug)]
+struct RipGrepLineSubmatch {
+ #[serde(rename(deserialize = "match"))]
+ _match: RipGrepLineText,
+ start: usize,
+ end: usize,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_deserialize() {
+ let line = r#"{"type":"match","data":{"path":{"text":"src/cli.rs"},"lines":{"text":" fn from_clap_and_git_config(\n"},"line_number":null,"absolute_offset":35837,"submatches":[{"match":{"text":"fn"},"start":4,"end":6}]}}"#;
+ let ripgrep_line: RipGrepLine = serde_json::from_str(line).unwrap();
+ assert_eq!(
+ ripgrep_line,
+ RipGrepLine {
+ _type: grep::LineType::Match,
+ data: RipGrepLineData {
+ path: RipGrepLineText {
+ text: "src/cli.rs".into()
+ },
+ lines: RipGrepLineText {
+ text: " fn from_clap_and_git_config(\n".into(),
+ },
+ line_number: None,
+ absolute_offset: 35837,
+ submatches: vec![RipGrepLineSubmatch {
+ _match: RipGrepLineText { text: "fn".into() },
+ start: 4,
+ end: 6
+ }]
+ }
+ }
+ )
+ }
+
+ #[test]
+ fn test_deserialize_2() {
+ let line = r#"{"type":"match","data":{"path":{"text":"src/handlers/submodule.rs"},"lines":{"text":" .paint(minus_commit.chars().take(7).collect::<String>()),\n"},"line_number":41,"absolute_offset":1430,"submatches":[{"match":{"text":"("},"start":30,"end":31},{"match":{"text":"("},"start":49,"end":50},{"match":{"text":")"},"start":50,"end":51},{"match":{"text":"("},"start":56,"end":57},{"match":{"text":")"},"start":58,"end":59},{"match":{"text":"("},"start":77,"end":78},{"match":{"text":")"},"start":78,"end":79},{"match":{"text":")"},"start":79,"end":80}]}}"#;
+ let ripgrep_line: RipGrepLine = serde_json::from_str(line).unwrap();
+ assert_eq!(
+ ripgrep_line,
+ RipGrepLine {
+ _type: grep::LineType::Match,
+ data: RipGrepLineData {
+ path: RipGrepLineText {
+ text: "src/handlers/submodule.rs".into()
+ },
+ lines: RipGrepLineText {
+ text: " .paint(minus_commit.chars().take(7).collect::<String>()),\n".into(),
+ },
+ line_number: Some(41),
+ absolute_offset: 1430,
+ submatches: vec![
+ RipGrepLineSubmatch {
+ _match: RipGrepLineText { text: "(".into() },
+ start: 30,
+ end: 31
+ },
+ RipGrepLineSubmatch {
+ _match: RipGrepLineText { text: "(".into() },
+ start: 49,
+ end: 50
+ },
+ RipGrepLineSubmatch {
+ _match: RipGrepLineText { text: ")".into() },
+ start: 50,
+ end: 51
+ },
+ RipGrepLineSubmatch {
+ _match: RipGrepLineText { text: "(".into() },
+ start: 56,
+ end: 57
+ },
+ RipGrepLineSubmatch {
+ _match: RipGrepLineText { text: ")".into() },
+ start: 58,
+ end: 59
+ },
+ RipGrepLineSubmatch {
+ _match: RipGrepLineText { text: "(".into() },
+ start: 77,
+ end: 78
+ },
+ RipGrepLineSubmatch {
+ _match: RipGrepLineText { text: ")".into() },
+ start: 78,
+ end: 79
+ },
+ RipGrepLineSubmatch {
+ _match: RipGrepLineText { text: ")".into() },
+ start: 79,
+ end: 80
+ },
+ ]
+ }
+ }
+ )
+ }
+}