diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index a5949127f5..cc1c66d949 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -13,23 +13,28 @@ jobs: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} content: | ๐Ÿ“ฃ Zed ${{ github.event.release.tag_name }} was just released! - + Restart your Zed or head to https://zed.dev/releases/latest to grab it. - + ```md # Changelog - + ${{ github.event.release.body }} ``` discourse_release: + if: ${{ ! github.event.release.prerelease }} runs-on: ubuntu-latest steps: + - uses: actions/checkout@v3 - name: Install Node uses: actions/setup-node@v2 - if: ${{ ! github.event.release.prerelease }} with: - node-version: '16' - - run: script/discourse_release ${{ secrets.DISCOURSE_RELEASES_API_KEY }} ${{ github.event.release.tag_name }} ${{ github.event.release.body }} + node-version: "19" + - run: > + node "./script/discourse_release" + ${{ secrets.DISCOURSE_RELEASES_API_KEY }} + ${{ github.event.release.tag_name }} + ${{ github.event.release.body }} mixpanel_release: runs-on: ubuntu-latest steps: @@ -40,7 +45,7 @@ jobs: architecture: "x64" cache: "pip" - run: pip install -r script/mixpanel_release/requirements.txt - - run: > + - run: > python script/mixpanel_release/main.py ${{ github.event.release.tag_name }} ${{ secrets.MIXPANEL_PROJECT_ID }} diff --git a/Cargo.lock b/Cargo.lock index 8c7f08bf9b..b5eac21cf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8228,7 +8228,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.72.0" +version = "0.72.5" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 0dc0ce6f42..6d5a5cb549 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -63,6 +63,7 @@ pub struct ContextMenu { visible: bool, previously_focused_view_id: Option, clicked: bool, + parent_view_id: usize, _actions_observation: Subscription, } @@ -114,6 +115,8 @@ impl View for ContextMenu { impl ContextMenu { pub fn new(cx: &mut ViewContext) -> Self { + let parent_view_id = cx.parent().unwrap(); + Self { show_count: 0, anchor_position: Default::default(), @@ -123,6 +126,7 @@ impl ContextMenu { visible: Default::default(), previously_focused_view_id: Default::default(), clicked: false, + parent_view_id, _actions_observation: cx.observe_actions(Self::action_dispatched), } } @@ -251,6 +255,7 @@ impl ContextMenu { } fn render_menu_for_measurement(&self, cx: &mut RenderContext) -> impl Element { + let window_id = cx.window_id(); let style = cx.global::().theme.context_menu.clone(); Flex::row() .with_child( @@ -289,6 +294,8 @@ impl ContextMenu { Some(ix) == self.selected_index, ); KeystrokeLabel::new( + window_id, + self.parent_view_id, action.boxed_clone(), style.keystroke.container, style.keystroke.text.clone(), @@ -318,6 +325,7 @@ impl ContextMenu { let style = cx.global::().theme.context_menu.clone(); + let window_id = cx.window_id(); MouseEventHandler::::new(0, cx, |_, cx| { Flex::column() .with_children(self.items.iter().enumerate().map(|(ix, item)| { @@ -337,6 +345,8 @@ impl ContextMenu { ) .with_child({ KeystrokeLabel::new( + window_id, + self.parent_view_id, action.boxed_clone(), style.keystroke.container, style.keystroke.text.clone(), diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 56c1def935..4120f38cfd 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -31,7 +31,7 @@ use workspace::{ use crate::system_specs::SystemSpecs; const FEEDBACK_CHAR_LIMIT: RangeInclusive = 10..=5000; -const FEEDBACK_PLACEHOLDER_TEXT: &str = "Thanks for spending time with Zed. Enter your feedback here as Markdown. Save the tab to submit your feedback."; +const FEEDBACK_PLACEHOLDER_TEXT: &str = "Save to submit feedback as Markdown."; const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = "Feedback failed to submit, see error log for details."; @@ -125,7 +125,9 @@ impl FeedbackEditor { _: ModelHandle, cx: &mut ViewContext, ) -> Task> { - let feedback_char_count = self.editor.read(cx).text(cx).chars().count(); + let feedback_text = self.editor.read(cx).text(cx); + let feedback_char_count = feedback_text.chars().count(); + let feedback_text = feedback_text.trim().to_string(); let error = if feedback_char_count < *FEEDBACK_CHAR_LIMIT.start() { Some(format!( @@ -154,7 +156,6 @@ impl FeedbackEditor { let this = cx.handle(); let client = cx.global::>().clone(); - let feedback_text = self.editor.read(cx).text(cx); let specs = self.system_specs.clone(); cx.spawn(|_, mut cx| async move { diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 95967ed485..d953de0e2b 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -910,15 +910,14 @@ impl MutableAppContext { .map_or(false, |window| window.is_fullscreen) } - pub fn window_bounds(&self, window_id: usize) -> WindowBounds { - self.presenters_and_platform_windows[&window_id].1.bounds() + pub fn window_bounds(&self, window_id: usize) -> Option { + let (_, window) = self.presenters_and_platform_windows.get(&window_id)?; + Some(window.bounds()) } - pub fn window_display_uuid(&self, window_id: usize) -> Uuid { - self.presenters_and_platform_windows[&window_id] - .1 - .screen() - .display_uuid() + pub fn window_display_uuid(&self, window_id: usize) -> Option { + let (_, window) = self.presenters_and_platform_windows.get(&window_id)?; + window.screen().display_uuid() } pub fn render_view(&mut self, params: RenderParams) -> Result { @@ -1333,6 +1332,31 @@ impl MutableAppContext { self.action_deserializers.keys().copied() } + /// Return keystrokes that would dispatch the given action on the given view. + pub(crate) fn keystrokes_for_action( + &mut self, + window_id: usize, + view_id: usize, + action: &dyn Action, + ) -> Option> { + let mut contexts = Vec::new(); + for view_id in self.ancestors(window_id, view_id) { + if let Some(view) = self.views.get(&(window_id, view_id)) { + contexts.push(view.keymap_context(self)); + } + } + + self.keystroke_matcher + .bindings_for_action_type(action.as_any().type_id()) + .find_map(|b| { + if b.match_context(&contexts) { + b.keystrokes().map(|s| s.into()) + } else { + None + } + }) + } + pub fn available_actions( &self, window_id: usize, @@ -1340,8 +1364,10 @@ impl MutableAppContext { ) -> impl Iterator, SmallVec<[&Binding; 1]>)> { let mut action_types: HashSet<_> = self.global_actions.keys().copied().collect(); + let mut contexts = Vec::new(); for view_id in self.ancestors(window_id, view_id) { if let Some(view) = self.views.get(&(window_id, view_id)) { + contexts.push(view.keymap_context(self)); let view_type = view.as_any().type_id(); if let Some(actions) = self.actions.get(&view_type) { action_types.extend(actions.keys().copied()); @@ -1358,6 +1384,7 @@ impl MutableAppContext { deserialize("{}").ok()?, self.keystroke_matcher .bindings_for_action_type(*type_id) + .filter(|b| b.match_context(&contexts)) .collect(), )) } else { @@ -1385,34 +1412,6 @@ impl MutableAppContext { self.global_actions.contains_key(&action_type) } - /// Return keystrokes that would dispatch the given action closest to the focused view, if there are any. - pub(crate) fn keystrokes_for_action( - &mut self, - window_id: usize, - view_stack: &[usize], - action: &dyn Action, - ) -> Option> { - self.keystroke_matcher.contexts.clear(); - for view_id in view_stack.iter().rev() { - let view = self - .cx - .views - .get(&(window_id, *view_id)) - .expect("view in responder chain does not exist"); - self.keystroke_matcher - .contexts - .push(view.keymap_context(self.as_ref())); - let keystrokes = self - .keystroke_matcher - .keystrokes_for_action(action, &self.keystroke_matcher.contexts); - if keystrokes.is_some() { - return keystrokes; - } - } - - None - } - // Traverses the parent tree. Walks down the tree toward the passed // view calling visit with true. Then walks back up the tree calling visit with false. // If `visit` returns false this function will immediately return. @@ -1916,10 +1915,11 @@ impl MutableAppContext { { self.update(|this| { let view_id = post_inc(&mut this.next_entity_id); + // Make sure we can tell child views about their parent + this.cx.parents.insert((window_id, view_id), parent_id); let mut cx = ViewContext::new(this, window_id, view_id); let handle = if let Some(view) = build_view(&mut cx) { this.cx.views.insert((window_id, view_id), Box::new(view)); - this.cx.parents.insert((window_id, view_id), parent_id); if let Some(window) = this.cx.windows.get_mut(&window_id) { window .invalidation @@ -1929,6 +1929,7 @@ impl MutableAppContext { } Some(ViewHandle::new(window_id, view_id, &this.cx.ref_counts)) } else { + this.cx.parents.remove(&(window_id, view_id)); None }; handle @@ -2375,12 +2376,15 @@ impl MutableAppContext { callback(is_fullscreen, this) }); - let bounds = this.window_bounds(window_id); - let uuid = this.window_display_uuid(window_id); - let mut bounds_observations = this.window_bounds_observations.clone(); - bounds_observations.emit(window_id, this, |callback, this| { - callback(bounds, uuid, this) - }); + if let Some((uuid, bounds)) = this + .window_display_uuid(window_id) + .zip(this.window_bounds(window_id)) + { + let mut bounds_observations = this.window_bounds_observations.clone(); + bounds_observations.emit(window_id, this, |callback, this| { + callback(bounds, uuid, this) + }); + } Some(()) }); @@ -2559,14 +2563,17 @@ impl MutableAppContext { } fn handle_window_moved(&mut self, window_id: usize) { - let bounds = self.window_bounds(window_id); - let display = self.window_display_uuid(window_id); - self.window_bounds_observations - .clone() - .emit(window_id, self, move |callback, this| { - callback(bounds, display, this); - true - }); + if let Some((display, bounds)) = self + .window_display_uuid(window_id) + .zip(self.window_bounds(window_id)) + { + self.window_bounds_observations + .clone() + .emit(window_id, self, move |callback, this| { + callback(bounds, display, this); + true + }); + } } pub fn focus(&mut self, window_id: usize, view_id: Option) { @@ -2808,6 +2815,16 @@ impl AppContext { })) } + /// Returns the id of the parent of the given view, or none if the given + /// view is the root. + fn parent(&self, window_id: usize, view_id: usize) -> Option { + if let Some(ParentId::View(view_id)) = self.parents.get(&(window_id, view_id)) { + Some(*view_id) + } else { + None + } + } + pub fn is_child_focused(&self, view: impl Into) -> bool { let view = view.into(); if let Some(focused_view_id) = self.focused_view_id(view.window_id) { @@ -3731,10 +3748,6 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.toggle_window_full_screen(self.window_id) } - pub fn window_bounds(&self) -> WindowBounds { - self.app.window_bounds(self.window_id) - } - pub fn prompt( &self, level: PromptLevel, @@ -3851,6 +3864,10 @@ impl<'a, T: View> ViewContext<'a, T> { .build_and_insert_view(self.window_id, ParentId::View(self.view_id), build_view) } + pub fn parent(&mut self) -> Option { + self.cx.parent(self.window_id, self.view_id) + } + pub fn reparent(&mut self, view_handle: impl Into) { let view_handle = view_handle.into(); if self.window_id != view_handle.window_id { diff --git a/crates/gpui/src/elements/keystroke_label.rs b/crates/gpui/src/elements/keystroke_label.rs index ca317d9e11..6553b2fa8d 100644 --- a/crates/gpui/src/elements/keystroke_label.rs +++ b/crates/gpui/src/elements/keystroke_label.rs @@ -12,15 +12,21 @@ pub struct KeystrokeLabel { action: Box, container_style: ContainerStyle, text_style: TextStyle, + window_id: usize, + view_id: usize, } impl KeystrokeLabel { pub fn new( + window_id: usize, + view_id: usize, action: Box, container_style: ContainerStyle, text_style: TextStyle, ) -> Self { Self { + window_id, + view_id, action, container_style, text_style, @@ -37,7 +43,10 @@ impl Element for KeystrokeLabel { constraint: SizeConstraint, cx: &mut LayoutContext, ) -> (Vector2F, ElementBox) { - let mut element = if let Some(keystrokes) = cx.keystrokes_for_action(self.action.as_ref()) { + let mut element = if let Some(keystrokes) = + cx.app + .keystrokes_for_action(self.window_id, self.view_id, self.action.as_ref()) + { Flex::row() .with_children(keystrokes.iter().map(|keystroke| { Label::new(keystroke.to_string(), self.text_style.clone()) diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs index dbcecf9c24..562f12295c 100644 --- a/crates/gpui/src/elements/tooltip.rs +++ b/crates/gpui/src/elements/tooltip.rs @@ -61,11 +61,14 @@ impl Tooltip { ) -> Self { struct ElementState(Tag); struct MouseEventHandlerState(Tag); + let focused_view_id = cx.focused_view_id(cx.window_id); let state_handle = cx.default_element_state::, Rc>(id); let state = state_handle.read(cx).clone(); let tooltip = if state.visible.get() { let mut collapsed_tooltip = Self::render_tooltip( + cx.window_id, + focused_view_id, text.clone(), style.clone(), action.as_ref().map(|a| a.boxed_clone()), @@ -74,7 +77,7 @@ impl Tooltip { .boxed(); Some( Overlay::new( - Self::render_tooltip(text, style, action, false) + Self::render_tooltip(cx.window_id, focused_view_id, text, style, action, false) .constrained() .dynamically(move |constraint, cx| { SizeConstraint::strict_along( @@ -128,6 +131,8 @@ impl Tooltip { } pub fn render_tooltip( + window_id: usize, + focused_view_id: Option, text: String, style: TooltipStyle, action: Option>, @@ -144,13 +149,18 @@ impl Tooltip { text.flex(1., false).aligned().boxed() } }) - .with_children(action.map(|action| { - let keystroke_label = - KeystrokeLabel::new(action, style.keystroke.container, style.keystroke.text); + .with_children(action.and_then(|action| { + let keystroke_label = KeystrokeLabel::new( + window_id, + focused_view_id?, + action, + style.keystroke.container, + style.keystroke.text, + ); if measure { - keystroke_label.boxed() + Some(keystroke_label.boxed()) } else { - keystroke_label.aligned().boxed() + Some(keystroke_label.aligned().boxed()) } })) .contained() diff --git a/crates/gpui/src/keymap_matcher/binding.rs b/crates/gpui/src/keymap_matcher/binding.rs index afd65d4f04..8146437884 100644 --- a/crates/gpui/src/keymap_matcher/binding.rs +++ b/crates/gpui/src/keymap_matcher/binding.rs @@ -41,7 +41,7 @@ impl Binding { }) } - fn match_context(&self, contexts: &[KeymapContext]) -> bool { + pub fn match_context(&self, contexts: &[KeymapContext]) -> bool { self.context_predicate .as_ref() .map(|predicate| predicate.eval(contexts)) diff --git a/crates/gpui/src/keymap_matcher/keymap_context.rs b/crates/gpui/src/keymap_matcher/keymap_context.rs index 28f5f80c83..b19989b210 100644 --- a/crates/gpui/src/keymap_matcher/keymap_context.rs +++ b/crates/gpui/src/keymap_matcher/keymap_context.rs @@ -43,7 +43,7 @@ impl KeymapContextPredicate { pub fn eval(&self, contexts: &[KeymapContext]) -> bool { let Some(context) = contexts.first() else { return false }; match self { - Self::Identifier(name) => context.set.contains(name.as_str()), + Self::Identifier(name) => (&context.set).contains(name.as_str()), Self::Equal(left, right) => context .map .get(left) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index d1aaed7f47..57e8f89539 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -124,7 +124,7 @@ pub trait InputHandler { pub trait Screen: Debug { fn as_any(&self) -> &dyn Any; fn bounds(&self) -> RectF; - fn display_uuid(&self) -> Uuid; + fn display_uuid(&self) -> Option; } pub trait Window { diff --git a/crates/gpui/src/platform/mac/geometry.rs b/crates/gpui/src/platform/mac/geometry.rs index 0f3b1f6fce..6a47968118 100644 --- a/crates/gpui/src/platform/mac/geometry.rs +++ b/crates/gpui/src/platform/mac/geometry.rs @@ -14,12 +14,12 @@ use pathfinder_geometry::{ pub trait Vector2FExt { /// Converts self to an NSPoint with y axis pointing up. - fn to_screen_ns_point(&self, native_window: id) -> NSPoint; + fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint; } impl Vector2FExt for Vector2F { - fn to_screen_ns_point(&self, native_window: id) -> NSPoint { + fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint { unsafe { - let point = NSPoint::new(self.x() as f64, -self.y() as f64); + let point = NSPoint::new(self.x() as f64, window_height - self.y() as f64); msg_send![native_window, convertPointToScreen: point] } } diff --git a/crates/gpui/src/platform/mac/screen.rs b/crates/gpui/src/platform/mac/screen.rs index 27fb4b12d1..98b6a66f03 100644 --- a/crates/gpui/src/platform/mac/screen.rs +++ b/crates/gpui/src/platform/mac/screen.rs @@ -35,7 +35,7 @@ impl Screen { .map(|ix| Screen { native_screen: native_screens.objectAtIndex(ix), }) - .find(|screen| platform::Screen::display_uuid(screen) == uuid) + .find(|screen| platform::Screen::display_uuid(screen) == Some(uuid)) } } @@ -58,7 +58,7 @@ impl platform::Screen for Screen { self } - fn display_uuid(&self) -> uuid::Uuid { + fn display_uuid(&self) -> Option { unsafe { // Screen ids are not stable. Further, the default device id is also unstable across restarts. // CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use. @@ -74,8 +74,12 @@ impl platform::Screen for Screen { (&mut device_id) as *mut _ as *mut c_void, ); let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID); + if cfuuid.is_null() { + return None; + } + let bytes = CFUUIDGetUUIDBytes(cfuuid); - Uuid::from_bytes([ + Some(Uuid::from_bytes([ bytes.byte0, bytes.byte1, bytes.byte2, @@ -92,7 +96,7 @@ impl platform::Screen for Screen { bytes.byte13, bytes.byte14, bytes.byte15, - ]) + ])) } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index bc934703be..e67aa25e13 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -483,6 +483,7 @@ impl Window { let native_view: id = msg_send![VIEW_CLASS, alloc]; let native_view = NSView::init(native_view); + assert!(!native_view.is_null()); let window = Self(Rc::new(RefCell::new(WindowState { @@ -828,12 +829,14 @@ impl platform::Window for Window { let self_id = self_borrow.id; unsafe { - let window_frame = self_borrow.frame(); let app = NSApplication::sharedApplication(nil); // Convert back to screen coordinates - let screen_point = - (position + window_frame.origin()).to_screen_ns_point(self_borrow.native_window); + let screen_point = position.to_screen_ns_point( + self_borrow.native_window, + self_borrow.content_size().y() as f64, + ); + let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0]; let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number]; diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 6c8637948e..aa73aebc90 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -238,8 +238,8 @@ impl super::Screen for Screen { RectF::new(Vector2F::zero(), Vector2F::new(1920., 1080.)) } - fn display_uuid(&self) -> uuid::Uuid { - uuid::Uuid::new_v4() + fn display_uuid(&self) -> Option { + Some(uuid::Uuid::new_v4()) } } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 66c991f0a8..c0785e11f3 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -4,7 +4,6 @@ use crate::{ font_cache::FontCache, geometry::rect::RectF, json::{self, ToJson}, - keymap_matcher::Keystroke, platform::{CursorStyle, Event}, scene::{ CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, @@ -604,14 +603,6 @@ pub struct LayoutContext<'a> { } impl<'a> LayoutContext<'a> { - pub(crate) fn keystrokes_for_action( - &mut self, - action: &dyn Action, - ) -> Option> { - self.app - .keystrokes_for_action(self.window_id, &self.view_stack, action) - } - fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F { let print_error = |view_id| { format!( diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index b7199a5287..49a2570edb 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -64,6 +64,7 @@ struct Request<'a, T> { #[derive(Serialize, Deserialize)] struct AnyResponse<'a> { + jsonrpc: &'a str, id: usize, #[serde(default)] error: Option, @@ -203,8 +204,9 @@ impl LanguageServer { } else { on_unhandled_notification(msg); } - } else if let Ok(AnyResponse { id, error, result }) = - serde_json::from_slice(&buffer) + } else if let Ok(AnyResponse { + id, error, result, .. + }) = serde_json::from_slice(&buffer) { if let Some(handler) = response_handlers .lock() @@ -460,35 +462,57 @@ impl LanguageServer { method, Box::new(move |id, params, cx| { if let Some(id) = id { - if let Some(params) = serde_json::from_str(params).log_err() { - let response = f(params, cx.clone()); - cx.foreground() - .spawn({ - let outbound_tx = outbound_tx.clone(); - async move { - let response = match response.await { - Ok(result) => Response { - jsonrpc: JSON_RPC_VERSION, - id, - result: Some(result), - error: None, - }, - Err(error) => Response { - jsonrpc: JSON_RPC_VERSION, - id, - result: None, - error: Some(Error { - message: error.to_string(), - }), - }, - }; - if let Some(response) = serde_json::to_vec(&response).log_err() - { - outbound_tx.try_send(response).ok(); + match serde_json::from_str(params) { + Ok(params) => { + let response = f(params, cx.clone()); + cx.foreground() + .spawn({ + let outbound_tx = outbound_tx.clone(); + async move { + let response = match response.await { + Ok(result) => Response { + jsonrpc: JSON_RPC_VERSION, + id, + result: Some(result), + error: None, + }, + Err(error) => Response { + jsonrpc: JSON_RPC_VERSION, + id, + result: None, + error: Some(Error { + message: error.to_string(), + }), + }, + }; + if let Some(response) = + serde_json::to_vec(&response).log_err() + { + outbound_tx.try_send(response).ok(); + } } - } - }) - .detach(); + }) + .detach(); + } + Err(error) => { + log::error!( + "error deserializing {} request: {:?}, message: {:?}", + method, + error, + params + ); + let response = AnyResponse { + jsonrpc: JSON_RPC_VERSION, + id, + result: None, + error: Some(Error { + message: error.to_string(), + }), + }; + if let Some(response) = serde_json::to_vec(&response).log_err() { + outbound_tx.try_send(response).ok(); + } + } } } }), diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index d7de7ae718..f613ba4df2 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -11,7 +11,10 @@ use highlighted_workspace_location::HighlightedWorkspaceLocation; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate}; use settings::Settings; -use workspace::{OpenPaths, Workspace, WorkspaceLocation, WORKSPACE_DB}; +use workspace::{ + notifications::simple_message_notification::MessageNotification, OpenPaths, Workspace, + WorkspaceLocation, WORKSPACE_DB, +}; actions!(projects, [OpenRecent]); @@ -42,7 +45,7 @@ impl RecentProjectsView { fn toggle(_: &mut Workspace, _: &OpenRecent, cx: &mut ViewContext) { cx.spawn(|workspace, mut cx| async move { - let workspace_locations = cx + let workspace_locations: Vec<_> = cx .background() .spawn(async { WORKSPACE_DB @@ -56,12 +59,20 @@ impl RecentProjectsView { .await; workspace.update(&mut cx, |workspace, cx| { - workspace.toggle_modal(cx, |_, cx| { - let view = cx.add_view(|cx| Self::new(workspace_locations, cx)); - cx.subscribe(&view, Self::on_event).detach(); - view - }); - }) + if !workspace_locations.is_empty() { + workspace.toggle_modal(cx, |_, cx| { + let view = cx.add_view(|cx| Self::new(workspace_locations, cx)); + cx.subscribe(&view, Self::on_event).detach(); + view + }); + } else { + workspace.show_notification(0, cx, |cx| { + cx.add_view(|_| { + MessageNotification::new_message("No recent projects to open.") + }) + }) + } + }); }) .detach(); } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index d3d5c437c5..71a40685e9 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -259,11 +259,7 @@ impl Item for ProjectSearchView { .boxed(), ) .with_children(self.model.read(cx).active_query.as_ref().map(|query| { - let query_text = if query.as_str().len() > MAX_TAB_TITLE_LEN { - query.as_str()[..MAX_TAB_TITLE_LEN].to_string() + "โ€ฆ" - } else { - query.as_str().to_string() - }; + let query_text = util::truncate_and_trailoff(query.as_str(), MAX_TAB_TITLE_LEN); Label::new(query_text, tab_theme.label.clone()) .aligned() @@ -575,9 +571,9 @@ impl ProjectSearchView { self.active_match_index = None; } else { let prev_search_id = mem::replace(&mut self.search_id, self.model.read(cx).search_id); - let reset_selections = self.search_id != prev_search_id; + let is_new_search = self.search_id != prev_search_id; self.results_editor.update(cx, |editor, cx| { - if reset_selections { + if is_new_search { editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges(match_ranges.first().cloned()) }); @@ -588,7 +584,7 @@ impl ProjectSearchView { cx, ); }); - if self.query_editor.is_focused(cx) { + if is_new_search && self.query_editor.is_focused(cx) { self.focus_results_editor(cx); } } diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index 8cdbfc6438..ea8fdee2a8 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -46,10 +46,10 @@ pub fn truncate(s: &str, max_chars: usize) -> &str { pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); - if s.len() > max_chars { - format!("{}โ€ฆ", truncate(s, max_chars.saturating_sub(3))) - } else { - s.to_string() + let truncation_ix = s.char_indices().map(|(i, _)| i).nth(max_chars); + match truncation_ix { + Some(length) => s[..length].to_string() + "โ€ฆ", + None => s.to_string(), } } @@ -276,4 +276,12 @@ mod tests { assert_eq!(foo, None); } + + #[test] + fn test_trancate_and_trailoff() { + assert_eq!(truncate_and_trailoff("", 5), ""); + assert_eq!(truncate_and_trailoff("รจรจรจรจรจรจ", 7), "รจรจรจรจรจรจ"); + assert_eq!(truncate_and_trailoff("รจรจรจรจรจรจ", 6), "รจรจรจรจรจรจ"); + assert_eq!(truncate_and_trailoff("รจรจรจรจรจรจ", 5), "รจรจรจรจรจโ€ฆ"); + } } diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 43feede190..72dec114d9 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -174,7 +174,7 @@ pub mod simple_message_notification { } impl MessageNotification { - pub fn new_messsage>(message: S) -> MessageNotification { + pub fn new_message>(message: S) -> MessageNotification { Self { message: message.as_ref().to_string(), click_action: None, @@ -320,7 +320,7 @@ where Err(err) => { workspace.show_notification(0, cx, |cx| { cx.add_view(|_cx| { - simple_message_notification::MessageNotification::new_messsage(format!( + simple_message_notification::MessageNotification::new_message(format!( "Error: {:?}", err, )) diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 7cb1158a06..26e4ef2494 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.72.0" +version = "0.72.5" publish = false [lib] diff --git a/crates/zed/RELEASE_CHANNEL b/crates/zed/RELEASE_CHANNEL index 90012116c0..870bbe4e50 100644 --- a/crates/zed/RELEASE_CHANNEL +++ b/crates/zed/RELEASE_CHANNEL @@ -1 +1 @@ -dev \ No newline at end of file +stable \ No newline at end of file diff --git a/styles/src/buildLicenses.ts b/styles/src/buildLicenses.ts index e83496d91d..5026faef4e 100644 --- a/styles/src/buildLicenses.ts +++ b/styles/src/buildLicenses.ts @@ -41,8 +41,7 @@ function getLicenseText(schemeMeta: Meta[], callback: (meta: Meta, license_text: const { statusCode } = res; if (statusCode < 200 || statusCode >= 300) { - throw new Error('Failed to fetch license file.\n' + - `Status Code: ${statusCode}`); + throw new Error(`Failed to fetch license for: ${meta.name}, Status Code: ${statusCode}`); } res.setEncoding('utf8'); diff --git a/styles/src/themes/atelier-cave.ts b/styles/src/themes/atelier-cave.ts index b7b06381d8..0959cabace 100644 --- a/styles/src/themes/atelier-cave.ts +++ b/styles/src/themes/atelier-cave.ts @@ -56,8 +56,8 @@ export const meta: Meta = { author: "atelierbram", license: { SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/atelierbram/syntax-highlighting/master/LICENSE", - license_checksum: "6c2353bb9dd0b7b211364d98184ab482e54f40f611eda0c02974c3a1f9e6193c" + https_url: "https://atelierbram.mit-license.org/license.txt", + license_checksum: "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5" }, url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/" } \ No newline at end of file diff --git a/styles/src/themes/atelier-sulphurpool.ts b/styles/src/themes/atelier-sulphurpool.ts index 2e2f708442..fa51b1ec80 100644 --- a/styles/src/themes/atelier-sulphurpool.ts +++ b/styles/src/themes/atelier-sulphurpool.ts @@ -35,8 +35,8 @@ export const meta: Meta = { author: "atelierbram", license: { SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/atelierbram/syntax-highlighting/master/LICENSE", - license_checksum: "6c2353bb9dd0b7b211364d98184ab482e54f40f611eda0c02974c3a1f9e6193c" + https_url: "https://atelierbram.mit-license.org/license.txt", + license_checksum: "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5" }, - url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune/" + url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool/" } \ No newline at end of file