task: Expose current buffer language as $ZED_LANGUAGE variable (#51614)

Matt Van Horn , Matt Van Horn , and Kirill Bulatov created

Closes #12628

Adds a `$ZED_LANGUAGE` task variable that resolves to the language name
of the active buffer (e.g., "Rust", "Python", "Shell Script"). This lets
tasks adapt behavior based on language without parsing file extensions.

Use cases from the issue:
- Pass syntax highlighting language to external tools (e.g., `rg --type
$ZED_LANGUAGE`)
- Adjust comment wrapping width per language
- Handle extensionless files and untitled buffers that have a language
assigned

VS Code provides equivalent functionality via
`${command:activeEditorLanguageId}`. Neovim exposes it as `&filetype`.

## Changes

- Added `Language` variant to `VariableName` in
`crates/task/src/task.rs`
- Populated from `buffer.language().name()` in
`BasicContextProvider::build_context`
(`crates/project/src/task_inventory.rs`), following the same pattern as
`File` and `Stem`
- Updated test fixtures in `crates/tasks_ui/src/tasks_ui.rs` to include
the new variable
- Added `ZED_LANGUAGE` to the variable list in `docs/src/tasks.md`

## Testing

Updated the existing `test_task_variables` test in `tasks_ui` to verify
`ZED_LANGUAGE` resolves to "Rust" and "TypeScript" for the respective
test buffers.

This contribution was developed with AI assistance (Claude Code).

Release Notes:

- Added `$ZED_LANGUAGE` task variable that exposes the current buffer's
language name

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Kirill Bulatov <mail4score@gmail.com>

Change summary

crates/project/src/task_inventory.rs |  4 ++++
crates/task/src/task.rs              |  4 ++++
crates/tasks_ui/src/tasks_ui.rs      | 13 +++++++++++--
docs/src/tasks.md                    |  1 +
4 files changed, 20 insertions(+), 2 deletions(-)

Detailed changes

crates/project/src/task_inventory.rs 🔗

@@ -985,6 +985,10 @@ impl ContextProvider for BasicContextProvider {
             task_variables.insert(VariableName::File, path.to_string_lossy().into_owned());
         }
 
+        if let Some(language) = buffer.language() {
+            task_variables.insert(VariableName::Language, language.name().to_string());
+        }
+
         Task::ready(Ok(task_variables))
     }
 }

crates/task/src/task.rs 🔗

@@ -172,6 +172,8 @@ pub enum VariableName {
     Column,
     /// Text from the latest selection.
     SelectedText,
+    /// The language of the currently opened buffer (e.g., "Rust", "Python").
+    Language,
     /// The symbol selected by the symbol tagging system, specifically the @run capture in a runnables.scm
     RunnableSymbol,
     /// Open a Picker to select a process ID to use in place
@@ -209,6 +211,7 @@ impl FromStr for VariableName {
             "SYMBOL" => Self::Symbol,
             "RUNNABLE_SYMBOL" => Self::RunnableSymbol,
             "SELECTED_TEXT" => Self::SelectedText,
+            "LANGUAGE" => Self::Language,
             "ROW" => Self::Row,
             "COLUMN" => Self::Column,
             _ => {
@@ -243,6 +246,7 @@ impl std::fmt::Display for VariableName {
             Self::Row => write!(f, "{ZED_VARIABLE_NAME_PREFIX}ROW"),
             Self::Column => write!(f, "{ZED_VARIABLE_NAME_PREFIX}COLUMN"),
             Self::SelectedText => write!(f, "{ZED_VARIABLE_NAME_PREFIX}SELECTED_TEXT"),
+            Self::Language => write!(f, "{ZED_VARIABLE_NAME_PREFIX}LANGUAGE"),
             Self::RunnableSymbol => write!(f, "{ZED_VARIABLE_NAME_PREFIX}RUNNABLE_SYMBOL"),
             Self::PickProcessId => write!(f, "{ZED_VARIABLE_NAME_PREFIX}PICK_PID"),
             Self::Custom(s) => write!(

crates/tasks_ui/src/tasks_ui.rs 🔗

@@ -439,7 +439,10 @@ mod tests {
         let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
         let rust_language = Arc::new(
             Language::new(
-                LanguageConfig::default(),
+                LanguageConfig {
+                    name: "Rust".into(),
+                    ..Default::default()
+                },
                 Some(tree_sitter_rust::LANGUAGE.into()),
             )
             .with_outline_query(
@@ -455,7 +458,10 @@ mod tests {
 
         let typescript_language = Arc::new(
             Language::new(
-                LanguageConfig::default(),
+                LanguageConfig {
+                    name: "TypeScript".into(),
+                    ..Default::default()
+                },
                 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
             )
             .with_outline_query(
@@ -534,6 +540,7 @@ mod tests {
                     (VariableName::WorktreeRoot, path!("/dir").into()),
                     (VariableName::Row, "1".into()),
                     (VariableName::Column, "1".into()),
+                    (VariableName::Language, "Rust".into()),
                 ]),
                 project_env: HashMap::default(),
             }
@@ -568,6 +575,7 @@ mod tests {
                     (VariableName::Column, "15".into()),
                     (VariableName::SelectedText, "is_i".into()),
                     (VariableName::Symbol, "this_is_a_rust_file".into()),
+                    (VariableName::Language, "Rust".into()),
                 ]),
                 project_env: HashMap::default(),
             }
@@ -596,6 +604,7 @@ mod tests {
                     (VariableName::Row, "1".into()),
                     (VariableName::Column, "1".into()),
                     (VariableName::Symbol, "this_is_a_test".into()),
+                    (VariableName::Language, "TypeScript".into()),
                 ]),
                 project_env: HashMap::default(),
             }

docs/src/tasks.md 🔗

@@ -89,6 +89,7 @@ These variables allow you to pull information from the current editor and use it
 - `ZED_STEM`: stem (filename without extension) of the currently opened file (e.g. `main`)
 - `ZED_SYMBOL`: currently selected symbol; should match the last symbol shown in a symbol breadcrumb (e.g. `mod tests > fn test_task_contexts`)
 - `ZED_SELECTED_TEXT`: currently selected text
+- `ZED_LANGUAGE`: language of the currently opened buffer (e.g. `Rust`, `Python`, `Shell Script`)
 - `ZED_WORKTREE_ROOT`: absolute path to the root of the current worktree. (e.g. `/Users/my-user/path/to/project`)
 - `ZED_CUSTOM_RUST_PACKAGE`: (Rust-specific) name of the parent package of $ZED_FILE source file.