Enhance programming language support in syntax highlighting by adding aliases and new languages (#9471)

## Description

`ProgrammingLanguage::to_extension` in `app/src/ai/agent/mod.rs` is the choke point for syntax highlighting in AI blocks. When the agent emits a markdown code fence, the language token becomes a `ProgrammingLanguage`, and `to_extension()` is called to construct a `snippet.<ext>` path that `set_language_with_path` uses to look up the tree-sitter grammar (see `app/src/ai/blocklist/block.rs:2607-2617`, `block.rs:2675`, and `block/cli.rs:732-740`). If `to_extension` returns `None`, `set_language_with_path` is never called and the block renders as plain text.

The function omits a number of languages that the `languages` crate fully supports via `language_by_filename`, so common AI code-fence labels render with no syntax highlighting today:

- `jsx`, `tsx` — every React snippet
- `vue` — every Vue component snippet
- `xml` — every config / Android / .NET snippet
- `dockerfile` (and `docker`, `containerfile`) — every container snippet
- `starlark`, `objective-c` (and `objc`)

This PR adds those arms and folds in the most common markdown-fence aliases (`rs`, `py`, `js`, `ts`, `yml`, `kt`, `rb`, `golang`, `terraform`, `tf`) so coverage matches `language_by_filename`. The TODO comment at the top of the function (`INT-605: Refactor so we don't have to edit this function and the languages crate`) anticipates exactly this kind of sync.

The `Self::Shell(Bash | Zsh | Fish)` arms are intentionally left as-is — only PowerShell currently exposes an extension, and extending the other shells looks like a separate behavior change.

## Testing

- Added `test_programming_language_to_extension` in `app/src/ai/agent/mod_test.rs` covering 46 cases: every existing arm + every new arm + every alias + a round-trip note that each returned extension is one `language_by_filename` actually accepts. The new arms (`jsx`, `tsx`, `xml`, `vue`, `dockerfile`, `starlark`, `objective-c`, `objc`) and aliases (`rs`, `golang`, `py`, `js`, `ts`, `yml`, `c++`, `rb`, `kt`, `terraform`, `tf`, `docker`, `containerfile`) all fail on master and pass after the change.
- `cargo fmt -p warp_terminal -- --check` passes locally.

## Agent Mode
- [ ] Warp Agent Mode - This PR was created via Warp's AI Agent Mode

## Changelog Entries for Stable

CHANGELOG-BUG-FIX: AI code blocks tagged `vue`, `xml`, `dockerfile`, `jsx`, `tsx`, `objective-c`, or `starlark` now render with syntax highlighting. Common aliases like `rs`, `py`, `js`, `ts`, `yml`, `kt`, `rb`, `golang`, `terraform`, and `docker` are also recognized.
This commit is contained in:
Abdalla Eldoumani
2026-04-29 15:47:23 -06:00
committed by GitHub
parent 404bfbeb8f
commit af5eed14fe
2 changed files with 100 additions and 9 deletions

View File

@@ -717,13 +717,19 @@ impl ProgrammingLanguage {
#[cfg_attr(target_family = "wasm", allow(unused))]
pub fn to_extension(&self) -> Option<&str> {
match self {
// The arms below cover both canonical language names emitted by the agent (e.g.
// "rust", "kotlin") and common markdown code-fence aliases (e.g. "rs", "kt") to keep
// syntax highlighting working when the model uses either. The set of recognized
// languages here is kept in sync with `SUPPORTED_LANGUAGES` in the `languages` crate.
Self::Other(language) => match language.to_lowercase().as_str() {
"rust" => Some("rs"),
"go" => Some("go"),
"python" => Some("py"),
"javascript" => Some("js"),
"typescript" => Some("ts"),
"yaml" => Some("yaml"),
"rust" | "rs" => Some("rs"),
"go" | "golang" => Some("go"),
"python" | "py" => Some("py"),
"javascript" | "js" => Some("js"),
"typescript" | "ts" => Some("ts"),
"jsx" => Some("jsx"),
"tsx" => Some("tsx"),
"yaml" | "yml" => Some("yaml"),
"cpp" | "c++" => Some("cpp"),
"java" => Some("java"),
"groovy" => Some("java"),
@@ -733,17 +739,22 @@ impl ProgrammingLanguage {
"css" => Some("css"),
"c" => Some("c"),
"json" => Some("json"),
"hcl" => Some("hcl"),
"hcl" | "terraform" | "tf" => Some("hcl"),
"lua" => Some("lua"),
"ruby" => Some("rb"),
"ruby" | "rb" => Some("rb"),
"php" => Some("php"),
"toml" => Some("toml"),
"swift" => Some("swift"),
"kotlin" => Some("kt"),
"kotlin" | "kt" => Some("kt"),
"powershell" => Some("ps1"),
"elixir" => Some("exs"),
"scala" => Some("scala"),
"sql" => Some("sql"),
"objective-c" | "objc" => Some("m"),
"starlark" => Some("bzl"),
"xml" => Some("xml"),
"vue" => Some("vue"),
"dockerfile" | "docker" | "containerfile" => Some("dockerfile"),
_ => None,
},
Self::Shell(ShellType::PowerShell) => Some("ps1"),

View File

@@ -152,6 +152,86 @@ fn test_programming_language_from_string() {
);
}
#[test]
fn test_programming_language_to_extension() {
// Each entry is (markdown language token, expected extension). The expected extension
// must resolve back to a recognized language via `languages::language_by_filename` so that
// syntax highlighting is applied to the AI block.
let cases: &[(&str, &str)] = &[
// Canonical names.
("rust", "rs"),
("go", "go"),
("python", "py"),
("javascript", "js"),
("typescript", "ts"),
("yaml", "yaml"),
("cpp", "cpp"),
("java", "java"),
("c#", "cs"),
("csharp", "cs"),
("html", "html"),
("css", "css"),
("c", "c"),
("json", "json"),
("hcl", "hcl"),
("lua", "lua"),
("ruby", "rb"),
("php", "php"),
("toml", "toml"),
("swift", "swift"),
("kotlin", "kt"),
("powershell", "ps1"),
("elixir", "exs"),
("scala", "scala"),
("sql", "sql"),
// Languages newly covered by this fix — previously fell through to None and rendered
// without syntax highlighting in AI blocks even though the `languages` crate supports them.
("jsx", "jsx"),
("tsx", "tsx"),
("xml", "xml"),
("vue", "vue"),
("dockerfile", "dockerfile"),
("starlark", "bzl"),
("objective-c", "m"),
("objc", "m"),
// Common markdown code-fence aliases.
("rs", "rs"),
("golang", "go"),
("py", "py"),
("js", "js"),
("ts", "ts"),
("yml", "yaml"),
("c++", "cpp"),
("rb", "rb"),
("kt", "kt"),
("terraform", "hcl"),
("tf", "hcl"),
("docker", "dockerfile"),
("containerfile", "dockerfile"),
];
for (token, expected_extension) in cases {
let language = ProgrammingLanguage::from((*token).to_string());
assert_eq!(
language.to_extension(),
Some(*expected_extension),
"expected to_extension({token:?}) to be Some({expected_extension:?})",
);
}
// PowerShell remains the only Shell variant whose extension is exposed; this preserves
// existing behavior for the other Shell variants which are intentionally not extended here.
assert_eq!(
ProgrammingLanguage::Shell(ShellType::PowerShell).to_extension(),
Some("ps1"),
);
// Unrecognized tokens still return None.
assert_eq!(
ProgrammingLanguage::Other("definitely-not-a-language".to_string()).to_extension(),
None,
);
}
#[test]
fn format_for_copy_preserves_visual_markdown_sections() {
let output = AIAgentOutput {