• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ilai-deutel / kibi / 21543479100

31 Jan 2026 11:02AM UTC coverage: 69.562% (+1.3%) from 68.233%
21543479100

Pull #416

github

web-flow
Merge b5f4c1f82 into 2685b408a
Pull Request #416: Add Ctrl+/ comment toggle functionality

51 of 52 new or added lines in 4 files covered. (98.08%)

1 existing line in 1 file now uncovered.

889 of 1278 relevant lines covered (69.56%)

1074.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.63
/src/syntax.rs
1
use std::fmt::{self, Display, Formatter};
2
use std::path::{Path, PathBuf};
3

4
use crate::config::{self, parse_value as pv, parse_values as pvs};
5

6
/// Type of syntax highlighting for a single rendered character.
7
///
8
/// Each `HLType` is associated with a color, via its discriminant. The ANSI
9
/// color is equal to the discriminant, modulo 100. The colors are described
10
/// here: <https://en.wikipedia.org/wiki/ANSI_escape_code#Colors>
11
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12
pub enum HlType {
13
    Normal = 39,     // Default foreground color
14
    Number = 31,     // Red
15
    Match = 46,      // Cyan
16
    String = 32,     // Green
17
    MlString = 132,  // Green
18
    Comment = 34,    // Blue
19
    MlComment = 134, // Blue
20
    Keyword1 = 33,   // Yellow
21
    Keyword2 = 35,   // Magenta
22
}
23

24
impl Display for HlType {
25
    /// Write the ANSI color escape sequence for the `HLType` using the given
26
    /// formatter.
27
    fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "\x1b[{}m", (*self as u32) % 100) }
×
28
}
29

30
/// Configuration for syntax highlighting.
31
#[derive(Clone, Debug, Default, Eq, PartialEq)]
32
pub struct Conf {
33
    /// The name of the language, e.g. "Rust".
34
    pub name: String,
35
    /// Whether to highlight numbers.
36
    pub highlight_numbers: bool,
37
    /// Quotes for single-line strings.
38
    pub sl_string_quotes: Vec<char>,
39
    /// The tokens that starts a single-line comment, e.g. "//".
40
    pub sl_comment_start: Vec<String>,
41
    /// The tokens that start and end a multi-line comment, e.g. ("/*", "*/").
42
    pub ml_comment_delims: Option<(String, String)>,
43
    /// The token that start and end a multi-line strings, e.g. "\"\"\"" for
44
    /// Python.
45
    pub ml_string_delim: Option<String>,
46
    /// Keywords to highlight and there corresponding `HLType` (typically
47
    /// `HLType::Keyword1` or `HLType::Keyword2`)
48
    pub keywords: Vec<(HlType, Vec<String>)>,
49
}
50

51
impl Conf {
52
    /// Return the syntax configuration corresponding to the given file
53
    /// name, if a matching INI file is found in a config directory.
54
    /// If no matching configuration is found, return the default.
55
    pub fn find(name: &str, data_dirs: &[String]) -> Self {
×
56
        for data_dir in data_dirs {
×
57
            match PathBuf::from(data_dir).join("syntax.d").read_dir() {
×
58
                Ok(dir_entries) =>
×
59
                    for dir_entry in dir_entries {
×
60
                        match dir_entry.map(|dir_entry| Self::parse(&dir_entry.path())) {
×
61
                            // sfix = suffixes
NEW
62
                            Ok((sc, sfix)) if sfix.iter().any(|s| name.ends_with(s)) => return sc,
×
63
                            Ok((..)) => (),
×
64
                            Err(e) => eprintln!("Error iterating through {data_dir}/syntax.d: {e}"),
×
65
                        }
66
                    },
67
                Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
×
68
                Err(e) => eprintln!("Error iterating through {data_dir}/syntax.d: {e}"),
×
69
            }
70
        }
71
        Self::default()
×
72
    }
×
73

74
    /// Load and parse a `SyntaxConf` from file.
75
    pub fn parse(path: &Path) -> (Self, Vec<String>) {
583✔
76
        let (mut sc, mut suffixes) = (Self::default(), Vec::new());
583✔
77
        config::process_ini_file(path, &mut |key, val| {
4,389✔
78
            match key {
4,389✔
79
                "name" => sc.name = pv(val)?,
4,389✔
80
                "extensions" => suffixes.extend(val.split(',').map(|u| format!(".{}", u.trim()))),
3,817✔
81
                "highlight_numbers" => sc.highlight_numbers = pv(val)?,
3,245✔
82
                "singleline_string_quotes" => sc.sl_string_quotes = pvs(val)?,
2,717✔
83
                "singleline_comment_start" => sc.sl_comment_start = pvs(val)?,
2,200✔
84
                "multiline_comment_delims" =>
1,683✔
85
                    sc.ml_comment_delims = match val.split_once(',') {
451✔
86
                        Some((v1, v2)) if !v2.contains(',') => Some((pv(v1)?, pv(v2)?)),
451✔
87
                        _ => return Err(format!("Expected 2 delimiters, got {val}")),
×
88
                    },
89
                "multiline_string_delim" => sc.ml_string_delim = Some(pv(val)?),
1,232✔
90
                "keywords_1" => sc.keywords.push((HlType::Keyword1, pvs(val)?)),
957✔
91
                "keywords_2" => sc.keywords.push((HlType::Keyword2, pvs(val)?)),
429✔
92
                _ => return Err(String::from("Invalid key")),
×
93
            }
94
            Ok(())
4,389✔
95
        });
4,389✔
96
        (sc, suffixes)
583✔
97
    }
583✔
98
}
99

100
#[cfg(test)]
101
#[cfg(not(target_family = "wasm"))] // No filesystem on wasm
102
mod tests {
103
    use std::collections::HashSet;
104
    use std::fs;
105

106
    use tempfile::TempDir;
107

108
    use super::*;
109

110
    #[test]
111
    fn syntax_d_files() {
11✔
112
        let mut file_count = 0;
11✔
113
        let mut syntax_names = HashSet::new();
11✔
114
        for path in fs::read_dir("./syntax.d").unwrap() {
572✔
115
            let (conf, extensions) = Conf::parse(&path.unwrap().path());
572✔
116
            assert!(!extensions.is_empty());
572✔
117
            syntax_names.insert(conf.name);
572✔
118
            file_count += 1;
572✔
119
        }
120
        assert!(file_count > 0);
11✔
121
        assert_eq!(file_count, syntax_names.len());
11✔
122
    }
11✔
123

124
    #[test]
125
    fn conf_from_invalid_path() {
11✔
126
        let tmp_dir = TempDir::new().expect("Could not create temporary directory");
11✔
127
        let tmp_path = tmp_dir.path().join("path_does_not_exist.ini");
11✔
128
        assert_eq!(Conf::parse(&tmp_path), (Conf::default(), Vec::<String>::new()));
11✔
129
    }
11✔
130
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc