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 0eea82fa02..2132939c1c 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 98762b306a..19b8eec189 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 7c870e4720..4b1f9d3a38 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 9fd34166ff..5281009155 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 78f84d7da2..02a7c03c90 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 c9a14bf236..75e7735f24 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 8f1d66e47a..4156c0883d 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 2132939c1c..469bebee1a 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 49fc74d47b..9803a2aa2f 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 c04145294c..66bd44b26f 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 19b8eec189..72e1c24d6b 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 4b1f9d3a38..33f9f22e11 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 02a7c03c90..15195f2434 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 75e7735f24..9299988ffe 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 053883e938..d36f571308 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 2866ae8f63..daafbee57b 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 e2239e7671..dc62783eb3 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 91d02b1aa0..4d49fb7149 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 64683faa96..3678f8f116 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 3d79ecadd6..5f3ddb8b99 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 9a45e08164..9741f16784 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 7f43aaff1a..18df77e5c3 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 d252decb3a..15a5839524 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 2b954b5774..74406146c1 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 9741f16784..95609bf43f 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 18df77e5c3..c9895739d9 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 ee61ecf24d..98bd0112a9 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 c9a14bf236..c513155e6f 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 d9024975e4..43b26efc37 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 95609bf43f..a35b8dc79a 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, + 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, _: 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 c9895739d9..87303c3c26 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 15a5839524..54b26b830c 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 74406146c1..393e54165f 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 c18db3b684..6f5252fecf 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 98bd0112a9..e2976824b5 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 a35b8dc79a..dd4a17c13a 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 e2976824b5..6907b02948 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 4d49fb7149..0544355359 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 7aafce3bbb..499acae085 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 dd4a17c13a..430c6875d0 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 d83cbf29d4..e92c4bf186 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 6907b02948..43b58bd02b 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 62d2c6fb31..59110f73c6 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 430c6875d0..2f84f702be 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(()) }