1use std::borrow::Cow;
2use std::path::Path;
3
4use tree_sitter::Language;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Lang {
9 Swift,
10 TypeScript,
11 Tsx,
12 JavaScript,
14 Python,
15 Go,
16 Rust,
17 Kotlin,
18}
19
20impl Lang {
21 pub fn from_path(path: &str) -> Option<Lang> {
22 let ext = Path::new(path).extension()?.to_str()?;
23 match ext {
24 "swift" => Some(Lang::Swift),
25 "ts" | "mts" | "cts" => Some(Lang::TypeScript),
26 "tsx" => Some(Lang::Tsx),
27 "js" | "mjs" | "cjs" | "jsx" => Some(Lang::JavaScript),
28 "py" | "pyi" => Some(Lang::Python),
29 "go" => Some(Lang::Go),
30 "rs" => Some(Lang::Rust),
31 "kt" | "kts" => Some(Lang::Kotlin),
32 _ => None,
33 }
34 }
35
36 pub fn name(self) -> &'static str {
37 match self {
38 Lang::Swift => "Swift",
39 Lang::TypeScript => "TypeScript",
40 Lang::Tsx => "TSX",
41 Lang::JavaScript => "JavaScript",
42 Lang::Python => "Python",
43 Lang::Go => "Go",
44 Lang::Rust => "Rust",
45 Lang::Kotlin => "Kotlin",
46 }
47 }
48
49 pub fn grammar(self) -> Language {
50 match self {
51 Lang::Swift => tree_sitter_swift::LANGUAGE.into(),
52 Lang::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
53 Lang::Tsx | Lang::JavaScript => tree_sitter_typescript::LANGUAGE_TSX.into(),
54 Lang::Python => tree_sitter_python::LANGUAGE.into(),
55 Lang::Go => tree_sitter_go::LANGUAGE.into(),
56 Lang::Rust => tree_sitter_rust::LANGUAGE.into(),
57 Lang::Kotlin => tree_sitter_kotlin_ng::LANGUAGE.into(),
58 }
59 }
60
61 pub fn is_comment(self, kind: &str) -> bool {
62 match self {
63 Lang::Swift => matches!(kind, "comment" | "multiline_comment"),
64 Lang::TypeScript | Lang::Tsx | Lang::JavaScript => {
65 matches!(kind, "comment" | "html_comment")
66 }
67 Lang::Python | Lang::Go => kind == "comment",
68 Lang::Rust | Lang::Kotlin => matches!(kind, "line_comment" | "block_comment"),
69 }
70 }
71}
72
73pub fn parsable(lang: Lang, src: &str) -> Cow<'_, str> {
86 if lang != Lang::Swift {
87 return Cow::Borrowed(src);
88 }
89 let mut out = src.as_bytes().to_vec();
90 let mut blank = |range: std::ops::Range<usize>, with: &[u8]| {
91 out[range.clone()].copy_from_slice(&with[..range.len()]);
92 };
93
94 for (at, _) in src.match_indices("()") {
95 let before = src[..at].trim_end();
96 let after = src[at + 2..].trim_start();
97 let value_position = matches!(before.chars().last(), Some('(' | ',' | ':' | '=' | '?'))
98 && !before.ends_with("->")
99 && !["->", "throws", "async"]
100 .iter()
101 .any(|word| after.starts_with(word));
102 if value_position {
103 blank(at..at + 2, b"[]");
104 }
105 }
106 for (at, _) in src.match_indices("await ") {
107 let before = src[..at].trim_end();
108 let opens_condition = ["if", "while", "guard"].iter().any(|keyword| {
109 before.ends_with(keyword)
110 && !before[..before.len() - keyword.len()]
111 .ends_with(|c: char| c.is_alphanumeric() || c == '_')
112 });
113 if opens_condition {
114 blank(at..at + 5, b" ");
115 }
116 }
117 for (at, _) in src.match_indices("nonisolated(unsafe)") {
118 blank(at + 11..at + 19, b" ");
119 }
120
121 if out == src.as_bytes() {
122 return Cow::Borrowed(src);
123 }
124 Cow::Owned(String::from_utf8(out).unwrap_or_else(|_| src.to_string()))
126}