on master help sign in
Detect filetype from shebang line (#1001)
commit77dbbc73f9c9b6599bc39b18625285685fe2e4b1 authorath3 <45574139+ath3@users.noreply.github.com> dateMon 8 Nov 2021, 16:19 (5 years ago) parent29e6849
5 files changed+65−2
book/src/guides/adding_languages.md+2−1 helix-core/src/indent.rs+1 helix-core/src/syntax.rs+24 helix-view/src/document.rs+3−1 languages.toml+35
book/src/guides/adding_languages.md+2−1
@@ -33,10 +33,11 @@ These are the available keys and descriptions for the file.
3333| scope | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.<name>` or `text.<name>` in case of markup languages |
3434| injection-regex | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. |
3535| file-types | The filetypes of the language, for example `["yml", "yaml"]` |
36+| shebangs | The interpreters from the shebang line, for example `["sh", "bash"]` |
3637| roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
3738| auto-format | Whether to autoformat this language when saving |
3839| comment-token | The token to use as a comment-token |
39−| indent | The indent to use. Has sub keys `tab-width` and `unit` |
40+| indent | The indent to use. Has sub keys `tab-width` and `unit` |
4041| config | Language server configuration |
4142
4243## Queries
helix-core/src/indent.rs+1
@@ -450,6 +450,7 @@ where
450450 language: vec![LanguageConfiguration {
451451 scope: "source.rust".to_string(),
452452 file_types: vec!["rs".to_string()],
453+ shebangs: vec![],
453454 language_id: "Rust".to_string(),
454455 highlight_config: OnceCell::new(),
455456 config: None,
helix-core/src/syntax.rs+24
@@ -14,6 +14,8 @@ use std::{
1414 cell::RefCell,
1515 collections::{HashMap, HashSet},
1616 fmt,
17+ fs::File,
18+ io::Read,
1719 path::Path,
1820 sync::Arc,
1921};
@@ -52,6 +54,7 @@ pub struct LanguageConfiguration {
5254 pub language_id: String,
5355 pub scope: String, // source.rust
5456 pub file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc>
57+ pub shebangs: Vec<String>, // interpreter(s) associated with language
5558 pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
5659 pub comment_token: Option<String>,
5760
@@ -254,6 +257,7 @@ pub struct Loader {
254257 // highlight_names ?
255258 language_configs: Vec<Arc<LanguageConfiguration>>,
256259 language_config_ids_by_file_type: HashMap<String, usize>, // Vec<usize>
260+ language_config_ids_by_shebang: HashMap<String, usize>,
257261}
258262
259263impl Loader {
@@ -261,6 +265,7 @@ impl Loader {
261265 let mut loader = Self {
262266 language_configs: Vec::new(),
263267 language_config_ids_by_file_type: HashMap::new(),
268+ language_config_ids_by_shebang: HashMap::new(),
264269 };
265270
266271 for config in config.language {
@@ -273,6 +278,11 @@ impl Loader {
273278 .language_config_ids_by_file_type
274279 .insert(file_type.clone(), language_id);
275280 }
281+ for shebang in &config.shebangs {
282+ loader
283+ .language_config_ids_by_shebang
284+ .insert(shebang.clone(), language_id);
285+ }
276286
277287 loader.language_configs.push(Arc::new(config));
278288 }
@@ -298,6 +308,20 @@ impl Loader {
298308 // TODO: content_regex handling conflict resolution
299309 }
300310
311+ pub fn language_config_for_shebang(&self, path: &Path) -> Option<Arc<LanguageConfiguration>> {
312+ // Read the first 128 bytes of the file. If its a shebang line, try to find the language
313+ let file = File::open(path).ok()?;
314+ let mut buf = String::with_capacity(128);
315+ file.take(128).read_to_string(&mut buf).ok()?;
316+ static SHEBANG_REGEX: Lazy<Regex> =
317+ Lazy::new(|| Regex::new(r"^#!\s*(?:\S*[/\\](?:env\s+)?)?([^\s\.\d]+)").unwrap());
318+ let configuration_id = SHEBANG_REGEX
319+ .captures(&buf)
320+ .and_then(|cap| self.language_config_ids_by_shebang.get(&cap[1]));
321+
322+ configuration_id.and_then(|&id| self.language_configs.get(id).cloned())
323+ }
324+
301325 pub fn language_config_for_scope(&self, scope: &str) -> Option<Arc<LanguageConfiguration>> {
302326 self.language_configs
303327 .iter()
helix-view/src/document.rs+3−1
@@ -494,7 +494,9 @@ impl Document {
494494 /// Detect the programming language based on the file type.
495495 pub fn detect_language(&mut self, theme: Option<&Theme>, config_loader: &syntax::Loader) {
496496 if let Some(path) = &self.path {
497− let language_config = config_loader.language_config_for_file_name(path);
497+ let language_config = config_loader
498+ .language_config_for_file_name(path)
499+ .or_else(|| config_loader.language_config_for_shebang(path));
498500 self.set_language(theme, language_config);
499501 }
500502 }
languages.toml+35
@@ -3,6 +3,7 @@ name = "rust"
33scope = "source.rust"
44injection-regex = "rust"
55file-types = ["rs"]
6+shebangs = []
67roots = []
78auto-format = true
89comment-token = "//"
@@ -17,6 +18,7 @@ name = "toml"
1718scope = "source.toml"
1819injection-regex = "toml"
1920file-types = ["toml"]
21+shebangs = []
2022roots = []
2123comment-token = "#"
2224
@@ -27,6 +29,7 @@ name = "protobuf"
2729scope = "source.proto"
2830injection-regex = "protobuf"
2931file-types = ["proto"]
32+shebangs = []
3033roots = []
3134comment-token = "//"
3235
@@ -37,6 +40,7 @@ name = "elixir"
3740scope = "source.elixir"
3841injection-regex = "elixir"
3942file-types = ["ex", "exs"]
43+shebangs = []
4044roots = []
4145comment-token = "#"
4246
@@ -48,6 +52,7 @@ name = "mint"
4852scope = "source.mint"
4953injection-regex = "mint"
5054file-types = ["mint"]
55+shebangs = []
5156roots = []
5257comment-token = "//"
5358
@@ -59,6 +64,7 @@ name = "json"
5964scope = "source.json"
6065injection-regex = "json"
6166file-types = ["json"]
67+shebangs = []
6268roots = []
6369
6470indent = { tab-width = 2, unit = " " }
@@ -68,6 +74,7 @@ name = "c"
6874scope = "source.c"
6975injection-regex = "c"
7076file-types = ["c"] # TODO: ["h"]
77+shebangs = []
7178roots = []
7279comment-token = "//"
7380
@@ -79,6 +86,7 @@ name = "cpp"
7986scope = "source.cpp"
8087injection-regex = "cpp"
8188file-types = ["cc", "hh", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino"]
89+shebangs = []
8290roots = []
8391comment-token = "//"
8492
@@ -90,6 +98,7 @@ name = "c-sharp"
9098scope = "source.csharp"
9199injection-regex = "c-?sharp"
92100file-types = ["cs"]
101+shebangs = []
93102roots = []
94103comment-token = "//"
95104
@@ -100,6 +109,7 @@ name = "go"
100109scope = "source.go"
101110injection-regex = "go"
102111file-types = ["go"]
112+shebangs = []
103113roots = ["Gopkg.toml", "go.mod"]
104114auto-format = true
105115comment-token = "//"
@@ -113,6 +123,7 @@ name = "javascript"
113123scope = "source.js"
114124injection-regex = "^(js|javascript)$"
115125file-types = ["js", "mjs"]
126+shebangs = []
116127roots = []
117128comment-token = "//"
118129# TODO: highlights-jsx, highlights-params
@@ -124,6 +135,7 @@ name = "typescript"
124135scope = "source.ts"
125136injection-regex = "^(ts|typescript)$"
126137file-types = ["ts"]
138+shebangs = []
127139roots = []
128140# TODO: highlights-jsx, highlights-params
129141
@@ -135,6 +147,7 @@ name = "tsx"
135147scope = "source.tsx"
136148injection-regex = "^(tsx)$" # |typescript
137149file-types = ["tsx"]
150+shebangs = []
138151roots = []
139152# TODO: highlights-jsx, highlights-params
140153
@@ -146,6 +159,7 @@ name = "css"
146159scope = "source.css"
147160injection-regex = "css"
148161file-types = ["css"]
162+shebangs = []
149163roots = []
150164
151165indent = { tab-width = 2, unit = " " }
@@ -155,6 +169,7 @@ name = "html"
155169scope = "text.html.basic"
156170injection-regex = "html"
157171file-types = ["html"]
172+shebangs = []
158173roots = []
159174
160175indent = { tab-width = 2, unit = " " }
@@ -164,6 +179,7 @@ name = "python"
164179scope = "source.python"
165180injection-regex = "python"
166181file-types = ["py"]
182+shebangs = ["python"]
167183roots = []
168184comment-token = "#"
169185
@@ -176,6 +192,7 @@ name = "nix"
176192scope = "source.nix"
177193injection-regex = "nix"
178194file-types = ["nix"]
195+shebangs = []
179196roots = []
180197comment-token = "#"
181198
@@ -187,6 +204,7 @@ name = "ruby"
187204scope = "source.ruby"
188205injection-regex = "ruby"
189206file-types = ["rb"]
207+shebangs = ["ruby"]
190208roots = []
191209comment-token = "#"
192210
@@ -198,6 +216,7 @@ name = "bash"
198216scope = "source.bash"
199217injection-regex = "bash"
200218file-types = ["sh", "bash"]
219+shebangs = ["sh", "bash", "dash"]
201220roots = []
202221comment-token = "#"
203222
@@ -209,6 +228,7 @@ name = "php"
209228scope = "source.php"
210229injection-regex = "php"
211230file-types = ["php"]
231+shebangs = ["php"]
212232roots = []
213233
214234indent = { tab-width = 4, unit = " " }
@@ -218,6 +238,7 @@ name = "latex"
218238scope = "source.tex"
219239injection-regex = "tex"
220240file-types = ["tex"]
241+shebangs = []
221242roots = []
222243comment-token = "%"
223244
@@ -228,6 +249,7 @@ name = "julia"
228249scope = "source.julia"
229250injection-regex = "julia"
230251file-types = ["jl"]
252+shebangs = []
231253roots = []
232254comment-token = "#"
233255language-server = { command = "julia", args = [
@@ -253,6 +275,7 @@ name = "java"
253275scope = "source.java"
254276injection-regex = "java"
255277file-types = ["java"]
278+shebangs = []
256279roots = []
257280indent = { tab-width = 4, unit = " " }
258281
@@ -261,6 +284,7 @@ name = "ledger"
261284scope = "source.ledger"
262285injection-regex = "ledger"
263286file-types = ["ldg", "ledger", "journal"]
287+shebangs = []
264288roots = []
265289comment-token = ";"
266290indent = { tab-width = 4, unit = " " }
@@ -270,6 +294,7 @@ name = "ocaml"
270294scope = "source.ocaml"
271295injection-regex = "ocaml"
272296file-types = ["ml"]
297+shebangs = []
273298roots = []
274299comment-token = "(**)"
275300indent = { tab-width = 2, unit = " " }
@@ -278,6 +303,7 @@ indent = { tab-width = 2, unit = " " }
278303name = "ocaml-interface"
279304scope = "source.ocaml.interface"
280305file-types = ["mli"]
306+shebangs = []
281307roots = []
282308comment-token = "(**)"
283309indent = { tab-width = 2, unit = " "}
@@ -286,6 +312,7 @@ indent = { tab-width = 2, unit = " "}
286312name = "lua"
287313scope = "source.lua"
288314file-types = ["lua"]
315+shebangs = []
289316roots = []
290317comment-token = "--"
291318indent = { tab-width = 2, unit = " " }
@@ -295,6 +322,7 @@ name = "svelte"
295322scope = "source.svelte"
296323injection-regex = "svelte"
297324file-types = ["svelte"]
325+shebangs = []
298326roots = []
299327indent = { tab-width = 2, unit = " " }
300328language-server = { command = "svelteserver", args = ["--stdio"] }
@@ -305,6 +333,7 @@ name = "vue"
305333scope = "source.vue"
306334injection-regex = "vue"
307335file-types = ["vue"]
336+shebangs = []
308337roots = []
309338indent = { tab-width = 2, unit = " " }
310339
@@ -312,6 +341,7 @@ indent = { tab-width = 2, unit = " " }
312341name = "yaml"
313342scope = "source.yaml"
314343file-types = ["yml", "yaml"]
344+shebangs = []
315345roots = []
316346comment-token = "#"
317347indent = { tab-width = 2, unit = " " }
@@ -331,6 +361,7 @@ name = "zig"
331361scope = "source.zig"
332362injection-regex = "zig"
333363file-types = ["zig"]
364+shebangs = []
334365roots = ["build.zig"]
335366auto-format = true
336367comment-token = "//"
@@ -343,6 +374,7 @@ name = "prolog"
343374scope = "source.prolog"
344375roots = []
345376file-types = ["pl", "prolog"]
377+shebangs = ["swipl"]
346378comment-token = "%"
347379
348380language-server = { command = "swipl", args = [
@@ -354,6 +386,7 @@ language-server = { command = "swipl", args = [
354386name = "tsq"
355387scope = "source.tsq"
356388file-types = ["scm"]
389+shebangs = []
357390roots = []
358391comment-token = ";"
359392indent = { tab-width = 2, unit = " " }
@@ -362,6 +395,7 @@ indent = { tab-width = 2, unit = " " }
362395name = "cmake"
363396scope = "source.cmake"
364397file-types = ["cmake", "CMakeLists.txt"]
398+shebangs = []
365399roots = []
366400comment-token = "#"
367401indent = { tab-width = 2, unit = " " }
@@ -371,6 +405,7 @@ language-server = { command = "cmake-language-server" }
371405name = "perl"
372406scope = "source.perl"
373407file-types = ["pl", "pm"]
408+shebangs = ["perl"]
374409roots = []
375410comment-token = "#"
376411indent = { tab-width = 2, unit = " " }