diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 28c20d95bb..4b81bebc02 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -11,13 +11,12 @@ use gpui::{ }; use language::{ language_settings::{all_language_settings, language_settings}, - point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16, - ToPointUtf16, + point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, + LanguageServerName, PointUtf16, ToPointUtf16, }; -use log::{debug, error}; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; -use request::{LogMessage, StatusNotification}; +use request::StatusNotification; use settings::SettingsStore; use smol::{fs, io::BufReader, stream::StreamExt}; use std::{ @@ -41,10 +40,15 @@ actions!( [Suggest, NextSuggestion, PreviousSuggestion, Reinstall] ); -pub fn init(http: Arc, node_runtime: Arc, cx: &mut AppContext) { +pub fn init( + new_server_id: LanguageServerId, + http: Arc, + node_runtime: Arc, + cx: &mut AppContext, +) { let copilot = cx.add_model({ let node_runtime = node_runtime.clone(); - move |cx| Copilot::start(http, node_runtime, cx) + move |cx| Copilot::start(new_server_id, http, node_runtime, cx) }); cx.set_global(copilot.clone()); @@ -125,6 +129,7 @@ impl CopilotServer { } struct RunningCopilotServer { + name: LanguageServerName, lsp: Arc, sign_in_status: SignInStatus, registered_buffers: HashMap, @@ -268,10 +273,15 @@ pub struct Copilot { node_runtime: Arc, server: CopilotServer, buffers: HashSet>, + server_id: LanguageServerId, +} + +pub enum Event { + CopilotLanguageServerStarted, } impl Entity for Copilot { - type Event = (); + type Event = Event; fn app_will_quit( &mut self, @@ -298,11 +308,13 @@ impl Copilot { } fn start( + new_server_id: LanguageServerId, http: Arc, node_runtime: Arc, cx: &mut ModelContext, ) -> Self { let mut this = Self { + server_id: new_server_id, http, node_runtime, server: CopilotServer::Disabled, @@ -315,13 +327,16 @@ impl Copilot { } fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext) { + let server_id = self.server_id; let http = self.http.clone(); let node_runtime = self.node_runtime.clone(); if all_language_settings(None, cx).copilot_enabled(None, None) { if matches!(self.server, CopilotServer::Disabled) { let start_task = cx .spawn({ - move |this, cx| Self::start_language_server(http, node_runtime, this, cx) + move |this, cx| { + Self::start_language_server(server_id, http, node_runtime, this, cx) + } }) .shared(); self.server = CopilotServer::Starting { task: start_task }; @@ -342,9 +357,11 @@ impl Copilot { let http = util::http::FakeHttpClient::create(|_| async { unreachable!() }); let node_runtime = FakeNodeRuntime::new(); let this = cx.add_model(|_| Self { + server_id: LanguageServerId(0), http: http.clone(), node_runtime, server: CopilotServer::Running(RunningCopilotServer { + name: LanguageServerName(Arc::from("copilot")), lsp: Arc::new(server), sign_in_status: SignInStatus::Authorized, registered_buffers: Default::default(), @@ -355,6 +372,7 @@ impl Copilot { } fn start_language_server( + new_server_id: LanguageServerId, http: Arc, node_runtime: Arc, this: ModelHandle, @@ -369,27 +387,8 @@ impl Copilot { path: node_path, arguments, }; - let server = LanguageServer::new( - LanguageServerId(0), - binary, - Path::new("/"), - None, - cx.clone(), - )?; - - server - .on_notification::(|params, _cx| { - match params.level { - // Copilot is pretty aggressive about logging - 0 => debug!("copilot: {}", params.message), - 1 => debug!("copilot: {}", params.message), - _ => error!("copilot: {}", params.message), - } - - debug!("copilot metadata: {}", params.metadata_str); - debug!("copilot extra: {:?}", params.extra); - }) - .detach(); + let server = + LanguageServer::new(new_server_id, binary, Path::new("/"), None, cx.clone())?; server .on_notification::( @@ -427,10 +426,12 @@ impl Copilot { match server { Ok((server, status)) => { this.server = CopilotServer::Running(RunningCopilotServer { + name: LanguageServerName(Arc::from("copilot")), lsp: server, sign_in_status: SignInStatus::SignedOut, registered_buffers: Default::default(), }); + cx.emit(Event::CopilotLanguageServerStarted); this.update_sign_in_status(status, cx); } Err(error) => { @@ -547,9 +548,10 @@ impl Copilot { .spawn({ let http = self.http.clone(); let node_runtime = self.node_runtime.clone(); + let server_id = self.server_id; move |this, cx| async move { clear_copilot_dir().await; - Self::start_language_server(http, node_runtime, this, cx).await + Self::start_language_server(server_id, http, node_runtime, this, cx).await } }) .shared(); @@ -563,6 +565,14 @@ impl Copilot { cx.foreground().spawn(start_task) } + pub fn language_server(&self) -> Option<(&LanguageServerName, &Arc)> { + if let CopilotServer::Running(server) = &self.server { + Some((&server.name, &server.lsp)) + } else { + None + } + } + pub fn register_buffer(&mut self, buffer: &ModelHandle, cx: &mut ModelContext) { let weak_buffer = buffer.downgrade(); self.buffers.insert(weak_buffer.clone()); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 07bea434e0..7d113a88af 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1018,6 +1018,10 @@ impl LanguageRegistry { .log_err(); }) } + + pub fn next_language_server_id(&self) -> LanguageServerId { + self.state.write().next_language_server_id() + } } impl LanguageRegistryState { diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 587e6ed25a..48be24e22f 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -13,7 +13,7 @@ use gpui::{ }; use language::{Buffer, LanguageServerId, LanguageServerName}; use lsp::IoKind; -use project::{search::SearchQuery, Project, Worktree}; +use project::{search::SearchQuery, Project}; use std::{borrow::Cow, sync::Arc}; use theme::{ui, Theme}; use workspace::{ @@ -38,7 +38,8 @@ struct ProjectState { struct LanguageServerState { log_buffer: ModelHandle, rpc_state: Option, - _subscription: Option, + _io_logs_subscription: Option, + _lsp_logs_subscription: Option, } struct LanguageServerRpcState { @@ -69,7 +70,7 @@ enum MessageKind { pub(crate) struct LogMenuItem { pub server_id: LanguageServerId, pub server_name: LanguageServerName, - pub worktree: ModelHandle, + pub worktree_root_name: String, pub rpc_trace_enabled: bool, pub rpc_trace_selected: bool, pub logs_selected: bool, @@ -134,8 +135,6 @@ impl LogStore { } pub fn add_project(&mut self, project: &ModelHandle, cx: &mut ModelContext) { - use project::Event::*; - let weak_project = project.downgrade(); self.projects.insert( weak_project, @@ -146,13 +145,13 @@ impl LogStore { this.projects.remove(&weak_project); }), cx.subscribe(project, |this, project, event, cx| match event { - LanguageServerAdded(id) => { + project::Event::LanguageServerAdded(id) => { this.add_language_server(&project, *id, cx); } - LanguageServerRemoved(id) => { + project::Event::LanguageServerRemoved(id) => { this.remove_language_server(&project, *id, cx); } - LanguageServerLog(id, message) => { + project::Event::LanguageServerLog(id, message) => { this.add_language_server_log(&project, *id, message, cx); } _ => {} @@ -176,21 +175,37 @@ impl LogStore { log_buffer: cx .add_model(|cx| Buffer::new(0, cx.model_id() as u64, "")) .clone(), - _subscription: None, + _io_logs_subscription: None, + _lsp_logs_subscription: None, } }); let server = project.read(cx).language_server_for_id(id); let weak_project = project.downgrade(); let io_tx = self.io_tx.clone(); - server_state._subscription = server.map(|server| { + server_state._io_logs_subscription = server.as_ref().map(|server| { server.on_io(move |io_kind, message| { io_tx .unbounded_send((weak_project, id, io_kind, message.to_string())) .ok(); }) }); - + let this = cx.weak_handle(); + let weak_project = project.downgrade(); + server_state._lsp_logs_subscription = server.map(|server| { + let server_id = server.server_id(); + server.on_notification::({ + move |params, mut cx| { + if let Some((project, this)) = + weak_project.upgrade(&mut cx).zip(this.upgrade(&mut cx)) + { + this.update(&mut cx, |this, cx| { + this.add_language_server_log(&project, server_id, ¶ms.message, cx); + }); + } + } + }) + }); Some(server_state.log_buffer.clone()) } @@ -201,7 +216,16 @@ impl LogStore { message: &str, cx: &mut ModelContext, ) -> Option<()> { - let buffer = self.add_language_server(&project, id, cx)?; + let buffer = match self + .projects + .get_mut(&project.downgrade())? + .servers + .get(&id) + .map(|state| state.log_buffer.clone()) + { + Some(existing_buffer) => existing_buffer, + None => self.add_language_server(&project, id, cx)?, + }; buffer.update(cx, |buffer, cx| { let len = buffer.len(); let has_newline = message.ends_with("\n"); @@ -288,19 +312,15 @@ impl LogStore { language_server_id: LanguageServerId, io_kind: IoKind, message: &str, - cx: &mut AppContext, + cx: &mut ModelContext, ) -> Option<()> { let is_received = match io_kind { IoKind::StdOut => true, IoKind::StdIn => false, IoKind::StdErr => { let project = project.upgrade(cx)?; - project.update(cx, |_, cx| { - cx.emit(project::Event::LanguageServerLog( - language_server_id, - format!("stderr: {}\n", message.trim()), - )) - }); + let message = format!("stderr: {}\n", message.trim()); + self.add_language_server_log(&project, language_server_id, &message, cx); return Some(()); } }; @@ -388,7 +408,7 @@ impl LspLogView { Some(LogMenuItem { server_id, server_name: language_server_name, - worktree, + worktree_root_name: worktree.read(cx).root_name().to_string(), rpc_trace_enabled: state.rpc_state.is_some(), rpc_trace_selected: self.is_showing_rpc_trace && self.current_server_id == Some(server_id), @@ -396,6 +416,24 @@ impl LspLogView { && self.current_server_id == Some(server_id), }) }) + .chain( + self.project + .read(cx) + .supplementary_language_servers() + .filter_map(|(&server_id, (name, _))| { + let state = state.servers.get(&server_id)?; + Some(LogMenuItem { + server_id, + server_name: name.clone(), + worktree_root_name: "supplementary".to_string(), + rpc_trace_enabled: state.rpc_state.is_some(), + rpc_trace_selected: self.is_showing_rpc_trace + && self.current_server_id == Some(server_id), + logs_selected: !self.is_showing_rpc_trace + && self.current_server_id == Some(server_id), + }) + }), + ) .collect::>(); rows.sort_by_key(|row| row.server_id); rows.dedup_by_key(|row| row.server_id); @@ -613,7 +651,7 @@ impl View for LspLogToolbarItemView { Self::render_language_server_menu_item( row.server_id, row.server_name, - row.worktree, + &row.worktree_root_name, row.rpc_trace_enabled, row.logs_selected, row.rpc_trace_selected, @@ -745,15 +783,14 @@ impl LspLogToolbarItemView { cx: &mut ViewContext, ) -> impl Element { enum ToggleMenu {} - MouseEventHandler::new::(0, cx, move |state, cx| { + MouseEventHandler::new::(0, cx, move |state, _| { let label: Cow = current_server .and_then(|row| { - let worktree = row.worktree.read(cx); Some( format!( "{} ({}) - {}", row.server_name.0, - worktree.root_name(), + row.worktree_root_name, if row.rpc_trace_selected { RPC_MESSAGES } else { @@ -778,7 +815,7 @@ impl LspLogToolbarItemView { fn render_language_server_menu_item( id: LanguageServerId, name: LanguageServerName, - worktree: ModelHandle, + worktree_root_name: &str, rpc_trace_enabled: bool, logs_selected: bool, rpc_trace_selected: bool, @@ -792,7 +829,7 @@ impl LspLogToolbarItemView { .with_child({ let style = &theme.toolbar_dropdown_menu.section_header; Label::new( - format!("{} ({})", name.0, worktree.read(cx).root_name()), + format!("{} ({})", name.0, worktree_root_name), style.text.clone(), ) .contained() diff --git a/crates/language_tools/src/lsp_log_tests.rs b/crates/language_tools/src/lsp_log_tests.rs index d26000ebc7..0830c3dac9 100644 --- a/crates/language_tools/src/lsp_log_tests.rs +++ b/crates/language_tools/src/lsp_log_tests.rs @@ -77,7 +77,14 @@ async fn test_lsp_logs(cx: &mut TestAppContext) { &[LogMenuItem { server_id: language_server.server.server_id(), server_name: LanguageServerName("the-rust-language-server".into()), - worktree: project.read(cx).worktrees(cx).next().unwrap(), + worktree_root_name: project + .read(cx) + .worktrees(cx) + .next() + .unwrap() + .read(cx) + .root_name() + .to_string(), rpc_trace_enabled: false, rpc_trace_selected: false, logs_selected: true, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b4e698e08a..7b6aba72c4 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -108,6 +108,8 @@ pub struct Project { active_entry: Option, buffer_ordered_messages_tx: mpsc::UnboundedSender, languages: Arc, + supplementary_language_servers: + HashMap)>, language_servers: HashMap, language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>, language_server_statuses: BTreeMap, @@ -147,7 +149,8 @@ pub struct Project { _maintain_buffer_languages: Task<()>, _maintain_workspace_config: Task<()>, terminals: Terminals, - copilot_enabled: bool, + copilot_lsp_subscription: Option, + copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, } @@ -618,6 +621,8 @@ impl Project { let (tx, rx) = mpsc::unbounded(); cx.spawn_weak(|this, cx| Self::send_buffer_ordered_messages(this, rx, cx)) .detach(); + let copilot_lsp_subscription = + Copilot::global(cx).map(|copilot| subscribe_for_copilot_events(&copilot, cx)); Self { worktrees: Default::default(), buffer_ordered_messages_tx: tx, @@ -647,6 +652,7 @@ impl Project { fs, next_entry_id: Default::default(), next_diagnostic_group_id: Default::default(), + supplementary_language_servers: HashMap::default(), language_servers: Default::default(), language_server_ids: Default::default(), language_server_statuses: Default::default(), @@ -658,7 +664,8 @@ impl Project { terminals: Terminals { local_handles: Vec::new(), }, - copilot_enabled: Copilot::global(cx).is_some(), + copilot_lsp_subscription, + copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), } }) @@ -694,6 +701,8 @@ impl Project { let (tx, rx) = mpsc::unbounded(); cx.spawn_weak(|this, cx| Self::send_buffer_ordered_messages(this, rx, cx)) .detach(); + let copilot_lsp_subscription = + Copilot::global(cx).map(|copilot| subscribe_for_copilot_events(&copilot, cx)); let mut this = Self { worktrees: Vec::new(), buffer_ordered_messages_tx: tx, @@ -723,6 +732,7 @@ impl Project { remote_id, replica_id, }), + supplementary_language_servers: HashMap::default(), language_servers: Default::default(), language_server_ids: Default::default(), language_server_statuses: response @@ -751,7 +761,8 @@ impl Project { terminals: Terminals { local_handles: Vec::new(), }, - copilot_enabled: Copilot::global(cx).is_some(), + copilot_lsp_subscription, + copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), }; for worktree in worktrees { @@ -882,12 +893,14 @@ impl Project { self.restart_language_servers(worktree, language, cx); } - if !self.copilot_enabled && Copilot::global(cx).is_some() { - self.copilot_enabled = true; - for buffer in self.opened_buffers.values() { - if let Some(buffer) = buffer.upgrade(cx) { - self.register_buffer_with_copilot(&buffer, cx); + if self.copilot_lsp_subscription.is_none() { + if let Some(copilot) = Copilot::global(cx) { + for buffer in self.opened_buffers.values() { + if let Some(buffer) = buffer.upgrade(cx) { + self.register_buffer_with_copilot(&buffer, cx); + } } + self.copilot_lsp_subscription = Some(subscribe_for_copilot_events(&copilot, cx)); } } @@ -2789,18 +2802,6 @@ impl Project { None => return Ok(None), }; - language_server - .on_notification::({ - move |params, mut cx| { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |_, cx| { - cx.emit(Event::LanguageServerLog(server_id, params.message)) - }); - } - } - }) - .detach(); - language_server .on_notification::({ let adapter = adapter.clone(); @@ -7954,9 +7955,23 @@ impl Project { }) } + pub fn supplementary_language_servers( + &self, + ) -> impl '_ + + Iterator< + Item = ( + &LanguageServerId, + &(LanguageServerName, Arc), + ), + > { + self.supplementary_language_servers.iter() + } + pub fn language_server_for_id(&self, id: LanguageServerId) -> Option> { if let LanguageServerState::Running { server, .. } = self.language_servers.get(&id)? { Some(server.clone()) + } else if let Some((_, server)) = self.supplementary_language_servers.get(&id) { + Some(Arc::clone(server)) } else { None } @@ -8016,6 +8031,43 @@ impl Project { } } +fn subscribe_for_copilot_events( + copilot: &ModelHandle, + cx: &mut ModelContext<'_, Project>, +) -> gpui::Subscription { + cx.subscribe( + copilot, + |project, copilot, copilot_event, cx| match copilot_event { + copilot::Event::CopilotLanguageServerStarted => { + if let Some((name, copilot_server)) = copilot.read(cx).language_server() { + let new_server_id = copilot_server.server_id(); + if let hash_map::Entry::Vacant(v) = + project.supplementary_language_servers.entry(new_server_id) + { + let weak_project = cx.weak_handle(); + let copilot_log_subscription = copilot_server + .on_notification::( + move |params, mut cx| { + if let Some(project) = weak_project.upgrade(&mut cx) { + project.update(&mut cx, |_, cx| { + cx.emit(Event::LanguageServerLog( + new_server_id, + params.message, + )); + }) + } + }, + ); + project.copilot_log_subscription = Some(copilot_log_subscription); + v.insert((name.clone(), Arc::clone(copilot_server))); + cx.emit(Event::LanguageServerAdded(new_server_id)); + } + } + } + }, + ) +} + fn glob_literal_prefix<'a>(glob: &'a str) -> &'a str { let mut literal_end = 0; for (i, part) in glob.split(path::MAIN_SEPARATOR).enumerate() { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c800e4e110..d22e26c1f5 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -129,6 +129,7 @@ fn main() { let client = client::Client::new(http.clone(), cx); let mut languages = LanguageRegistry::new(login_shell_env_loaded); + let copilot_language_server_id = languages.next_language_server_id(); languages.set_executor(cx.background().clone()); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); let languages = Arc::new(languages); @@ -159,7 +160,7 @@ fn main() { semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx); vim::init(cx); terminal_view::init(cx); - copilot::init(http.clone(), node_runtime, cx); + copilot::init(copilot_language_server_id, http.clone(), node_runtime, cx); ai::init(cx); component_test::init(cx);