From fc36c706d33acaeb020347f3228dc125adaf3d27 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Tue, 8 Mar 2022 15:34:44 -0800 Subject: [PATCH 01/12] Add missing mouse button events and mouse history navigation Co-Authored-By: Max Brunsfeld Co-Authored-By: Nathan Sobo --- crates/gpui/src/elements/event_handler.rs | 38 +++++++++++- crates/gpui/src/platform/event.rs | 24 +++++++ crates/gpui/src/platform/mac/event.rs | 42 +++++++++++++ crates/gpui/src/platform/mac/window.rs | 16 +++++ crates/workspace/src/pane.rs | 76 +++++++++++++++-------- crates/zed/src/zed.rs | 20 +++--- 6 files changed, 179 insertions(+), 37 deletions(-) diff --git a/crates/gpui/src/elements/event_handler.rs b/crates/gpui/src/elements/event_handler.rs index 0eea82fa020a635060db4f77a6fee4a05e70566c..2132939c1ce062dd6eacd1eb47a293885cda7bda 100644 --- a/crates/gpui/src/elements/event_handler.rs +++ b/crates/gpui/src/elements/event_handler.rs @@ -10,6 +10,8 @@ pub struct EventHandler { child: ElementBox, capture: Option bool>>, mouse_down: Option bool>>, + right_mouse_down: Option bool>>, + other_mouse_down: Option bool>>, } impl EventHandler { @@ -18,6 +20,8 @@ impl EventHandler { child, capture: None, mouse_down: None, + right_mouse_down: None, + other_mouse_down: None, } } @@ -29,6 +33,22 @@ impl EventHandler { self } + pub fn on_right_mouse_down(mut self, callback: F) -> Self + where + F: 'static + FnMut(&mut EventContext) -> bool, + { + self.right_mouse_down = Some(Box::new(callback)); + self + } + + pub fn on_other_mouse_down(mut self, callback: F) -> Self + where + F: 'static + FnMut(u16, &mut EventContext) -> bool, + { + self.other_mouse_down = Some(Box::new(callback)); + self + } + pub fn capture(mut self, callback: F) -> Self where F: 'static + FnMut(&Event, RectF, &mut EventContext) -> bool, @@ -86,7 +106,23 @@ impl Element for EventHandler { } } false - } + }, + Event::RightMouseDown { position, .. } => { + if let Some(callback) = self.right_mouse_down.as_mut() { + if bounds.contains_point(*position) { + return callback(cx); + } + } + false + }, + Event::OtherMouseDown { position, button, .. } => { + if let Some(callback) = self.other_mouse_down.as_mut() { + if bounds.contains_point(*position) { + return callback(*button, cx); + } + } + false + }, _ => false, } } diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 98762b306ad86d41b2a0a8ab9421eb970b9051a0..19b8eec189d3b6675a6fa4bfe730c9e68334db7b 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -26,6 +26,30 @@ pub enum Event { LeftMouseDragged { position: Vector2F, }, + RightMouseDown { + position: Vector2F, + ctrl: bool, + alt: bool, + shift: bool, + cmd: bool, + click_count: usize, + }, + RightMouseUp { + position: Vector2F, + }, + OtherMouseDown { + position: Vector2F, + button: u16, + ctrl: bool, + alt: bool, + shift: bool, + cmd: bool, + click_count: usize, + }, + OtherMouseUp { + position: Vector2F, + button: u16, + }, MouseMoved { position: Vector2F, left_mouse_down: bool, diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index 7c870e4720ed7fd8b3a6c13166e379d2dfc19b44..4b1f9d3a38623b09e9963266dd3250aed0a79d31 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -125,6 +125,48 @@ impl Event { window_height - native_event.locationInWindow().y as f32, ), }), + NSEventType::NSRightMouseDown => { + let modifiers = native_event.modifierFlags(); + window_height.map(|window_height| Self::RightMouseDown { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), + alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), + shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), + cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), + click_count: native_event.clickCount() as usize, + }) + } + NSEventType::NSRightMouseUp => window_height.map(|window_height| Self::RightMouseUp { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + }), + NSEventType::NSOtherMouseDown => { + let modifiers = native_event.modifierFlags(); + window_height.map(|window_height| Self::OtherMouseDown { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + button: native_event.buttonNumber() as u16, + ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), + alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), + shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), + cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), + click_count: native_event.clickCount() as usize, + }) + } + NSEventType::NSOtherMouseUp => window_height.map(|window_height| Self::OtherMouseUp { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + button: native_event.buttonNumber() as u16, + }), NSEventType::NSLeftMouseDragged => { window_height.map(|window_height| Self::LeftMouseDragged { position: vec2f( diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 9fd34166ff0a3b4da18de4ce6fa04c49e3ed748b..52810091556241d671a8c93ec13cedb9175c3da9 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -95,6 +95,22 @@ unsafe fn build_classes() { sel!(mouseUp:), handle_view_event as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(rightMouseDown:), + handle_view_event as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(rightMouseUp:), + handle_view_event as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(otherMouseDown:), + handle_view_event as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(otherMouseUp:), + handle_view_event as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(mouseMoved:), handle_view_event as extern "C" fn(&Object, Sel, id), diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 78f84d7da2d807a458b25b8ec4e3bf8b802ce61c..02a7c03c909e3213fbfe7b8137e9537cca5e69cb 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -8,7 +8,7 @@ use gpui::{ keymap::Binding, platform::CursorStyle, AnyViewHandle, Entity, MutableAppContext, Quad, RenderContext, Task, View, ViewContext, - ViewHandle, + ViewHandle, WeakViewHandle, }; use postage::watch; use project::ProjectPath; @@ -27,8 +27,8 @@ action!(ActivateNextItem); action!(CloseActiveItem); action!(CloseInactiveItems); action!(CloseItem, usize); -action!(GoBack); -action!(GoForward); +action!(GoBack, Option>); +action!(GoForward, Option>); const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; @@ -54,11 +54,19 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(|pane: &mut Pane, action: &Split, cx| { pane.split(action.0, cx); }); - cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| { - Pane::go_back(workspace, cx).detach(); + cx.add_action(|workspace: &mut Workspace, action: &GoBack, cx| { + Pane::go_back( + workspace, + action.0.as_ref().and_then(|weak_handle| weak_handle.upgrade(cx)), + cx + ).detach(); }); - cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| { - Pane::go_forward(workspace, cx).detach(); + cx.add_action(|workspace: &mut Workspace, action: &GoForward, cx| { + Pane::go_forward( + workspace, + action.0.as_ref().and_then(|weak_handle| weak_handle.upgrade(cx)), + cx + ).detach(); }); cx.add_bindings(vec![ @@ -70,8 +78,8 @@ pub fn init(cx: &mut MutableAppContext) { Binding::new("cmd-k down", Split(SplitDirection::Down), Some("Pane")), Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")), Binding::new("cmd-k right", Split(SplitDirection::Right), Some("Pane")), - Binding::new("ctrl--", GoBack, Some("Pane")), - Binding::new("shift-ctrl-_", GoForward, Some("Pane")), + Binding::new("ctrl--", GoBack(None), Some("Pane")), + Binding::new("shift-ctrl-_", GoForward(None), Some("Pane")), ]); } @@ -163,19 +171,19 @@ impl Pane { cx.emit(Event::Activate); } - pub fn go_back(workspace: &mut Workspace, cx: &mut ViewContext) -> Task<()> { + pub fn go_back(workspace: &mut Workspace, pane: Option>, cx: &mut ViewContext) -> Task<()> { Self::navigate_history( workspace, - workspace.active_pane().clone(), + pane.unwrap_or_else(|| workspace.active_pane().clone()), NavigationMode::GoingBack, cx, ) } - pub fn go_forward(workspace: &mut Workspace, cx: &mut ViewContext) -> Task<()> { + pub fn go_forward(workspace: &mut Workspace, pane: Option>, cx: &mut ViewContext) -> Task<()> { Self::navigate_history( workspace, - workspace.active_pane().clone(), + pane.unwrap_or_else(|| workspace.active_pane().clone()), NavigationMode::GoingForward, cx, ) @@ -187,6 +195,8 @@ impl Pane { mode: NavigationMode, cx: &mut ViewContext, ) -> Task<()> { + workspace.activate_pane(pane.clone(), cx); + let to_load = pane.update(cx, |pane, cx| { // Retrieve the weak item handle from the history. let entry = pane.nav_history.borrow_mut().pop(mode)?; @@ -634,19 +644,33 @@ impl View for Pane { } fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - if let Some(active_item) = self.active_item() { - Flex::column() - .with_child(self.render_tabs(cx)) - .with_children( - self.active_toolbar() - .as_ref() - .map(|view| ChildView::new(view).boxed()), - ) - .with_child(ChildView::new(active_item).flexible(1., true).boxed()) - .named("pane") - } else { - Empty::new().named("pane") - } + let this = cx.handle(); + + EventHandler::new( + if let Some(active_item) = self.active_item() { + Flex::column() + .with_child(self.render_tabs(cx)) + .with_children( + self.active_toolbar() + .as_ref() + .map(|view| ChildView::new(view).boxed()), + ) + .with_child(ChildView::new(active_item).flexible(1., true).boxed()) + .boxed() + } else { + Empty::new().boxed() + } + ) + .on_other_mouse_down(move |button, cx| { + match button { + 3 => cx.dispatch_action(GoBack(Some(this.clone()))), + 4 => cx.dispatch_action(GoForward(Some(this.clone()))), + _ => return false, + }; + true + }) + .named("pane") + } fn on_focus(&mut self, cx: &mut ViewContext) { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c9a14bf2364441e002e5208af7b53aa6e2420830..75e7735f2404f2fbb8fb99940dace867415a253a 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -747,44 +747,44 @@ mod tests { (file3.clone(), DisplayPoint::new(15, 0)) ); - workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await; + workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; assert_eq!( active_location(&workspace, cx), (file3.clone(), DisplayPoint::new(0, 0)) ); - workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await; + workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; assert_eq!( active_location(&workspace, cx), (file2.clone(), DisplayPoint::new(0, 0)) ); - workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await; + workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; assert_eq!( active_location(&workspace, cx), (file1.clone(), DisplayPoint::new(10, 0)) ); - workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await; + workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; assert_eq!( active_location(&workspace, cx), (file1.clone(), DisplayPoint::new(0, 0)) ); // Go back one more time and ensure we don't navigate past the first item in the history. - workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await; + workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; assert_eq!( active_location(&workspace, cx), (file1.clone(), DisplayPoint::new(0, 0)) ); - workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await; + workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await; assert_eq!( active_location(&workspace, cx), (file1.clone(), DisplayPoint::new(10, 0)) ); - workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await; + workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await; assert_eq!( active_location(&workspace, cx), (file2.clone(), DisplayPoint::new(0, 0)) @@ -798,7 +798,7 @@ mod tests { .update(cx, |pane, cx| pane.close_item(editor3.id(), cx)); drop(editor3); }); - workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await; + workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await; assert_eq!( active_location(&workspace, cx), (file3.clone(), DisplayPoint::new(0, 0)) @@ -818,12 +818,12 @@ mod tests { }) .await .unwrap(); - workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await; + workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; assert_eq!( active_location(&workspace, cx), (file1.clone(), DisplayPoint::new(10, 0)) ); - workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await; + workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await; assert_eq!( active_location(&workspace, cx), (file3.clone(), DisplayPoint::new(0, 0)) From 3dc100adfbdb87137d2aeebdee2149f8cb5f2bde Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 9 Mar 2022 11:27:44 -0800 Subject: [PATCH 02/12] Reset receive timeout only on reads from websocket connection, not writes Also, increase the receive timeout to 30 seconds. We'll still respond immediately to explicit disconnection, but when there are temporary network blips that delay pings, we think we should err on the side of keeping the connection alive. This is in response to a false positive 'host disconnected' state that we observed when pairing today, while the host (Keith) still clearly had a working internet connection, because we were screen sharing. Co-Authored-By: Keith Simmons --- crates/rpc/src/peer.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/rpc/src/peer.rs b/crates/rpc/src/peer.rs index 8f1d66e47a4b8fd92f02e2aa2c1318505af6e4a3..4156c0883d9c897ab2dc64a2d9df8c958cfa5a96 100644 --- a/crates/rpc/src/peer.rs +++ b/crates/rpc/src/peer.rs @@ -96,6 +96,7 @@ pub struct ConnectionState { const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(1); const WRITE_TIMEOUT: Duration = Duration::from_secs(2); +const RECEIVE_TIMEOUT: Duration = Duration::from_secs(30); impl Peer { pub fn new() -> Arc { @@ -147,14 +148,14 @@ impl Peer { let keepalive_timer = create_timer(KEEPALIVE_INTERVAL).fuse(); futures::pin_mut!(keepalive_timer); + // Disconnect if we don't receive messages at least this frequently. + let receive_timeout = create_timer(RECEIVE_TIMEOUT).fuse(); + futures::pin_mut!(receive_timeout); + loop { let read_message = reader.read().fuse(); futures::pin_mut!(read_message); - // Disconnect if we don't receive messages at least this frequently. - let receive_timeout = create_timer(3 * KEEPALIVE_INTERVAL).fuse(); - futures::pin_mut!(receive_timeout); - loop { futures::select_biased! { outgoing = outgoing_rx.next().fuse() => match outgoing { @@ -170,6 +171,7 @@ impl Peer { }, incoming = read_message => { let incoming = incoming.context("received invalid RPC message")?; + receive_timeout.set(create_timer(RECEIVE_TIMEOUT).fuse()); if let proto::Message::Envelope(incoming) = incoming { if incoming_tx.send(incoming).await.is_err() { return Ok(()); From 6ee0cceb14337c48495c56c62e50508bf1febfd2 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Wed, 9 Mar 2022 15:04:04 -0800 Subject: [PATCH 03/12] Switch to using mouse navigation events instead of other in order to get rid of opaque button id --- crates/gpui/src/elements/event_handler.rs | 28 +++++---- crates/gpui/src/gpui.rs | 2 +- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/event.rs | 14 +++-- crates/gpui/src/platform/mac/event.rs | 40 +++++++++--- crates/workspace/src/pane.rs | 74 ++++++++++++++--------- crates/zed/src/zed.rs | 40 +++++++++--- 7 files changed, 132 insertions(+), 68 deletions(-) diff --git a/crates/gpui/src/elements/event_handler.rs b/crates/gpui/src/elements/event_handler.rs index 2132939c1ce062dd6eacd1eb47a293885cda7bda..469bebee1a43f8c7b5b0c542e3e337173d9f0f63 100644 --- a/crates/gpui/src/elements/event_handler.rs +++ b/crates/gpui/src/elements/event_handler.rs @@ -3,7 +3,7 @@ use serde_json::json; use crate::{ geometry::vector::Vector2F, DebugContext, Element, ElementBox, Event, EventContext, - LayoutContext, PaintContext, SizeConstraint, + LayoutContext, NavigationDirection, PaintContext, SizeConstraint, }; pub struct EventHandler { @@ -11,7 +11,7 @@ pub struct EventHandler { capture: Option bool>>, mouse_down: Option bool>>, right_mouse_down: Option bool>>, - other_mouse_down: Option bool>>, + navigate_mouse_down: Option bool>>, } impl EventHandler { @@ -21,7 +21,7 @@ impl EventHandler { capture: None, mouse_down: None, right_mouse_down: None, - other_mouse_down: None, + navigate_mouse_down: None, } } @@ -41,11 +41,11 @@ impl EventHandler { self } - pub fn on_other_mouse_down(mut self, callback: F) -> Self + pub fn on_navigate_mouse_down(mut self, callback: F) -> Self where - F: 'static + FnMut(u16, &mut EventContext) -> bool, + F: 'static + FnMut(NavigationDirection, &mut EventContext) -> bool, { - self.other_mouse_down = Some(Box::new(callback)); + self.navigate_mouse_down = Some(Box::new(callback)); self } @@ -106,7 +106,7 @@ impl Element for EventHandler { } } false - }, + } Event::RightMouseDown { position, .. } => { if let Some(callback) = self.right_mouse_down.as_mut() { if bounds.contains_point(*position) { @@ -114,15 +114,19 @@ impl Element for EventHandler { } } false - }, - Event::OtherMouseDown { position, button, .. } => { - if let Some(callback) = self.other_mouse_down.as_mut() { + } + Event::NavigateMouseDown { + position, + direction, + .. + } => { + if let Some(callback) = self.navigate_mouse_down.as_mut() { if bounds.contains_point(*position) { - return callback(*button, cx); + return callback(*direction, cx); } } false - }, + } _ => false, } } diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 49fc74d47bef083b450d26620d4cc9dfed21b222..9803a2aa2f70c23660953653b9c3c32ff4ad850f 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -29,7 +29,7 @@ pub mod keymap; pub mod platform; pub use gpui_macros::test; pub use platform::FontSystem; -pub use platform::{Event, PathPromptOptions, Platform, PromptLevel}; +pub use platform::{Event, NavigationDirection, PathPromptOptions, Platform, PromptLevel}; pub use presenter::{ Axis, DebugContext, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt, }; diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index c04145294c494d7e1b72cc4845fd6453bf25e3df..66bd44b26f239d1c2faff0538d9e400350e02f30 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -19,7 +19,7 @@ use crate::{ }; use anyhow::Result; use async_task::Runnable; -pub use event::Event; +pub use event::{Event, NavigationDirection}; use postage::oneshot; use std::{ any::Any, diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 19b8eec189d3b6675a6fa4bfe730c9e68334db7b..72e1c24d6b2d157825b115bb8a3c9122fe72a97d 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -1,5 +1,11 @@ use crate::{geometry::vector::Vector2F, keymap::Keystroke}; +#[derive(Copy, Clone, Debug)] +pub enum NavigationDirection { + Back, + Forward, +} + #[derive(Clone, Debug)] pub enum Event { KeyDown { @@ -37,18 +43,18 @@ pub enum Event { RightMouseUp { position: Vector2F, }, - OtherMouseDown { + NavigateMouseDown { position: Vector2F, - button: u16, + direction: NavigationDirection, ctrl: bool, alt: bool, shift: bool, cmd: bool, click_count: usize, }, - OtherMouseUp { + NavigateMouseUp { position: Vector2F, - button: u16, + direction: NavigationDirection, }, MouseMoved { position: Vector2F, diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index 4b1f9d3a38623b09e9963266dd3250aed0a79d31..33f9f22e116b0b2005a0e79947de345dc69d0dcf 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -1,4 +1,8 @@ -use crate::{geometry::vector::vec2f, keymap::Keystroke, platform::Event}; +use crate::{ + geometry::vector::vec2f, + keymap::Keystroke, + platform::{Event, NavigationDirection}, +}; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventType}, base::{id, nil, YES}, @@ -146,13 +150,20 @@ impl Event { ), }), NSEventType::NSOtherMouseDown => { + let direction = match native_event.buttonNumber() { + 3 => NavigationDirection::Back, + 4 => NavigationDirection::Forward, + // Other mouse buttons aren't tracked currently + _ => return None, + }; + let modifiers = native_event.modifierFlags(); - window_height.map(|window_height| Self::OtherMouseDown { + window_height.map(|window_height| Self::NavigateMouseDown { position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), - button: native_event.buttonNumber() as u16, + direction, ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), @@ -160,13 +171,22 @@ impl Event { click_count: native_event.clickCount() as usize, }) } - NSEventType::NSOtherMouseUp => window_height.map(|window_height| Self::OtherMouseUp { - position: vec2f( - native_event.locationInWindow().x as f32, - window_height - native_event.locationInWindow().y as f32, - ), - button: native_event.buttonNumber() as u16, - }), + NSEventType::NSOtherMouseUp => { + let direction = match native_event.buttonNumber() { + 3 => NavigationDirection::Back, + 4 => NavigationDirection::Forward, + // Other mouse buttons aren't tracked currently + _ => return None, + }; + + window_height.map(|window_height| Self::NavigateMouseUp { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + direction, + }) + } NSEventType::NSLeftMouseDragged => { window_height.map(|window_height| Self::LeftMouseDragged { position: vec2f( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 02a7c03c909e3213fbfe7b8137e9537cca5e69cb..15195f24341e6c7a321323ac58eac711dfe9b64d 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -6,7 +6,7 @@ use gpui::{ elements::*, geometry::{rect::RectF, vector::vec2f}, keymap::Binding, - platform::CursorStyle, + platform::{CursorStyle, NavigationDirection}, AnyViewHandle, Entity, MutableAppContext, Quad, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; @@ -57,16 +57,24 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(|workspace: &mut Workspace, action: &GoBack, cx| { Pane::go_back( workspace, - action.0.as_ref().and_then(|weak_handle| weak_handle.upgrade(cx)), - cx - ).detach(); + action + .0 + .as_ref() + .and_then(|weak_handle| weak_handle.upgrade(cx)), + cx, + ) + .detach(); }); cx.add_action(|workspace: &mut Workspace, action: &GoForward, cx| { Pane::go_forward( workspace, - action.0.as_ref().and_then(|weak_handle| weak_handle.upgrade(cx)), - cx - ).detach(); + action + .0 + .as_ref() + .and_then(|weak_handle| weak_handle.upgrade(cx)), + cx, + ) + .detach(); }); cx.add_bindings(vec![ @@ -171,7 +179,11 @@ impl Pane { cx.emit(Event::Activate); } - pub fn go_back(workspace: &mut Workspace, pane: Option>, cx: &mut ViewContext) -> Task<()> { + pub fn go_back( + workspace: &mut Workspace, + pane: Option>, + cx: &mut ViewContext, + ) -> Task<()> { Self::navigate_history( workspace, pane.unwrap_or_else(|| workspace.active_pane().clone()), @@ -180,7 +192,11 @@ impl Pane { ) } - pub fn go_forward(workspace: &mut Workspace, pane: Option>, cx: &mut ViewContext) -> Task<()> { + pub fn go_forward( + workspace: &mut Workspace, + pane: Option>, + cx: &mut ViewContext, + ) -> Task<()> { Self::navigate_history( workspace, pane.unwrap_or_else(|| workspace.active_pane().clone()), @@ -646,31 +662,29 @@ impl View for Pane { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let this = cx.handle(); - EventHandler::new( - if let Some(active_item) = self.active_item() { - Flex::column() - .with_child(self.render_tabs(cx)) - .with_children( - self.active_toolbar() - .as_ref() - .map(|view| ChildView::new(view).boxed()), - ) - .with_child(ChildView::new(active_item).flexible(1., true).boxed()) - .boxed() - } else { - Empty::new().boxed() + EventHandler::new(if let Some(active_item) = self.active_item() { + Flex::column() + .with_child(self.render_tabs(cx)) + .with_children( + self.active_toolbar() + .as_ref() + .map(|view| ChildView::new(view).boxed()), + ) + .with_child(ChildView::new(active_item).flexible(1., true).boxed()) + .boxed() + } else { + Empty::new().boxed() + }) + .on_navigate_mouse_down(move |direction, cx| { + let this = this.clone(); + match direction { + NavigationDirection::Back => cx.dispatch_action(GoBack(Some(this))), + NavigationDirection::Forward => cx.dispatch_action(GoForward(Some(this))), } - ) - .on_other_mouse_down(move |button, cx| { - match button { - 3 => cx.dispatch_action(GoBack(Some(this.clone()))), - 4 => cx.dispatch_action(GoForward(Some(this.clone()))), - _ => return false, - }; + true }) .named("pane") - } fn on_focus(&mut self, cx: &mut ViewContext) { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 75e7735f2404f2fbb8fb99940dace867415a253a..9299988ffee5934106d3b36f22a985b162c4623f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -747,44 +747,58 @@ mod tests { (file3.clone(), DisplayPoint::new(15, 0)) ); - workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; + workspace + .update(cx, |w, cx| Pane::go_back(w, None, cx)) + .await; assert_eq!( active_location(&workspace, cx), (file3.clone(), DisplayPoint::new(0, 0)) ); - workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; + workspace + .update(cx, |w, cx| Pane::go_back(w, None, cx)) + .await; assert_eq!( active_location(&workspace, cx), (file2.clone(), DisplayPoint::new(0, 0)) ); - workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; + workspace + .update(cx, |w, cx| Pane::go_back(w, None, cx)) + .await; assert_eq!( active_location(&workspace, cx), (file1.clone(), DisplayPoint::new(10, 0)) ); - workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; + workspace + .update(cx, |w, cx| Pane::go_back(w, None, cx)) + .await; assert_eq!( active_location(&workspace, cx), (file1.clone(), DisplayPoint::new(0, 0)) ); // Go back one more time and ensure we don't navigate past the first item in the history. - workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; + workspace + .update(cx, |w, cx| Pane::go_back(w, None, cx)) + .await; assert_eq!( active_location(&workspace, cx), (file1.clone(), DisplayPoint::new(0, 0)) ); - workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await; + workspace + .update(cx, |w, cx| Pane::go_forward(w, None, cx)) + .await; assert_eq!( active_location(&workspace, cx), (file1.clone(), DisplayPoint::new(10, 0)) ); - workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await; + workspace + .update(cx, |w, cx| Pane::go_forward(w, None, cx)) + .await; assert_eq!( active_location(&workspace, cx), (file2.clone(), DisplayPoint::new(0, 0)) @@ -798,7 +812,9 @@ mod tests { .update(cx, |pane, cx| pane.close_item(editor3.id(), cx)); drop(editor3); }); - workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await; + workspace + .update(cx, |w, cx| Pane::go_forward(w, None, cx)) + .await; assert_eq!( active_location(&workspace, cx), (file3.clone(), DisplayPoint::new(0, 0)) @@ -818,12 +834,16 @@ mod tests { }) .await .unwrap(); - workspace.update(cx, |w, cx| Pane::go_back(w, None, cx)).await; + workspace + .update(cx, |w, cx| Pane::go_back(w, None, cx)) + .await; assert_eq!( active_location(&workspace, cx), (file1.clone(), DisplayPoint::new(10, 0)) ); - workspace.update(cx, |w, cx| Pane::go_forward(w, None, cx)).await; + workspace + .update(cx, |w, cx| Pane::go_forward(w, None, cx)) + .await; assert_eq!( active_location(&workspace, cx), (file3.clone(), DisplayPoint::new(0, 0)) From 49e38e6e003c71cb402c42bfbbfb95789b9f6c91 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 9 Mar 2022 20:31:29 -0700 Subject: [PATCH 04/12] Eliminate ToFoldPoint trait Just make it a method on FoldMap --- crates/editor/src/display_map.rs | 8 ++-- crates/editor/src/display_map/fold_map.rs | 50 ++++++++++------------- crates/editor/src/display_map/tab_map.rs | 10 ++--- 3 files changed, 30 insertions(+), 38 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 053883e93888914bbb8277f65fe54c5850ccb44a..d36f5713088c7d1428244d90dbb4075b47e19a0b 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -6,7 +6,7 @@ mod wrap_map; use crate::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; -use fold_map::{FoldMap, ToFoldPoint as _}; +use fold_map::FoldMap; use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle}; use language::{Point, Subscription as BufferSubscription}; use std::ops::Range; @@ -200,7 +200,7 @@ impl DisplaySnapshot { pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { - let mut fold_point = point.to_fold_point(&self.folds_snapshot, Bias::Left); + let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Left); *fold_point.column_mut() = 0; point = fold_point.to_buffer_point(&self.folds_snapshot); @@ -216,7 +216,7 @@ impl DisplaySnapshot { pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { - let mut fold_point = point.to_fold_point(&self.folds_snapshot, Bias::Right); + let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Right); *fold_point.column_mut() = self.folds_snapshot.line_len(fold_point.row()); point = fold_point.to_buffer_point(&self.folds_snapshot); @@ -231,7 +231,7 @@ impl DisplaySnapshot { } fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { - let fold_point = point.to_fold_point(&self.folds_snapshot, bias); + let fold_point = self.folds_snapshot.to_fold_point(point, bias); let tab_point = self.tabs_snapshot.to_tab_point(fold_point); let wrap_point = self.wraps_snapshot.from_tab_point(tab_point); let block_point = self.blocks_snapshot.to_block_point(wrap_point); diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 2866ae8f636122ef0cda82bbd8d8cdf52ea176d7..daafbee57bf9a19aa51eefc04da3f4a1642f1c06 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -12,10 +12,6 @@ use std::{ }; use sum_tree::{Bias, Cursor, FilterCursor, SumTree}; -pub trait ToFoldPoint { - fn to_fold_point(&self, snapshot: &FoldSnapshot, bias: Bias) -> FoldPoint; -} - #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct FoldPoint(pub super::Point); @@ -75,26 +71,6 @@ impl FoldPoint { } } -impl ToFoldPoint for Point { - fn to_fold_point(&self, snapshot: &FoldSnapshot, bias: Bias) -> FoldPoint { - let mut cursor = snapshot.transforms.cursor::<(Point, FoldPoint)>(); - cursor.seek(self, Bias::Right, &()); - if cursor.item().map_or(false, |t| t.is_fold()) { - if bias == Bias::Left || *self == cursor.start().0 { - cursor.start().1 - } else { - cursor.end(&()).1 - } - } else { - let overshoot = *self - cursor.start().0; - FoldPoint(cmp::min( - cursor.start().1 .0 + overshoot, - cursor.end(&()).1 .0, - )) - } - } -} - pub struct FoldMapWriter<'a>(&'a mut FoldMap); impl<'a> FoldMapWriter<'a> { @@ -554,6 +530,24 @@ impl FoldSnapshot { summary } + pub fn to_fold_point(&self, point: Point, bias: Bias) -> FoldPoint { + let mut cursor = self.transforms.cursor::<(Point, FoldPoint)>(); + cursor.seek(&point, Bias::Right, &()); + if cursor.item().map_or(false, |t| t.is_fold()) { + if bias == Bias::Left || point == cursor.start().0 { + cursor.start().1 + } else { + cursor.end(&()).1 + } + } else { + let overshoot = point - cursor.start().0; + FoldPoint(cmp::min( + cursor.start().1 .0 + overshoot, + cursor.end(&()).1 .0, + )) + } + } + pub fn len(&self) -> FoldOffset { FoldOffset(self.transforms.summary().output.bytes) } @@ -1356,7 +1350,7 @@ mod tests { let buffer_point = fold_point.to_buffer_point(&snapshot); let buffer_offset = buffer_point.to_offset(&buffer_snapshot); assert_eq!( - buffer_point.to_fold_point(&snapshot, Right), + snapshot.to_fold_point(buffer_point, Right), fold_point, "{:?} -> fold point", buffer_point, @@ -1428,10 +1422,8 @@ mod tests { } for fold_range in map.merged_fold_ranges() { - let fold_point = fold_range - .start - .to_point(&buffer_snapshot) - .to_fold_point(&snapshot, Right); + let fold_point = + snapshot.to_fold_point(fold_range.start.to_point(&buffer_snapshot), Right); assert!(snapshot.is_line_folded(fold_point.row())); } diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index e2239e76715278ba84e0ade5d3b56c7bc9e0f082..dc62783eb31b485a155bb8f34011e740be4c261f 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -1,4 +1,4 @@ -use super::fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot, ToFoldPoint}; +use super::fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot}; use crate::MultiBufferSnapshot; use language::{rope, Chunk}; use parking_lot::Mutex; @@ -201,10 +201,6 @@ impl TabSnapshot { TabPoint::new(input.row(), expanded as u32) } - pub fn from_point(&self, point: Point, bias: Bias) -> TabPoint { - self.to_tab_point(point.to_fold_point(&self.fold_snapshot, bias)) - } - pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, usize, usize) { let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0)); let expanded = output.column() as usize; @@ -217,6 +213,10 @@ impl TabSnapshot { ) } + pub fn from_point(&self, point: Point, bias: Bias) -> TabPoint { + self.to_tab_point(self.fold_snapshot.to_fold_point(point, bias)) + } + pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { self.to_fold_point(point, bias) .0 From ee6d7fc6d56f6cce9570d0394c96aba0c06ea469 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Mar 2022 10:05:00 +0100 Subject: [PATCH 05/12] Delete till previous tabstop when backspacing within indent column --- crates/editor/src/editor.rs | 55 +++++++++++++++++++++++-------- crates/editor/src/multi_buffer.rs | 2 +- crates/language/src/buffer.rs | 34 ++++++++++++++----- 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 91d02b1aa0c99af374759a037e90882ef7c6d51d..4d49fb71496864e7e1936f2608ba6557525c86d1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2638,11 +2638,25 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); for selection in &mut selections { if selection.is_empty() { - let head = selection.head().to_display_point(&display_map); - let cursor = movement::left(&display_map, head) - .unwrap() - .to_point(&display_map); - selection.set_head(cursor); + let old_head = selection.head(); + let (buffer, line_buffer_range) = display_map + .buffer_snapshot + .buffer_line_for_row(old_head.row) + .unwrap(); + let indent_column = buffer.indent_column_for_line(line_buffer_range.start.row); + let mut new_head = + movement::left(&display_map, old_head.to_display_point(&display_map)) + .unwrap() + .to_point(&display_map); + if old_head.column <= indent_column && old_head.column > 0 { + let indent = buffer.indent_size(); + new_head = cmp::min( + new_head, + Point::new(old_head.row, ((old_head.column - 1) / indent) * indent), + ); + } + + selection.set_head(new_head); selection.goal = SelectionGoal::None; } } @@ -7153,14 +7167,13 @@ mod tests { #[gpui::test] fn test_backspace(cx: &mut gpui::MutableAppContext) { - let buffer = - MultiBuffer::build_simple("one two three\nfour five six\nseven eight nine\nten\n", cx); let settings = Settings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| { - build_editor(buffer.clone(), settings, cx) + build_editor(MultiBuffer::build_simple("", cx), settings, cx) }); view.update(cx, |view, cx| { + view.set_text("one two three\nfour five six\nseven eight nine\nten\n", cx); view.select_display_ranges( &[ // an empty selection - the preceding character is deleted @@ -7173,12 +7186,28 @@ mod tests { cx, ); view.backspace(&Backspace, cx); - }); + assert_eq!(view.text(cx), "oe two three\nfou five six\nseven ten\n"); - assert_eq!( - buffer.read(cx).read(cx).text(), - "oe two three\nfou five six\nseven ten\n" - ); + view.set_text(" one\n two\n three\n four", cx); + view.select_display_ranges( + &[ + // cursors at the the end of leading indent - last indent is deleted + DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4), + DisplayPoint::new(1, 8)..DisplayPoint::new(1, 8), + // cursors inside leading indent - overlapping indent deletions are coalesced + DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4), + DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5), + DisplayPoint::new(2, 6)..DisplayPoint::new(2, 6), + // cursor at the beginning of a line - preceding newline is deleted + DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), + // selection inside leading indent - only the selected character is deleted + DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3), + ], + cx, + ); + view.backspace(&Backspace, cx); + assert_eq!(view.text(cx), "one\n two\n three four"); + }); } #[gpui::test] diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 64683faa962229a77ba6384881eed7ca4ea87d1d..3678f8f1164bab6e7fa55073508e0fefeb594f1f 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1657,7 +1657,7 @@ impl MultiBufferSnapshot { } } - fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range)> { + pub fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range)> { let mut cursor = self.excerpts.cursor::(); cursor.seek(&Point::new(row, 0), Bias::Right, &()); if let Some(excerpt) = cursor.item() { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 3d79ecadd649d587200facd035b7e33c084629cf..5f3ddb8b99f2af6f7542ebb54e7a88b2cac4431d 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -47,9 +47,6 @@ lazy_static! { static ref QUERY_CURSORS: Mutex> = Default::default(); } -// TODO - Make this configurable -const INDENT_SIZE: u32 = 4; - pub struct Buffer { text: TextBuffer, file: Option>, @@ -70,6 +67,7 @@ pub struct Buffer { file_update_count: usize, completion_triggers: Vec, deferred_ops: OperationQueue, + indent_size: u32, } pub struct BufferSnapshot { @@ -81,9 +79,9 @@ pub struct BufferSnapshot { file_update_count: usize, remote_selections: TreeMap, selections_update_count: usize, - is_parsing: bool, language: Option>, parse_count: usize, + indent_size: u32, } #[derive(Clone, Debug)] @@ -416,6 +414,8 @@ impl Buffer { file_update_count: 0, completion_triggers: Default::default(), deferred_ops: OperationQueue::new(), + // TODO: make this configurable + indent_size: 4, } } @@ -428,10 +428,10 @@ impl Buffer { diagnostics: self.diagnostics.clone(), diagnostics_update_count: self.diagnostics_update_count, file_update_count: self.file_update_count, - is_parsing: self.parsing_in_background, language: self.language.clone(), parse_count: self.parse_count, selections_update_count: self.selections_update_count, + indent_size: self.indent_size, } } @@ -768,7 +768,11 @@ impl Buffer { .before_edit .indent_column_for_line(suggestion.basis_row) }); - let delta = if suggestion.indent { INDENT_SIZE } else { 0 }; + let delta = if suggestion.indent { + snapshot.indent_size + } else { + 0 + }; old_suggestions.insert( *old_to_new_rows.get(&old_row).unwrap(), indentation_basis + delta, @@ -787,7 +791,11 @@ impl Buffer { .into_iter() .flatten(); for (new_row, suggestion) in new_edited_row_range.zip(suggestions) { - let delta = if suggestion.indent { INDENT_SIZE } else { 0 }; + let delta = if suggestion.indent { + snapshot.indent_size + } else { + 0 + }; let new_indentation = indent_columns .get(&suggestion.basis_row) .copied() @@ -819,7 +827,11 @@ impl Buffer { .into_iter() .flatten(); for (row, suggestion) in inserted_row_range.zip(suggestions) { - let delta = if suggestion.indent { INDENT_SIZE } else { 0 }; + let delta = if suggestion.indent { + snapshot.indent_size + } else { + 0 + }; let new_indentation = indent_columns .get(&suggestion.basis_row) .copied() @@ -1868,6 +1880,10 @@ impl BufferSnapshot { pub fn file_update_count(&self) -> usize { self.file_update_count } + + pub fn indent_size(&self) -> u32 { + self.indent_size + } } impl Clone for BufferSnapshot { @@ -1881,9 +1897,9 @@ impl Clone for BufferSnapshot { selections_update_count: self.selections_update_count, diagnostics_update_count: self.diagnostics_update_count, file_update_count: self.file_update_count, - is_parsing: self.is_parsing, language: self.language.clone(), parse_count: self.parse_count, + indent_size: self.indent_size, } } } From 4bbf5ed0b979b633d52c736281b55ae7e8f7660f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Mar 2022 12:00:33 +0100 Subject: [PATCH 06/12] Listen to all LSP progress notifications and broadcast them to peers --- crates/project/src/project.rs | 286 +++++++++++++++++++++------------- crates/rpc/proto/zed.proto | 30 +++- crates/rpc/src/proto.rs | 6 +- crates/server/src/rpc.rs | 21 +-- 4 files changed, 207 insertions(+), 136 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9a45e0816410b23c595279294fd8a8f3bbf971a1..9741f167841407aae26da7daac22cdd49bc0e087 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -28,7 +28,6 @@ use rand::prelude::*; use search::SearchQuery; use sha2::{Digest, Sha256}; use similar::{ChangeTag, TextDiff}; -use smol::block_on; use std::{ cell::RefCell, cmp::{self, Ordering}, @@ -115,6 +114,21 @@ pub enum Event { DiagnosticsUpdated(ProjectPath), } +enum LspEvent { + WorkStart { + token: String, + }, + WorkProgress { + token: String, + message: Option, + percentage: Option, + }, + WorkEnd { + token: String, + }, + DiagnosticsUpdate(lsp::PublishDiagnosticsParams), +} + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] pub struct ProjectPath { pub worktree_id: WorktreeId, @@ -203,8 +217,7 @@ impl Project { client.add_entity_message_handler(Self::handle_add_collaborator); client.add_entity_message_handler(Self::handle_buffer_reloaded); client.add_entity_message_handler(Self::handle_buffer_saved); - client.add_entity_message_handler(Self::handle_disk_based_diagnostics_updated); - client.add_entity_message_handler(Self::handle_disk_based_diagnostics_updating); + client.add_entity_message_handler(Self::handle_lsp_event); client.add_entity_message_handler(Self::handle_remove_collaborator); client.add_entity_message_handler(Self::handle_register_worktree); client.add_entity_message_handler(Self::handle_unregister_worktree); @@ -1155,12 +1168,6 @@ impl Project { language: Arc, cx: &mut ModelContext, ) { - enum LspEvent { - DiagnosticsStart, - DiagnosticsUpdate(lsp::PublishDiagnosticsParams), - DiagnosticsFinish, - } - let key = (worktree_id, language.name()); self.started_language_servers .entry(key.clone()) @@ -1171,76 +1178,50 @@ impl Project { self.client.http_client(), cx, ); - let rpc = self.client.clone(); cx.spawn_weak(|this, mut cx| async move { let mut language_server = language_server?.await.log_err()?; let this = this.upgrade(&cx)?; + let (lsp_events_tx, lsp_events_rx) = smol::channel::unbounded(); - let disk_based_sources = language - .disk_based_diagnostic_sources() - .cloned() - .unwrap_or_default(); - let disk_based_diagnostics_progress_token = - language.disk_based_diagnostics_progress_token().cloned(); - let has_disk_based_diagnostic_progress_token = - disk_based_diagnostics_progress_token.is_some(); - let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); - - // Listen for `PublishDiagnostics` notifications. language_server .on_notification::({ - let diagnostics_tx = diagnostics_tx.clone(); + let lsp_events_tx = lsp_events_tx.clone(); move |params| { - if !has_disk_based_diagnostic_progress_token { - block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok(); - } - block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params))) + lsp_events_tx + .try_send(LspEvent::DiagnosticsUpdate(params)) .ok(); - if !has_disk_based_diagnostic_progress_token { - block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok(); - } } }) .detach(); - // Listen for `Progress` notifications. Send an event when the language server - // transitions between running jobs and not running any jobs. - let mut running_jobs_for_this_server: i32 = 0; language_server .on_notification::(move |params| { let token = match params.token { - lsp::NumberOrString::Number(_) => None, - lsp::NumberOrString::String(token) => Some(token), + lsp::NumberOrString::String(token) => token, + lsp::NumberOrString::Number(token) => { + log::info!("skipping numeric progress token {}", token); + return; + } }; - if token == disk_based_diagnostics_progress_token { - match params.value { - lsp::ProgressParamsValue::WorkDone(progress) => { - match progress { - lsp::WorkDoneProgress::Begin(_) => { - running_jobs_for_this_server += 1; - if running_jobs_for_this_server == 1 { - block_on( - diagnostics_tx - .send(LspEvent::DiagnosticsStart), - ) - .ok(); - } - } - lsp::WorkDoneProgress::End(_) => { - running_jobs_for_this_server -= 1; - if running_jobs_for_this_server == 0 { - block_on( - diagnostics_tx - .send(LspEvent::DiagnosticsFinish), - ) - .ok(); - } - } - _ => {} - } + match params.value { + lsp::ProgressParamsValue::WorkDone(progress) => match progress { + lsp::WorkDoneProgress::Begin(_) => { + lsp_events_tx.try_send(LspEvent::WorkStart { token }).ok(); } - } + lsp::WorkDoneProgress::Report(report) => { + lsp_events_tx + .try_send(LspEvent::WorkProgress { + token, + message: report.message, + percentage: report.percentage.map(|p| p as usize), + }) + .ok(); + } + lsp::WorkDoneProgress::End(_) => { + lsp_events_tx.try_send(LspEvent::WorkEnd { token }).ok(); + } + }, } }) .detach(); @@ -1249,43 +1230,11 @@ impl Project { cx.spawn(|mut cx| { let this = this.downgrade(); async move { - while let Ok(message) = diagnostics_rx.recv().await { + while let Ok(event) = lsp_events_rx.recv().await { let this = this.upgrade(&cx)?; - match message { - LspEvent::DiagnosticsStart => { - this.update(&mut cx, |this, cx| { - this.disk_based_diagnostics_started(cx); - if let Some(project_id) = this.remote_id() { - rpc.send(proto::DiskBasedDiagnosticsUpdating { - project_id, - }) - .log_err(); - } - }); - } - LspEvent::DiagnosticsUpdate(mut params) => { - language.process_diagnostics(&mut params); - this.update(&mut cx, |this, cx| { - this.update_diagnostics( - params, - &disk_based_sources, - cx, - ) - .log_err(); - }); - } - LspEvent::DiagnosticsFinish => { - this.update(&mut cx, |this, cx| { - this.disk_based_diagnostics_finished(cx); - if let Some(project_id) = this.remote_id() { - rpc.send(proto::DiskBasedDiagnosticsUpdated { - project_id, - }) - .log_err(); - } - }); - } - } + this.update(&mut cx, |this, cx| { + this.on_local_lsp_event(event, &language, cx) + }); } Some(()) } @@ -1358,6 +1307,107 @@ impl Project { }); } + fn on_local_lsp_event( + &mut self, + event: LspEvent, + language: &Arc, + cx: &mut ModelContext, + ) { + let disk_diagnostics_token = language.disk_based_diagnostics_progress_token(); + match event { + LspEvent::WorkStart { token } => { + if Some(&token) == disk_diagnostics_token { + self.disk_based_diagnostics_started(cx); + self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( + proto::LspDiskBasedDiagnosticsUpdating {}, + )); + } else { + self.on_lsp_work_start(token.clone(), cx); + self.send_lsp_event(proto::lsp_event::Variant::WorkStart( + proto::LspWorkStart { token }, + )); + } + } + LspEvent::WorkProgress { + token, + message, + percentage, + } => { + if Some(&token) != disk_diagnostics_token { + self.on_lsp_work_progress(token.clone(), message.clone(), percentage, cx); + self.send_lsp_event(proto::lsp_event::Variant::WorkProgress( + proto::LspWorkProgress { + token, + message, + percentage: percentage.map(|p| p as u32), + }, + )); + } + } + LspEvent::WorkEnd { token } => { + if Some(&token) == disk_diagnostics_token { + self.disk_based_diagnostics_finished(cx); + self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( + proto::LspDiskBasedDiagnosticsUpdated {}, + )); + } else { + self.on_lsp_work_end(token.clone(), cx); + self.send_lsp_event(proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { + token, + })); + } + } + LspEvent::DiagnosticsUpdate(mut params) => { + language.process_diagnostics(&mut params); + + if disk_diagnostics_token.is_none() { + self.disk_based_diagnostics_started(cx); + self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( + proto::LspDiskBasedDiagnosticsUpdating {}, + )); + } + self.update_diagnostics( + params, + language + .disk_based_diagnostic_sources() + .unwrap_or(&Default::default()), + cx, + ) + .log_err(); + if disk_diagnostics_token.is_none() { + self.disk_based_diagnostics_finished(cx); + self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( + proto::LspDiskBasedDiagnosticsUpdated {}, + )); + } + } + } + } + + fn on_lsp_work_start(&mut self, token: String, cx: &mut ModelContext) {} + + fn on_lsp_work_progress( + &mut self, + token: String, + message: Option, + percentage: Option, + cx: &mut ModelContext, + ) { + } + + fn on_lsp_work_end(&mut self, token: String, cx: &mut ModelContext) {} + + fn send_lsp_event(&self, event: proto::lsp_event::Variant) { + if let Some(project_id) = self.remote_id() { + self.client + .send(proto::LspEvent { + project_id, + variant: Some(event), + }) + .log_err(); + } + } + pub fn update_diagnostics( &mut self, params: lsp::PublishDiagnosticsParams, @@ -3096,23 +3146,41 @@ impl Project { }) } - async fn handle_disk_based_diagnostics_updating( + async fn handle_lsp_event( this: ModelHandle, - _: TypedEnvelope, + envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { - this.update(&mut cx, |this, cx| this.disk_based_diagnostics_started(cx)); - Ok(()) - } + match envelope + .payload + .variant + .ok_or_else(|| anyhow!("invalid variant"))? + { + proto::lsp_event::Variant::WorkStart(payload) => this.update(&mut cx, |this, cx| { + this.on_lsp_work_start(payload.token, cx); + }), + proto::lsp_event::Variant::WorkProgress(payload) => this.update(&mut cx, |this, cx| { + this.on_lsp_work_progress( + payload.token, + payload.message, + payload.percentage.map(|p| p as usize), + cx, + ); + }), + proto::lsp_event::Variant::WorkEnd(payload) => this.update(&mut cx, |this, cx| { + this.on_lsp_work_end(payload.token, cx); + }), + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(_) => { + this.update(&mut cx, |this, cx| { + this.disk_based_diagnostics_started(cx); + }) + } + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(_) => { + this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx)); + } + } - async fn handle_disk_based_diagnostics_updated( - this: ModelHandle, - _: TypedEnvelope, - _: Arc, - mut cx: AsyncAppContext, - ) -> Result<()> { - this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx)); Ok(()) } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 7f43aaff1a9e361d63d1ca58c0e43f76c591de26..18df77e5c329160bdeb63f1b71e571704451e3bf 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -37,8 +37,7 @@ message Envelope { UnregisterWorktree unregister_worktree = 29; UpdateWorktree update_worktree = 31; UpdateDiagnosticSummary update_diagnostic_summary = 32; - DiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 33; - DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 34; + LspEvent lsp_event = 33; OpenBuffer open_buffer = 35; OpenBufferResponse open_buffer_response = 36; @@ -424,14 +423,35 @@ message DiagnosticSummary { uint32 hint_count = 5; } -message DiskBasedDiagnosticsUpdating { +message LspEvent { uint64 project_id = 1; + oneof variant { + LspWorkStart work_start = 2; + LspWorkProgress work_progress = 3; + LspWorkEnd work_end = 4; + LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 5; + LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 6; + } } -message DiskBasedDiagnosticsUpdated { - uint64 project_id = 1; +message LspWorkStart { + string token = 1; +} + +message LspWorkProgress { + string token = 1; + optional string message = 2; + optional uint32 percentage = 3; } +message LspWorkEnd { + string token = 1; +} + +message LspDiskBasedDiagnosticsUpdating {} + +message LspDiskBasedDiagnosticsUpdated {} + message GetChannels {} message GetChannelsResponse { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index d252decb3acdfc116d077d2f3967a909cb5d2b20..15a5839524d22556b01c40921a311ec87668f754 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -146,8 +146,6 @@ messages!( (BufferReloaded, Foreground), (BufferSaved, Foreground), (ChannelMessageSent, Foreground), - (DiskBasedDiagnosticsUpdated, Background), - (DiskBasedDiagnosticsUpdating, Background), (Error, Foreground), (FormatBuffers, Foreground), (FormatBuffersResponse, Foreground), @@ -175,6 +173,7 @@ messages!( (JoinProjectResponse, Foreground), (LeaveChannel, Foreground), (LeaveProject, Foreground), + (LspEvent, Background), (OpenBuffer, Background), (OpenBufferForSymbol, Background), (OpenBufferForSymbolResponse, Background), @@ -246,8 +245,6 @@ entity_messages!( ApplyCompletionAdditionalEdits, BufferReloaded, BufferSaved, - DiskBasedDiagnosticsUpdated, - DiskBasedDiagnosticsUpdating, FormatBuffers, GetCodeActions, GetCompletions, @@ -257,6 +254,7 @@ entity_messages!( GetProjectSymbols, JoinProject, LeaveProject, + LspEvent, OpenBuffer, OpenBufferForSymbol, PerformRename, diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 2b954b57741d008aff46423561af5fdc261c1aa9..74406146c14275e10f8da45d4e9f3625a9d8c402 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -84,8 +84,7 @@ impl Server { .add_message_handler(Server::unregister_worktree) .add_request_handler(Server::update_worktree) .add_message_handler(Server::update_diagnostic_summary) - .add_message_handler(Server::disk_based_diagnostics_updating) - .add_message_handler(Server::disk_based_diagnostics_updated) + .add_message_handler(Server::lsp_event) .add_request_handler(Server::forward_project_request::) .add_request_handler(Server::forward_project_request::) .add_request_handler(Server::forward_project_request::) @@ -535,23 +534,9 @@ impl Server { Ok(()) } - async fn disk_based_diagnostics_updating( + async fn lsp_event( self: Arc, - request: TypedEnvelope, - ) -> tide::Result<()> { - let receiver_ids = self - .state() - .project_connection_ids(request.payload.project_id, request.sender_id)?; - broadcast(request.sender_id, receiver_ids, |connection_id| { - self.peer - .forward_send(request.sender_id, connection_id, request.payload.clone()) - })?; - Ok(()) - } - - async fn disk_based_diagnostics_updated( - self: Arc, - request: TypedEnvelope, + request: TypedEnvelope, ) -> tide::Result<()> { let receiver_ids = self .state() From 4243f0c339166cad2ba7ccc7c8731134b9d85b7f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Mar 2022 16:09:47 +0100 Subject: [PATCH 07/12] Render pending language server work in status bar --- crates/project/src/project.rs | 162 +++++++++++++++++++++-------- crates/rpc/proto/zed.proto | 11 +- crates/workspace/src/lsp_status.rs | 31 +++++- crates/zed/src/zed.rs | 1 + 4 files changed, 151 insertions(+), 54 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9741f167841407aae26da7daac22cdd49bc0e087..95609bf43f36fe5d448a1cbf45e3803442c41f4a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -51,6 +51,8 @@ pub struct Project { languages: Arc, language_servers: HashMap<(WorktreeId, Arc), Arc>, started_language_servers: HashMap<(WorktreeId, Arc), Task>>>, + pending_language_server_work: HashMap<(usize, String), LspWorkProgress>, + next_language_server_id: usize, client: Arc, user_store: ModelHandle, fs: Arc, @@ -120,8 +122,7 @@ enum LspEvent { }, WorkProgress { token: String, - message: Option, - percentage: Option, + progress: LspWorkProgress, }, WorkEnd { token: String, @@ -129,6 +130,12 @@ enum LspEvent { DiagnosticsUpdate(lsp::PublishDiagnosticsParams), } +#[derive(Clone, Default)] +pub struct LspWorkProgress { + pub message: Option, + pub percentage: Option, +} + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] pub struct ProjectPath { pub worktree_id: WorktreeId, @@ -317,6 +324,8 @@ impl Project { language_servers_with_diagnostics_running: 0, language_servers: Default::default(), started_language_servers: Default::default(), + pending_language_server_work: Default::default(), + next_language_server_id: 0, nonce: StdRng::from_entropy().gen(), } }) @@ -386,6 +395,8 @@ impl Project { language_servers_with_diagnostics_running: 0, language_servers: Default::default(), started_language_servers: Default::default(), + pending_language_server_work: Default::default(), + next_language_server_id: 0, opened_buffers: Default::default(), buffer_snapshots: Default::default(), nonce: StdRng::from_entropy().gen(), @@ -1172,6 +1183,7 @@ impl Project { self.started_language_servers .entry(key.clone()) .or_insert_with(|| { + let server_id = post_inc(&mut self.next_language_server_id); let language_server = self.languages.start_language_server( language.clone(), worktree_path, @@ -1213,8 +1225,12 @@ impl Project { lsp_events_tx .try_send(LspEvent::WorkProgress { token, - message: report.message, - percentage: report.percentage.map(|p| p as usize), + progress: LspWorkProgress { + message: report.message, + percentage: report + .percentage + .map(|p| p as usize), + }, }) .ok(); } @@ -1233,7 +1249,7 @@ impl Project { while let Ok(event) = lsp_events_rx.recv().await { let this = this.upgrade(&cx)?; this.update(&mut cx, |this, cx| { - this.on_local_lsp_event(event, &language, cx) + this.on_local_lsp_event(server_id, event, &language, cx) }); } Some(()) @@ -1309,6 +1325,7 @@ impl Project { fn on_local_lsp_event( &mut self, + language_server_id: usize, event: LspEvent, language: &Arc, cx: &mut ModelContext, @@ -1318,43 +1335,53 @@ impl Project { LspEvent::WorkStart { token } => { if Some(&token) == disk_diagnostics_token { self.disk_based_diagnostics_started(cx); - self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( - proto::LspDiskBasedDiagnosticsUpdating {}, - )); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( + proto::LspDiskBasedDiagnosticsUpdating {}, + ), + ); } else { - self.on_lsp_work_start(token.clone(), cx); - self.send_lsp_event(proto::lsp_event::Variant::WorkStart( - proto::LspWorkStart { token }, - )); + self.on_lsp_work_start(language_server_id, token.clone(), cx); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::WorkStart(proto::LspWorkStart { token }), + ); } } - LspEvent::WorkProgress { - token, - message, - percentage, - } => { + LspEvent::WorkProgress { token, progress } => { if Some(&token) != disk_diagnostics_token { - self.on_lsp_work_progress(token.clone(), message.clone(), percentage, cx); - self.send_lsp_event(proto::lsp_event::Variant::WorkProgress( - proto::LspWorkProgress { + self.on_lsp_work_progress( + language_server_id, + token.clone(), + progress.clone(), + cx, + ); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::WorkProgress(proto::LspWorkProgress { token, - message, - percentage: percentage.map(|p| p as u32), - }, - )); + message: progress.message, + percentage: progress.percentage.map(|p| p as u32), + }), + ); } } LspEvent::WorkEnd { token } => { if Some(&token) == disk_diagnostics_token { self.disk_based_diagnostics_finished(cx); - self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( - proto::LspDiskBasedDiagnosticsUpdated {}, - )); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( + proto::LspDiskBasedDiagnosticsUpdated {}, + ), + ); } else { - self.on_lsp_work_end(token.clone(), cx); - self.send_lsp_event(proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { - token, - })); + self.on_lsp_work_end(language_server_id, token.clone(), cx); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { token }), + ); } } LspEvent::DiagnosticsUpdate(mut params) => { @@ -1362,9 +1389,12 @@ impl Project { if disk_diagnostics_token.is_none() { self.disk_based_diagnostics_started(cx); - self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( - proto::LspDiskBasedDiagnosticsUpdating {}, - )); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( + proto::LspDiskBasedDiagnosticsUpdating {}, + ), + ); } self.update_diagnostics( params, @@ -1376,38 +1406,74 @@ impl Project { .log_err(); if disk_diagnostics_token.is_none() { self.disk_based_diagnostics_finished(cx); - self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( - proto::LspDiskBasedDiagnosticsUpdated {}, - )); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( + proto::LspDiskBasedDiagnosticsUpdated {}, + ), + ); } } } } - fn on_lsp_work_start(&mut self, token: String, cx: &mut ModelContext) {} + fn on_lsp_work_start( + &mut self, + language_server_id: usize, + token: String, + cx: &mut ModelContext, + ) { + self.pending_language_server_work.insert( + (language_server_id, token), + LspWorkProgress { + message: None, + percentage: None, + }, + ); + cx.notify(); + } fn on_lsp_work_progress( &mut self, + language_server_id: usize, token: String, - message: Option, - percentage: Option, + progress: LspWorkProgress, cx: &mut ModelContext, ) { + self.pending_language_server_work + .insert((language_server_id, token), progress); + cx.notify(); } - fn on_lsp_work_end(&mut self, token: String, cx: &mut ModelContext) {} + fn on_lsp_work_end( + &mut self, + language_server_id: usize, + token: String, + cx: &mut ModelContext, + ) { + self.pending_language_server_work + .remove(&(language_server_id, token)); + cx.notify(); + } - fn send_lsp_event(&self, event: proto::lsp_event::Variant) { + fn broadcast_lsp_event(&self, language_server_id: usize, event: proto::lsp_event::Variant) { if let Some(project_id) = self.remote_id() { self.client .send(proto::LspEvent { project_id, + language_server_id: language_server_id as u64, variant: Some(event), }) .log_err(); } } + pub fn pending_language_server_work(&self) -> impl Iterator { + self.pending_language_server_work + .iter() + .map(|((_, token), progress)| (token.as_str(), progress)) + } + pub fn update_diagnostics( &mut self, params: lsp::PublishDiagnosticsParams, @@ -3152,24 +3218,28 @@ impl Project { _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { + let language_server_id = envelope.payload.language_server_id as usize; match envelope .payload .variant .ok_or_else(|| anyhow!("invalid variant"))? { proto::lsp_event::Variant::WorkStart(payload) => this.update(&mut cx, |this, cx| { - this.on_lsp_work_start(payload.token, cx); + this.on_lsp_work_start(language_server_id, payload.token, cx); }), proto::lsp_event::Variant::WorkProgress(payload) => this.update(&mut cx, |this, cx| { this.on_lsp_work_progress( + language_server_id, payload.token, - payload.message, - payload.percentage.map(|p| p as usize), + LspWorkProgress { + message: payload.message, + percentage: payload.percentage.map(|p| p as usize), + }, cx, ); }), proto::lsp_event::Variant::WorkEnd(payload) => this.update(&mut cx, |this, cx| { - this.on_lsp_work_end(payload.token, cx); + this.on_lsp_work_end(language_server_id, payload.token, cx); }), proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(_) => { this.update(&mut cx, |this, cx| { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 18df77e5c329160bdeb63f1b71e571704451e3bf..c9895739d98af1026afe6b1864ae3f563da5480e 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -425,12 +425,13 @@ message DiagnosticSummary { message LspEvent { uint64 project_id = 1; + uint64 language_server_id = 2; oneof variant { - LspWorkStart work_start = 2; - LspWorkProgress work_progress = 3; - LspWorkEnd work_end = 4; - LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 5; - LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 6; + LspWorkStart work_start = 3; + LspWorkProgress work_progress = 4; + LspWorkEnd work_end = 5; + LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 6; + LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7; } } diff --git a/crates/workspace/src/lsp_status.rs b/crates/workspace/src/lsp_status.rs index ee61ecf24d9fc1e509377f460689dc68d9885563..98bd0112a9c9b8fd644ebe4891cbef39c2948dde 100644 --- a/crates/workspace/src/lsp_status.rs +++ b/crates/workspace/src/lsp_status.rs @@ -1,11 +1,13 @@ use crate::{ItemViewHandle, Settings, StatusItemView}; use futures::StreamExt; use gpui::{ - action, elements::*, platform::CursorStyle, Entity, MutableAppContext, RenderContext, View, - ViewContext, + action, elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext, + RenderContext, View, ViewContext, }; use language::{LanguageRegistry, LanguageServerBinaryStatus}; use postage::watch; +use project::Project; +use std::fmt::Write; use std::sync::Arc; action!(DismissErrorMessage); @@ -15,6 +17,7 @@ pub struct LspStatus { checking_for_update: Vec, downloading: Vec, failed: Vec, + project: ModelHandle, } pub fn init(cx: &mut MutableAppContext) { @@ -23,6 +26,7 @@ pub fn init(cx: &mut MutableAppContext) { impl LspStatus { pub fn new( + project: &ModelHandle, languages: Arc, settings_rx: watch::Receiver, cx: &mut ViewContext, @@ -62,11 +66,14 @@ impl LspStatus { } }) .detach(); + cx.observe(project, |_, _, cx| cx.notify()).detach(); + Self { settings_rx, checking_for_update: Default::default(), downloading: Default::default(), failed: Default::default(), + project: project.clone(), } } @@ -87,7 +94,24 @@ impl View for LspStatus { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let theme = &self.settings_rx.borrow().theme; - if !self.downloading.is_empty() { + + let mut pending_work = self.project.read(cx).pending_language_server_work(); + if let Some((progress_token, progress)) = pending_work.next() { + let mut message = progress + .message + .clone() + .unwrap_or_else(|| progress_token.to_string()); + if let Some(percentage) = progress.percentage { + write!(&mut message, " ({}%)", percentage).unwrap(); + } + + let additional_work_count = pending_work.count(); + if additional_work_count > 0 { + write!(&mut message, " + {} more", additional_work_count).unwrap(); + } + + Label::new(message, theme.workspace.status_bar.lsp_message.clone()).boxed() + } else if !self.downloading.is_empty() { Label::new( format!( "Downloading {} language server{}...", @@ -112,6 +136,7 @@ impl View for LspStatus { ) .boxed() } else if !self.failed.is_empty() { + drop(pending_work); MouseEventHandler::new::(0, cx, |_, _| { Label::new( format!( diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c9a14bf2364441e002e5208af7b53aa6e2420830..c513155e6f0a72b13405d3993296368633a8334e 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -101,6 +101,7 @@ pub fn build_workspace( }); let lsp_status = cx.add_view(|cx| { workspace::lsp_status::LspStatus::new( + workspace.project(), app_state.languages.clone(), app_state.settings.clone(), cx, From 45fb470f4d8b16801f530a0cf4ef05d6fb0242b7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Mar 2022 16:45:13 +0100 Subject: [PATCH 08/12] Display language server name in status bar --- crates/lsp/src/lsp.rs | 22 ++- crates/project/src/project.rs | 207 +++++++++++++++++++---------- crates/rpc/proto/zed.proto | 16 ++- crates/rpc/src/proto.rs | 6 +- crates/server/src/rpc.rs | 28 +++- crates/server/src/rpc/store.rs | 20 +++ crates/workspace/src/lsp_status.rs | 15 ++- 7 files changed, 228 insertions(+), 86 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d9024975e4fe635051c05656f3050a8547ac9a07..43b26efc373d6ed70a577c4cfc304f40af60857e 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -35,6 +35,7 @@ type ResponseHandler = Box)>; pub struct LanguageServer { next_id: AtomicUsize, outbound_tx: channel::Sender>, + name: String, capabilities: ServerCapabilities, notification_handlers: Arc>>, response_handlers: Arc>>, @@ -118,9 +119,11 @@ impl LanguageServer { .spawn()?; let stdin = server.stdin.take().unwrap(); let stdout = server.stdout.take().unwrap(); - Ok(Self::new_internal( - stdin, stdout, root_path, options, background, - )) + let mut server = Self::new_internal(stdin, stdout, root_path, options, background); + if let Some(name) = binary_path.file_name() { + server.name = name.to_string_lossy().to_string(); + } + Ok(server) } fn new_internal( @@ -222,6 +225,7 @@ impl LanguageServer { Self { notification_handlers, response_handlers, + name: Default::default(), capabilities: Default::default(), next_id: Default::default(), outbound_tx, @@ -292,7 +296,13 @@ impl LanguageServer { }; let response = this.request::(params).await?; - Arc::get_mut(&mut this).unwrap().capabilities = response.capabilities; + { + let this = Arc::get_mut(&mut this).unwrap(); + if let Some(info) = response.server_info { + this.name = info.name; + } + this.capabilities = response.capabilities; + } this.notify::(InitializedParams {})?; Ok(this) } @@ -355,6 +365,10 @@ impl LanguageServer { } } + pub fn name<'a>(self: &'a Arc) -> &'a str { + &self.name + } + pub fn capabilities<'a>(self: &'a Arc) -> &'a ServerCapabilities { &self.capabilities } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 95609bf43f36fe5d448a1cbf45e3803442c41f4a..a35b8dc79ad073c407dcea7a212a97cf70b64b17 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -7,7 +7,7 @@ pub mod worktree; use anyhow::{anyhow, Context, Result}; use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; use clock::ReplicaId; -use collections::{hash_map, HashMap, HashSet}; +use collections::{hash_map, BTreeMap, HashMap, HashSet}; use futures::{future::Shared, Future, FutureExt, StreamExt, TryFutureExt}; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ @@ -51,7 +51,8 @@ pub struct Project { languages: Arc, language_servers: HashMap<(WorktreeId, Arc), Arc>, started_language_servers: HashMap<(WorktreeId, Arc), Task>>>, - pending_language_server_work: HashMap<(usize, String), LspWorkProgress>, + pending_language_server_work: BTreeMap<(usize, String), LanguageServerProgress>, + language_server_names: HashMap, next_language_server_id: usize, client: Arc, user_store: ModelHandle, @@ -116,13 +117,13 @@ pub enum Event { DiagnosticsUpdated(ProjectPath), } -enum LspEvent { +enum LanguageServerEvent { WorkStart { token: String, }, WorkProgress { token: String, - progress: LspWorkProgress, + progress: LanguageServerProgress, }, WorkEnd { token: String, @@ -131,7 +132,7 @@ enum LspEvent { } #[derive(Clone, Default)] -pub struct LspWorkProgress { +pub struct LanguageServerProgress { pub message: Option, pub percentage: Option, } @@ -224,7 +225,8 @@ impl Project { client.add_entity_message_handler(Self::handle_add_collaborator); client.add_entity_message_handler(Self::handle_buffer_reloaded); client.add_entity_message_handler(Self::handle_buffer_saved); - client.add_entity_message_handler(Self::handle_lsp_event); + client.add_entity_message_handler(Self::handle_start_language_server); + client.add_entity_message_handler(Self::handle_update_language_server); client.add_entity_message_handler(Self::handle_remove_collaborator); client.add_entity_message_handler(Self::handle_register_worktree); client.add_entity_message_handler(Self::handle_unregister_worktree); @@ -325,6 +327,7 @@ impl Project { language_servers: Default::default(), started_language_servers: Default::default(), pending_language_server_work: Default::default(), + language_server_names: Default::default(), next_language_server_id: 0, nonce: StdRng::from_entropy().gen(), } @@ -396,6 +399,11 @@ impl Project { language_servers: Default::default(), started_language_servers: Default::default(), pending_language_server_work: Default::default(), + language_server_names: response + .language_servers + .into_iter() + .map(|s| (s.id as usize, s.name)) + .collect(), next_language_server_id: 0, opened_buffers: Default::default(), buffer_snapshots: Default::default(), @@ -1193,14 +1201,15 @@ impl Project { cx.spawn_weak(|this, mut cx| async move { let mut language_server = language_server?.await.log_err()?; let this = this.upgrade(&cx)?; - let (lsp_events_tx, lsp_events_rx) = smol::channel::unbounded(); + let (language_server_events_tx, language_server_events_rx) = + smol::channel::unbounded(); language_server .on_notification::({ - let lsp_events_tx = lsp_events_tx.clone(); + let language_server_events_tx = language_server_events_tx.clone(); move |params| { - lsp_events_tx - .try_send(LspEvent::DiagnosticsUpdate(params)) + language_server_events_tx + .try_send(LanguageServerEvent::DiagnosticsUpdate(params)) .ok(); } }) @@ -1219,13 +1228,15 @@ impl Project { match params.value { lsp::ProgressParamsValue::WorkDone(progress) => match progress { lsp::WorkDoneProgress::Begin(_) => { - lsp_events_tx.try_send(LspEvent::WorkStart { token }).ok(); + language_server_events_tx + .try_send(LanguageServerEvent::WorkStart { token }) + .ok(); } lsp::WorkDoneProgress::Report(report) => { - lsp_events_tx - .try_send(LspEvent::WorkProgress { + language_server_events_tx + .try_send(LanguageServerEvent::WorkProgress { token, - progress: LspWorkProgress { + progress: LanguageServerProgress { message: report.message, percentage: report .percentage @@ -1235,7 +1246,9 @@ impl Project { .ok(); } lsp::WorkDoneProgress::End(_) => { - lsp_events_tx.try_send(LspEvent::WorkEnd { token }).ok(); + language_server_events_tx + .try_send(LanguageServerEvent::WorkEnd { token }) + .ok(); } }, } @@ -1246,10 +1259,10 @@ impl Project { cx.spawn(|mut cx| { let this = this.downgrade(); async move { - while let Ok(event) = lsp_events_rx.recv().await { + while let Ok(event) = language_server_events_rx.recv().await { let this = this.upgrade(&cx)?; this.update(&mut cx, |this, cx| { - this.on_local_lsp_event(server_id, event, &language, cx) + this.on_lsp_event(server_id, event, &language, cx) }); } Some(()) @@ -1261,6 +1274,20 @@ impl Project { this.update(&mut cx, |this, cx| { this.language_servers .insert(key.clone(), language_server.clone()); + this.language_server_names + .insert(server_id, language_server.name().to_string()); + + if let Some(project_id) = this.remote_id() { + this.client + .send(proto::StartLanguageServer { + project_id, + server: Some(proto::LanguageServer { + id: server_id as u64, + name: language_server.name().to_string(), + }), + }) + .log_err(); + } // Tell the language server about every open buffer in the worktree that matches the language. for buffer in this.opened_buffers.values() { @@ -1315,6 +1342,7 @@ impl Project { } } + cx.notify(); Some(()) }); @@ -1323,33 +1351,35 @@ impl Project { }); } - fn on_local_lsp_event( + fn on_lsp_event( &mut self, language_server_id: usize, - event: LspEvent, + event: LanguageServerEvent, language: &Arc, cx: &mut ModelContext, ) { let disk_diagnostics_token = language.disk_based_diagnostics_progress_token(); match event { - LspEvent::WorkStart { token } => { + LanguageServerEvent::WorkStart { token } => { if Some(&token) == disk_diagnostics_token { self.disk_based_diagnostics_started(cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating( proto::LspDiskBasedDiagnosticsUpdating {}, ), ); } else { self.on_lsp_work_start(language_server_id, token.clone(), cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::WorkStart(proto::LspWorkStart { token }), + proto::update_language_server::Variant::WorkStart(proto::LspWorkStart { + token, + }), ); } } - LspEvent::WorkProgress { token, progress } => { + LanguageServerEvent::WorkProgress { token, progress } => { if Some(&token) != disk_diagnostics_token { self.on_lsp_work_progress( language_server_id, @@ -1357,41 +1387,45 @@ impl Project { progress.clone(), cx, ); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::WorkProgress(proto::LspWorkProgress { - token, - message: progress.message, - percentage: progress.percentage.map(|p| p as u32), - }), + proto::update_language_server::Variant::WorkProgress( + proto::LspWorkProgress { + token, + message: progress.message, + percentage: progress.percentage.map(|p| p as u32), + }, + ), ); } } - LspEvent::WorkEnd { token } => { + LanguageServerEvent::WorkEnd { token } => { if Some(&token) == disk_diagnostics_token { self.disk_based_diagnostics_finished(cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( proto::LspDiskBasedDiagnosticsUpdated {}, ), ); } else { self.on_lsp_work_end(language_server_id, token.clone(), cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { token }), + proto::update_language_server::Variant::WorkEnd(proto::LspWorkEnd { + token, + }), ); } } - LspEvent::DiagnosticsUpdate(mut params) => { + LanguageServerEvent::DiagnosticsUpdate(mut params) => { language.process_diagnostics(&mut params); if disk_diagnostics_token.is_none() { self.disk_based_diagnostics_started(cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating( proto::LspDiskBasedDiagnosticsUpdating {}, ), ); @@ -1406,9 +1440,9 @@ impl Project { .log_err(); if disk_diagnostics_token.is_none() { self.disk_based_diagnostics_finished(cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( proto::LspDiskBasedDiagnosticsUpdated {}, ), ); @@ -1425,7 +1459,7 @@ impl Project { ) { self.pending_language_server_work.insert( (language_server_id, token), - LspWorkProgress { + LanguageServerProgress { message: None, percentage: None, }, @@ -1437,7 +1471,7 @@ impl Project { &mut self, language_server_id: usize, token: String, - progress: LspWorkProgress, + progress: LanguageServerProgress, cx: &mut ModelContext, ) { self.pending_language_server_work @@ -1456,10 +1490,14 @@ impl Project { cx.notify(); } - fn broadcast_lsp_event(&self, language_server_id: usize, event: proto::lsp_event::Variant) { + fn broadcast_language_server_update( + &self, + language_server_id: usize, + event: proto::update_language_server::Variant, + ) { if let Some(project_id) = self.remote_id() { self.client - .send(proto::LspEvent { + .send(proto::UpdateLanguageServer { project_id, language_server_id: language_server_id as u64, variant: Some(event), @@ -1468,10 +1506,15 @@ impl Project { } } - pub fn pending_language_server_work(&self) -> impl Iterator { - self.pending_language_server_work - .iter() - .map(|((_, token), progress)| (token.as_str(), progress)) + pub fn pending_language_server_work( + &self, + ) -> impl Iterator { + self.pending_language_server_work.iter().filter_map( + |((language_server_id, token), progress)| { + let name = self.language_server_names.get(language_server_id)?; + Some((name.as_str(), token.as_str(), progress)) + }, + ) } pub fn update_diagnostics( @@ -3212,9 +3255,27 @@ impl Project { }) } - async fn handle_lsp_event( + async fn handle_start_language_server( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result<()> { + let server = envelope + .payload + .server + .ok_or_else(|| anyhow!("invalid server"))?; + this.update(&mut cx, |this, cx| { + this.language_server_names + .insert(server.id as usize, server.name); + cx.notify(); + }); + Ok(()) + } + + async fn handle_update_language_server( this: ModelHandle, - envelope: TypedEnvelope, + envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { @@ -3224,29 +3285,35 @@ impl Project { .variant .ok_or_else(|| anyhow!("invalid variant"))? { - proto::lsp_event::Variant::WorkStart(payload) => this.update(&mut cx, |this, cx| { - this.on_lsp_work_start(language_server_id, payload.token, cx); - }), - proto::lsp_event::Variant::WorkProgress(payload) => this.update(&mut cx, |this, cx| { - this.on_lsp_work_progress( - language_server_id, - payload.token, - LspWorkProgress { - message: payload.message, - percentage: payload.percentage.map(|p| p as usize), - }, - cx, - ); - }), - proto::lsp_event::Variant::WorkEnd(payload) => this.update(&mut cx, |this, cx| { - this.on_lsp_work_end(language_server_id, payload.token, cx); - }), - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(_) => { + proto::update_language_server::Variant::WorkStart(payload) => { + this.update(&mut cx, |this, cx| { + this.on_lsp_work_start(language_server_id, payload.token, cx); + }) + } + proto::update_language_server::Variant::WorkProgress(payload) => { + this.update(&mut cx, |this, cx| { + this.on_lsp_work_progress( + language_server_id, + payload.token, + LanguageServerProgress { + message: payload.message, + percentage: payload.percentage.map(|p| p as usize), + }, + cx, + ); + }) + } + proto::update_language_server::Variant::WorkEnd(payload) => { + this.update(&mut cx, |this, cx| { + this.on_lsp_work_end(language_server_id, payload.token, cx); + }) + } + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(_) => { this.update(&mut cx, |this, cx| { this.disk_based_diagnostics_started(cx); }) } - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(_) => { + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(_) => { this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx)); } } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index c9895739d98af1026afe6b1864ae3f563da5480e..87303c3c2674587aa459365f2df04c836ba41a0e 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -37,7 +37,8 @@ message Envelope { UnregisterWorktree unregister_worktree = 29; UpdateWorktree update_worktree = 31; UpdateDiagnosticSummary update_diagnostic_summary = 32; - LspEvent lsp_event = 33; + StartLanguageServer start_language_server = 33; + UpdateLanguageServer update_language_server = 34; OpenBuffer open_buffer = 35; OpenBufferResponse open_buffer_response = 36; @@ -121,6 +122,7 @@ message JoinProjectResponse { uint32 replica_id = 1; repeated Worktree worktrees = 2; repeated Collaborator collaborators = 3; + repeated LanguageServer language_servers = 4; } message LeaveProject { @@ -409,6 +411,16 @@ message LocalTimestamp { uint32 value = 2; } +message LanguageServer { + uint64 id = 1; + string name = 2; +} + +message StartLanguageServer { + uint64 project_id = 1; + LanguageServer server = 2; +} + message UpdateDiagnosticSummary { uint64 project_id = 1; uint64 worktree_id = 2; @@ -423,7 +435,7 @@ message DiagnosticSummary { uint32 hint_count = 5; } -message LspEvent { +message UpdateLanguageServer { uint64 project_id = 1; uint64 language_server_id = 2; oneof variant { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 15a5839524d22556b01c40921a311ec87668f754..54b26b830ca584f74110c74e5b18d3371d092c18 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -171,9 +171,10 @@ messages!( (JoinChannelResponse, Foreground), (JoinProject, Foreground), (JoinProjectResponse, Foreground), + (StartLanguageServer, Foreground), + (UpdateLanguageServer, Foreground), (LeaveChannel, Foreground), (LeaveProject, Foreground), - (LspEvent, Background), (OpenBuffer, Background), (OpenBufferForSymbol, Background), (OpenBufferForSymbolResponse, Background), @@ -254,7 +255,6 @@ entity_messages!( GetProjectSymbols, JoinProject, LeaveProject, - LspEvent, OpenBuffer, OpenBufferForSymbol, PerformRename, @@ -262,11 +262,13 @@ entity_messages!( RemoveProjectCollaborator, SaveBuffer, SearchProject, + StartLanguageServer, UnregisterWorktree, UnshareProject, UpdateBuffer, UpdateBufferFile, UpdateDiagnosticSummary, + UpdateLanguageServer, RegisterWorktree, UpdateWorktree, ); diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 74406146c14275e10f8da45d4e9f3625a9d8c402..393e54165f9b424958c713b00f17eab49827b89c 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -83,8 +83,9 @@ impl Server { .add_request_handler(Server::register_worktree) .add_message_handler(Server::unregister_worktree) .add_request_handler(Server::update_worktree) + .add_message_handler(Server::start_language_server) + .add_message_handler(Server::update_language_server) .add_message_handler(Server::update_diagnostic_summary) - .add_message_handler(Server::lsp_event) .add_request_handler(Server::forward_project_request::) .add_request_handler(Server::forward_project_request::) .add_request_handler(Server::forward_project_request::) @@ -385,6 +386,7 @@ impl Server { worktrees, replica_id: joined.replica_id as u32, collaborators, + language_servers: joined.project.language_servers.clone(), }; let connection_ids = joined.project.connection_ids(); let contact_user_ids = joined.project.authorized_user_ids(); @@ -534,9 +536,29 @@ impl Server { Ok(()) } - async fn lsp_event( + async fn start_language_server( + mut self: Arc, + request: TypedEnvelope, + ) -> tide::Result<()> { + let receiver_ids = self.state_mut().start_language_server( + request.payload.project_id, + request.sender_id, + request + .payload + .server + .clone() + .ok_or_else(|| anyhow!("invalid language server"))?, + )?; + broadcast(request.sender_id, receiver_ids, |connection_id| { + self.peer + .forward_send(request.sender_id, connection_id, request.payload.clone()) + })?; + Ok(()) + } + + async fn update_language_server( self: Arc, - request: TypedEnvelope, + request: TypedEnvelope, ) -> tide::Result<()> { let receiver_ids = self .state() diff --git a/crates/server/src/rpc/store.rs b/crates/server/src/rpc/store.rs index c18db3b684ed65ee098fee8ac6c4b0a112e13b94..6f5252fecf9e82a14c80a76fe75c4e33cdee65c2 100644 --- a/crates/server/src/rpc/store.rs +++ b/crates/server/src/rpc/store.rs @@ -25,6 +25,7 @@ pub struct Project { pub host_user_id: UserId, pub share: Option, pub worktrees: HashMap, + pub language_servers: Vec, } pub struct Worktree { @@ -240,6 +241,7 @@ impl Store { host_user_id, share: None, worktrees: Default::default(), + language_servers: Default::default(), }, ); self.next_project_id += 1; @@ -438,6 +440,24 @@ impl Store { Err(anyhow!("no such worktree"))? } + pub fn start_language_server( + &mut self, + project_id: u64, + connection_id: ConnectionId, + language_server: proto::LanguageServer, + ) -> tide::Result> { + let project = self + .projects + .get_mut(&project_id) + .ok_or_else(|| anyhow!("no such project"))?; + if project.host_connection_id == connection_id { + project.language_servers.push(language_server); + return Ok(project.connection_ids()); + } + + Err(anyhow!("no such project"))? + } + pub fn join_project( &mut self, connection_id: ConnectionId, diff --git a/crates/workspace/src/lsp_status.rs b/crates/workspace/src/lsp_status.rs index 98bd0112a9c9b8fd644ebe4891cbef39c2948dde..e2976824b5215d979c1cc01884784f6ce8382cb7 100644 --- a/crates/workspace/src/lsp_status.rs +++ b/crates/workspace/src/lsp_status.rs @@ -96,11 +96,16 @@ impl View for LspStatus { let theme = &self.settings_rx.borrow().theme; let mut pending_work = self.project.read(cx).pending_language_server_work(); - if let Some((progress_token, progress)) = pending_work.next() { - let mut message = progress - .message - .clone() - .unwrap_or_else(|| progress_token.to_string()); + if let Some((lang_server_name, progress_token, progress)) = pending_work.next() { + let mut message = lang_server_name.to_string(); + + message.push_str(": "); + if let Some(progress_message) = progress.message.as_ref() { + message.push_str(progress_message); + } else { + message.push_str(progress_token); + } + if let Some(percentage) = progress.percentage { write!(&mut message, " ({}%)", percentage).unwrap(); } From 5157b428961929446930efbf9c70df24b014e7e7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Mar 2022 17:04:36 +0100 Subject: [PATCH 09/12] Extract a `LanguageServerStatus` struct --- crates/project/src/project.rs | 130 ++++++++++++++++++----------- crates/workspace/src/lsp_status.rs | 26 +++++- 2 files changed, 106 insertions(+), 50 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a35b8dc79ad073c407dcea7a212a97cf70b64b17..dd4a17c13a0685b45fb7e0a738b71f6c91db2233 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -51,8 +51,7 @@ pub struct Project { languages: Arc, language_servers: HashMap<(WorktreeId, Arc), Arc>, started_language_servers: HashMap<(WorktreeId, Arc), Task>>>, - pending_language_server_work: BTreeMap<(usize, String), LanguageServerProgress>, - language_server_names: HashMap, + language_server_statuses: BTreeMap, next_language_server_id: usize, client: Arc, user_store: ModelHandle, @@ -131,6 +130,12 @@ enum LanguageServerEvent { DiagnosticsUpdate(lsp::PublishDiagnosticsParams), } +pub struct LanguageServerStatus { + pub name: String, + pub pending_work: BTreeMap, + pending_diagnostic_updates: isize, +} + #[derive(Clone, Default)] pub struct LanguageServerProgress { pub message: Option, @@ -326,8 +331,7 @@ impl Project { language_servers_with_diagnostics_running: 0, language_servers: Default::default(), started_language_servers: Default::default(), - pending_language_server_work: Default::default(), - language_server_names: Default::default(), + language_server_statuses: Default::default(), next_language_server_id: 0, nonce: StdRng::from_entropy().gen(), } @@ -398,11 +402,19 @@ impl Project { language_servers_with_diagnostics_running: 0, language_servers: Default::default(), started_language_servers: Default::default(), - pending_language_server_work: Default::default(), - language_server_names: response + language_server_statuses: response .language_servers .into_iter() - .map(|s| (s.id as usize, s.name)) + .map(|server| { + ( + server.id as usize, + LanguageServerStatus { + name: server.name, + pending_work: Default::default(), + pending_diagnostic_updates: 0, + }, + ) + }) .collect(), next_language_server_id: 0, opened_buffers: Default::default(), @@ -1274,8 +1286,14 @@ impl Project { this.update(&mut cx, |this, cx| { this.language_servers .insert(key.clone(), language_server.clone()); - this.language_server_names - .insert(server_id, language_server.name().to_string()); + this.language_server_statuses.insert( + server_id, + LanguageServerStatus { + name: language_server.name().to_string(), + pending_work: Default::default(), + pending_diagnostic_updates: 0, + }, + ); if let Some(project_id) = this.remote_id() { this.client @@ -1359,16 +1377,26 @@ impl Project { cx: &mut ModelContext, ) { let disk_diagnostics_token = language.disk_based_diagnostics_progress_token(); + let language_server_status = + if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) { + status + } else { + return; + }; + match event { LanguageServerEvent::WorkStart { token } => { if Some(&token) == disk_diagnostics_token { - self.disk_based_diagnostics_started(cx); - self.broadcast_language_server_update( - language_server_id, - proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating( - proto::LspDiskBasedDiagnosticsUpdating {}, - ), - ); + language_server_status.pending_diagnostic_updates += 1; + if language_server_status.pending_diagnostic_updates == 1 { + self.disk_based_diagnostics_started(cx); + self.broadcast_language_server_update( + language_server_id, + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating( + proto::LspDiskBasedDiagnosticsUpdating {}, + ), + ); + } } else { self.on_lsp_work_start(language_server_id, token.clone(), cx); self.broadcast_language_server_update( @@ -1401,13 +1429,16 @@ impl Project { } LanguageServerEvent::WorkEnd { token } => { if Some(&token) == disk_diagnostics_token { - self.disk_based_diagnostics_finished(cx); - self.broadcast_language_server_update( - language_server_id, - proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( - proto::LspDiskBasedDiagnosticsUpdated {}, - ), - ); + language_server_status.pending_diagnostic_updates -= 1; + if language_server_status.pending_diagnostic_updates == 0 { + self.disk_based_diagnostics_finished(cx); + self.broadcast_language_server_update( + language_server_id, + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( + proto::LspDiskBasedDiagnosticsUpdated {}, + ), + ); + } } else { self.on_lsp_work_end(language_server_id, token.clone(), cx); self.broadcast_language_server_update( @@ -1457,14 +1488,16 @@ impl Project { token: String, cx: &mut ModelContext, ) { - self.pending_language_server_work.insert( - (language_server_id, token), - LanguageServerProgress { - message: None, - percentage: None, - }, - ); - cx.notify(); + if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) { + status.pending_work.insert( + token, + LanguageServerProgress { + message: None, + percentage: None, + }, + ); + cx.notify(); + } } fn on_lsp_work_progress( @@ -1474,9 +1507,10 @@ impl Project { progress: LanguageServerProgress, cx: &mut ModelContext, ) { - self.pending_language_server_work - .insert((language_server_id, token), progress); - cx.notify(); + if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) { + status.pending_work.insert(token, progress); + cx.notify(); + } } fn on_lsp_work_end( @@ -1485,9 +1519,10 @@ impl Project { token: String, cx: &mut ModelContext, ) { - self.pending_language_server_work - .remove(&(language_server_id, token)); - cx.notify(); + if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) { + status.pending_work.remove(&token); + cx.notify(); + } } fn broadcast_language_server_update( @@ -1506,15 +1541,8 @@ impl Project { } } - pub fn pending_language_server_work( - &self, - ) -> impl Iterator { - self.pending_language_server_work.iter().filter_map( - |((language_server_id, token), progress)| { - let name = self.language_server_names.get(language_server_id)?; - Some((name.as_str(), token.as_str(), progress)) - }, - ) + pub fn language_server_statuses(&self) -> impl Iterator { + self.language_server_statuses.values() } pub fn update_diagnostics( @@ -3266,8 +3294,14 @@ impl Project { .server .ok_or_else(|| anyhow!("invalid server"))?; this.update(&mut cx, |this, cx| { - this.language_server_names - .insert(server.id as usize, server.name); + this.language_server_statuses.insert( + server.id as usize, + LanguageServerStatus { + name: server.name, + pending_work: Default::default(), + pending_diagnostic_updates: 0, + }, + ); cx.notify(); }); Ok(()) diff --git a/crates/workspace/src/lsp_status.rs b/crates/workspace/src/lsp_status.rs index e2976824b5215d979c1cc01884784f6ce8382cb7..6907b02948c1022e4ce13851cfacb2b7bf40d161 100644 --- a/crates/workspace/src/lsp_status.rs +++ b/crates/workspace/src/lsp_status.rs @@ -1,12 +1,13 @@ use crate::{ItemViewHandle, Settings, StatusItemView}; use futures::StreamExt; +use gpui::AppContext; use gpui::{ action, elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext, RenderContext, View, ViewContext, }; use language::{LanguageRegistry, LanguageServerBinaryStatus}; use postage::watch; -use project::Project; +use project::{LanguageServerProgress, Project}; use std::fmt::Write; use std::sync::Arc; @@ -81,6 +82,27 @@ impl LspStatus { self.failed.clear(); cx.notify(); } + + fn pending_language_server_work<'a>( + &self, + cx: &'a AppContext, + ) -> impl Iterator { + self.project + .read(cx) + .language_server_statuses() + .filter_map(|status| { + if status.pending_work.is_empty() { + None + } else { + Some( + status.pending_work.iter().map(|(token, progress)| { + (status.name.as_str(), token.as_str(), progress) + }), + ) + } + }) + .flatten() + } } impl Entity for LspStatus { @@ -95,7 +117,7 @@ impl View for LspStatus { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let theme = &self.settings_rx.borrow().theme; - let mut pending_work = self.project.read(cx).pending_language_server_work(); + let mut pending_work = self.pending_language_server_work(cx); if let Some((lang_server_name, progress_token, progress)) = pending_work.next() { let mut message = lang_server_name.to_string(); From 5f62f69907676e9d52f58a69c0dc38a532ccb15a Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 20:04:16 -0800 Subject: [PATCH 10/12] Add unwrap check if buffer_line not available --- crates/editor/src/editor.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4d49fb71496864e7e1936f2608ba6557525c86d1..05443553591d9c5eb00d962122ef464c622cfb1c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2639,21 +2639,22 @@ impl Editor { for selection in &mut selections { if selection.is_empty() { let old_head = selection.head(); - let (buffer, line_buffer_range) = display_map - .buffer_snapshot - .buffer_line_for_row(old_head.row) - .unwrap(); - let indent_column = buffer.indent_column_for_line(line_buffer_range.start.row); let mut new_head = movement::left(&display_map, old_head.to_display_point(&display_map)) .unwrap() .to_point(&display_map); - if old_head.column <= indent_column && old_head.column > 0 { - let indent = buffer.indent_size(); - new_head = cmp::min( - new_head, - Point::new(old_head.row, ((old_head.column - 1) / indent) * indent), - ); + if let Some((buffer, line_buffer_range)) = display_map + .buffer_snapshot + .buffer_line_for_row(old_head.row) + { + let indent_column = buffer.indent_column_for_line(line_buffer_range.start.row); + if old_head.column <= indent_column && old_head.column > 0 { + let indent = buffer.indent_size(); + new_head = cmp::min( + new_head, + Point::new(old_head.row, ((old_head.column - 1) / indent) * indent), + ); + } } selection.set_head(new_head); From 7a454003fe18722f545402334d8632ab66ade6b7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Mar 2022 09:59:13 +0100 Subject: [PATCH 11/12] Show the last in-progress task from language servers --- Cargo.lock | 1 + crates/project/src/project.rs | 10 ++++++++-- crates/workspace/Cargo.toml | 1 + crates/workspace/src/lsp_status.rs | 15 ++++++++++----- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7aafce3bbb00774249f6ae29e8bbeed1fda559bb..499acae0854c66fef91c541f8eb2be58c56e55f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5852,6 +5852,7 @@ dependencies = [ "postage", "project", "serde_json", + "smallvec", "theme", "util", ] diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index dd4a17c13a0685b45fb7e0a738b71f6c91db2233..430c6875d0be02492223d1feaaef3071767585b3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -136,10 +136,11 @@ pub struct LanguageServerStatus { pending_diagnostic_updates: isize, } -#[derive(Clone, Default)] +#[derive(Clone, Debug)] pub struct LanguageServerProgress { pub message: Option, pub percentage: Option, + pub last_update_at: Instant, } #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] @@ -1253,6 +1254,7 @@ impl Project { percentage: report .percentage .map(|p| p as usize), + last_update_at: Instant::now(), }, }) .ok(); @@ -1494,6 +1496,7 @@ impl Project { LanguageServerProgress { message: None, percentage: None, + last_update_at: Instant::now(), }, ); cx.notify(); @@ -1541,7 +1544,9 @@ impl Project { } } - pub fn language_server_statuses(&self) -> impl Iterator { + pub fn language_server_statuses( + &self, + ) -> impl DoubleEndedIterator { self.language_server_statuses.values() } @@ -3332,6 +3337,7 @@ impl Project { LanguageServerProgress { message: payload.message, percentage: payload.percentage.map(|p| p as usize), + last_update_at: Instant::now(), }, cx, ); diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index d83cbf29d4f8f64c8666e19323b8fd305bc1cae8..e92c4bf186b76bb9e12499e9a536380b9a98f6e6 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -24,6 +24,7 @@ futures = "0.3" log = "0.4" parking_lot = "0.11.1" postage = { version = "0.4.1", features = ["futures-traits"] } +smallvec = { version = "1.6", features = ["union"] } [dev-dependencies] client = { path = "../client", features = ["test-support"] } diff --git a/crates/workspace/src/lsp_status.rs b/crates/workspace/src/lsp_status.rs index 6907b02948c1022e4ce13851cfacb2b7bf40d161..43b58bd02b2c59881c252924ed88c0b7d0073ad6 100644 --- a/crates/workspace/src/lsp_status.rs +++ b/crates/workspace/src/lsp_status.rs @@ -8,6 +8,8 @@ use gpui::{ use language::{LanguageRegistry, LanguageServerBinaryStatus}; use postage::watch; use project::{LanguageServerProgress, Project}; +use smallvec::SmallVec; +use std::cmp::Reverse; use std::fmt::Write; use std::sync::Arc; @@ -90,15 +92,18 @@ impl LspStatus { self.project .read(cx) .language_server_statuses() + .rev() .filter_map(|status| { if status.pending_work.is_empty() { None } else { - Some( - status.pending_work.iter().map(|(token, progress)| { - (status.name.as_str(), token.as_str(), progress) - }), - ) + let mut pending_work = status + .pending_work + .iter() + .map(|(token, progress)| (status.name.as_str(), token.as_str(), progress)) + .collect::>(); + pending_work.sort_by_key(|(_, _, progress)| Reverse(progress.last_update_at)); + Some(pending_work) } }) .flatten() From 18b1e9d35f789a26f5094ea5df855755c593ccde Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Mar 2022 10:02:37 +0100 Subject: [PATCH 12/12] Don't starve main thread when lots of messages/events arrive at once --- crates/client/src/client.rs | 3 +++ crates/project/src/project.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 62d2c6fb31e780c89a26b7c35134d8ad2ef7869d..59110f73c643cfac93730a0ef5e1900d896397eb 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -631,6 +631,9 @@ impl Client { } else { log::info!("unhandled message {}", type_name); } + + // Don't starve the main thread when receiving lots of messages at once. + smol::future::yield_now().await; } } }) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 430c6875d0be02492223d1feaaef3071767585b3..2f84f702bec9a169af6767f0ed3695f8c6f0d6ae 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1278,6 +1278,9 @@ impl Project { this.update(&mut cx, |this, cx| { this.on_lsp_event(server_id, event, &language, cx) }); + + // Don't starve the main thread when lots of events arrive all at once. + smol::future::yield_now().await; } Some(()) }