mirror of https://github.com/helix-editor/helix
initial basic cobertura coverage gutter
parent
7ebf650029
commit
6006a69b44
|
@ -1557,6 +1557,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rustix 1.0.3",
|
"rustix 1.0.3",
|
||||||
|
"quick-xml",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"slotmap",
|
"slotmap",
|
||||||
|
@ -2121,6 +2122,16 @@ dependencies = [
|
||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.31.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quickcheck"
|
name = "quickcheck"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
|
|
@ -51,6 +51,7 @@ log = "~0.4"
|
||||||
|
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
quick-xml = { version = "0.31.0", features = ["serialize"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
clipboard-win = { version = "5.4", features = ["std"] }
|
clipboard-win = { version = "5.4", features = ["std"] }
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
use quick_xml::de::from_reader;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
|
pub struct Coverage {
|
||||||
|
pub files: HashMap<std::path::PathBuf, FileCoverage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FileCoverage {
|
||||||
|
pub lines: HashMap<u32, bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct RawCoverage {
|
||||||
|
#[serde(rename = "@version")]
|
||||||
|
version: String,
|
||||||
|
sources: Sources,
|
||||||
|
packages: Packages,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Sources {
|
||||||
|
source: Vec<Source>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Source {
|
||||||
|
#[serde(rename = "$value")]
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Packages {
|
||||||
|
package: Vec<Package>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Package {
|
||||||
|
#[serde(rename = "@name")]
|
||||||
|
name: String,
|
||||||
|
classes: Classes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Classes {
|
||||||
|
class: Vec<Class>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Class {
|
||||||
|
#[serde(rename = "@name")]
|
||||||
|
name: String,
|
||||||
|
#[serde(rename = "@filename")]
|
||||||
|
filename: String,
|
||||||
|
lines: Lines,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Lines {
|
||||||
|
line: Vec<Line>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Line {
|
||||||
|
#[serde(rename = "@number")]
|
||||||
|
number: u32,
|
||||||
|
#[serde(rename = "@hits")]
|
||||||
|
hits: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(path: std::path::PathBuf) -> Option<Coverage> {
|
||||||
|
let file = File::open(path).ok()?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let tmp: RawCoverage = from_reader(reader).ok()?;
|
||||||
|
Some(tmp.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RawCoverage> for Coverage {
|
||||||
|
fn from(coverage: RawCoverage) -> Self {
|
||||||
|
let mut files = HashMap::new();
|
||||||
|
for package in coverage.packages.package {
|
||||||
|
for class in package.classes.class {
|
||||||
|
let mut lines = HashMap::new();
|
||||||
|
for line in class.lines.line {
|
||||||
|
lines.insert(line.number - 1, line.hits > 0);
|
||||||
|
}
|
||||||
|
for source in &coverage.sources.source {
|
||||||
|
let path: std::path::PathBuf = [&source.name, &class.filename].iter().collect();
|
||||||
|
if path.exists() {
|
||||||
|
files.insert(path, FileCoverage { lines });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Coverage { files }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use quick_xml::de::from_str;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
static TEST_STRING: &str = r#"<?xml version="1.0" ?>
|
||||||
|
<coverage version="7.3.0" timestamp="4333222111000">
|
||||||
|
<sources>
|
||||||
|
<source>a_src</source>
|
||||||
|
</sources>
|
||||||
|
<packages>
|
||||||
|
<package name="a package">
|
||||||
|
<classes>
|
||||||
|
<class name="a class" filename="file.ext">
|
||||||
|
<lines>
|
||||||
|
<line number="3" hits="1"/>
|
||||||
|
<line number="5" hits="0"/>
|
||||||
|
</lines>
|
||||||
|
</class>
|
||||||
|
<class name="another class" filename="other.ext">
|
||||||
|
<lines>
|
||||||
|
<line number="1" hits="0"/>
|
||||||
|
<line number="7" hits="1"/>
|
||||||
|
</lines>
|
||||||
|
</class>
|
||||||
|
</classes>
|
||||||
|
</package>
|
||||||
|
</packages>
|
||||||
|
</coverage>"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_raw_coverage_from_string() {
|
||||||
|
let result: RawCoverage = from_str(TEST_STRING).unwrap();
|
||||||
|
println!("result is {:?}", result);
|
||||||
|
assert_eq!(result.version, "7.3.0");
|
||||||
|
assert_eq!(result.sources.source[0].name, "a_src");
|
||||||
|
assert_eq!(result.packages.package[0].name, "a package");
|
||||||
|
let class = &result.packages.package[0].classes.class[0];
|
||||||
|
assert_eq!(class.name, "a class");
|
||||||
|
assert_eq!(class.filename, "file.ext");
|
||||||
|
assert_eq!(class.lines.line[0].number, 3);
|
||||||
|
assert_eq!(class.lines.line[0].hits, 1);
|
||||||
|
assert_eq!(class.lines.line[1].number, 5);
|
||||||
|
assert_eq!(class.lines.line[1].hits, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_convert_raw_coverage_to_coverage() {
|
||||||
|
let tmp: RawCoverage = from_str(TEST_STRING).unwrap();
|
||||||
|
let result: Coverage = tmp.into();
|
||||||
|
assert_eq!(result.files.len(), 2);
|
||||||
|
let first = result.files.get(&PathBuf::from("a_src/file.ext")).unwrap();
|
||||||
|
assert!(first.lines.get(&0).is_none());
|
||||||
|
assert_eq!(first.lines.get(&3), Some(&true));
|
||||||
|
assert_eq!(first.lines.get(&5), Some(&false));
|
||||||
|
let second = result.files.get(&PathBuf::from("a_src/other.ext")).unwrap();
|
||||||
|
assert!(second.lines.get(&3).is_none());
|
||||||
|
assert_eq!(second.lines.get(&1), Some(&false));
|
||||||
|
assert_eq!(second.lines.get(&7), Some(&true));
|
||||||
|
}
|
||||||
|
}
|
|
@ -152,7 +152,7 @@ pub struct Document {
|
||||||
/// update from the LSP
|
/// update from the LSP
|
||||||
pub inlay_hints_oudated: bool,
|
pub inlay_hints_oudated: bool,
|
||||||
|
|
||||||
path: Option<PathBuf>,
|
pub path: Option<PathBuf>,
|
||||||
relative_path: OnceCell<Option<PathBuf>>,
|
relative_path: OnceCell<Option<PathBuf>>,
|
||||||
encoding: &'static encoding::Encoding,
|
encoding: &'static encoding::Encoding,
|
||||||
has_bom: bool,
|
has_bom: bool,
|
||||||
|
|
|
@ -420,7 +420,7 @@ pub fn get_terminal_provider() -> Option<TerminalConfig> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(windows, target_arch = "wasm32")))]
|
#[cfg(not(any(windows, target_os = "wasm32")))]
|
||||||
pub fn get_terminal_provider() -> Option<TerminalConfig> {
|
pub fn get_terminal_provider() -> Option<TerminalConfig> {
|
||||||
use helix_stdx::env::{binary_exists, env_var_is_set};
|
use helix_stdx::env::{binary_exists, env_var_is_set};
|
||||||
|
|
||||||
|
@ -711,6 +711,8 @@ pub enum GutterType {
|
||||||
Spacer,
|
Spacer,
|
||||||
/// Highlight local changes
|
/// Highlight local changes
|
||||||
Diff,
|
Diff,
|
||||||
|
/// Highlight local changes
|
||||||
|
Coverage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for GutterType {
|
impl std::str::FromStr for GutterType {
|
||||||
|
@ -722,8 +724,9 @@ impl std::str::FromStr for GutterType {
|
||||||
"spacer" => Ok(Self::Spacer),
|
"spacer" => Ok(Self::Spacer),
|
||||||
"line-numbers" => Ok(Self::LineNumbers),
|
"line-numbers" => Ok(Self::LineNumbers),
|
||||||
"diff" => Ok(Self::Diff),
|
"diff" => Ok(Self::Diff),
|
||||||
|
"coverage" => Ok(Self::Coverage),
|
||||||
_ => anyhow::bail!(
|
_ => anyhow::bail!(
|
||||||
"Gutter type can only be `diagnostics`, `spacer`, `line-numbers` or `diff`."
|
"Gutter type can only be `diagnostics`, `spacer`, `line-numbers`, `diff` or `coverage`."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use std::fmt::Write;
|
use std::{fmt::Write, path::PathBuf};
|
||||||
|
|
||||||
use helix_core::syntax::LanguageServerFeature;
|
use helix_core::syntax::LanguageServerFeature;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
coverage,
|
||||||
editor::GutterType,
|
editor::GutterType,
|
||||||
graphics::{Style, UnderlineStyle},
|
graphics::{Style, UnderlineStyle},
|
||||||
Document, Editor, Theme, View,
|
Document, Editor, Theme, View,
|
||||||
|
@ -32,6 +33,7 @@ impl GutterType {
|
||||||
GutterType::LineNumbers => line_numbers(editor, doc, view, theme, is_focused),
|
GutterType::LineNumbers => line_numbers(editor, doc, view, theme, is_focused),
|
||||||
GutterType::Spacer => padding(editor, doc, view, theme, is_focused),
|
GutterType::Spacer => padding(editor, doc, view, theme, is_focused),
|
||||||
GutterType::Diff => diff(editor, doc, view, theme, is_focused),
|
GutterType::Diff => diff(editor, doc, view, theme, is_focused),
|
||||||
|
GutterType::Coverage => coverage(editor, doc, view, theme, is_focused),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +43,7 @@ impl GutterType {
|
||||||
GutterType::LineNumbers => line_numbers_width(view, doc),
|
GutterType::LineNumbers => line_numbers_width(view, doc),
|
||||||
GutterType::Spacer => 1,
|
GutterType::Spacer => 1,
|
||||||
GutterType::Diff => 1,
|
GutterType::Diff => 1,
|
||||||
|
GutterType::Coverage => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,6 +142,50 @@ pub fn diff<'doc>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn coverage<'doc>(
|
||||||
|
_editor: &'doc Editor,
|
||||||
|
doc: &'doc Document,
|
||||||
|
_view: &View,
|
||||||
|
theme: &Theme,
|
||||||
|
_is_focused: bool,
|
||||||
|
) -> GutterFn<'doc> {
|
||||||
|
let covered = theme.get("diff.plus.gutter");
|
||||||
|
let not_covered = theme.get("diff.minus.gutter");
|
||||||
|
if let Some(cov) = coverage::parse(PathBuf::from("report/coverage.xml")) {
|
||||||
|
if let Some(mut path) = doc.path.clone() {
|
||||||
|
if let Ok(cwd) = std::env::current_dir() {
|
||||||
|
if let Ok(tmp) = path.strip_prefix(cwd) {
|
||||||
|
path = tmp.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(file) = cov.files.get(&path) {
|
||||||
|
let this_file = coverage::FileCoverage {
|
||||||
|
lines: file.lines.clone(),
|
||||||
|
};
|
||||||
|
return Box::new(
|
||||||
|
move |line: usize,
|
||||||
|
_selected: bool,
|
||||||
|
_first_visual_line: bool,
|
||||||
|
out: &mut String| {
|
||||||
|
if let Some(line_coverage) = this_file.lines.get(&(line as u32)) {
|
||||||
|
let (icon, style) = if *line_coverage {
|
||||||
|
("┃", covered)
|
||||||
|
} else {
|
||||||
|
("┃", not_covered)
|
||||||
|
};
|
||||||
|
write!(out, "{}", icon).unwrap();
|
||||||
|
Some(style)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Box::new(move |_, _, _, _| None);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn line_numbers<'doc>(
|
pub fn line_numbers<'doc>(
|
||||||
editor: &'doc Editor,
|
editor: &'doc Editor,
|
||||||
doc: &'doc Document,
|
doc: &'doc Document,
|
||||||
|
|
|
@ -4,6 +4,7 @@ pub mod macros;
|
||||||
pub mod annotations;
|
pub mod annotations;
|
||||||
pub mod base64;
|
pub mod base64;
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
|
pub mod coverage;
|
||||||
pub mod document;
|
pub mod document;
|
||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
|
Loading…
Reference in New Issue