Skip to main content

badness_parser/semantic/
completion.rs

1//! Static word lists and metadata used only by completion and hover.
2
3use std::collections::HashMap;
4use std::sync::LazyLock;
5
6use serde::Deserialize;
7
8// The baked `.sty`/`.cls` name lists are generated from TeX Live's tlpdb. File
9// order is completion rank; comments and the primary/secondary separator are not
10// candidates.
11const PACKAGE_NAMES_TXT: &str = include_str!("../../data/package_names.txt");
12const CLASS_NAMES_TXT: &str = include_str!("../../data/class_names.txt");
13
14static PACKAGE_NAMES: LazyLock<Vec<&'static str>> =
15    LazyLock::new(|| parse_name_list(PACKAGE_NAMES_TXT));
16static CLASS_NAMES: LazyLock<Vec<&'static str>> =
17    LazyLock::new(|| parse_name_list(CLASS_NAMES_TXT));
18
19fn parse_name_list(text: &'static str) -> Vec<&'static str> {
20    text.lines()
21        .filter(|line| !line.is_empty() && !line.starts_with('#') && *line != "---")
22        .collect()
23}
24
25/// All known `.sty` package name stems, in completion-rank order.
26pub fn package_names() -> &'static [&'static str] {
27    &PACKAGE_NAMES
28}
29
30/// All known `.cls` class name stems, in completion-rank order.
31pub fn class_names() -> &'static [&'static str] {
32    &CLASS_NAMES
33}
34
35const COLORS_JSON: &str = include_str!("../../data/colors.json");
36const TIKZ_LIBRARIES_JSON: &str = include_str!("../../data/tikz_libraries.json");
37const ARG_ENUMS_JSON: &str = include_str!("../../data/arg_enums.json");
38
39#[derive(Deserialize)]
40struct ColorsData {
41    names: Vec<String>,
42    models: Vec<String>,
43}
44
45#[derive(Deserialize)]
46struct TikzLibrariesData {
47    tikz: Vec<String>,
48    pgf: Vec<String>,
49}
50
51static COLORS: LazyLock<ColorsData> = LazyLock::new(|| {
52    serde_json::from_str(COLORS_JSON).expect("bundled data/colors.json must be valid")
53});
54static TIKZ_LIBRARIES: LazyLock<TikzLibrariesData> = LazyLock::new(|| {
55    serde_json::from_str(TIKZ_LIBRARIES_JSON)
56        .expect("bundled data/tikz_libraries.json must be valid")
57});
58static ARG_ENUMS: LazyLock<HashMap<String, HashMap<usize, Vec<String>>>> = LazyLock::new(|| {
59    serde_json::from_str(ARG_ENUMS_JSON).expect("bundled data/arg_enums.json must be valid")
60});
61
62fn as_static_slice(names: &'static [String]) -> Vec<&'static str> {
63    names.iter().map(String::as_str).collect()
64}
65
66/// Built-in color names for color-argument completion.
67pub fn color_names() -> &'static [&'static str] {
68    static NAMES: LazyLock<Vec<&'static str>> = LazyLock::new(|| as_static_slice(&COLORS.names));
69    &NAMES
70}
71
72/// Built-in color models for `\definecolor` model completion.
73pub fn color_models() -> &'static [&'static str] {
74    static MODELS: LazyLock<Vec<&'static str>> = LazyLock::new(|| as_static_slice(&COLORS.models));
75    &MODELS
76}
77
78/// Built-in TikZ library names.
79pub fn tikz_libraries() -> &'static [&'static str] {
80    static LIBS: LazyLock<Vec<&'static str>> =
81        LazyLock::new(|| as_static_slice(&TIKZ_LIBRARIES.tikz));
82    &LIBS
83}
84
85/// Built-in PGF library names.
86pub fn pgf_libraries() -> &'static [&'static str] {
87    static LIBS: LazyLock<Vec<&'static str>> =
88        LazyLock::new(|| as_static_slice(&TIKZ_LIBRARIES.pgf));
89    &LIBS
90}
91
92/// Suggested values for a command's brace-group argument.
93pub fn arg_enum_values(name: &str, index: usize) -> Option<&'static [String]> {
94    ARG_ENUMS.get(name)?.get(&index).map(Vec::as_slice)
95}
96
97type PackageMetaMap = phf::Map<&'static str, PackageMeta>;
98
99/// Shipped CTAN metadata for one package or class stem.
100#[derive(Debug, Clone, Copy)]
101pub struct PackageMeta {
102    pub desc: Option<&'static str>,
103    pub ctan: Option<&'static str>,
104}
105
106impl PackageMeta {
107    /// The canonical CTAN package page, when a catalogue id is known.
108    pub fn ctan_url(&self) -> Option<String> {
109        self.ctan.map(|id| format!("https://ctan.org/pkg/{id}"))
110    }
111}
112
113const fn meta(desc: Option<&'static str>, ctan: Option<&'static str>) -> PackageMeta {
114    PackageMeta { desc, ctan }
115}
116
117include!(concat!(env!("OUT_DIR"), "/package_metadata.rs"));
118
119/// The shipped CTAN metadata for a package or class stem.
120pub fn package_metadata(name: &str) -> Option<&'static PackageMeta> {
121    PACKAGE_METADATA.get(name)
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn arg_enums_load_and_resolve() {
130        assert_eq!(
131            arg_enum_values("pagenumbering", 0),
132            Some(
133                ["arabic", "roman", "Roman", "alph", "Alph"]
134                    .map(String::from)
135                    .as_slice()
136            )
137        );
138        assert!(arg_enum_values("pagestyle", 0).is_some());
139        assert!(arg_enum_values("pagestyle", 1).is_none());
140        assert!(arg_enum_values("definitelynotacommand", 0).is_none());
141    }
142
143    #[test]
144    fn package_metadata_resolves_stem_to_ctan_facts() {
145        let meta = package_metadata("amsmath").expect("amsmath in metadata DB");
146        assert_eq!(meta.desc, Some("AMS mathematical facilities for LaTeX"));
147        assert_eq!(
148            meta.ctan_url().as_deref(),
149            Some("https://ctan.org/pkg/latex-amsmath")
150        );
151        assert!(package_metadata("definitely-not-a-real-package").is_none());
152    }
153}