198 lines · 6.4 KB · Rust
RawDownload
use anyhow::{Result, anyhow};use futures::channel::mpsc;use futures::{FutureExt as _, StreamExt as _};use gpui::{AsyncApp, Entity, Task};use language::{Buffer, LanguageId, LanguageNotFound, LanguageServerId, ParseStatus};use project::lsp_store::OpenLspBufferHandle;use project::{Project, ProjectPath, Worktree};use std::collections::HashSet;use std::sync::Arc;use std::time::Duration;use util::rel_path::RelPath; pub fn open_buffer(    project: Entity<Project>,    worktree: Entity<Worktree>,    path: Arc<RelPath>,    cx: &AsyncApp,) -> Task<Result<Entity<Buffer>>> {    cx.spawn(async move |cx| {        let project_path = worktree.read_with(cx, |worktree, _cx| ProjectPath {            worktree_id: worktree.id(),            path,        })?;         let buffer = project            .update(cx, |project, cx| project.open_buffer(project_path, cx))?            .await?;         let mut parse_status = buffer.read_with(cx, |buffer, _cx| buffer.parse_status())?;        while *parse_status.borrow() != ParseStatus::Idle {            parse_status.changed().await?;        }         Ok(buffer)    })} pub async fn open_buffer_with_language_server(    project: Entity<Project>,    worktree: Entity<Worktree>,    path: Arc<RelPath>,    ready_languages: &mut HashSet<LanguageId>,    cx: &mut AsyncApp,) -> Result<(OpenLspBufferHandle, LanguageServerId, Entity<Buffer>)> {    let buffer = open_buffer(project.clone(), worktree, path.clone(), cx).await?;     let (lsp_open_handle, path_style) = project.update(cx, |project, cx| {        (            project.register_buffer_with_language_servers(&buffer, cx),            project.path_style(cx),        )    })?;     let language_registry = project.read_with(cx, |project, _| project.languages().clone())?;    let result = language_registry        .load_language_for_file_path(path.as_std_path())        .await;     if let Err(error) = result        && !error.is::<LanguageNotFound>()    {        anyhow::bail!(error);    }     let Some(language_id) = buffer.read_with(cx, |buffer, _cx| {        buffer.language().map(|language| language.id())    })?    else {        return Err(anyhow!("No language for {}", path.display(path_style)));    };     let log_prefix = format!("{} | ", path.display(path_style));    if !ready_languages.contains(&language_id) {        wait_for_lang_server(&project, &buffer, log_prefix, cx).await?;        ready_languages.insert(language_id);    }     let lsp_store = project.read_with(cx, |project, _cx| project.lsp_store())?;     // hacky wait for buffer to be registered with the language server    for _ in 0..100 {        let Some(language_server_id) = lsp_store.update(cx, |lsp_store, cx| {            buffer.update(cx, |buffer, cx| {                lsp_store                    .language_servers_for_local_buffer(&buffer, cx)                    .next()                    .map(|(_, language_server)| language_server.server_id())            })        })?        else {            cx.background_executor()                .timer(Duration::from_millis(10))                .await;            continue;        };         return Ok((lsp_open_handle, language_server_id, buffer));    }     return Err(anyhow!("No language server found for buffer"));} // TODO: Dedupe with similar function in crates/eval/src/instance.rspub fn wait_for_lang_server(    project: &Entity<Project>,    buffer: &Entity<Buffer>,    log_prefix: String,    cx: &mut AsyncApp,) -> Task<Result<()>> {    eprintln!("{}⏵ Waiting for language server", log_prefix);     let (mut tx, mut rx) = mpsc::channel(1);     let lsp_store = project        .read_with(cx, |project, _| project.lsp_store())        .unwrap();     let has_lang_server = buffer        .update(cx, |buffer, cx| {            lsp_store.update(cx, |lsp_store, cx| {                lsp_store                    .language_servers_for_local_buffer(buffer, cx)                    .next()                    .is_some()            })        })        .unwrap_or(false);     if has_lang_server {        project            .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))            .unwrap()            .detach();    }    let (mut added_tx, mut added_rx) = mpsc::channel(1);     let subscriptions = [        cx.subscribe(&lsp_store, {            let log_prefix = log_prefix.clone();            move |_, event, _| {                if let project::LspStoreEvent::LanguageServerUpdate {                    message:                        client::proto::update_language_server::Variant::WorkProgress(                            client::proto::LspWorkProgress {                                message: Some(message),                                ..                            },                        ),                    ..                } = event                {                    eprintln!("{}⟲ {message}", log_prefix)                }            }        }),        cx.subscribe(project, {            let buffer = buffer.clone();            move |project, event, cx| match event {                project::Event::LanguageServerAdded(_, _, _) => {                    let buffer = buffer.clone();                    project                        .update(cx, |project, cx| project.save_buffer(buffer, cx))                        .detach();                    added_tx.try_send(()).ok();                }                project::Event::DiskBasedDiagnosticsFinished { .. } => {                    tx.try_send(()).ok();                }                _ => {}            }        }),    ];     cx.spawn(async move |cx| {        if !has_lang_server {            // some buffers never have a language server, so this aborts quickly in that case.            let timeout = cx.background_executor().timer(Duration::from_secs(500));            futures::select! {                _ = added_rx.next() => {},                _ = timeout.fuse() => {                    anyhow::bail!("Waiting for language server add timed out after 5 seconds");                }            };        }        let timeout = cx.background_executor().timer(Duration::from_secs(60 * 5));        let result = futures::select! {            _ = rx.next() => {                eprintln!("{}⚑ Language server idle", log_prefix);                anyhow::Ok(())            },            _ = timeout.fuse() => {                anyhow::bail!("LSP wait timed out after 5 minutes");            }        };        drop(subscriptions);        result    })}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t