go.rs

   1use anyhow::{Context as _, Result};
   2use async_trait::async_trait;
   3use collections::HashMap;
   4use futures::StreamExt;
   5use gpui::{App, AsyncApp, Entity, Task};
   6use http_client::github::latest_github_release;
   7pub use language::*;
   8use language::{
   9    LanguageName, LanguageToolchainStore, LspAdapterDelegate, LspInstaller,
  10    language_settings::LanguageSettings,
  11};
  12use lsp::{LanguageServerBinary, LanguageServerName};
  13
  14use project::lsp_store::language_server_settings;
  15use regex::Regex;
  16use serde_json::{Value, json};
  17use settings::SemanticTokenRules;
  18use smol::fs;
  19use std::{
  20    borrow::Cow,
  21    ffi::{OsStr, OsString},
  22    ops::Range,
  23    path::{Path, PathBuf},
  24    process::Output,
  25    str,
  26    sync::{
  27        Arc, LazyLock,
  28        atomic::{AtomicBool, Ordering::SeqCst},
  29    },
  30};
  31use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
  32use util::{ResultExt, fs::remove_matching, maybe, merge_json_value_into};
  33
  34pub(crate) fn semantic_token_rules() -> SemanticTokenRules {
  35    let content = grammars::get_file("go/semantic_token_rules.json")
  36        .expect("missing go/semantic_token_rules.json");
  37    let json = std::str::from_utf8(&content.data).expect("invalid utf-8 in semantic_token_rules");
  38    settings::parse_json_with_comments::<SemanticTokenRules>(json)
  39        .expect("failed to parse go semantic_token_rules.json")
  40}
  41
  42fn server_binary_arguments() -> Vec<OsString> {
  43    vec!["-mode=stdio".into()]
  44}
  45
  46#[derive(Copy, Clone)]
  47pub struct GoLspAdapter;
  48
  49impl GoLspAdapter {
  50    const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("gopls");
  51}
  52
  53static VERSION_REGEX: LazyLock<Regex> =
  54    LazyLock::new(|| Regex::new(r"\d+\.\d+\.\d+").expect("Failed to create VERSION_REGEX"));
  55
  56static GO_ESCAPE_SUBTEST_NAME_REGEX: LazyLock<Regex> = LazyLock::new(|| {
  57    Regex::new(r#"[.*+?^${}()|\[\]\\"']"#).expect("Failed to create GO_ESCAPE_SUBTEST_NAME_REGEX")
  58});
  59
  60const BINARY: &str = if cfg!(target_os = "windows") {
  61    "gopls.exe"
  62} else {
  63    "gopls"
  64};
  65
  66impl LspInstaller for GoLspAdapter {
  67    type BinaryVersion = Option<String>;
  68
  69    async fn fetch_latest_server_version(
  70        &self,
  71        delegate: &dyn LspAdapterDelegate,
  72        _: bool,
  73        cx: &mut AsyncApp,
  74    ) -> Result<Option<String>> {
  75        static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false);
  76
  77        const NOTIFICATION_MESSAGE: &str =
  78            "Could not install the Go language server `gopls`, because `go` was not found.";
  79
  80        if delegate.which("go".as_ref()).await.is_none() {
  81            if DID_SHOW_NOTIFICATION
  82                .compare_exchange(false, true, SeqCst, SeqCst)
  83                .is_ok()
  84            {
  85                cx.update(|cx| {
  86                    delegate.show_notification(NOTIFICATION_MESSAGE, cx);
  87                });
  88            }
  89            anyhow::bail!(
  90                "Could not install the Go language server `gopls`, because `go` was not found."
  91            );
  92        }
  93
  94        let release =
  95            latest_github_release("golang/tools", false, false, delegate.http_client()).await?;
  96        let version: Option<String> = release.tag_name.strip_prefix("gopls/v").map(str::to_string);
  97        if version.is_none() {
  98            log::warn!(
  99                "couldn't infer gopls version from GitHub release tag name '{}'",
 100                release.tag_name
 101            );
 102        }
 103        Ok(version)
 104    }
 105
 106    async fn check_if_user_installed(
 107        &self,
 108        delegate: &dyn LspAdapterDelegate,
 109        _: Option<Toolchain>,
 110        _: &AsyncApp,
 111    ) -> Option<LanguageServerBinary> {
 112        let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
 113        Some(LanguageServerBinary {
 114            path,
 115            arguments: server_binary_arguments(),
 116            env: None,
 117        })
 118    }
 119
 120    async fn fetch_server_binary(
 121        &self,
 122        version: Option<String>,
 123        container_dir: PathBuf,
 124        delegate: &dyn LspAdapterDelegate,
 125    ) -> Result<LanguageServerBinary> {
 126        let go = delegate.which("go".as_ref()).await.unwrap_or("go".into());
 127        let go_version_output = util::command::new_command(&go)
 128            .args(["version"])
 129            .output()
 130            .await
 131            .context("failed to get go version via `go version` command`")?;
 132        let go_version = parse_version_output(&go_version_output)?;
 133
 134        if let Some(version) = version {
 135            let binary_path = container_dir.join(format!("gopls_{version}_go_{go_version}"));
 136            if let Ok(metadata) = fs::metadata(&binary_path).await
 137                && metadata.is_file()
 138            {
 139                remove_matching(&container_dir, |entry| {
 140                    entry != binary_path && entry.file_name() != Some(OsStr::new("gobin"))
 141                })
 142                .await;
 143
 144                return Ok(LanguageServerBinary {
 145                    path: binary_path.to_path_buf(),
 146                    arguments: server_binary_arguments(),
 147                    env: None,
 148                });
 149            }
 150        } else if let Some(path) = get_cached_server_binary(&container_dir).await {
 151            return Ok(path);
 152        }
 153
 154        let gobin_dir = container_dir.join("gobin");
 155        fs::create_dir_all(&gobin_dir).await?;
 156        let install_output = util::command::new_command(go)
 157            .env("GO111MODULE", "on")
 158            .env("GOBIN", &gobin_dir)
 159            .args(["install", "golang.org/x/tools/gopls@latest"])
 160            .output()
 161            .await?;
 162
 163        if !install_output.status.success() {
 164            log::error!(
 165                "failed to install gopls via `go install`. stdout: {:?}, stderr: {:?}",
 166                String::from_utf8_lossy(&install_output.stdout),
 167                String::from_utf8_lossy(&install_output.stderr)
 168            );
 169            anyhow::bail!(
 170                "failed to install gopls with `go install`. Is `go` installed and in the PATH? Check logs for more information."
 171            );
 172        }
 173
 174        let installed_binary_path = gobin_dir.join(BINARY);
 175        let version_output = util::command::new_command(&installed_binary_path)
 176            .arg("version")
 177            .output()
 178            .await
 179            .context("failed to run installed gopls binary")?;
 180        let gopls_version = parse_version_output(&version_output)?;
 181        let binary_path = container_dir.join(format!("gopls_{gopls_version}_go_{go_version}"));
 182        fs::rename(&installed_binary_path, &binary_path).await?;
 183
 184        Ok(LanguageServerBinary {
 185            path: binary_path.to_path_buf(),
 186            arguments: server_binary_arguments(),
 187            env: None,
 188        })
 189    }
 190
 191    async fn cached_server_binary(
 192        &self,
 193        container_dir: PathBuf,
 194        _: &dyn LspAdapterDelegate,
 195    ) -> Option<LanguageServerBinary> {
 196        get_cached_server_binary(&container_dir).await
 197    }
 198}
 199
 200#[async_trait(?Send)]
 201impl LspAdapter for GoLspAdapter {
 202    fn name(&self) -> LanguageServerName {
 203        Self::SERVER_NAME
 204    }
 205
 206    async fn initialization_options(
 207        self: Arc<Self>,
 208        delegate: &Arc<dyn LspAdapterDelegate>,
 209        cx: &mut AsyncApp,
 210    ) -> Result<Option<serde_json::Value>> {
 211        let semantic_tokens_enabled = cx.update(|cx| {
 212            LanguageSettings::resolve(None, Some(&LanguageName::new("Go")), cx)
 213                .semantic_tokens
 214                .enabled()
 215        });
 216
 217        let mut default_config = json!({
 218            "usePlaceholders": false,
 219            "hints": {
 220                "assignVariableTypes": true,
 221                "compositeLiteralFields": true,
 222                "compositeLiteralTypes": true,
 223                "constantValues": true,
 224                "functionTypeParameters": true,
 225                "parameterNames": true,
 226                "rangeVariableTypes": true
 227            },
 228            "semanticTokens": semantic_tokens_enabled
 229        });
 230
 231        let project_initialization_options = cx.update(|cx| {
 232            language_server_settings(delegate.as_ref(), &self.name(), cx)
 233                .and_then(|s| s.initialization_options.clone())
 234        });
 235
 236        if let Some(override_options) = project_initialization_options {
 237            merge_json_value_into(override_options, &mut default_config);
 238        }
 239
 240        Ok(Some(default_config))
 241    }
 242
 243    async fn workspace_configuration(
 244        self: Arc<Self>,
 245        delegate: &Arc<dyn LspAdapterDelegate>,
 246        _: Option<Toolchain>,
 247        _: Option<lsp::Uri>,
 248        cx: &mut AsyncApp,
 249    ) -> Result<Value> {
 250        Ok(cx
 251            .update(|cx| {
 252                language_server_settings(delegate.as_ref(), &self.name(), cx)
 253                    .and_then(|settings| settings.settings.clone())
 254            })
 255            .unwrap_or_default())
 256    }
 257
 258    async fn label_for_completion(
 259        &self,
 260        completion: &lsp::CompletionItem,
 261        language: &Arc<Language>,
 262    ) -> Option<CodeLabel> {
 263        let label = &completion.label;
 264
 265        // Gopls returns nested fields and methods as completions.
 266        // To syntax highlight these, combine their final component
 267        // with their detail.
 268        let name_offset = label.rfind('.').unwrap_or(0);
 269
 270        match completion.kind.zip(completion.detail.as_ref()) {
 271            Some((lsp::CompletionItemKind::MODULE, detail)) => {
 272                let text = format!("{label} {detail}");
 273                let source = Rope::from(format!("import {text}").as_str());
 274                let runs = language.highlight_text(&source, 7..7 + text[name_offset..].len());
 275                let filter_range = completion
 276                    .filter_text
 277                    .as_deref()
 278                    .and_then(|filter_text| {
 279                        text.find(filter_text)
 280                            .map(|start| start..start + filter_text.len())
 281                    })
 282                    .unwrap_or(0..label.len());
 283                return Some(CodeLabel::new(text, filter_range, runs));
 284            }
 285            Some((
 286                lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE,
 287                detail,
 288            )) => {
 289                let text = format!("{label} {detail}");
 290                let source =
 291                    Rope::from(format!("var {} {}", &text[name_offset..], detail).as_str());
 292                let runs = adjust_runs(
 293                    name_offset,
 294                    language.highlight_text(&source, 4..4 + text[name_offset..].len()),
 295                );
 296                let filter_range = completion
 297                    .filter_text
 298                    .as_deref()
 299                    .and_then(|filter_text| {
 300                        text.find(filter_text)
 301                            .map(|start| start..start + filter_text.len())
 302                    })
 303                    .unwrap_or(0..label.len());
 304                return Some(CodeLabel::new(text, filter_range, runs));
 305            }
 306            Some((lsp::CompletionItemKind::STRUCT, _)) => {
 307                let text = format!("{label} struct {{}}");
 308                let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
 309                let runs = adjust_runs(
 310                    name_offset,
 311                    language.highlight_text(&source, 5..5 + text[name_offset..].len()),
 312                );
 313                let filter_range = completion
 314                    .filter_text
 315                    .as_deref()
 316                    .and_then(|filter_text| {
 317                        text.find(filter_text)
 318                            .map(|start| start..start + filter_text.len())
 319                    })
 320                    .unwrap_or(0..label.len());
 321                return Some(CodeLabel::new(text, filter_range, runs));
 322            }
 323            Some((lsp::CompletionItemKind::INTERFACE, _)) => {
 324                let text = format!("{label} interface {{}}");
 325                let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
 326                let runs = adjust_runs(
 327                    name_offset,
 328                    language.highlight_text(&source, 5..5 + text[name_offset..].len()),
 329                );
 330                let filter_range = completion
 331                    .filter_text
 332                    .as_deref()
 333                    .and_then(|filter_text| {
 334                        text.find(filter_text)
 335                            .map(|start| start..start + filter_text.len())
 336                    })
 337                    .unwrap_or(0..label.len());
 338                return Some(CodeLabel::new(text, filter_range, runs));
 339            }
 340            Some((lsp::CompletionItemKind::FIELD, detail)) => {
 341                let text = format!("{label} {detail}");
 342                let source =
 343                    Rope::from(format!("type T struct {{ {} }}", &text[name_offset..]).as_str());
 344                let runs = adjust_runs(
 345                    name_offset,
 346                    language.highlight_text(&source, 16..16 + text[name_offset..].len()),
 347                );
 348                let filter_range = completion
 349                    .filter_text
 350                    .as_deref()
 351                    .and_then(|filter_text| {
 352                        text.find(filter_text)
 353                            .map(|start| start..start + filter_text.len())
 354                    })
 355                    .unwrap_or(0..label.len());
 356                return Some(CodeLabel::new(text, filter_range, runs));
 357            }
 358            Some((lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD, detail)) => {
 359                if let Some(signature) = detail.strip_prefix("func") {
 360                    let text = format!("{label}{signature}");
 361                    let source = Rope::from(format!("func {} {{}}", &text[name_offset..]).as_str());
 362                    let runs = adjust_runs(
 363                        name_offset,
 364                        language.highlight_text(&source, 5..5 + text[name_offset..].len()),
 365                    );
 366                    let filter_range = completion
 367                        .filter_text
 368                        .as_deref()
 369                        .and_then(|filter_text| {
 370                            text.find(filter_text)
 371                                .map(|start| start..start + filter_text.len())
 372                        })
 373                        .unwrap_or(0..label.len());
 374                    return Some(CodeLabel::new(text, filter_range, runs));
 375                }
 376            }
 377            _ => {}
 378        }
 379        None
 380    }
 381
 382    async fn label_for_symbol(
 383        &self,
 384        symbol: &language::Symbol,
 385        language: &Arc<Language>,
 386    ) -> Option<CodeLabel> {
 387        let name = &symbol.name;
 388        let (text, filter_range, display_range) = match symbol.kind {
 389            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
 390                let text = format!("func {} () {{}}", name);
 391                let filter_range = 5..5 + name.len();
 392                let display_range = 0..filter_range.end;
 393                (text, filter_range, display_range)
 394            }
 395            lsp::SymbolKind::STRUCT => {
 396                let text = format!("type {} struct {{}}", name);
 397                let filter_range = 5..5 + name.len();
 398                let display_range = 0..text.len();
 399                (text, filter_range, display_range)
 400            }
 401            lsp::SymbolKind::INTERFACE => {
 402                let text = format!("type {} interface {{}}", name);
 403                let filter_range = 5..5 + name.len();
 404                let display_range = 0..text.len();
 405                (text, filter_range, display_range)
 406            }
 407            lsp::SymbolKind::CLASS => {
 408                let text = format!("type {} T", name);
 409                let filter_range = 5..5 + name.len();
 410                let display_range = 0..filter_range.end;
 411                (text, filter_range, display_range)
 412            }
 413            lsp::SymbolKind::CONSTANT => {
 414                let text = format!("const {} = nil", name);
 415                let filter_range = 6..6 + name.len();
 416                let display_range = 0..filter_range.end;
 417                (text, filter_range, display_range)
 418            }
 419            lsp::SymbolKind::VARIABLE => {
 420                let text = format!("var {} = nil", name);
 421                let filter_range = 4..4 + name.len();
 422                let display_range = 0..filter_range.end;
 423                (text, filter_range, display_range)
 424            }
 425            lsp::SymbolKind::MODULE => {
 426                let text = format!("package {}", name);
 427                let filter_range = 8..8 + name.len();
 428                let display_range = 0..filter_range.end;
 429                (text, filter_range, display_range)
 430            }
 431            _ => return None,
 432        };
 433
 434        Some(CodeLabel::new(
 435            text[display_range.clone()].to_string(),
 436            filter_range,
 437            language.highlight_text(&text.as_str().into(), display_range),
 438        ))
 439    }
 440
 441    fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> {
 442        static REGEX: LazyLock<Regex> =
 443            LazyLock::new(|| Regex::new(r"(?m)\n\s*").expect("Failed to create REGEX"));
 444        Some(REGEX.replace_all(message, "\n\n").to_string())
 445    }
 446}
 447
 448fn parse_version_output(output: &Output) -> Result<&str> {
 449    let version_stdout =
 450        str::from_utf8(&output.stdout).context("version command produced invalid utf8 output")?;
 451
 452    let version = VERSION_REGEX
 453        .find(version_stdout)
 454        .with_context(|| format!("failed to parse version output '{version_stdout}'"))?
 455        .as_str();
 456
 457    Ok(version)
 458}
 459
 460async fn get_cached_server_binary(container_dir: &Path) -> Option<LanguageServerBinary> {
 461    maybe!(async {
 462        let mut last_binary_path = None;
 463        let mut entries = fs::read_dir(container_dir).await?;
 464        while let Some(entry) = entries.next().await {
 465            let entry = entry?;
 466            if entry.file_type().await?.is_file()
 467                && entry
 468                    .file_name()
 469                    .to_str()
 470                    .is_some_and(|name| name.starts_with("gopls_"))
 471            {
 472                last_binary_path = Some(entry.path());
 473            }
 474        }
 475
 476        let path = last_binary_path.context("no cached binary")?;
 477        anyhow::Ok(LanguageServerBinary {
 478            path,
 479            arguments: server_binary_arguments(),
 480            env: None,
 481        })
 482    })
 483    .await
 484    .log_err()
 485}
 486
 487fn adjust_runs(
 488    delta: usize,
 489    mut runs: Vec<(Range<usize>, HighlightId)>,
 490) -> Vec<(Range<usize>, HighlightId)> {
 491    for (range, _) in &mut runs {
 492        range.start += delta;
 493        range.end += delta;
 494    }
 495    runs
 496}
 497
 498pub(crate) struct GoContextProvider;
 499
 500const GO_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("GO_PACKAGE"));
 501const GO_MODULE_ROOT_TASK_VARIABLE: VariableName =
 502    VariableName::Custom(Cow::Borrowed("GO_MODULE_ROOT"));
 503const GO_SUBTEST_NAME_TASK_VARIABLE: VariableName =
 504    VariableName::Custom(Cow::Borrowed("GO_SUBTEST_NAME"));
 505const GO_TABLE_TEST_CASE_NAME_TASK_VARIABLE: VariableName =
 506    VariableName::Custom(Cow::Borrowed("GO_TABLE_TEST_CASE_NAME"));
 507const GO_SUITE_NAME_TASK_VARIABLE: VariableName =
 508    VariableName::Custom(Cow::Borrowed("GO_SUITE_NAME"));
 509
 510impl ContextProvider for GoContextProvider {
 511    fn build_context(
 512        &self,
 513        variables: &TaskVariables,
 514        location: ContextLocation<'_>,
 515        _: Option<HashMap<String, String>>,
 516        _: Arc<dyn LanguageToolchainStore>,
 517        cx: &mut gpui::App,
 518    ) -> Task<Result<TaskVariables>> {
 519        let local_abs_path = location
 520            .file_location
 521            .buffer
 522            .read(cx)
 523            .file()
 524            .and_then(|file| Some(file.as_local()?.abs_path(cx)));
 525
 526        let go_package_variable = local_abs_path
 527            .as_deref()
 528            .and_then(|local_abs_path| local_abs_path.parent())
 529            .map(|buffer_dir| {
 530                // Prefer the relative form `./my-nested-package/is-here` over
 531                // absolute path, because it's more readable in the modal, but
 532                // the absolute path also works.
 533                let package_name = variables
 534                    .get(&VariableName::WorktreeRoot)
 535                    .and_then(|worktree_abs_path| buffer_dir.strip_prefix(worktree_abs_path).ok())
 536                    .map(|relative_pkg_dir| {
 537                        if relative_pkg_dir.as_os_str().is_empty() {
 538                            ".".into()
 539                        } else {
 540                            format!("./{}", relative_pkg_dir.to_string_lossy())
 541                        }
 542                    })
 543                    .unwrap_or_else(|| format!("{}", buffer_dir.to_string_lossy()));
 544
 545                (GO_PACKAGE_TASK_VARIABLE.clone(), package_name)
 546            });
 547
 548        let go_module_root_variable = local_abs_path
 549            .as_deref()
 550            .and_then(|local_abs_path| local_abs_path.parent())
 551            .map(|buffer_dir| {
 552                // Walk dirtree up until getting the first go.mod file
 553                let module_dir = buffer_dir
 554                    .ancestors()
 555                    .find(|dir| dir.join("go.mod").is_file())
 556                    .map(|dir| dir.to_string_lossy().into_owned())
 557                    .unwrap_or_else(|| ".".to_string());
 558
 559                (GO_MODULE_ROOT_TASK_VARIABLE.clone(), module_dir)
 560            });
 561
 562        let _subtest_name = variables.get(&VariableName::Custom(Cow::Borrowed("_subtest_name")));
 563
 564        let go_subtest_variable = extract_subtest_name(_subtest_name.unwrap_or(""))
 565            .map(|subtest_name| (GO_SUBTEST_NAME_TASK_VARIABLE.clone(), subtest_name));
 566
 567        let _table_test_case_name = variables.get(&VariableName::Custom(Cow::Borrowed(
 568            "_table_test_case_name",
 569        )));
 570
 571        let go_table_test_case_variable = _table_test_case_name
 572            .and_then(extract_subtest_name)
 573            .map(|case_name| (GO_TABLE_TEST_CASE_NAME_TASK_VARIABLE.clone(), case_name));
 574
 575        let _suite_name = variables.get(&VariableName::Custom(Cow::Borrowed("_suite_name")));
 576
 577        let go_suite_variable = _suite_name
 578            .and_then(extract_subtest_name)
 579            .map(|suite_name| (GO_SUITE_NAME_TASK_VARIABLE.clone(), suite_name));
 580
 581        Task::ready(Ok(TaskVariables::from_iter(
 582            [
 583                go_package_variable,
 584                go_subtest_variable,
 585                go_table_test_case_variable,
 586                go_suite_variable,
 587                go_module_root_variable,
 588            ]
 589            .into_iter()
 590            .flatten(),
 591        )))
 592    }
 593
 594    fn associated_tasks(&self, _: Option<Entity<Buffer>>, _: &App) -> Task<Option<TaskTemplates>> {
 595        let package_cwd = if GO_PACKAGE_TASK_VARIABLE.template_value() == "." {
 596            None
 597        } else {
 598            Some("$ZED_DIRNAME".to_string())
 599        };
 600        let module_cwd = Some(GO_MODULE_ROOT_TASK_VARIABLE.template_value());
 601
 602        Task::ready(Some(TaskTemplates(vec![
 603            TaskTemplate {
 604                label: format!(
 605                    "go test {} -v -run Test{}/{}",
 606                    GO_PACKAGE_TASK_VARIABLE.template_value(),
 607                    GO_SUITE_NAME_TASK_VARIABLE.template_value(),
 608                    VariableName::Symbol.template_value(),
 609                ),
 610                command: "go".into(),
 611                args: vec![
 612                    "test".into(),
 613                    "-v".into(),
 614                    "-run".into(),
 615                    format!(
 616                        "\\^Test{}\\$/\\^{}\\$",
 617                        GO_SUITE_NAME_TASK_VARIABLE.template_value(),
 618                        VariableName::Symbol.template_value(),
 619                    ),
 620                ],
 621                cwd: package_cwd.clone(),
 622                tags: vec!["go-testify-suite".to_owned()],
 623                ..TaskTemplate::default()
 624            },
 625            TaskTemplate {
 626                label: format!(
 627                    "go test {} -v -run {}/{}",
 628                    GO_PACKAGE_TASK_VARIABLE.template_value(),
 629                    VariableName::Symbol.template_value(),
 630                    GO_TABLE_TEST_CASE_NAME_TASK_VARIABLE.template_value(),
 631                ),
 632                command: "go".into(),
 633                args: vec![
 634                    "test".into(),
 635                    "-v".into(),
 636                    "-run".into(),
 637                    format!(
 638                        "\\^{}\\$/\\^{}\\$",
 639                        VariableName::Symbol.template_value(),
 640                        GO_TABLE_TEST_CASE_NAME_TASK_VARIABLE.template_value(),
 641                    ),
 642                ],
 643                cwd: package_cwd.clone(),
 644                tags: vec![
 645                    "go-table-test-case".to_owned(),
 646                    "go-table-test-case-without-explicit-variable".to_owned(),
 647                ],
 648                ..TaskTemplate::default()
 649            },
 650            TaskTemplate {
 651                label: format!(
 652                    "go test {} -run {}",
 653                    GO_PACKAGE_TASK_VARIABLE.template_value(),
 654                    VariableName::Symbol.template_value(),
 655                ),
 656                command: "go".into(),
 657                args: vec![
 658                    "test".into(),
 659                    "-run".into(),
 660                    format!("\\^{}\\$", VariableName::Symbol.template_value(),),
 661                ],
 662                tags: vec!["go-test".to_owned()],
 663                cwd: package_cwd.clone(),
 664                ..TaskTemplate::default()
 665            },
 666            TaskTemplate {
 667                label: format!(
 668                    "go test {} -run {}",
 669                    GO_PACKAGE_TASK_VARIABLE.template_value(),
 670                    VariableName::Symbol.template_value(),
 671                ),
 672                command: "go".into(),
 673                args: vec![
 674                    "test".into(),
 675                    "-run".into(),
 676                    format!("\\^{}\\$", VariableName::Symbol.template_value(),),
 677                ],
 678                tags: vec!["go-example".to_owned()],
 679                cwd: package_cwd.clone(),
 680                ..TaskTemplate::default()
 681            },
 682            TaskTemplate {
 683                label: format!("go test {}", GO_PACKAGE_TASK_VARIABLE.template_value()),
 684                command: "go".into(),
 685                args: vec!["test".into()],
 686                cwd: package_cwd.clone(),
 687                ..TaskTemplate::default()
 688            },
 689            TaskTemplate {
 690                label: "go test ./...".into(),
 691                command: "go".into(),
 692                args: vec!["test".into(), "./...".into()],
 693                cwd: module_cwd.clone(),
 694                ..TaskTemplate::default()
 695            },
 696            TaskTemplate {
 697                label: format!(
 698                    "go test {} -v -run {}/{}",
 699                    GO_PACKAGE_TASK_VARIABLE.template_value(),
 700                    VariableName::Symbol.template_value(),
 701                    GO_SUBTEST_NAME_TASK_VARIABLE.template_value(),
 702                ),
 703                command: "go".into(),
 704                args: vec![
 705                    "test".into(),
 706                    "-v".into(),
 707                    "-run".into(),
 708                    format!(
 709                        "'^{}$/^{}$'",
 710                        VariableName::Symbol.template_value(),
 711                        GO_SUBTEST_NAME_TASK_VARIABLE.template_value(),
 712                    ),
 713                ],
 714                cwd: package_cwd.clone(),
 715                tags: vec!["go-subtest".to_owned()],
 716                ..TaskTemplate::default()
 717            },
 718            TaskTemplate {
 719                label: format!(
 720                    "go test {} -bench {}",
 721                    GO_PACKAGE_TASK_VARIABLE.template_value(),
 722                    VariableName::Symbol.template_value()
 723                ),
 724                command: "go".into(),
 725                args: vec![
 726                    "test".into(),
 727                    "-benchmem".into(),
 728                    "-run='^$'".into(),
 729                    "-bench".into(),
 730                    format!("\\^{}\\$", VariableName::Symbol.template_value()),
 731                ],
 732                cwd: package_cwd.clone(),
 733                tags: vec!["go-benchmark".to_owned()],
 734                ..TaskTemplate::default()
 735            },
 736            TaskTemplate {
 737                label: format!(
 738                    "go test {} -fuzz=Fuzz -run {}",
 739                    GO_PACKAGE_TASK_VARIABLE.template_value(),
 740                    VariableName::Symbol.template_value(),
 741                ),
 742                command: "go".into(),
 743                args: vec![
 744                    "test".into(),
 745                    "-fuzz=Fuzz".into(),
 746                    "-run".into(),
 747                    format!("\\^{}\\$", VariableName::Symbol.template_value(),),
 748                ],
 749                tags: vec!["go-fuzz".to_owned()],
 750                cwd: package_cwd.clone(),
 751                ..TaskTemplate::default()
 752            },
 753            TaskTemplate {
 754                label: format!("go run {}", GO_PACKAGE_TASK_VARIABLE.template_value(),),
 755                command: "go".into(),
 756                args: vec!["run".into(), ".".into()],
 757                cwd: package_cwd.clone(),
 758                tags: vec!["go-main".to_owned()],
 759                ..TaskTemplate::default()
 760            },
 761            TaskTemplate {
 762                label: format!("go generate {}", GO_PACKAGE_TASK_VARIABLE.template_value()),
 763                command: "go".into(),
 764                args: vec!["generate".into()],
 765                cwd: package_cwd,
 766                tags: vec!["go-generate".to_owned()],
 767                ..TaskTemplate::default()
 768            },
 769            TaskTemplate {
 770                label: "go generate ./...".into(),
 771                command: "go".into(),
 772                args: vec!["generate".into(), "./...".into()],
 773                cwd: module_cwd,
 774                ..TaskTemplate::default()
 775            },
 776        ])))
 777    }
 778}
 779
 780fn extract_subtest_name(input: &str) -> Option<String> {
 781    let content = if input.starts_with('`') && input.ends_with('`') {
 782        input.trim_matches('`')
 783    } else {
 784        input.trim_matches('"')
 785    };
 786
 787    let processed = content
 788        .chars()
 789        .map(|c| if c.is_whitespace() { '_' } else { c })
 790        .collect::<String>();
 791
 792    Some(
 793        GO_ESCAPE_SUBTEST_NAME_REGEX
 794            .replace_all(&processed, |caps: &regex::Captures| {
 795                format!("\\{}", &caps[0])
 796            })
 797            .to_string(),
 798    )
 799}
 800
 801#[cfg(test)]
 802mod tests {
 803    use super::*;
 804    use crate::language;
 805    use gpui::{AppContext, Hsla, TestAppContext};
 806    use theme::SyntaxTheme;
 807
 808    #[gpui::test]
 809    async fn test_go_label_for_completion() {
 810        let adapter = Arc::new(GoLspAdapter);
 811        let language = language("go", tree_sitter_go::LANGUAGE.into());
 812
 813        let theme = SyntaxTheme::new_test([
 814            ("type", Hsla::default()),
 815            ("keyword", Hsla::default()),
 816            ("function", Hsla::default()),
 817            ("number", Hsla::default()),
 818            ("property", Hsla::default()),
 819        ]);
 820        language.set_theme(&theme);
 821
 822        let grammar = language.grammar().unwrap();
 823        let highlight_function = grammar.highlight_id_for_name("function").unwrap();
 824        let highlight_type = grammar.highlight_id_for_name("type").unwrap();
 825        let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
 826        let highlight_number = grammar.highlight_id_for_name("number").unwrap();
 827        let highlight_field = grammar.highlight_id_for_name("property").unwrap();
 828
 829        assert_eq!(
 830            adapter
 831                .label_for_completion(
 832                    &lsp::CompletionItem {
 833                        kind: Some(lsp::CompletionItemKind::FUNCTION),
 834                        label: "Hello".to_string(),
 835                        detail: Some("func(a B) c.D".to_string()),
 836                        ..Default::default()
 837                    },
 838                    &language
 839                )
 840                .await,
 841            Some(CodeLabel::new(
 842                "Hello(a B) c.D".to_string(),
 843                0..5,
 844                vec![
 845                    (0..5, highlight_function),
 846                    (8..9, highlight_type),
 847                    (13..14, highlight_type),
 848                ]
 849            ))
 850        );
 851
 852        // Nested methods
 853        assert_eq!(
 854            adapter
 855                .label_for_completion(
 856                    &lsp::CompletionItem {
 857                        kind: Some(lsp::CompletionItemKind::METHOD),
 858                        label: "one.two.Three".to_string(),
 859                        detail: Some("func() [3]interface{}".to_string()),
 860                        ..Default::default()
 861                    },
 862                    &language
 863                )
 864                .await,
 865            Some(CodeLabel::new(
 866                "one.two.Three() [3]interface{}".to_string(),
 867                0..13,
 868                vec![
 869                    (8..13, highlight_function),
 870                    (17..18, highlight_number),
 871                    (19..28, highlight_keyword),
 872                ],
 873            ))
 874        );
 875
 876        // Nested fields
 877        assert_eq!(
 878            adapter
 879                .label_for_completion(
 880                    &lsp::CompletionItem {
 881                        kind: Some(lsp::CompletionItemKind::FIELD),
 882                        label: "two.Three".to_string(),
 883                        detail: Some("a.Bcd".to_string()),
 884                        ..Default::default()
 885                    },
 886                    &language
 887                )
 888                .await,
 889            Some(CodeLabel::new(
 890                "two.Three a.Bcd".to_string(),
 891                0..9,
 892                vec![(4..9, highlight_field), (12..15, highlight_type)],
 893            ))
 894        );
 895    }
 896
 897    #[gpui::test]
 898    fn test_go_test_main_ignored(cx: &mut TestAppContext) {
 899        let language = language("go", tree_sitter_go::LANGUAGE.into());
 900
 901        let example_test = r#"
 902        package main
 903
 904        func TestMain(m *testing.M) {
 905            os.Exit(m.Run())
 906        }
 907        "#;
 908
 909        let buffer =
 910            cx.new(|cx| crate::Buffer::local(example_test, cx).with_language(language.clone(), cx));
 911        cx.executor().run_until_parked();
 912
 913        let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
 914            let snapshot = buffer.snapshot();
 915            snapshot.runnable_ranges(0..example_test.len()).collect()
 916        });
 917
 918        let tag_strings: Vec<String> = runnables
 919            .iter()
 920            .flat_map(|r| &r.runnable.tags)
 921            .map(|tag| tag.0.to_string())
 922            .collect();
 923
 924        assert!(
 925            !tag_strings.contains(&"go-test".to_string()),
 926            "Should NOT find go-test tag, found: {:?}",
 927            tag_strings
 928        );
 929    }
 930
 931    #[gpui::test]
 932    fn test_testify_suite_detection(cx: &mut TestAppContext) {
 933        let language = language("go", tree_sitter_go::LANGUAGE.into());
 934
 935        let testify_suite = r#"
 936        package main
 937
 938        import (
 939            "testing"
 940
 941            "github.com/stretchr/testify/suite"
 942        )
 943
 944        type ExampleSuite struct {
 945            suite.Suite
 946        }
 947
 948        func TestExampleSuite(t *testing.T) {
 949            suite.Run(t, new(ExampleSuite))
 950        }
 951
 952        func (s *ExampleSuite) TestSomething_Success() {
 953            // test code
 954        }
 955        "#;
 956
 957        let buffer = cx
 958            .new(|cx| crate::Buffer::local(testify_suite, cx).with_language(language.clone(), cx));
 959        cx.executor().run_until_parked();
 960
 961        let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
 962            let snapshot = buffer.snapshot();
 963            snapshot.runnable_ranges(0..testify_suite.len()).collect()
 964        });
 965
 966        let tag_strings: Vec<String> = runnables
 967            .iter()
 968            .flat_map(|r| &r.runnable.tags)
 969            .map(|tag| tag.0.to_string())
 970            .collect();
 971
 972        assert!(
 973            tag_strings.contains(&"go-test".to_string()),
 974            "Should find go-test tag, found: {:?}",
 975            tag_strings
 976        );
 977        assert!(
 978            tag_strings.contains(&"go-testify-suite".to_string()),
 979            "Should find go-testify-suite tag, found: {:?}",
 980            tag_strings
 981        );
 982    }
 983
 984    #[gpui::test]
 985    fn test_go_runnable_detection(cx: &mut TestAppContext) {
 986        let language = language("go", tree_sitter_go::LANGUAGE.into());
 987
 988        let interpreted_string_subtest = r#"
 989        package main
 990
 991        import "testing"
 992
 993        func TestExample(t *testing.T) {
 994            t.Run("subtest with double quotes", func(t *testing.T) {
 995                // test code
 996            })
 997        }
 998        "#;
 999
1000        let raw_string_subtest = r#"
1001        package main
1002
1003        import "testing"
1004
1005        func TestExample(t *testing.T) {
1006            t.Run(`subtest with
1007            multiline
1008            backticks`, func(t *testing.T) {
1009                // test code
1010            })
1011        }
1012        "#;
1013
1014        let buffer = cx.new(|cx| {
1015            crate::Buffer::local(interpreted_string_subtest, cx).with_language(language.clone(), cx)
1016        });
1017        cx.executor().run_until_parked();
1018
1019        let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
1020            let snapshot = buffer.snapshot();
1021            snapshot
1022                .runnable_ranges(0..interpreted_string_subtest.len())
1023                .collect()
1024        });
1025
1026        let tag_strings: Vec<String> = runnables
1027            .iter()
1028            .flat_map(|r| &r.runnable.tags)
1029            .map(|tag| tag.0.to_string())
1030            .collect();
1031
1032        assert!(
1033            tag_strings.contains(&"go-test".to_string()),
1034            "Should find go-test tag, found: {:?}",
1035            tag_strings
1036        );
1037        assert!(
1038            tag_strings.contains(&"go-subtest".to_string()),
1039            "Should find go-subtest tag, found: {:?}",
1040            tag_strings
1041        );
1042
1043        let buffer = cx.new(|cx| {
1044            crate::Buffer::local(raw_string_subtest, cx).with_language(language.clone(), cx)
1045        });
1046        cx.executor().run_until_parked();
1047
1048        let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
1049            let snapshot = buffer.snapshot();
1050            snapshot
1051                .runnable_ranges(0..raw_string_subtest.len())
1052                .collect()
1053        });
1054
1055        let tag_strings: Vec<String> = runnables
1056            .iter()
1057            .flat_map(|r| &r.runnable.tags)
1058            .map(|tag| tag.0.to_string())
1059            .collect();
1060
1061        assert!(
1062            tag_strings.contains(&"go-test".to_string()),
1063            "Should find go-test tag, found: {:?}",
1064            tag_strings
1065        );
1066        assert!(
1067            tag_strings.contains(&"go-subtest".to_string()),
1068            "Should find go-subtest tag, found: {:?}",
1069            tag_strings
1070        );
1071    }
1072
1073    #[gpui::test]
1074    fn test_go_example_test_detection(cx: &mut TestAppContext) {
1075        let language = language("go", tree_sitter_go::LANGUAGE.into());
1076
1077        let example_test = r#"
1078        package main
1079
1080        import "fmt"
1081
1082        func Example() {
1083            fmt.Println("Hello, world!")
1084            // Output: Hello, world!
1085        }
1086        "#;
1087
1088        let buffer =
1089            cx.new(|cx| crate::Buffer::local(example_test, cx).with_language(language.clone(), cx));
1090        cx.executor().run_until_parked();
1091
1092        let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
1093            let snapshot = buffer.snapshot();
1094            snapshot.runnable_ranges(0..example_test.len()).collect()
1095        });
1096
1097        let tag_strings: Vec<String> = runnables
1098            .iter()
1099            .flat_map(|r| &r.runnable.tags)
1100            .map(|tag| tag.0.to_string())
1101            .collect();
1102
1103        assert!(
1104            tag_strings.contains(&"go-example".to_string()),
1105            "Should find go-example tag, found: {:?}",
1106            tag_strings
1107        );
1108    }
1109
1110    #[gpui::test]
1111    fn test_go_table_test_slice_detection(cx: &mut TestAppContext) {
1112        let language = language("go", tree_sitter_go::LANGUAGE.into());
1113
1114        let table_test = r#"
1115        package main
1116
1117        import "testing"
1118
1119        func TestExample(t *testing.T) {
1120            _ = "some random string"
1121
1122            testCases := []struct{
1123                name string
1124                anotherStr string
1125            }{
1126                {
1127                    name: "test case 1",
1128                    anotherStr: "foo",
1129                },
1130                {
1131                    name: "test case 2",
1132                    anotherStr: "bar",
1133                },
1134                {
1135                    name: "test case 3",
1136                    anotherStr: "baz",
1137                },
1138            }
1139
1140            notATableTest := []struct{
1141                name string
1142            }{
1143                {
1144                    name: "some string",
1145                },
1146                {
1147                    name: "some other string",
1148                },
1149            }
1150
1151            for _, tc := range testCases {
1152                t.Run(tc.name, func(t *testing.T) {
1153                    // test code here
1154                })
1155            }
1156        }
1157        "#;
1158
1159        let buffer =
1160            cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
1161        cx.executor().run_until_parked();
1162
1163        let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
1164            let snapshot = buffer.snapshot();
1165            snapshot.runnable_ranges(0..table_test.len()).collect()
1166        });
1167
1168        let tag_strings: Vec<String> = runnables
1169            .iter()
1170            .flat_map(|r| &r.runnable.tags)
1171            .map(|tag| tag.0.to_string())
1172            .collect();
1173
1174        assert!(
1175            tag_strings.contains(&"go-test".to_string()),
1176            "Should find go-test tag, found: {:?}",
1177            tag_strings
1178        );
1179        assert!(
1180            tag_strings.contains(&"go-table-test-case".to_string()),
1181            "Should find go-table-test-case tag, found: {:?}",
1182            tag_strings
1183        );
1184
1185        let go_test_count = tag_strings.iter().filter(|&tag| tag == "go-test").count();
1186        // This is currently broken; see #39148
1187        // let go_table_test_count = tag_strings
1188        //     .iter()
1189        //     .filter(|&tag| tag == "go-table-test-case")
1190        //     .count();
1191
1192        assert!(
1193            go_test_count == 1,
1194            "Should find exactly 1 go-test, found: {}",
1195            go_test_count
1196        );
1197        // assert!(
1198        //     go_table_test_count == 3,
1199        //     "Should find exactly 3 go-table-test-case, found: {}",
1200        //     go_table_test_count
1201        // );
1202    }
1203
1204    #[gpui::test]
1205    fn test_go_table_test_slice_without_explicit_variable_detection(cx: &mut TestAppContext) {
1206        let language = language("go", tree_sitter_go::LANGUAGE.into());
1207
1208        let table_test = r#"
1209        package main
1210
1211        import "testing"
1212
1213        func TestExample(t *testing.T) {
1214            for _, tc := range []struct{
1215                name string
1216                anotherStr string
1217            }{
1218                {
1219                    name: "test case 1",
1220                    anotherStr: "foo",
1221                },
1222                {
1223                    name: "test case 2",
1224                    anotherStr: "bar",
1225                },
1226                {
1227                    name: "test case 3",
1228                    anotherStr: "baz",
1229                },
1230            } {
1231                t.Run(tc.name, func(t *testing.T) {
1232                    // test code here
1233                })
1234            }
1235        }
1236        "#;
1237
1238        let buffer =
1239            cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
1240        cx.executor().run_until_parked();
1241
1242        let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
1243            let snapshot = buffer.snapshot();
1244            snapshot.runnable_ranges(0..table_test.len()).collect()
1245        });
1246
1247        let tag_strings: Vec<String> = runnables
1248            .iter()
1249            .flat_map(|r| &r.runnable.tags)
1250            .map(|tag| tag.0.to_string())
1251            .collect();
1252
1253        assert!(
1254            tag_strings.contains(&"go-test".to_string()),
1255            "Should find go-test tag, found: {:?}",
1256            tag_strings
1257        );
1258        assert!(
1259            tag_strings.contains(&"go-table-test-case-without-explicit-variable".to_string()),
1260            "Should find go-table-test-case-without-explicit-variable tag, found: {:?}",
1261            tag_strings
1262        );
1263
1264        let go_test_count = tag_strings.iter().filter(|&tag| tag == "go-test").count();
1265
1266        assert!(
1267            go_test_count == 1,
1268            "Should find exactly 1 go-test, found: {}",
1269            go_test_count
1270        );
1271    }
1272
1273    #[gpui::test]
1274    fn test_go_table_test_map_without_explicit_variable_detection(cx: &mut TestAppContext) {
1275        let language = language("go", tree_sitter_go::LANGUAGE.into());
1276
1277        let table_test = r#"
1278        package main
1279
1280        import "testing"
1281
1282        func TestExample(t *testing.T) {
1283            for name, tc := range map[string]struct {
1284          		someStr string
1285          		fail    bool
1286           	}{
1287          		"test failure": {
1288         			someStr: "foo",
1289         			fail:    true,
1290          		},
1291          		"test success": {
1292         			someStr: "bar",
1293         			fail:    false,
1294          		},
1295           	} {
1296                t.Run(name, func(t *testing.T) {
1297                    // test code here
1298                })
1299            }
1300        }
1301        "#;
1302
1303        let buffer =
1304            cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
1305        cx.executor().run_until_parked();
1306
1307        let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
1308            let snapshot = buffer.snapshot();
1309            snapshot.runnable_ranges(0..table_test.len()).collect()
1310        });
1311
1312        let tag_strings: Vec<String> = runnables
1313            .iter()
1314            .flat_map(|r| &r.runnable.tags)
1315            .map(|tag| tag.0.to_string())
1316            .collect();
1317
1318        assert!(
1319            tag_strings.contains(&"go-test".to_string()),
1320            "Should find go-test tag, found: {:?}",
1321            tag_strings
1322        );
1323        assert!(
1324            tag_strings.contains(&"go-table-test-case-without-explicit-variable".to_string()),
1325            "Should find go-table-test-case-without-explicit-variable tag, found: {:?}",
1326            tag_strings
1327        );
1328
1329        let go_test_count = tag_strings.iter().filter(|&tag| tag == "go-test").count();
1330        let go_table_test_count = tag_strings
1331            .iter()
1332            .filter(|&tag| tag == "go-table-test-case-without-explicit-variable")
1333            .count();
1334
1335        assert!(
1336            go_test_count == 1,
1337            "Should find exactly 1 go-test, found: {}",
1338            go_test_count
1339        );
1340        assert!(
1341            go_table_test_count == 2,
1342            "Should find exactly 2 go-table-test-case-without-explicit-variable, found: {}",
1343            go_table_test_count
1344        );
1345    }
1346
1347    #[gpui::test]
1348    fn test_go_table_test_slice_ignored(cx: &mut TestAppContext) {
1349        let language = language("go", tree_sitter_go::LANGUAGE.into());
1350
1351        let table_test = r#"
1352        package main
1353
1354        func Example() {
1355            _ = "some random string"
1356
1357            notATableTest := []struct{
1358                name string
1359            }{
1360                {
1361                    name: "some string",
1362                },
1363                {
1364                    name: "some other string",
1365                },
1366            }
1367        }
1368        "#;
1369
1370        let buffer =
1371            cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
1372        cx.executor().run_until_parked();
1373
1374        let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
1375            let snapshot = buffer.snapshot();
1376            snapshot.runnable_ranges(0..table_test.len()).collect()
1377        });
1378
1379        let tag_strings: Vec<String> = runnables
1380            .iter()
1381            .flat_map(|r| &r.runnable.tags)
1382            .map(|tag| tag.0.to_string())
1383            .collect();
1384
1385        assert!(
1386            !tag_strings.contains(&"go-test".to_string()),
1387            "Should find go-test tag, found: {:?}",
1388            tag_strings
1389        );
1390        assert!(
1391            !tag_strings.contains(&"go-table-test-case".to_string()),
1392            "Should find go-table-test-case tag, found: {:?}",
1393            tag_strings
1394        );
1395    }
1396
1397    #[gpui::test]
1398    fn test_go_table_test_map_detection(cx: &mut TestAppContext) {
1399        let language = language("go", tree_sitter_go::LANGUAGE.into());
1400
1401        let table_test = r#"
1402        package main
1403
1404        import "testing"
1405
1406        func TestExample(t *testing.T) {
1407            _ = "some random string"
1408
1409           	testCases := map[string]struct {
1410          		someStr string
1411          		fail    bool
1412           	}{
1413          		"test failure": {
1414         			someStr: "foo",
1415         			fail:    true,
1416          		},
1417          		"test success": {
1418         			someStr: "bar",
1419         			fail:    false,
1420          		},
1421           	}
1422
1423           	notATableTest := map[string]struct {
1424          		someStr string
1425           	}{
1426          		"some string": {
1427         			someStr: "foo",
1428          		},
1429          		"some other string": {
1430         			someStr: "bar",
1431          		},
1432           	}
1433
1434            for name, tc := range testCases {
1435                t.Run(name, func(t *testing.T) {
1436                    // test code here
1437                })
1438            }
1439        }
1440        "#;
1441
1442        let buffer =
1443            cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
1444        cx.executor().run_until_parked();
1445
1446        let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
1447            let snapshot = buffer.snapshot();
1448            snapshot.runnable_ranges(0..table_test.len()).collect()
1449        });
1450
1451        let tag_strings: Vec<String> = runnables
1452            .iter()
1453            .flat_map(|r| &r.runnable.tags)
1454            .map(|tag| tag.0.to_string())
1455            .collect();
1456
1457        assert!(
1458            tag_strings.contains(&"go-test".to_string()),
1459            "Should find go-test tag, found: {:?}",
1460            tag_strings
1461        );
1462        assert!(
1463            tag_strings.contains(&"go-table-test-case".to_string()),
1464            "Should find go-table-test-case tag, found: {:?}",
1465            tag_strings
1466        );
1467
1468        let go_test_count = tag_strings.iter().filter(|&tag| tag == "go-test").count();
1469        let go_table_test_count = tag_strings
1470            .iter()
1471            .filter(|&tag| tag == "go-table-test-case")
1472            .count();
1473
1474        assert!(
1475            go_test_count == 1,
1476            "Should find exactly 1 go-test, found: {}",
1477            go_test_count
1478        );
1479        assert!(
1480            go_table_test_count == 2,
1481            "Should find exactly 2 go-table-test-case, found: {}",
1482            go_table_test_count
1483        );
1484    }
1485
1486    #[gpui::test]
1487    fn test_go_table_test_map_ignored(cx: &mut TestAppContext) {
1488        let language = language("go", tree_sitter_go::LANGUAGE.into());
1489
1490        let table_test = r#"
1491        package main
1492
1493        func Example() {
1494            _ = "some random string"
1495
1496           	notATableTest := map[string]struct {
1497          		someStr string
1498           	}{
1499          		"some string": {
1500         			someStr: "foo",
1501          		},
1502          		"some other string": {
1503         			someStr: "bar",
1504          		},
1505           	}
1506        }
1507        "#;
1508
1509        let buffer =
1510            cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
1511        cx.executor().run_until_parked();
1512
1513        let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
1514            let snapshot = buffer.snapshot();
1515            snapshot.runnable_ranges(0..table_test.len()).collect()
1516        });
1517
1518        let tag_strings: Vec<String> = runnables
1519            .iter()
1520            .flat_map(|r| &r.runnable.tags)
1521            .map(|tag| tag.0.to_string())
1522            .collect();
1523
1524        assert!(
1525            !tag_strings.contains(&"go-test".to_string()),
1526            "Should find go-test tag, found: {:?}",
1527            tag_strings
1528        );
1529        assert!(
1530            !tag_strings.contains(&"go-table-test-case".to_string()),
1531            "Should find go-table-test-case tag, found: {:?}",
1532            tag_strings
1533        );
1534    }
1535
1536    #[test]
1537    fn test_extract_subtest_name() {
1538        // Interpreted string literal
1539        let input_double_quoted = r#""subtest with double quotes""#;
1540        let result = extract_subtest_name(input_double_quoted);
1541        assert_eq!(result, Some(r#"subtest_with_double_quotes"#.to_string()));
1542
1543        let input_double_quoted_with_backticks = r#""test with `backticks` inside""#;
1544        let result = extract_subtest_name(input_double_quoted_with_backticks);
1545        assert_eq!(result, Some(r#"test_with_`backticks`_inside"#.to_string()));
1546
1547        // Raw string literal
1548        let input_with_backticks = r#"`subtest with backticks`"#;
1549        let result = extract_subtest_name(input_with_backticks);
1550        assert_eq!(result, Some(r#"subtest_with_backticks"#.to_string()));
1551
1552        let input_raw_with_quotes = r#"`test with "quotes" and other chars`"#;
1553        let result = extract_subtest_name(input_raw_with_quotes);
1554        assert_eq!(
1555            result,
1556            Some(r#"test_with_\"quotes\"_and_other_chars"#.to_string())
1557        );
1558
1559        let input_multiline = r#"`subtest with
1560        multiline
1561        backticks`"#;
1562        let result = extract_subtest_name(input_multiline);
1563        assert_eq!(
1564            result,
1565            Some(r#"subtest_with_________multiline_________backticks"#.to_string())
1566        );
1567
1568        let input_with_double_quotes = r#"`test with "double quotes"`"#;
1569        let result = extract_subtest_name(input_with_double_quotes);
1570        assert_eq!(result, Some(r#"test_with_\"double_quotes\""#.to_string()));
1571    }
1572}