Detailed changes
@@ -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<dyn HttpClient>, node_runtime: Arc<dyn NodeRuntime>, cx: &mut AppContext) {
+pub fn init(
+ new_server_id: LanguageServerId,
+ http: Arc<dyn HttpClient>,
+ node_runtime: Arc<dyn NodeRuntime>,
+ 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<LanguageServer>,
sign_in_status: SignInStatus,
registered_buffers: HashMap<usize, RegisteredBuffer>,
@@ -268,10 +273,15 @@ pub struct Copilot {
node_runtime: Arc<dyn NodeRuntime>,
server: CopilotServer,
buffers: HashSet<WeakModelHandle<Buffer>>,
+ 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<dyn HttpClient>,
node_runtime: Arc<dyn NodeRuntime>,
cx: &mut ModelContext<Self>,
) -> 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<Copilot>) {
+ 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<dyn HttpClient>,
node_runtime: Arc<dyn NodeRuntime>,
this: ModelHandle<Self>,
@@ -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::<LogMessage, _>(|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::<StatusNotification, _>(
@@ -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<LanguageServer>)> {
+ if let CopilotServer::Running(server) = &self.server {
+ Some((&server.name, &server.lsp))
+ } else {
+ None
+ }
+ }
+
pub fn register_buffer(&mut self, buffer: &ModelHandle<Buffer>, cx: &mut ModelContext<Self>) {
let weak_buffer = buffer.downgrade();
self.buffers.insert(weak_buffer.clone());
@@ -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 {
@@ -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<Buffer>,
rpc_state: Option<LanguageServerRpcState>,
- _subscription: Option<lsp::Subscription>,
+ _io_logs_subscription: Option<lsp::Subscription>,
+ _lsp_logs_subscription: Option<lsp::Subscription>,
}
struct LanguageServerRpcState {
@@ -69,7 +70,7 @@ enum MessageKind {
pub(crate) struct LogMenuItem {
pub server_id: LanguageServerId,
pub server_name: LanguageServerName,
- pub worktree: ModelHandle<Worktree>,
+ 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<Project>, cx: &mut ModelContext<Self>) {
- 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::<lsp::notification::LogMessage, _>({
+ 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<Self>,
) -> 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<Self>,
) -> 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::<Vec<_>>();
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<Self>,
) -> impl Element<Self> {
enum ToggleMenu {}
- MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, cx| {
+ MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, _| {
let label: Cow<str> = 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>,
+ 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()
@@ -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,
@@ -108,6 +108,8 @@ pub struct Project {
active_entry: Option<ProjectEntryId>,
buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
languages: Arc<LanguageRegistry>,
+ supplementary_language_servers:
+ HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
language_servers: HashMap<LanguageServerId, LanguageServerState>,
language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
@@ -147,7 +149,8 @@ pub struct Project {
_maintain_buffer_languages: Task<()>,
_maintain_workspace_config: Task<()>,
terminals: Terminals,
- copilot_enabled: bool,
+ copilot_lsp_subscription: Option<gpui::Subscription>,
+ copilot_log_subscription: Option<lsp::Subscription>,
current_lsp_settings: HashMap<Arc<str>, 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::<ProjectSettings>(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::<ProjectSettings>(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::<lsp::notification::LogMessage, _>({
- 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::<lsp::notification::PublishDiagnostics, _>({
let adapter = adapter.clone();
@@ -7954,9 +7955,23 @@ impl Project {
})
}
+ pub fn supplementary_language_servers(
+ &self,
+ ) -> impl '_
+ + Iterator<
+ Item = (
+ &LanguageServerId,
+ &(LanguageServerName, Arc<LanguageServer>),
+ ),
+ > {
+ self.supplementary_language_servers.iter()
+ }
+
pub fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
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<Copilot>,
+ 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::<copilot::request::LogMessage, _>(
+ 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() {
@@ -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);