…lines 1–65 unchanged (65 lines)
66}
67
68/// Returns the best guess language for a given file extension.
69///
70/// Note that some extensions are not uniquely identifiable so the highest probability result is
71/// returned. It may or may not be correct.
72/// Every file extension CodeDiff recognises, lower-cased, and the language it means. The one
73/// table behind [`language_for_extension`] and the README's language list.
74pub const EXTENSIONS: &[(&[&str], Language)] = &[
75 (&["bash", "sh"], Language::ShellScript),
76 (&["bazel"], Language::Bazel),
77 (&["c", "h"], Language::C),
78 (&["cc", "cpp", "cxx", "hpp", "hh", "hxx"], Language::CPP),
79 (&["cs"], Language::CSharp),
80 (&["css", "scss"], Language::CSS),
81 (&["dart"], Language::Dart),
82 (&["el"], Language::Lisp),
83 (&["go"], Language::Go),
84 (&["html", "htm"], Language::HTML),
85 (&["java"], Language::Java),
86 (&["js", "mjs", "cjs", "jsx"], Language::JavaScript),
87 (&["json"], Language::JSON),
88 (&["kt"], Language::Kotlin),
89 (&["lua"], Language::LUA),
90 (&["md", "markdown"], Language::MarkDown),
91 (&["php"], Language::PHP),
92 (&["proto"], Language::ProtoBuf),
93 (&["py", "pyi", "pyw"], Language::Python),
94 (&["r"], Language::R),
95 (&["rb"], Language::Ruby),
96 (&["rs"], Language::Rust),
97 (&["scala"], Language::Scala),
98 (&["sql"], Language::SQL),
99 (&["swift"], Language::Swift),
100 (&["ts", "mts", "cts"], Language::TypeScript),
101 (&["tsx"], Language::TSX),
102 (&["vim"], Language::Vimscript),
103 (&["yaml", "yml"], Language::YAML),
104 (&["xml", "xht", "xhtml"], Language::XML),
105];
106
107pub fn language_for_extension(ext: &str) -> Option<Language> {
108 EXTENSIONS
109 .iter()
110 .find(|(extensions, _)| extensions.contains(&ext))
111 .map(|(_, language)| *language)
112}
113
114/// The language's name as a person writes it, where the variant name is not that.
115pub fn human_name(language: Language) -> String {
116 match language {
117 Language::CPP => "C++".into(),
118 Language::CSharp => "C#".into(),
119 Language::LUA => "Lua".into(),
120 Language::MarkDown => "Markdown".into(),
121 Language::ProtoBuf => "Protocol Buffers".into(),
122 Language::ShellScript => "Shell (Bash)".into(),
123 Language::Lisp => "Emacs Lisp".into(),
124 other => other.to_string(),
125 }
126}
127
128/// The README's "Supported languages" section, generated so it cannot drift from this file: one
129/// table of the languages with a grammar, and the extensions that are recognised but diffed as
130/// plain text because no grammar is compiled in.
131pub fn supported_languages_markdown() -> String {
132 use strum::IntoEnumIterator;
133
134 let extensions_of = |language: Language| -> String {
135 EXTENSIONS
136 .iter()
137 .filter(|(_, candidate)| *candidate == language)
138 .flat_map(|(extensions, _)| extensions.iter())
139 .map(|extension| format!("`.{extension}`"))
140 .collect::<Vec<_>>()
141 .join(", ")
142 };
143 let mut with_grammar: Vec<Language> = Vec::new();
144 let mut plain_text: Vec<Language> = Vec::new();
145 for language in Language::iter().filter(|language| *language != Language::Unknown) {
146 if to_treesitter(&language).is_some() {
147 with_grammar.push(language);
148 } else {
149 plain_text.push(language);
150 }
151 }
152 with_grammar.sort_by_key(|language| human_name(*language).to_lowercase());
153 plain_text.sort_by_key(|language| human_name(*language).to_lowercase());
154
155 let mut out = String::new();
156 out.push_str(&format!(
157 "{} languages are parsed with a tree-sitter grammar and diffed structurally:\n\n",
158 with_grammar.len()
159 ));
160 out.push_str("| Language | File extensions |\n|---|---|\n");
161 for language in &with_grammar {
162 out.push_str(&format!(
163 "| {} | {} |\n",
164 human_name(*language),
165 extensions_of(*language)
166 ));
167 }
168 out.push_str(
169 "\nRecognised by extension but diffed as plain text, since no grammar is compiled in: ",
170 );
171 out.push_str(
172 &plain_text
173 .iter()
174 .map(|language| format!("{} ({})", human_name(*language), extensions_of(*language)))
175 .collect::<Vec<_>>()
176 .join(", "),
177 );
178 out.push_str(".\n");
179 out
180}
181
182pub fn to_treesitter(language: &Language) -> Option<tree_sitter::Language> {
183 match language {
184 // alphabetically sorted
185 Language::C => Some(tree_sitter_c::LANGUAGE.into()),
186 Language::CPP => Some(tree_sitter_cpp::LANGUAGE.into()),
…lines 187–339 unchanged (153 lines)