Skip to content

Commit 24fbc9b

Browse files
committed
feat(core): add bash syntax highlighting
Register the Tree-sitter Bash grammar for shell file extensions and Markdown code fences. This lets `.sh`, `.bash`, and `.zsh` files plus `bash`, `sh`, `shell`, and `zsh` fences use the compiled-in highlighter. Document Bash as syntax-highlighted and keep the LSP wording separate from the syntax list.
1 parent 2fd919c commit 24fbc9b

4 files changed

Lines changed: 54 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ thiserror = "1.0"
3131
tokio = { version = "1.41.0", features = ["full"] }
3232
toml = "0.8.10"
3333
tree-sitter = "0.25"
34+
tree-sitter-bash = "0.25.1"
3435
tree-sitter-javascript = "0.25.0"
3536
tree-sitter-json = "0.24.8"
3637
tree-sitter-md = "0.5.3"

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ cursor_line = true
9090
enabled = true
9191

9292
# Built-in syntax highlighting covers Rust, Markdown, JavaScript,
93-
# TypeScript/TSX, JSON, TOML, YAML, and Python. LSP defaults are provided for
94-
# those file types and start only when a matching file is opened.
93+
# TypeScript/TSX, JSON, TOML, YAML, Python, and Bash. LSP defaults are
94+
# provided for common language-server-backed file types and start only when a
95+
# matching file is opened.
9596

9697
[lsp.servers.typescript]
9798
command = "typescript-language-server"

src/highlighter.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ fn language_id_for_name(name: &str) -> Option<&'static str> {
244244
"yaml" | "yml" => Some("yaml"),
245245
"py" | "python" => Some("python"),
246246
"md" | "markdown" => Some("markdown"),
247+
"bash" | "sh" | "shell" | "zsh" => Some("bash"),
247248
_ => None,
248249
}
249250
}
@@ -319,6 +320,13 @@ fn language_definitions() -> Vec<LanguageDefinition> {
319320
highlight_query: tree_sitter_python::HIGHLIGHTS_QUERY,
320321
injection_query: None,
321322
},
323+
LanguageDefinition {
324+
id: "bash",
325+
extensions: &["sh", "bash", "zsh"],
326+
language: || tree_sitter_bash::LANGUAGE.into(),
327+
highlight_query: tree_sitter_bash::HIGHLIGHT_QUERY,
328+
injection_query: None,
329+
},
322330
]
323331
}
324332

@@ -426,6 +434,10 @@ mod tests {
426434
highlighter.language_id_for_file(Some("config.yml")),
427435
Some("yaml")
428436
);
437+
assert_eq!(
438+
highlighter.language_id_for_file(Some("script.sh")),
439+
Some("bash")
440+
);
429441
assert_eq!(highlighter.language_id_for_file(Some("LICENSE")), None);
430442
}
431443

@@ -441,6 +453,7 @@ mod tests {
441453
("toml", "value = true\n"),
442454
("yaml", "value: true\n"),
443455
("python", "def main():\n return True\n"),
456+
("bash", "if [ -f Cargo.toml ]; then\n echo yes\nfi\n"),
444457
];
445458
let mut highlighter = highlighter();
446459

@@ -461,6 +474,8 @@ mod tests {
461474
assert_eq!(highlighter.language_id_for_name("py"), Some("python"));
462475
assert_eq!(highlighter.language_id_for_name("yml"), Some("yaml"));
463476
assert_eq!(highlighter.language_id_for_name("ts"), Some("typescript"));
477+
assert_eq!(highlighter.language_id_for_name("sh"), Some("bash"));
478+
assert_eq!(highlighter.language_id_for_name("shell"), Some("bash"));
464479
assert_eq!(highlighter.language_id_for_name("unknown"), None);
465480
}
466481

@@ -528,6 +543,30 @@ mod tests {
528543
);
529544
}
530545

546+
#[test]
547+
fn markdown_highlights_bash_fenced_code() {
548+
let mut highlighter = highlighter();
549+
let code = "```sh\nif [ -f Cargo.toml ]; then\n echo yes\nfi\n```\n";
550+
let styles = highlighter
551+
.highlight_for_file(Some("README.md"), code)
552+
.unwrap();
553+
let if_start = code.find("if").unwrap();
554+
let echo_start = code.find("echo").unwrap();
555+
556+
assert!(
557+
styles
558+
.iter()
559+
.any(|style| style.start <= if_start && style.end >= if_start + 2),
560+
"fenced shell `if` keyword should be highlighted at Markdown byte offsets"
561+
);
562+
assert!(
563+
styles
564+
.iter()
565+
.any(|style| style.start <= echo_start && style.end >= echo_start + 4),
566+
"fenced shell command should be highlighted at Markdown byte offsets"
567+
);
568+
}
569+
531570
#[test]
532571
fn markdown_resolves_fenced_code_by_registered_extension() {
533572
let mut highlighter = highlighter();

0 commit comments

Comments
 (0)