From eb02834582964b00429a2a2f6c789df67272457d Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Thu, 5 Jan 2023 17:58:52 -0500 Subject: [PATCH 001/108] In-app feedback WIP --- crates/editor/src/editor.rs | 9 + crates/theme/src/theme.rs | 17 ++ crates/workspace/src/pane.rs | 6 + crates/zed/src/feedback.rs | 50 ----- crates/zed/src/feedback_popover.rs | 326 +++++++++++++++++++++++++++++ crates/zed/src/main.rs | 5 +- crates/zed/src/system_specs.rs | 2 + crates/zed/src/zed.rs | 4 +- 8 files changed, 366 insertions(+), 53 deletions(-) delete mode 100644 crates/zed/src/feedback.rs create mode 100644 crates/zed/src/feedback_popover.rs diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d8ee49866b..f63d44ee67 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -990,6 +990,15 @@ impl Editor { Self::new(EditorMode::SingleLine, buffer, None, field_editor_style, cx) } + pub fn multi_line( + field_editor_style: Option>, + cx: &mut ViewContext, + ) -> Self { + let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx)); + let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + Self::new(EditorMode::Full, buffer, None, field_editor_style, cx) + } + pub fn auto_height( max_lines: usize, field_editor_style: Option>, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index bf6cb57adb..3ce8894238 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -25,6 +25,7 @@ pub struct Theme { pub command_palette: CommandPalette, pub picker: Picker, pub editor: Editor, + // pub feedback_box: Editor, pub search: Search, pub project_diagnostics: ProjectDiagnostics, pub breadcrumbs: ContainedText, @@ -119,6 +120,22 @@ pub struct ContactList { pub calling_indicator: ContainedText, } +// TODO FEEDBACK: Remove or use this +// #[derive(Deserialize, Default)] +// pub struct FeedbackPopover { +// #[serde(flatten)] +// pub container: ContainerStyle, +// pub height: f32, +// pub width: f32, +// pub invite_row_height: f32, +// pub invite_row: Interactive, +// } + +// #[derive(Deserialize, Default)] +// pub struct FeedbackBox { +// pub feedback_editor: FieldEditor, +// } + #[derive(Deserialize, Default)] pub struct ProjectRow { #[serde(flatten)] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 618c650e02..5456ecaf60 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -95,6 +95,11 @@ pub struct DeployNewMenu { position: Vector2F, } +#[derive(Clone, PartialEq)] +pub struct DeployFeedbackModal { + position: Vector2F, +} + impl_actions!(pane, [GoBack, GoForward, ActivateItem]); impl_internal_actions!( pane, @@ -104,6 +109,7 @@ impl_internal_actions!( DeployNewMenu, DeployDockMenu, MoveItem, + DeployFeedbackModal ] ); diff --git a/crates/zed/src/feedback.rs b/crates/zed/src/feedback.rs deleted file mode 100644 index 55597312ae..0000000000 --- a/crates/zed/src/feedback.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::OpenBrowser; -use gpui::{ - elements::{MouseEventHandler, Text}, - platform::CursorStyle, - Element, Entity, MouseButton, RenderContext, View, -}; -use settings::Settings; -use workspace::{item::ItemHandle, StatusItemView}; - -pub const NEW_ISSUE_URL: &str = "https://github.com/zed-industries/feedback/issues/new/choose"; - -pub struct FeedbackLink; - -impl Entity for FeedbackLink { - type Event = (); -} - -impl View for FeedbackLink { - fn ui_name() -> &'static str { - "FeedbackLink" - } - - fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> gpui::ElementBox { - MouseEventHandler::::new(0, cx, |state, cx| { - let theme = &cx.global::().theme; - let theme = &theme.workspace.status_bar.feedback; - Text::new( - "Give Feedback".to_string(), - theme.style_for(state, false).clone(), - ) - .boxed() - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, cx| { - cx.dispatch_action(OpenBrowser { - url: NEW_ISSUE_URL.into(), - }) - }) - .boxed() - } -} - -impl StatusItemView for FeedbackLink { - fn set_active_pane_item( - &mut self, - _: Option<&dyn ItemHandle>, - _: &mut gpui::ViewContext, - ) { - } -} diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs new file mode 100644 index 0000000000..52397562f9 --- /dev/null +++ b/crates/zed/src/feedback_popover.rs @@ -0,0 +1,326 @@ +use std::{ops::Range, sync::Arc}; + +use anyhow::bail; +use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN}; +use editor::Editor; +use futures::AsyncReadExt; +use gpui::{ + actions, + elements::{ + AnchorCorner, ChildView, Flex, MouseEventHandler, Overlay, OverlayFitMode, ParentElement, + Stack, Text, + }, + serde_json, CursorStyle, Element, ElementBox, Entity, MouseButton, MutableAppContext, + RenderContext, View, ViewContext, ViewHandle, +}; +use isahc::Request; +use lazy_static::lazy_static; +use serde::Serialize; +use settings::Settings; +use workspace::{item::ItemHandle, StatusItemView}; + +use crate::{feedback_popover, system_specs::SystemSpecs}; + +/* + TODO FEEDBACK + + Next steps from Mikayla: + 1: Find the bottom bar height and maybe guess some feedback button widths? + Basically, just use to position the modal + 2: Look at ContactList::render() and ContactPopover::render() for clues on how + to make the modal look nice. Copy the theme values from the contact list styles + + Now + Fix all layout issues, theming, buttons, etc + Make multi-line editor without line numbers + Some sort of feedback when something fails or succeeds + Naming of all UI stuff and separation out into files (follow a convention already in place) + Disable submit button when text length is 0 + Should we store staff boolean? + Put behind experiments flag + Move to separate crate + Render a character counter + All warnings + Remove all comments + Later + If a character limit is imposed, switch submit button over to a "GitHub Issue" button + Should editor by treated as a markdown file + Limit characters? + Disable submit button when text length is GTE to character limit + + Pay for AirTable + Add AirTable to system architecture diagram in Figma +*/ + +lazy_static! { + pub static ref ZED_SERVER_URL: String = + std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string()); +} + +const FEEDBACK_CHAR_COUNT_RANGE: Range = Range { + start: 5, + end: 1000, +}; + +actions!(feedback, [ToggleFeedbackPopover, SubmitFeedback]); + +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(FeedbackButton::toggle_feedback); + cx.add_action(FeedbackPopover::submit_feedback); +} + +pub struct FeedbackButton { + feedback_popover: Option>, +} + +impl FeedbackButton { + pub fn new() -> Self { + Self { + feedback_popover: None, + } + } + + pub fn toggle_feedback(&mut self, _: &ToggleFeedbackPopover, cx: &mut ViewContext) { + match self.feedback_popover.take() { + Some(_) => {} + None => { + let popover_view = cx.add_view(|_cx| FeedbackPopover::new(_cx)); + self.feedback_popover = Some(popover_view.clone()); + } + } + + cx.notify(); + } +} + +impl Entity for FeedbackButton { + type Event = (); +} + +impl View for FeedbackButton { + fn ui_name() -> &'static str { + "FeedbackButton" + } + + fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { + Stack::new() + .with_child( + MouseEventHandler::::new(0, cx, |state, cx| { + let theme = &cx.global::().theme; + let theme = &theme.workspace.status_bar.feedback; + + Text::new( + "Give Feedback".to_string(), + theme + .style_for(state, self.feedback_popover.is_some()) + .clone(), + ) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(ToggleFeedbackPopover) + }) + .boxed(), + ) + .with_children(self.feedback_popover.as_ref().map(|popover| { + Overlay::new( + ChildView::new(popover, cx) + .contained() + // .with_height(theme.contact_list.user_query_editor_height) + // .with_margin_top(-50.0) + // .with_margin_left(titlebar.toggle_contacts_button.default.button_width) + // .with_margin_right(-titlebar.toggle_contacts_button.default.button_width) + .boxed(), + ) + .with_fit_mode(OverlayFitMode::SwitchAnchor) + .with_anchor_corner(AnchorCorner::TopLeft) + .with_z_index(999) + .boxed() + })) + .boxed() + } +} + +impl StatusItemView for FeedbackButton { + fn set_active_pane_item( + &mut self, + _: Option<&dyn ItemHandle>, + _: &mut gpui::ViewContext, + ) { + // N/A + } +} + +pub struct FeedbackPopover { + feedback_editor: ViewHandle, + // _subscriptions: Vec, +} + +impl Entity for FeedbackPopover { + type Event = (); +} + +#[derive(Serialize)] +struct FeedbackRequestBody<'a> { + feedback_text: &'a str, + metrics_id: Option>, + system_specs: SystemSpecs, + token: &'a str, +} + +impl FeedbackPopover { + pub fn new(cx: &mut ViewContext) -> Self { + let feedback_editor = cx.add_view(|cx| { + let editor = Editor::multi_line( + Some(Arc::new(|theme| { + theme.contact_list.user_query_editor.clone() + })), + cx, + ); + editor + }); + + cx.focus(&feedback_editor); + + cx.subscribe(&feedback_editor, |this, _, event, cx| { + if let editor::Event::BufferEdited = event { + let buffer_len = this.feedback_editor.read(cx).buffer().read(cx).len(cx); + let feedback_chars_remaining = FEEDBACK_CHAR_COUNT_RANGE.end - buffer_len; + dbg!(feedback_chars_remaining); + } + }) + .detach(); + + // let active_call = ActiveCall::global(cx); + // let mut subscriptions = Vec::new(); + // subscriptions.push(cx.observe(&user_store, |this, _, cx| this.update_entries(cx))); + // subscriptions.push(cx.observe(&active_call, |this, _, cx| this.update_entries(cx))); + let this = Self { + feedback_editor, // _subscriptions: subscriptions, + }; + // this.update_entries(cx); + this + } + + fn submit_feedback(&mut self, _: &SubmitFeedback, cx: &mut ViewContext<'_, Self>) { + let feedback_text = self.feedback_editor.read(cx).text(cx); + let http_client = cx.global::>().clone(); + let system_specs = SystemSpecs::new(cx); + let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL); + + cx.spawn(|this, async_cx| { + async move { + // TODO FEEDBACK: Use or remove + // this.read_with(&async_cx, |this, cx| { + // // Now we have a &self and a &AppContext + // }); + + let metrics_id = None; + + let request = FeedbackRequestBody { + feedback_text: &feedback_text, + metrics_id, + system_specs, + token: ZED_SECRET_CLIENT_TOKEN, + }; + + let json_bytes = serde_json::to_vec(&request)?; + + let request = Request::post(feedback_endpoint) + .header("content-type", "application/json") + .body(json_bytes.into())?; + + let mut response = http_client.send(request).await?; + let mut body = String::new(); + response.body_mut().read_to_string(&mut body).await?; + + let response_status = response.status(); + + dbg!(response_status); + + if !response_status.is_success() { + // TODO FEEDBACK: Do some sort of error reporting here for if store fails + bail!("Error") + } + + // TODO FEEDBACK: Use or remove + // Will need to handle error cases + // async_cx.update(|cx| { + // this.update(cx, |this, cx| { + // this.handle_error(error); + // cx.notify(); + // cx.dispatch_action(ShowErrorPopover); + // this.error_text = "Embedding failed" + // }) + // }); + + Ok(()) + } + }) + .detach(); + } +} + +impl View for FeedbackPopover { + fn ui_name() -> &'static str { + "FeedbackPopover" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + enum SubmitFeedback {} + + let theme = cx.global::().theme.clone(); + let status_bar_height = theme.workspace.status_bar.height; + let submit_feedback_text_button_height = 20.0; + + // I'd like to just define: + + // 1. Overall popover width x height dimensions + // 2. Submit Feedback button height dimensions + // 3. Allow editor to dynamically fill in the remaining space + + Flex::column() + .with_child( + Flex::row() + .with_child( + ChildView::new(self.feedback_editor.clone(), cx) + .contained() + .with_style(theme.contact_list.user_query_editor.container) + .flex(1., true) + .boxed(), + ) + .constrained() + .with_width(theme.contacts_popover.width) + .with_height(theme.contacts_popover.height - submit_feedback_text_button_height) + .boxed(), + ) + .with_child( + MouseEventHandler::::new(0, cx, |state, _| { + let theme = &theme.workspace.status_bar.feedback; + + Text::new( + "Submit Feedback".to_string(), + theme.style_for(state, true).clone(), + ) + .constrained() + .with_height(submit_feedback_text_button_height) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(feedback_popover::SubmitFeedback) + }) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(feedback_popover::ToggleFeedbackPopover) + }) + .boxed(), + ) + .contained() + .with_style(theme.contacts_popover.container) + .constrained() + .with_width(theme.contacts_popover.width + 200.0) + .with_height(theme.contacts_popover.height) + .boxed() + } +} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 5ac7cb36b6..90527af555 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -41,7 +41,7 @@ use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ self, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, OpenPaths, Workspace, }; -use zed::{self, build_window_options, initialize_workspace, languages, menus}; +use zed::{self, build_window_options, feedback_popover, initialize_workspace, languages, menus}; fn main() { let http = http::client(); @@ -108,6 +108,9 @@ fn main() { watch_settings_file(default_settings, settings_file_content, themes.clone(), cx); watch_keymap_file(keymap_file, cx); + cx.set_global(http.clone()); + + feedback_popover::init(cx); context_menu::init(cx); project::Project::init(&client); client::init(client.clone(), cx); diff --git a/crates/zed/src/system_specs.rs b/crates/zed/src/system_specs.rs index b6c2c0fcba..fc55f76f35 100644 --- a/crates/zed/src/system_specs.rs +++ b/crates/zed/src/system_specs.rs @@ -2,9 +2,11 @@ use std::{env, fmt::Display}; use gpui::AppContext; use human_bytes::human_bytes; +use serde::Serialize; use sysinfo::{System, SystemExt}; use util::channel::ReleaseChannel; +#[derive(Debug, Serialize)] pub struct SystemSpecs { app_version: &'static str, release_channel: &'static str, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e6d50f43af..527da286d2 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1,4 +1,4 @@ -mod feedback; +pub mod feedback_popover; pub mod languages; pub mod menus; pub mod system_specs; @@ -369,7 +369,7 @@ pub fn initialize_workspace( let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - let feedback_link = cx.add_view(|_| feedback::FeedbackLink); + let feedback_link = cx.add_view(|_| feedback_popover::FeedbackButton::new()); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); From 404f59090cfd387cb33b11ad5e80336cd4bcb2c4 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Thu, 5 Jan 2023 18:14:28 -0500 Subject: [PATCH 002/108] Update TODO --- crates/zed/src/feedback_popover.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs index 52397562f9..e0721078a0 100644 --- a/crates/zed/src/feedback_popover.rs +++ b/crates/zed/src/feedback_popover.rs @@ -31,6 +31,7 @@ use crate::{feedback_popover, system_specs::SystemSpecs}; to make the modal look nice. Copy the theme values from the contact list styles Now + Obtain metrics_id Fix all layout issues, theming, buttons, etc Make multi-line editor without line numbers Some sort of feedback when something fails or succeeds From 658541ec9f1e133e56a7a8ac8eca6a41633ae017 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 6 Jan 2023 15:32:28 -0500 Subject: [PATCH 003/108] Add to TODO --- crates/zed/src/feedback_popover.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs index e0721078a0..556fb5922c 100644 --- a/crates/zed/src/feedback_popover.rs +++ b/crates/zed/src/feedback_popover.rs @@ -31,6 +31,7 @@ use crate::{feedback_popover, system_specs::SystemSpecs}; to make the modal look nice. Copy the theme values from the contact list styles Now + Rework all code relying on contacts list (search out "contacts") Obtain metrics_id Fix all layout issues, theming, buttons, etc Make multi-line editor without line numbers From 9d4cf2ff62247c0ef07fb580b640817aa2d0641f Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 6 Jan 2023 15:41:31 -0500 Subject: [PATCH 004/108] Move notes into PR --- crates/zed/src/feedback_popover.rs | 33 ------------------------------ 1 file changed, 33 deletions(-) diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs index 556fb5922c..46e7116e39 100644 --- a/crates/zed/src/feedback_popover.rs +++ b/crates/zed/src/feedback_popover.rs @@ -21,39 +21,6 @@ use workspace::{item::ItemHandle, StatusItemView}; use crate::{feedback_popover, system_specs::SystemSpecs}; -/* - TODO FEEDBACK - - Next steps from Mikayla: - 1: Find the bottom bar height and maybe guess some feedback button widths? - Basically, just use to position the modal - 2: Look at ContactList::render() and ContactPopover::render() for clues on how - to make the modal look nice. Copy the theme values from the contact list styles - - Now - Rework all code relying on contacts list (search out "contacts") - Obtain metrics_id - Fix all layout issues, theming, buttons, etc - Make multi-line editor without line numbers - Some sort of feedback when something fails or succeeds - Naming of all UI stuff and separation out into files (follow a convention already in place) - Disable submit button when text length is 0 - Should we store staff boolean? - Put behind experiments flag - Move to separate crate - Render a character counter - All warnings - Remove all comments - Later - If a character limit is imposed, switch submit button over to a "GitHub Issue" button - Should editor by treated as a markdown file - Limit characters? - Disable submit button when text length is GTE to character limit - - Pay for AirTable - Add AirTable to system architecture diagram in Figma -*/ - lazy_static! { pub static ref ZED_SERVER_URL: String = std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string()); From 5387695ee0ae283d0795229596c5cdf6b17ff7f7 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 6 Jan 2023 17:40:30 -0500 Subject: [PATCH 005/108] WIP Don't rely on contacts popover or contacts list for theming Add metrics id to request body Clean up some code and comments Co-Authored-By: Mikayla Maki --- crates/client/src/client.rs | 4 +++ crates/client/src/telemetry.rs | 4 +++ crates/theme/src/theme.rs | 28 +++++++--------- crates/zed/src/feedback_popover.rs | 54 +++++++++++------------------- crates/zed/src/main.rs | 2 +- styles/src/styleTree/app.ts | 2 ++ styles/src/styleTree/feedback.ts | 35 +++++++++++++++++++ 7 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 styles/src/styleTree/feedback.ts diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 6d9ec305b6..49ac0062f8 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1292,6 +1292,10 @@ impl Client { pub fn telemetry_log_file_path(&self) -> Option { self.telemetry.log_file_path() } + + pub fn metrics_id(&self) -> Option> { + self.telemetry.metrics_id() + } } impl WeakSubscriber { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index ce8b713996..4e32e020eb 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -261,6 +261,10 @@ impl Telemetry { } } + pub fn metrics_id(self: &Arc) -> Option> { + self.state.lock().metrics_id.clone() + } + fn flush(self: &Arc) { let mut state = self.state.lock(); let mut events = mem::take(&mut state.queue); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 3ce8894238..d3f0b03245 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -25,7 +25,7 @@ pub struct Theme { pub command_palette: CommandPalette, pub picker: Picker, pub editor: Editor, - // pub feedback_box: Editor, + pub feedback: Feedback, pub search: Search, pub project_diagnostics: ProjectDiagnostics, pub breadcrumbs: ContainedText, @@ -120,21 +120,19 @@ pub struct ContactList { pub calling_indicator: ContainedText, } -// TODO FEEDBACK: Remove or use this -// #[derive(Deserialize, Default)] -// pub struct FeedbackPopover { -// #[serde(flatten)] -// pub container: ContainerStyle, -// pub height: f32, -// pub width: f32, -// pub invite_row_height: f32, -// pub invite_row: Interactive, -// } +#[derive(Deserialize, Default)] +pub struct Feedback { + pub feedback_popover: FeedbackPopover, + pub feedback_editor: FieldEditor, +} -// #[derive(Deserialize, Default)] -// pub struct FeedbackBox { -// pub feedback_editor: FieldEditor, -// } +#[derive(Deserialize, Default)] +pub struct FeedbackPopover { + #[serde(flatten)] + pub container: ContainerStyle, + pub height: f32, + pub width: f32, +} #[derive(Deserialize, Default)] pub struct ProjectRow { diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs index 46e7116e39..b11ab1f4ac 100644 --- a/crates/zed/src/feedback_popover.rs +++ b/crates/zed/src/feedback_popover.rs @@ -1,7 +1,7 @@ use std::{ops::Range, sync::Arc}; use anyhow::bail; -use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN}; +use client::{Client, ZED_SECRET_CLIENT_TOKEN}; use editor::Editor; use futures::AsyncReadExt; use gpui::{ @@ -93,19 +93,11 @@ impl View for FeedbackButton { .boxed(), ) .with_children(self.feedback_popover.as_ref().map(|popover| { - Overlay::new( - ChildView::new(popover, cx) - .contained() - // .with_height(theme.contact_list.user_query_editor_height) - // .with_margin_top(-50.0) - // .with_margin_left(titlebar.toggle_contacts_button.default.button_width) - // .with_margin_right(-titlebar.toggle_contacts_button.default.button_width) - .boxed(), - ) - .with_fit_mode(OverlayFitMode::SwitchAnchor) - .with_anchor_corner(AnchorCorner::TopLeft) - .with_z_index(999) - .boxed() + Overlay::new(ChildView::new(popover, cx).contained().boxed()) + .with_fit_mode(OverlayFitMode::SwitchAnchor) + .with_anchor_corner(AnchorCorner::TopLeft) + .with_z_index(999) + .boxed() })) .boxed() } @@ -142,9 +134,7 @@ impl FeedbackPopover { pub fn new(cx: &mut ViewContext) -> Self { let feedback_editor = cx.add_view(|cx| { let editor = Editor::multi_line( - Some(Arc::new(|theme| { - theme.contact_list.user_query_editor.clone() - })), + Some(Arc::new(|theme| theme.feedback.feedback_editor.clone())), cx, ); editor @@ -174,19 +164,20 @@ impl FeedbackPopover { fn submit_feedback(&mut self, _: &SubmitFeedback, cx: &mut ViewContext<'_, Self>) { let feedback_text = self.feedback_editor.read(cx).text(cx); - let http_client = cx.global::>().clone(); + let zed_client = cx.global::>(); let system_specs = SystemSpecs::new(cx); let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL); - cx.spawn(|this, async_cx| { + let metrics_id = zed_client.metrics_id(); + let http_client = zed_client.http_client(); + + cx.spawn(|_, _| { async move { // TODO FEEDBACK: Use or remove // this.read_with(&async_cx, |this, cx| { // // Now we have a &self and a &AppContext // }); - let metrics_id = None; - let request = FeedbackRequestBody { feedback_text: &feedback_text, metrics_id, @@ -240,28 +231,23 @@ impl View for FeedbackPopover { enum SubmitFeedback {} let theme = cx.global::().theme.clone(); - let status_bar_height = theme.workspace.status_bar.height; let submit_feedback_text_button_height = 20.0; - // I'd like to just define: - - // 1. Overall popover width x height dimensions - // 2. Submit Feedback button height dimensions - // 3. Allow editor to dynamically fill in the remaining space - Flex::column() .with_child( Flex::row() .with_child( ChildView::new(self.feedback_editor.clone(), cx) .contained() - .with_style(theme.contact_list.user_query_editor.container) + .with_style(theme.feedback.feedback_editor.container) .flex(1., true) .boxed(), ) .constrained() - .with_width(theme.contacts_popover.width) - .with_height(theme.contacts_popover.height - submit_feedback_text_button_height) + .with_width(theme.feedback.feedback_popover.width) + .with_height( + theme.feedback.feedback_popover.height - submit_feedback_text_button_height, + ) .boxed(), ) .with_child( @@ -286,10 +272,10 @@ impl View for FeedbackPopover { .boxed(), ) .contained() - .with_style(theme.contacts_popover.container) + .with_style(theme.feedback.feedback_popover.container) .constrained() - .with_width(theme.contacts_popover.width + 200.0) - .with_height(theme.contacts_popover.height) + .with_width(theme.feedback.feedback_popover.width) + .with_height(theme.feedback.feedback_popover.height) .boxed() } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 90527af555..ce68895768 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -108,7 +108,7 @@ fn main() { watch_settings_file(default_settings, settings_file_content, themes.clone(), cx); watch_keymap_file(keymap_file, cx); - cx.set_global(http.clone()); + cx.set_global(client.clone()); feedback_popover::init(cx); context_menu::init(cx); diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index 267d830506..a51426b6f6 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -19,6 +19,7 @@ import terminal from "./terminal"; import contactList from "./contactList"; import incomingCallNotification from "./incomingCallNotification"; import { ColorScheme } from "../themes/common/colorScheme"; +import feedback from "./feedback"; export default function app(colorScheme: ColorScheme): Object { return { @@ -37,6 +38,7 @@ export default function app(colorScheme: ColorScheme): Object { projectDiagnostics: projectDiagnostics(colorScheme), projectPanel: projectPanel(colorScheme), contactsPopover: contactsPopover(colorScheme), + feedback: feedback(colorScheme), contactFinder: contactFinder(colorScheme), contactList: contactList(colorScheme), search: search(colorScheme), diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts new file mode 100644 index 0000000000..0521949871 --- /dev/null +++ b/styles/src/styleTree/feedback.ts @@ -0,0 +1,35 @@ +import { ColorScheme } from "../themes/common/colorScheme"; +import { background, border, text } from "./components"; + +export default function feedback(colorScheme: ColorScheme) { + let layer = colorScheme.middle; + return { + feedbackEditor: { + background: background(layer, "on"), + cornerRadius: 6, + text: text(layer, "mono", "on"), + placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }), + selection: colorScheme.players[0], + border: border(layer, "on"), + padding: { + bottom: 4, + left: 8, + right: 8, + top: 4, + }, + margin: { + left: 6, + } + }, + feedbackPopover: { + background: background(layer), + cornerRadius: 6, + padding: { top: 6 }, + margin: { top: -6 }, + shadow: colorScheme.popoverShadow, + border: border(layer), + width: 500, + height: 400 + } + } +} From 318a0b7ed007923ee73c095deac6bc9e61943a73 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Thu, 5 Jan 2023 17:58:52 -0500 Subject: [PATCH 006/108] In-app feedback WIP --- crates/editor/src/editor.rs | 9 + crates/theme/src/theme.rs | 17 ++ crates/workspace/src/pane.rs | 6 + crates/zed/src/feedback.rs | 50 ----- crates/zed/src/feedback_popover.rs | 326 +++++++++++++++++++++++++++++ crates/zed/src/main.rs | 5 +- crates/zed/src/system_specs.rs | 2 + crates/zed/src/zed.rs | 4 +- 8 files changed, 366 insertions(+), 53 deletions(-) delete mode 100644 crates/zed/src/feedback.rs create mode 100644 crates/zed/src/feedback_popover.rs diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 76092bbecf..67f17bc8f9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -991,6 +991,15 @@ impl Editor { Self::new(EditorMode::SingleLine, buffer, None, field_editor_style, cx) } + pub fn multi_line( + field_editor_style: Option>, + cx: &mut ViewContext, + ) -> Self { + let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx)); + let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + Self::new(EditorMode::Full, buffer, None, field_editor_style, cx) + } + pub fn auto_height( max_lines: usize, field_editor_style: Option>, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index e463310b98..92dd68ac1b 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -25,6 +25,7 @@ pub struct Theme { pub command_palette: CommandPalette, pub picker: Picker, pub editor: Editor, + // pub feedback_box: Editor, pub search: Search, pub project_diagnostics: ProjectDiagnostics, pub breadcrumbs: ContainedText, @@ -119,6 +120,22 @@ pub struct ContactList { pub calling_indicator: ContainedText, } +// TODO FEEDBACK: Remove or use this +// #[derive(Deserialize, Default)] +// pub struct FeedbackPopover { +// #[serde(flatten)] +// pub container: ContainerStyle, +// pub height: f32, +// pub width: f32, +// pub invite_row_height: f32, +// pub invite_row: Interactive, +// } + +// #[derive(Deserialize, Default)] +// pub struct FeedbackBox { +// pub feedback_editor: FieldEditor, +// } + #[derive(Deserialize, Default)] pub struct ProjectRow { #[serde(flatten)] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 618c650e02..5456ecaf60 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -95,6 +95,11 @@ pub struct DeployNewMenu { position: Vector2F, } +#[derive(Clone, PartialEq)] +pub struct DeployFeedbackModal { + position: Vector2F, +} + impl_actions!(pane, [GoBack, GoForward, ActivateItem]); impl_internal_actions!( pane, @@ -104,6 +109,7 @@ impl_internal_actions!( DeployNewMenu, DeployDockMenu, MoveItem, + DeployFeedbackModal ] ); diff --git a/crates/zed/src/feedback.rs b/crates/zed/src/feedback.rs deleted file mode 100644 index 55597312ae..0000000000 --- a/crates/zed/src/feedback.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::OpenBrowser; -use gpui::{ - elements::{MouseEventHandler, Text}, - platform::CursorStyle, - Element, Entity, MouseButton, RenderContext, View, -}; -use settings::Settings; -use workspace::{item::ItemHandle, StatusItemView}; - -pub const NEW_ISSUE_URL: &str = "https://github.com/zed-industries/feedback/issues/new/choose"; - -pub struct FeedbackLink; - -impl Entity for FeedbackLink { - type Event = (); -} - -impl View for FeedbackLink { - fn ui_name() -> &'static str { - "FeedbackLink" - } - - fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> gpui::ElementBox { - MouseEventHandler::::new(0, cx, |state, cx| { - let theme = &cx.global::().theme; - let theme = &theme.workspace.status_bar.feedback; - Text::new( - "Give Feedback".to_string(), - theme.style_for(state, false).clone(), - ) - .boxed() - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, cx| { - cx.dispatch_action(OpenBrowser { - url: NEW_ISSUE_URL.into(), - }) - }) - .boxed() - } -} - -impl StatusItemView for FeedbackLink { - fn set_active_pane_item( - &mut self, - _: Option<&dyn ItemHandle>, - _: &mut gpui::ViewContext, - ) { - } -} diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs new file mode 100644 index 0000000000..52397562f9 --- /dev/null +++ b/crates/zed/src/feedback_popover.rs @@ -0,0 +1,326 @@ +use std::{ops::Range, sync::Arc}; + +use anyhow::bail; +use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN}; +use editor::Editor; +use futures::AsyncReadExt; +use gpui::{ + actions, + elements::{ + AnchorCorner, ChildView, Flex, MouseEventHandler, Overlay, OverlayFitMode, ParentElement, + Stack, Text, + }, + serde_json, CursorStyle, Element, ElementBox, Entity, MouseButton, MutableAppContext, + RenderContext, View, ViewContext, ViewHandle, +}; +use isahc::Request; +use lazy_static::lazy_static; +use serde::Serialize; +use settings::Settings; +use workspace::{item::ItemHandle, StatusItemView}; + +use crate::{feedback_popover, system_specs::SystemSpecs}; + +/* + TODO FEEDBACK + + Next steps from Mikayla: + 1: Find the bottom bar height and maybe guess some feedback button widths? + Basically, just use to position the modal + 2: Look at ContactList::render() and ContactPopover::render() for clues on how + to make the modal look nice. Copy the theme values from the contact list styles + + Now + Fix all layout issues, theming, buttons, etc + Make multi-line editor without line numbers + Some sort of feedback when something fails or succeeds + Naming of all UI stuff and separation out into files (follow a convention already in place) + Disable submit button when text length is 0 + Should we store staff boolean? + Put behind experiments flag + Move to separate crate + Render a character counter + All warnings + Remove all comments + Later + If a character limit is imposed, switch submit button over to a "GitHub Issue" button + Should editor by treated as a markdown file + Limit characters? + Disable submit button when text length is GTE to character limit + + Pay for AirTable + Add AirTable to system architecture diagram in Figma +*/ + +lazy_static! { + pub static ref ZED_SERVER_URL: String = + std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string()); +} + +const FEEDBACK_CHAR_COUNT_RANGE: Range = Range { + start: 5, + end: 1000, +}; + +actions!(feedback, [ToggleFeedbackPopover, SubmitFeedback]); + +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(FeedbackButton::toggle_feedback); + cx.add_action(FeedbackPopover::submit_feedback); +} + +pub struct FeedbackButton { + feedback_popover: Option>, +} + +impl FeedbackButton { + pub fn new() -> Self { + Self { + feedback_popover: None, + } + } + + pub fn toggle_feedback(&mut self, _: &ToggleFeedbackPopover, cx: &mut ViewContext) { + match self.feedback_popover.take() { + Some(_) => {} + None => { + let popover_view = cx.add_view(|_cx| FeedbackPopover::new(_cx)); + self.feedback_popover = Some(popover_view.clone()); + } + } + + cx.notify(); + } +} + +impl Entity for FeedbackButton { + type Event = (); +} + +impl View for FeedbackButton { + fn ui_name() -> &'static str { + "FeedbackButton" + } + + fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { + Stack::new() + .with_child( + MouseEventHandler::::new(0, cx, |state, cx| { + let theme = &cx.global::().theme; + let theme = &theme.workspace.status_bar.feedback; + + Text::new( + "Give Feedback".to_string(), + theme + .style_for(state, self.feedback_popover.is_some()) + .clone(), + ) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(ToggleFeedbackPopover) + }) + .boxed(), + ) + .with_children(self.feedback_popover.as_ref().map(|popover| { + Overlay::new( + ChildView::new(popover, cx) + .contained() + // .with_height(theme.contact_list.user_query_editor_height) + // .with_margin_top(-50.0) + // .with_margin_left(titlebar.toggle_contacts_button.default.button_width) + // .with_margin_right(-titlebar.toggle_contacts_button.default.button_width) + .boxed(), + ) + .with_fit_mode(OverlayFitMode::SwitchAnchor) + .with_anchor_corner(AnchorCorner::TopLeft) + .with_z_index(999) + .boxed() + })) + .boxed() + } +} + +impl StatusItemView for FeedbackButton { + fn set_active_pane_item( + &mut self, + _: Option<&dyn ItemHandle>, + _: &mut gpui::ViewContext, + ) { + // N/A + } +} + +pub struct FeedbackPopover { + feedback_editor: ViewHandle, + // _subscriptions: Vec, +} + +impl Entity for FeedbackPopover { + type Event = (); +} + +#[derive(Serialize)] +struct FeedbackRequestBody<'a> { + feedback_text: &'a str, + metrics_id: Option>, + system_specs: SystemSpecs, + token: &'a str, +} + +impl FeedbackPopover { + pub fn new(cx: &mut ViewContext) -> Self { + let feedback_editor = cx.add_view(|cx| { + let editor = Editor::multi_line( + Some(Arc::new(|theme| { + theme.contact_list.user_query_editor.clone() + })), + cx, + ); + editor + }); + + cx.focus(&feedback_editor); + + cx.subscribe(&feedback_editor, |this, _, event, cx| { + if let editor::Event::BufferEdited = event { + let buffer_len = this.feedback_editor.read(cx).buffer().read(cx).len(cx); + let feedback_chars_remaining = FEEDBACK_CHAR_COUNT_RANGE.end - buffer_len; + dbg!(feedback_chars_remaining); + } + }) + .detach(); + + // let active_call = ActiveCall::global(cx); + // let mut subscriptions = Vec::new(); + // subscriptions.push(cx.observe(&user_store, |this, _, cx| this.update_entries(cx))); + // subscriptions.push(cx.observe(&active_call, |this, _, cx| this.update_entries(cx))); + let this = Self { + feedback_editor, // _subscriptions: subscriptions, + }; + // this.update_entries(cx); + this + } + + fn submit_feedback(&mut self, _: &SubmitFeedback, cx: &mut ViewContext<'_, Self>) { + let feedback_text = self.feedback_editor.read(cx).text(cx); + let http_client = cx.global::>().clone(); + let system_specs = SystemSpecs::new(cx); + let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL); + + cx.spawn(|this, async_cx| { + async move { + // TODO FEEDBACK: Use or remove + // this.read_with(&async_cx, |this, cx| { + // // Now we have a &self and a &AppContext + // }); + + let metrics_id = None; + + let request = FeedbackRequestBody { + feedback_text: &feedback_text, + metrics_id, + system_specs, + token: ZED_SECRET_CLIENT_TOKEN, + }; + + let json_bytes = serde_json::to_vec(&request)?; + + let request = Request::post(feedback_endpoint) + .header("content-type", "application/json") + .body(json_bytes.into())?; + + let mut response = http_client.send(request).await?; + let mut body = String::new(); + response.body_mut().read_to_string(&mut body).await?; + + let response_status = response.status(); + + dbg!(response_status); + + if !response_status.is_success() { + // TODO FEEDBACK: Do some sort of error reporting here for if store fails + bail!("Error") + } + + // TODO FEEDBACK: Use or remove + // Will need to handle error cases + // async_cx.update(|cx| { + // this.update(cx, |this, cx| { + // this.handle_error(error); + // cx.notify(); + // cx.dispatch_action(ShowErrorPopover); + // this.error_text = "Embedding failed" + // }) + // }); + + Ok(()) + } + }) + .detach(); + } +} + +impl View for FeedbackPopover { + fn ui_name() -> &'static str { + "FeedbackPopover" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + enum SubmitFeedback {} + + let theme = cx.global::().theme.clone(); + let status_bar_height = theme.workspace.status_bar.height; + let submit_feedback_text_button_height = 20.0; + + // I'd like to just define: + + // 1. Overall popover width x height dimensions + // 2. Submit Feedback button height dimensions + // 3. Allow editor to dynamically fill in the remaining space + + Flex::column() + .with_child( + Flex::row() + .with_child( + ChildView::new(self.feedback_editor.clone(), cx) + .contained() + .with_style(theme.contact_list.user_query_editor.container) + .flex(1., true) + .boxed(), + ) + .constrained() + .with_width(theme.contacts_popover.width) + .with_height(theme.contacts_popover.height - submit_feedback_text_button_height) + .boxed(), + ) + .with_child( + MouseEventHandler::::new(0, cx, |state, _| { + let theme = &theme.workspace.status_bar.feedback; + + Text::new( + "Submit Feedback".to_string(), + theme.style_for(state, true).clone(), + ) + .constrained() + .with_height(submit_feedback_text_button_height) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(feedback_popover::SubmitFeedback) + }) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(feedback_popover::ToggleFeedbackPopover) + }) + .boxed(), + ) + .contained() + .with_style(theme.contacts_popover.container) + .constrained() + .with_width(theme.contacts_popover.width + 200.0) + .with_height(theme.contacts_popover.height) + .boxed() + } +} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f4fa919c48..99b4c5a1a3 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -41,7 +41,7 @@ use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ self, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, OpenPaths, Workspace, }; -use zed::{self, build_window_options, initialize_workspace, languages, menus}; +use zed::{self, build_window_options, feedback_popover, initialize_workspace, languages, menus}; fn main() { let http = http::client(); @@ -108,6 +108,9 @@ fn main() { watch_settings_file(default_settings, settings_file_content, themes.clone(), cx); watch_keymap_file(keymap_file, cx); + cx.set_global(http.clone()); + + feedback_popover::init(cx); context_menu::init(cx); project::Project::init(&client); client::init(client.clone(), cx); diff --git a/crates/zed/src/system_specs.rs b/crates/zed/src/system_specs.rs index b6c2c0fcba..fc55f76f35 100644 --- a/crates/zed/src/system_specs.rs +++ b/crates/zed/src/system_specs.rs @@ -2,9 +2,11 @@ use std::{env, fmt::Display}; use gpui::AppContext; use human_bytes::human_bytes; +use serde::Serialize; use sysinfo::{System, SystemExt}; use util::channel::ReleaseChannel; +#[derive(Debug, Serialize)] pub struct SystemSpecs { app_version: &'static str, release_channel: &'static str, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c3af91306d..9fda149191 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1,4 +1,4 @@ -mod feedback; +pub mod feedback_popover; pub mod languages; pub mod menus; pub mod system_specs; @@ -369,7 +369,7 @@ pub fn initialize_workspace( let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - let feedback_link = cx.add_view(|_| feedback::FeedbackLink); + let feedback_link = cx.add_view(|_| feedback_popover::FeedbackButton::new()); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); From 1545b2ac61a8a9ea526225d4370cd230603decd5 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Thu, 5 Jan 2023 18:14:28 -0500 Subject: [PATCH 007/108] Update TODO --- crates/zed/src/feedback_popover.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs index 52397562f9..e0721078a0 100644 --- a/crates/zed/src/feedback_popover.rs +++ b/crates/zed/src/feedback_popover.rs @@ -31,6 +31,7 @@ use crate::{feedback_popover, system_specs::SystemSpecs}; to make the modal look nice. Copy the theme values from the contact list styles Now + Obtain metrics_id Fix all layout issues, theming, buttons, etc Make multi-line editor without line numbers Some sort of feedback when something fails or succeeds From 9fc7f546311ba087e95e575263b05ecadfd44298 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 6 Jan 2023 15:32:28 -0500 Subject: [PATCH 008/108] Add to TODO --- crates/zed/src/feedback_popover.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs index e0721078a0..556fb5922c 100644 --- a/crates/zed/src/feedback_popover.rs +++ b/crates/zed/src/feedback_popover.rs @@ -31,6 +31,7 @@ use crate::{feedback_popover, system_specs::SystemSpecs}; to make the modal look nice. Copy the theme values from the contact list styles Now + Rework all code relying on contacts list (search out "contacts") Obtain metrics_id Fix all layout issues, theming, buttons, etc Make multi-line editor without line numbers From 969477175270ee7b1411a07961ec104ecd86effa Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 6 Jan 2023 15:41:31 -0500 Subject: [PATCH 009/108] Move notes into PR --- crates/zed/src/feedback_popover.rs | 33 ------------------------------ 1 file changed, 33 deletions(-) diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs index 556fb5922c..46e7116e39 100644 --- a/crates/zed/src/feedback_popover.rs +++ b/crates/zed/src/feedback_popover.rs @@ -21,39 +21,6 @@ use workspace::{item::ItemHandle, StatusItemView}; use crate::{feedback_popover, system_specs::SystemSpecs}; -/* - TODO FEEDBACK - - Next steps from Mikayla: - 1: Find the bottom bar height and maybe guess some feedback button widths? - Basically, just use to position the modal - 2: Look at ContactList::render() and ContactPopover::render() for clues on how - to make the modal look nice. Copy the theme values from the contact list styles - - Now - Rework all code relying on contacts list (search out "contacts") - Obtain metrics_id - Fix all layout issues, theming, buttons, etc - Make multi-line editor without line numbers - Some sort of feedback when something fails or succeeds - Naming of all UI stuff and separation out into files (follow a convention already in place) - Disable submit button when text length is 0 - Should we store staff boolean? - Put behind experiments flag - Move to separate crate - Render a character counter - All warnings - Remove all comments - Later - If a character limit is imposed, switch submit button over to a "GitHub Issue" button - Should editor by treated as a markdown file - Limit characters? - Disable submit button when text length is GTE to character limit - - Pay for AirTable - Add AirTable to system architecture diagram in Figma -*/ - lazy_static! { pub static ref ZED_SERVER_URL: String = std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string()); From 0200fc5542f4a835cf67f8eccd00124d5555bd9a Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 6 Jan 2023 17:40:30 -0500 Subject: [PATCH 010/108] WIP Don't rely on contacts popover or contacts list for theming Add metrics id to request body Clean up some code and comments Co-Authored-By: Mikayla Maki --- crates/client/src/client.rs | 4 +++ crates/client/src/telemetry.rs | 4 +++ crates/theme/src/theme.rs | 28 +++++++--------- crates/zed/src/feedback_popover.rs | 54 +++++++++++------------------- crates/zed/src/main.rs | 2 +- styles/src/styleTree/app.ts | 2 ++ styles/src/styleTree/feedback.ts | 35 +++++++++++++++++++ 7 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 styles/src/styleTree/feedback.ts diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index aa46d64fcb..69cdb16116 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1294,6 +1294,10 @@ impl Client { pub fn telemetry_log_file_path(&self) -> Option { self.telemetry.log_file_path() } + + pub fn metrics_id(&self) -> Option> { + self.telemetry.metrics_id() + } } impl WeakSubscriber { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index ce8b713996..4e32e020eb 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -261,6 +261,10 @@ impl Telemetry { } } + pub fn metrics_id(self: &Arc) -> Option> { + self.state.lock().metrics_id.clone() + } + fn flush(self: &Arc) { let mut state = self.state.lock(); let mut events = mem::take(&mut state.queue); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 92dd68ac1b..3b118bbef9 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -25,7 +25,7 @@ pub struct Theme { pub command_palette: CommandPalette, pub picker: Picker, pub editor: Editor, - // pub feedback_box: Editor, + pub feedback: Feedback, pub search: Search, pub project_diagnostics: ProjectDiagnostics, pub breadcrumbs: ContainedText, @@ -120,21 +120,19 @@ pub struct ContactList { pub calling_indicator: ContainedText, } -// TODO FEEDBACK: Remove or use this -// #[derive(Deserialize, Default)] -// pub struct FeedbackPopover { -// #[serde(flatten)] -// pub container: ContainerStyle, -// pub height: f32, -// pub width: f32, -// pub invite_row_height: f32, -// pub invite_row: Interactive, -// } +#[derive(Deserialize, Default)] +pub struct Feedback { + pub feedback_popover: FeedbackPopover, + pub feedback_editor: FieldEditor, +} -// #[derive(Deserialize, Default)] -// pub struct FeedbackBox { -// pub feedback_editor: FieldEditor, -// } +#[derive(Deserialize, Default)] +pub struct FeedbackPopover { + #[serde(flatten)] + pub container: ContainerStyle, + pub height: f32, + pub width: f32, +} #[derive(Deserialize, Default)] pub struct ProjectRow { diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs index 46e7116e39..b11ab1f4ac 100644 --- a/crates/zed/src/feedback_popover.rs +++ b/crates/zed/src/feedback_popover.rs @@ -1,7 +1,7 @@ use std::{ops::Range, sync::Arc}; use anyhow::bail; -use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN}; +use client::{Client, ZED_SECRET_CLIENT_TOKEN}; use editor::Editor; use futures::AsyncReadExt; use gpui::{ @@ -93,19 +93,11 @@ impl View for FeedbackButton { .boxed(), ) .with_children(self.feedback_popover.as_ref().map(|popover| { - Overlay::new( - ChildView::new(popover, cx) - .contained() - // .with_height(theme.contact_list.user_query_editor_height) - // .with_margin_top(-50.0) - // .with_margin_left(titlebar.toggle_contacts_button.default.button_width) - // .with_margin_right(-titlebar.toggle_contacts_button.default.button_width) - .boxed(), - ) - .with_fit_mode(OverlayFitMode::SwitchAnchor) - .with_anchor_corner(AnchorCorner::TopLeft) - .with_z_index(999) - .boxed() + Overlay::new(ChildView::new(popover, cx).contained().boxed()) + .with_fit_mode(OverlayFitMode::SwitchAnchor) + .with_anchor_corner(AnchorCorner::TopLeft) + .with_z_index(999) + .boxed() })) .boxed() } @@ -142,9 +134,7 @@ impl FeedbackPopover { pub fn new(cx: &mut ViewContext) -> Self { let feedback_editor = cx.add_view(|cx| { let editor = Editor::multi_line( - Some(Arc::new(|theme| { - theme.contact_list.user_query_editor.clone() - })), + Some(Arc::new(|theme| theme.feedback.feedback_editor.clone())), cx, ); editor @@ -174,19 +164,20 @@ impl FeedbackPopover { fn submit_feedback(&mut self, _: &SubmitFeedback, cx: &mut ViewContext<'_, Self>) { let feedback_text = self.feedback_editor.read(cx).text(cx); - let http_client = cx.global::>().clone(); + let zed_client = cx.global::>(); let system_specs = SystemSpecs::new(cx); let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL); - cx.spawn(|this, async_cx| { + let metrics_id = zed_client.metrics_id(); + let http_client = zed_client.http_client(); + + cx.spawn(|_, _| { async move { // TODO FEEDBACK: Use or remove // this.read_with(&async_cx, |this, cx| { // // Now we have a &self and a &AppContext // }); - let metrics_id = None; - let request = FeedbackRequestBody { feedback_text: &feedback_text, metrics_id, @@ -240,28 +231,23 @@ impl View for FeedbackPopover { enum SubmitFeedback {} let theme = cx.global::().theme.clone(); - let status_bar_height = theme.workspace.status_bar.height; let submit_feedback_text_button_height = 20.0; - // I'd like to just define: - - // 1. Overall popover width x height dimensions - // 2. Submit Feedback button height dimensions - // 3. Allow editor to dynamically fill in the remaining space - Flex::column() .with_child( Flex::row() .with_child( ChildView::new(self.feedback_editor.clone(), cx) .contained() - .with_style(theme.contact_list.user_query_editor.container) + .with_style(theme.feedback.feedback_editor.container) .flex(1., true) .boxed(), ) .constrained() - .with_width(theme.contacts_popover.width) - .with_height(theme.contacts_popover.height - submit_feedback_text_button_height) + .with_width(theme.feedback.feedback_popover.width) + .with_height( + theme.feedback.feedback_popover.height - submit_feedback_text_button_height, + ) .boxed(), ) .with_child( @@ -286,10 +272,10 @@ impl View for FeedbackPopover { .boxed(), ) .contained() - .with_style(theme.contacts_popover.container) + .with_style(theme.feedback.feedback_popover.container) .constrained() - .with_width(theme.contacts_popover.width + 200.0) - .with_height(theme.contacts_popover.height) + .with_width(theme.feedback.feedback_popover.width) + .with_height(theme.feedback.feedback_popover.height) .boxed() } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 99b4c5a1a3..fe06ca2893 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -108,7 +108,7 @@ fn main() { watch_settings_file(default_settings, settings_file_content, themes.clone(), cx); watch_keymap_file(keymap_file, cx); - cx.set_global(http.clone()); + cx.set_global(client.clone()); feedback_popover::init(cx); context_menu::init(cx); diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index 267d830506..a51426b6f6 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -19,6 +19,7 @@ import terminal from "./terminal"; import contactList from "./contactList"; import incomingCallNotification from "./incomingCallNotification"; import { ColorScheme } from "../themes/common/colorScheme"; +import feedback from "./feedback"; export default function app(colorScheme: ColorScheme): Object { return { @@ -37,6 +38,7 @@ export default function app(colorScheme: ColorScheme): Object { projectDiagnostics: projectDiagnostics(colorScheme), projectPanel: projectPanel(colorScheme), contactsPopover: contactsPopover(colorScheme), + feedback: feedback(colorScheme), contactFinder: contactFinder(colorScheme), contactList: contactList(colorScheme), search: search(colorScheme), diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts new file mode 100644 index 0000000000..0521949871 --- /dev/null +++ b/styles/src/styleTree/feedback.ts @@ -0,0 +1,35 @@ +import { ColorScheme } from "../themes/common/colorScheme"; +import { background, border, text } from "./components"; + +export default function feedback(colorScheme: ColorScheme) { + let layer = colorScheme.middle; + return { + feedbackEditor: { + background: background(layer, "on"), + cornerRadius: 6, + text: text(layer, "mono", "on"), + placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }), + selection: colorScheme.players[0], + border: border(layer, "on"), + padding: { + bottom: 4, + left: 8, + right: 8, + top: 4, + }, + margin: { + left: 6, + } + }, + feedbackPopover: { + background: background(layer), + cornerRadius: 6, + padding: { top: 6 }, + margin: { top: -6 }, + shadow: colorScheme.popoverShadow, + border: border(layer), + width: 500, + height: 400 + } + } +} From 828f406b4fc068febcd20521a03ad2f2a5910c72 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 9 Jan 2023 10:54:13 -0800 Subject: [PATCH 011/108] Fixed issue where serialized terminal working directories would be lost in complex interactions Co-authored-by: Kay Co-authored-by: Julia --- crates/terminal_view/src/persistence.rs | 6 +++--- crates/terminal_view/src/terminal_view.rs | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/terminal_view/src/persistence.rs b/crates/terminal_view/src/persistence.rs index f090b384a4..26bd0931fe 100644 --- a/crates/terminal_view/src/persistence.rs +++ b/crates/terminal_view/src/persistence.rs @@ -42,10 +42,10 @@ impl TerminalDb { } query! { - pub async fn take_working_directory(item_id: ItemId, workspace_id: WorkspaceId) -> Result> { - DELETE FROM terminals + pub fn get_working_directory(item_id: ItemId, workspace_id: WorkspaceId) -> Result> { + SELECT working_directory + FROM terminals WHERE item_id = ? AND workspace_id = ? - RETURNING working_directory } } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index a4f90a8d72..e02a9758dd 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -706,8 +706,7 @@ impl Item for TerminalView { let window_id = cx.window_id(); cx.spawn(|pane, mut cx| async move { let cwd = TERMINAL_DB - .take_working_directory(item_id, workspace_id) - .await + .get_working_directory(item_id, workspace_id) .log_err() .flatten(); From c1e61b479cf87c1c5d0cee55322b1a53da1d564f Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 9 Jan 2023 13:54:37 -0500 Subject: [PATCH 012/108] Move feedback items into a feedback crate --- Cargo.lock | 28 +++++++-- Cargo.toml | 1 + crates/feedback/Cargo.toml | 26 ++++++++ crates/feedback/src/feedback.rs | 62 +++++++++++++++++++ .../{zed => feedback}/src/feedback_popover.rs | 6 ++ crates/{zed => feedback}/src/system_specs.rs | 0 crates/zed/Cargo.toml | 4 +- crates/zed/src/main.rs | 5 +- crates/zed/src/menus.rs | 6 +- crates/zed/src/zed.rs | 48 +------------- 10 files changed, 128 insertions(+), 58 deletions(-) create mode 100644 crates/feedback/Cargo.toml create mode 100644 crates/feedback/src/feedback.rs rename crates/{zed => feedback}/src/feedback_popover.rs (97%) rename crates/{zed => feedback}/src/system_specs.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 14574cc19a..b6bd88e507 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2022,6 +2022,26 @@ dependencies = [ "instant", ] +[[package]] +name = "feedback" +version = "0.1.0" +dependencies = [ + "anyhow", + "client", + "editor", + "futures 0.3.25", + "gpui", + "human_bytes", + "isahc", + "lazy_static", + "serde", + "settings", + "sysinfo", + "urlencoding", + "util", + "workspace", +] + [[package]] name = "file-per-thread-logger" version = "0.1.5" @@ -6239,9 +6259,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.27.1" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccb297c0afb439440834b4bcf02c5c9da8ec2e808e70f36b0d8e815ff403bd24" +checksum = "1620f9573034c573376acc550f3b9a2be96daeb08abb3c12c8523e1cee06e80f" dependencies = [ "cfg-if 1.0.0", "core-foundation-sys", @@ -8212,6 +8232,7 @@ dependencies = [ "easy-parallel", "editor", "env_logger", + "feedback", "file_finder", "fs", "fsevent", @@ -8219,7 +8240,6 @@ dependencies = [ "fuzzy", "go_to_line", "gpui", - "human_bytes", "ignore", "image", "indexmap", @@ -8253,7 +8273,6 @@ dependencies = [ "smallvec", "smol", "sum_tree", - "sysinfo", "tempdir", "terminal_view", "text", @@ -8282,7 +8301,6 @@ dependencies = [ "tree-sitter-typescript", "unindent", "url", - "urlencoding", "util", "vim", "workspace", diff --git a/Cargo.toml b/Cargo.toml index 1ace51dbd5..77469c0623 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "crates/diagnostics", "crates/drag_and_drop", "crates/editor", + "crates/feedback", "crates/file_finder", "crates/fs", "crates/fsevent", diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml new file mode 100644 index 0000000000..afb43f501f --- /dev/null +++ b/crates/feedback/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "feedback" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/feedback.rs" + +[features] +test-support = [] + +[dependencies] +anyhow = "1.0.38" +client = { path = "../client" } +editor = { path = "../editor" } +futures = "0.3" +gpui = { path = "../gpui" } +human_bytes = "0.4.1" +isahc = "1.7" +lazy_static = "1.4.0" +serde = { version = "1.0", features = ["derive", "rc"] } +settings = { path = "../settings" } +sysinfo = "0.27.1" +urlencoding = "2.1.2" +util = { path = "../util" } +workspace = { path = "../workspace" } \ No newline at end of file diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs new file mode 100644 index 0000000000..58cb682f3b --- /dev/null +++ b/crates/feedback/src/feedback.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; + +pub mod feedback_popover; +mod system_specs; +use gpui::{actions, impl_actions, ClipboardItem, ViewContext}; +use serde::Deserialize; +use system_specs::SystemSpecs; +use workspace::Workspace; + +// TODO FEEDBACK: This open brownser code is duplicated from the zed crate, where should we refactor it to? +#[derive(Deserialize, Clone, PartialEq)] +struct OpenBrowser { + url: Arc, +} + +impl_actions!(zed, [OpenBrowser]); + +actions!( + zed, + [CopySystemSpecsIntoClipboard, FileBugReport, RequestFeature,] +); + +pub fn init(cx: &mut gpui::MutableAppContext) { + feedback_popover::init(cx); + + cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); + + cx.add_action( + |_: &mut Workspace, _: &CopySystemSpecsIntoClipboard, cx: &mut ViewContext| { + let system_specs = SystemSpecs::new(cx).to_string(); + let item = ClipboardItem::new(system_specs.clone()); + cx.prompt( + gpui::PromptLevel::Info, + &format!("Copied into clipboard:\n\n{system_specs}"), + &["OK"], + ); + cx.write_to_clipboard(item); + }, + ); + + cx.add_action( + |_: &mut Workspace, _: &RequestFeature, cx: &mut ViewContext| { + let url = "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; + cx.dispatch_action(OpenBrowser { + url: url.into(), + }); + }, + ); + + cx.add_action( + |_: &mut Workspace, _: &FileBugReport, cx: &mut ViewContext| { + let system_specs_text = SystemSpecs::new(cx).to_string(); + let url = format!( + "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", + urlencoding::encode(&system_specs_text) + ); + cx.dispatch_action(OpenBrowser { + url: url.into(), + }); + }, + ); +} diff --git a/crates/zed/src/feedback_popover.rs b/crates/feedback/src/feedback_popover.rs similarity index 97% rename from crates/zed/src/feedback_popover.rs rename to crates/feedback/src/feedback_popover.rs index b11ab1f4ac..b9a9312952 100644 --- a/crates/zed/src/feedback_popover.rs +++ b/crates/feedback/src/feedback_popover.rs @@ -230,6 +230,12 @@ impl View for FeedbackPopover { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { enum SubmitFeedback {} + // I'd like to just define: + + // 1. Overall popover width x height dimensions (done) + // 2. Submit Feedback button height dimensions + // 3. Allow editor to dynamically fill in the remaining space + let theme = cx.global::().theme.clone(); let submit_feedback_text_button_height = 20.0; diff --git a/crates/zed/src/system_specs.rs b/crates/feedback/src/system_specs.rs similarity index 100% rename from crates/zed/src/system_specs.rs rename to crates/feedback/src/system_specs.rs diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d295deea71..b4b4b2475f 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -29,8 +29,8 @@ client = { path = "../client" } clock = { path = "../clock" } diagnostics = { path = "../diagnostics" } editor = { path = "../editor" } +feedback = { path = "../feedback" } file_finder = { path = "../file_finder" } -human_bytes = "0.4.1" search = { path = "../search" } fs = { path = "../fs" } fsevent = { path = "../fsevent" } @@ -49,7 +49,6 @@ recent_projects = { path = "../recent_projects" } rpc = { path = "../rpc" } settings = { path = "../settings" } sum_tree = { path = "../sum_tree" } -sysinfo = "0.27.1" text = { path = "../text" } terminal_view = { path = "../terminal_view" } theme = { path = "../theme" } @@ -110,7 +109,6 @@ tree-sitter-html = "0.19.0" tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"} tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} url = "2.2" -urlencoding = "2.1.2" [dev-dependencies] call = { path = "../call", features = ["test-support"] } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index fe06ca2893..bd13088f7f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -14,6 +14,7 @@ use client::{ http::{self, HttpClient}, UserStore, ZED_SECRET_CLIENT_TOKEN, }; + use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, @@ -41,7 +42,7 @@ use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ self, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, OpenPaths, Workspace, }; -use zed::{self, build_window_options, feedback_popover, initialize_workspace, languages, menus}; +use zed::{self, build_window_options, initialize_workspace, languages, menus}; fn main() { let http = http::client(); @@ -110,12 +111,12 @@ fn main() { cx.set_global(client.clone()); - feedback_popover::init(cx); context_menu::init(cx); project::Project::init(&client); client::init(client.clone(), cx); command_palette::init(cx); editor::init(cx); + feedback::init(cx); go_to_line::init(cx); file_finder::init(cx); outline::init(cx); diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index b46e8ad703..834eb751e1 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -340,15 +340,15 @@ pub fn menus() -> Vec> { MenuItem::Separator, MenuItem::Action { name: "Copy System Specs Into Clipboard", - action: Box::new(crate::CopySystemSpecsIntoClipboard), + action: Box::new(feedback::CopySystemSpecsIntoClipboard), }, MenuItem::Action { name: "File Bug Report", - action: Box::new(crate::FileBugReport), + action: Box::new(feedback::FileBugReport), }, MenuItem::Action { name: "Request Feature", - action: Box::new(crate::RequestFeature), + action: Box::new(feedback::RequestFeature), }, MenuItem::Separator, MenuItem::Action { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 9fda149191..21f5b5d342 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1,10 +1,7 @@ -pub mod feedback_popover; pub mod languages; pub mod menus; -pub mod system_specs; #[cfg(any(test, feature = "test-support"))] pub mod test; - use anyhow::{anyhow, Context, Result}; use assets::Assets; use breadcrumbs::Breadcrumbs; @@ -22,7 +19,7 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, ClipboardItem, TitlebarOptions, ViewContext, WindowKind, + AssetSource, AsyncAppContext, TitlebarOptions, ViewContext, WindowKind, }; use language::Rope; use lazy_static::lazy_static; @@ -34,7 +31,6 @@ use serde::Deserialize; use serde_json::to_string_pretty; use settings::{keymap_file_json_schema, settings_file_json_schema, Settings}; use std::{env, path::Path, str, sync::Arc}; -use system_specs::SystemSpecs; use util::{channel::ReleaseChannel, paths, ResultExt}; pub use workspace; use workspace::{sidebar::SidebarSide, AppState, Workspace}; @@ -69,9 +65,6 @@ actions!( ResetBufferFontSize, InstallCommandLineInterface, ResetDatabase, - CopySystemSpecsIntoClipboard, - RequestFeature, - FileBugReport ] ); @@ -250,41 +243,6 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { }, ); - cx.add_action( - |_: &mut Workspace, _: &CopySystemSpecsIntoClipboard, cx: &mut ViewContext| { - let system_specs = SystemSpecs::new(cx).to_string(); - let item = ClipboardItem::new(system_specs.clone()); - cx.prompt( - gpui::PromptLevel::Info, - &format!("Copied into clipboard:\n\n{system_specs}"), - &["OK"], - ); - cx.write_to_clipboard(item); - }, - ); - - cx.add_action( - |_: &mut Workspace, _: &RequestFeature, cx: &mut ViewContext| { - let url = "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; - cx.dispatch_action(OpenBrowser { - url: url.into(), - }); - }, - ); - - cx.add_action( - |_: &mut Workspace, _: &FileBugReport, cx: &mut ViewContext| { - let system_specs_text = SystemSpecs::new(cx).to_string(); - let url = format!( - "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", - urlencoding::encode(&system_specs_text) - ); - cx.dispatch_action(OpenBrowser { - url: url.into(), - }); - }, - ); - activity_indicator::init(cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); settings::KeymapFileContent::load_defaults(cx); @@ -369,12 +327,12 @@ pub fn initialize_workspace( let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - let feedback_link = cx.add_view(|_| feedback_popover::FeedbackButton::new()); + let feedback_button = cx.add_view(|_| feedback::feedback_popover::FeedbackButton::new()); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); status_bar.add_right_item(cursor_position, cx); - status_bar.add_right_item(feedback_link, cx); + status_bar.add_right_item(feedback_button, cx); }); auto_update::notify_of_any_new_update(cx.weak_handle(), cx); From d237bdaa9b17d4a0f7d6f49ca421c20d892661f4 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 9 Jan 2023 12:41:37 -0800 Subject: [PATCH 013/108] Added support for ALTER TABLE syntax in the syntax error checker function Co-authored-by: Kay --- crates/editor/src/persistence.rs | 9 ++- crates/sqlez/src/connection.rs | 99 +++++++++++++++++++++++++++----- 2 files changed, 93 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index 31ada105af..375bd77ff0 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -15,8 +15,13 @@ define_connection!( FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE ON UPDATE CASCADE - ) STRICT; - )]; + ) STRICT; + ), + sql! ( + ALTER TABLE editors ADD COLUMN scroll_top_row INTEGER; + ALTER TABLE editors ADD COLUMN scroll_vertical_offset REAL; + ALTER TABLE editors ADD COLUMN scroll_horizontal_offset REAL; + )]; ); impl EditorDb { diff --git a/crates/sqlez/src/connection.rs b/crates/sqlez/src/connection.rs index 3c6cb31196..55ba5f8294 100644 --- a/crates/sqlez/src/connection.rs +++ b/crates/sqlez/src/connection.rs @@ -93,36 +93,77 @@ impl Connection { let sql_start = remaining_sql.as_ptr(); unsafe { + let mut alter_table = None; while { let remaining_sql_str = remaining_sql.to_str().unwrap().trim(); - remaining_sql_str != ";" && !remaining_sql_str.is_empty() + let any_remaining_sql = remaining_sql_str != ";" && !remaining_sql_str.is_empty(); + if any_remaining_sql { + alter_table = parse_alter_table(remaining_sql_str); + } + any_remaining_sql } { let mut raw_statement = ptr::null_mut::(); let mut remaining_sql_ptr = ptr::null(); - sqlite3_prepare_v2( - self.sqlite3, - remaining_sql.as_ptr(), - -1, - &mut raw_statement, - &mut remaining_sql_ptr, - ); - let res = sqlite3_errcode(self.sqlite3); - let offset = sqlite3_error_offset(self.sqlite3); - let message = sqlite3_errmsg(self.sqlite3); + let (res, offset, message, _conn) = if let Some(table_to_alter) = alter_table { + // ALTER TABLE is a weird statement. When preparing the statement the table's + // existence is checked *before* syntax checking any other part of the statement. + // Therefore, we need to make sure that the table has been created before calling + // prepare. As we don't want to trash whatever database this is connected to, we + // create a new in-memory DB to test. + + let temp_connection = Connection::open_memory(None); + //This should always succeed, if it doesn't then you really should know about it + temp_connection + .exec(&format!( + "CREATE TABLE {table_to_alter}(__place_holder_column_for_syntax_checking)" + )) + .unwrap()() + .unwrap(); + + sqlite3_prepare_v2( + temp_connection.sqlite3, + remaining_sql.as_ptr(), + -1, + &mut raw_statement, + &mut remaining_sql_ptr, + ); + + ( + sqlite3_errcode(temp_connection.sqlite3), + sqlite3_error_offset(temp_connection.sqlite3), + sqlite3_errmsg(temp_connection.sqlite3), + Some(temp_connection), + ) + } else { + sqlite3_prepare_v2( + self.sqlite3, + remaining_sql.as_ptr(), + -1, + &mut raw_statement, + &mut remaining_sql_ptr, + ); + ( + sqlite3_errcode(self.sqlite3), + sqlite3_error_offset(self.sqlite3), + sqlite3_errmsg(self.sqlite3), + None, + ) + }; sqlite3_finalize(raw_statement); if res == 1 && offset >= 0 { + let sub_statement_correction = + remaining_sql.as_ptr() as usize - sql_start as usize; let err_msg = String::from_utf8_lossy(CStr::from_ptr(message as *const _).to_bytes()) .into_owned(); - let sub_statement_correction = - remaining_sql.as_ptr() as usize - sql_start as usize; return Some((err_msg, offset as usize + sub_statement_correction)); } remaining_sql = CStr::from_ptr(remaining_sql_ptr); + alter_table = None; } } None @@ -162,6 +203,25 @@ impl Connection { } } +fn parse_alter_table(remaining_sql_str: &str) -> Option { + let remaining_sql_str = remaining_sql_str.to_lowercase(); + if remaining_sql_str.starts_with("alter") { + if let Some(table_offset) = remaining_sql_str.find("table") { + let after_table_offset = table_offset + "table".len(); + let table_to_alter = remaining_sql_str + .chars() + .skip(after_table_offset) + .skip_while(|c| c.is_whitespace()) + .take_while(|c| !c.is_whitespace()) + .collect::(); + if !table_to_alter.is_empty() { + return Some(table_to_alter); + } + } + } + None +} + impl Drop for Connection { fn drop(&mut self) { unsafe { sqlite3_close(self.sqlite3) }; @@ -331,4 +391,17 @@ mod test { assert_eq!(res, Some(first_stmt.len() + second_offset + 1)); } + + #[test] + fn test_alter_table_syntax() { + let connection = Connection::open_memory(Some("test_alter_table_syntax")); + + assert!(connection + .sql_has_syntax_error("ALTER TABLE test ADD x TEXT") + .is_none()); + + assert!(connection + .sql_has_syntax_error("ALTER TABLE test AAD x TEXT") + .is_some()); + } } From ebbe6e7aa95da46f6a3db0fb48d9990e868ee35f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 9 Jan 2023 14:06:40 -0800 Subject: [PATCH 014/108] Add serializing and restoring editor scroll position Co-authored-by: Kay --- crates/editor/src/editor.rs | 2 - crates/editor/src/items.rs | 8 ++- crates/editor/src/persistence.rs | 52 ++++++++++++-- crates/editor/src/scroll.rs | 114 +++++++++++++++++++++++++------ crates/sqlez/src/bindable.rs | 35 ++++++++++ 5 files changed, 181 insertions(+), 30 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9feb3f11d0..f11061cb84 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3629,9 +3629,7 @@ impl Editor { } pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext) { - dbg!("undo"); if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) { - dbg!(tx_id); if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() { self.change_selections(None, cx, |s| { s.select_anchors(selections.to_vec()); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index ec678c6180..f8fa3c9df0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -8,6 +8,7 @@ use anyhow::{anyhow, Context, Result}; use collections::HashSet; use futures::future::try_join_all; use futures::FutureExt; + use gpui::{ elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -765,6 +766,7 @@ impl Item for Editor { fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext) { let workspace_id = workspace.database_id(); let item_id = cx.view_id(); + self.workspace_id = Some(workspace_id); fn serialize( buffer: ModelHandle, @@ -836,7 +838,11 @@ impl Item for Editor { .context("Project item at stored path was not a buffer")?; Ok(cx.update(|cx| { - cx.add_view(pane, |cx| Editor::for_buffer(buffer, Some(project), cx)) + cx.add_view(pane, |cx| { + let mut editor = Editor::for_buffer(buffer, Some(project), cx); + editor.read_scroll_position_from_db(item_id, workspace_id, cx); + editor + }) })) }) }) diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index 375bd77ff0..2d8d1a74fd 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -2,9 +2,19 @@ use std::path::PathBuf; use db::sqlez_macros::sql; use db::{define_connection, query}; + use workspace::{ItemId, WorkspaceDb, WorkspaceId}; define_connection!( + // Current table shape using pseudo-rust syntax: + // editors( + // item_id: usize, + // workspace_id: usize, + // path: PathBuf, + // scroll_top_row: usize, + // scroll_vertical_offset: f32, + // scroll_horizontal_offset: f32, + // ) pub static ref DB: EditorDb = &[sql! ( CREATE TABLE editors( @@ -18,9 +28,9 @@ define_connection!( ) STRICT; ), sql! ( - ALTER TABLE editors ADD COLUMN scroll_top_row INTEGER; - ALTER TABLE editors ADD COLUMN scroll_vertical_offset REAL; - ALTER TABLE editors ADD COLUMN scroll_horizontal_offset REAL; + ALTER TABLE editors ADD COLUMN scroll_top_row INTEGER NOT NULL DEFAULT 0; + ALTER TABLE editors ADD COLUMN scroll_horizontal_offset REAL NOT NULL DEFAULT 0; + ALTER TABLE editors ADD COLUMN scroll_vertical_offset REAL NOT NULL DEFAULT 0; )]; ); @@ -34,8 +44,40 @@ impl EditorDb { query! { pub async fn save_path(item_id: ItemId, workspace_id: WorkspaceId, path: PathBuf) -> Result<()> { - INSERT OR REPLACE INTO editors(item_id, workspace_id, path) - VALUES (?, ?, ?) + INSERT INTO editors + (item_id, workspace_id, path) + VALUES + (?1, ?2, ?3) + ON CONFLICT DO UPDATE SET + item_id = ?1, + workspace_id = ?2, + path = ?3 + } + } + + // Returns the scroll top row, and offset + query! { + pub fn get_scroll_position(item_id: ItemId, workspace_id: WorkspaceId) -> Result> { + SELECT scroll_top_row, scroll_horizontal_offset, scroll_vertical_offset + FROM editors + WHERE item_id = ? AND workspace_id = ? + } + } + + query! { + pub async fn save_scroll_position( + item_id: ItemId, + workspace_id: WorkspaceId, + top_row: u32, + vertical_offset: f32, + horizontal_offset: f32 + ) -> Result<()> { + UPDATE OR IGNORE editors + SET + scroll_top_row = ?3, + scroll_horizontal_offset = ?4, + scroll_vertical_offset = ?5 + WHERE item_id = ?1 AND workspace_id = ?2 } } } diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index eb89bfeec2..0ca837a843 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -11,11 +11,14 @@ use gpui::{ geometry::vector::{vec2f, Vector2F}, Axis, MutableAppContext, Task, ViewContext, }; -use language::Bias; +use language::{Bias, Point}; +use util::ResultExt; +use workspace::WorkspaceId; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, + persistence::DB, Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint, }; @@ -170,37 +173,68 @@ impl ScrollManager { scroll_position: Vector2F, map: &DisplaySnapshot, local: bool, + workspace_id: Option, cx: &mut ViewContext, ) { - let new_anchor = if scroll_position.y() <= 0. { - ScrollAnchor { - top_anchor: Anchor::min(), - offset: scroll_position.max(vec2f(0., 0.)), - } + let (new_anchor, top_row) = if scroll_position.y() <= 0. { + ( + ScrollAnchor { + top_anchor: Anchor::min(), + offset: scroll_position.max(vec2f(0., 0.)), + }, + 0, + ) } else { - let scroll_top_buffer_offset = - DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right); + let scroll_top_buffer_point = + DisplayPoint::new(scroll_position.y() as u32, 0).to_point(&map); let top_anchor = map .buffer_snapshot - .anchor_at(scroll_top_buffer_offset, Bias::Right); + .anchor_at(scroll_top_buffer_point, Bias::Right); - ScrollAnchor { - top_anchor, - offset: vec2f( - scroll_position.x(), - scroll_position.y() - top_anchor.to_display_point(&map).row() as f32, - ), - } + ( + ScrollAnchor { + top_anchor, + offset: vec2f( + scroll_position.x(), + scroll_position.y() - top_anchor.to_display_point(&map).row() as f32, + ), + }, + scroll_top_buffer_point.row, + ) }; - self.set_anchor(new_anchor, local, cx); + self.set_anchor(new_anchor, top_row, local, workspace_id, cx); } - fn set_anchor(&mut self, anchor: ScrollAnchor, local: bool, cx: &mut ViewContext) { + fn set_anchor( + &mut self, + anchor: ScrollAnchor, + top_row: u32, + local: bool, + workspace_id: Option, + cx: &mut ViewContext, + ) { self.anchor = anchor; cx.emit(Event::ScrollPositionChanged { local }); self.show_scrollbar(cx); self.autoscroll_request.take(); + if let Some(workspace_id) = workspace_id { + let item_id = cx.view_id(); + + cx.background() + .spawn(async move { + DB.save_scroll_position( + item_id, + workspace_id, + top_row, + anchor.offset.x(), + anchor.offset.y(), + ) + .await + .log_err() + }) + .detach() + } cx.notify(); } @@ -274,8 +308,13 @@ impl Editor { let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); hide_hover(self, cx); - self.scroll_manager - .set_scroll_position(scroll_position, &map, local, cx); + self.scroll_manager.set_scroll_position( + scroll_position, + &map, + local, + self.workspace_id, + cx, + ); } pub fn scroll_position(&self, cx: &mut ViewContext) -> Vector2F { @@ -285,7 +324,12 @@ impl Editor { pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext) { hide_hover(self, cx); - self.scroll_manager.set_anchor(scroll_anchor, true, cx); + let top_row = scroll_anchor + .top_anchor + .to_point(&self.buffer().read(cx).snapshot(cx)) + .row; + self.scroll_manager + .set_anchor(scroll_anchor, top_row, true, self.workspace_id, cx); } pub(crate) fn set_scroll_anchor_remote( @@ -294,7 +338,12 @@ impl Editor { cx: &mut ViewContext, ) { hide_hover(self, cx); - self.scroll_manager.set_anchor(scroll_anchor, false, cx); + let top_row = scroll_anchor + .top_anchor + .to_point(&self.buffer().read(cx).snapshot(cx)) + .row; + self.scroll_manager + .set_anchor(scroll_anchor, top_row, false, self.workspace_id, cx); } pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext) { @@ -345,4 +394,25 @@ impl Editor { Ordering::Greater } + + pub fn read_scroll_position_from_db( + &mut self, + item_id: usize, + workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) { + let scroll_position = DB.get_scroll_position(item_id, workspace_id); + if let Ok(Some((top_row, x, y))) = scroll_position { + let top_anchor = self + .buffer() + .read(cx) + .snapshot(cx) + .anchor_at(Point::new(top_row as u32, 0), Bias::Left); + let scroll_anchor = ScrollAnchor { + offset: Vector2F::new(x, y), + top_anchor, + }; + self.set_scroll_anchor(scroll_anchor, cx); + } + } } diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 3649037e50..62212d8f18 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -89,6 +89,26 @@ impl Column for f64 { } } +impl Bind for f32 { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + statement + .bind_double(start_index, *self as f64) + .with_context(|| format!("Failed to bind f64 at index {start_index}"))?; + Ok(start_index + 1) + } +} + +impl Column for f32 { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let result = statement + .column_double(start_index) + .with_context(|| format!("Failed to parse f32 at index {start_index}"))? + as f32; + + Ok((result, start_index + 1)) + } +} + impl Bind for i32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -122,6 +142,21 @@ impl Column for i64 { } } +impl Bind for u32 { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + (*self as i64) + .bind(statement, start_index) + .with_context(|| format!("Failed to bind usize at index {start_index}")) + } +} + +impl Column for u32 { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let result = statement.column_int64(start_index)?; + Ok((result as u32, start_index + 1)) + } +} + impl Bind for usize { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) From 176738d674d177d44ff0d71dfaa4ae78e4217d89 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Mon, 9 Jan 2023 16:18:04 -0800 Subject: [PATCH 015/108] Address issue with workspaces where single file worktrees such as those from git commit messages would get restored Co-authored-by: Mikayla --- crates/workspace/src/persistence.rs | 19 ++++++++++--------- crates/workspace/src/workspace.rs | 4 ++-- crates/zed/src/main.rs | 22 ++++++++++++++-------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index cd425c3a28..03a866f2f6 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -216,7 +216,9 @@ impl WorkspaceDb { let mut result = Vec::new(); let mut delete_tasks = Vec::new(); for (id, location) in self.recent_workspaces()? { - if location.paths().iter().all(|path| path.exists()) { + if location.paths().iter().all(|path| path.exists()) + && location.paths().iter().any(|path| path.is_dir()) + { result.push((id, location)); } else { delete_tasks.push(self.delete_stale_workspace(id)); @@ -227,14 +229,13 @@ impl WorkspaceDb { Ok(result) } - query! { - pub fn last_workspace() -> Result> { - SELECT workspace_location - FROM workspaces - WHERE workspace_location IS NOT NULL - ORDER BY timestamp DESC - LIMIT 1 - } + pub async fn last_workspace(&self) -> Result> { + Ok(self + .recent_workspaces_on_disk() + .await? + .into_iter() + .next() + .map(|(_, location)| location)) } fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8bf4cbce32..ac6a9f38ed 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2680,8 +2680,8 @@ pub fn activate_workspace_for_project( None } -pub fn last_opened_workspace_paths() -> Option { - DB.last_workspace().log_err().flatten() +pub async fn last_opened_workspace_paths() -> Option { + DB.last_workspace().await.log_err().flatten() } #[allow(clippy::type_complexity)] diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f4fa919c48..3861c53e79 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -170,7 +170,8 @@ fn main() { cx.platform().activate(true); let paths = collect_path_args(); if paths.is_empty() { - restore_or_create_workspace(cx); + cx.spawn(|cx| async move { restore_or_create_workspace(cx).await }) + .detach() } else { cx.dispatch_global_action(OpenPaths { paths }); } @@ -179,7 +180,8 @@ fn main() { cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) .detach(); } else { - restore_or_create_workspace(cx); + cx.spawn(|cx| async move { restore_or_create_workspace(cx).await }) + .detach() } cx.spawn(|cx| async move { while let Some(connection) = cli_connections_rx.next().await { @@ -203,13 +205,17 @@ fn main() { }); } -fn restore_or_create_workspace(cx: &mut gpui::MutableAppContext) { - if let Some(location) = workspace::last_opened_workspace_paths() { - cx.dispatch_global_action(OpenPaths { - paths: location.paths().as_ref().clone(), - }) +async fn restore_or_create_workspace(mut cx: AsyncAppContext) { + if let Some(location) = workspace::last_opened_workspace_paths().await { + cx.update(|cx| { + cx.dispatch_global_action(OpenPaths { + paths: location.paths().as_ref().clone(), + }) + }); } else { - cx.dispatch_global_action(NewFile); + cx.update(|cx| { + cx.dispatch_global_action(NewFile); + }); } } From 866f0e1344f1818aec4186a54b316b4bdfd1dc0a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 15:06:48 -0800 Subject: [PATCH 016/108] Add the ability to opt-out of panic reporting Co-authored-by: Kay --- assets/settings/default.json | 7 ++ crates/settings/src/settings.rs | 32 ++++++- crates/zed/src/main.rs | 152 +++++++++++++++++--------------- 3 files changed, 119 insertions(+), 72 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 5b73d7643c..3924e84d61 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -79,6 +79,13 @@ "hard_tabs": false, // How many columns a tab should occupy. "tab_size": 4, + // Control what info Zed sends to our servers + "telemetry": { + // Send debug info like crash reports. + "diagnostics": true, + // Send anonymized usage data like what languages you're using Zed with. + "metrics": true + }, // Git gutter behavior configuration. "git": { // Control whether the git gutter is shown. May take 2 values: diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index f0c64a1bb9..dd2be6e318 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -51,9 +51,17 @@ pub struct Settings { pub language_overrides: HashMap, EditorSettings>, pub lsp: HashMap, LspSettings>, pub theme: Arc, + pub telemetry_defaults: TelemetrySettings, + pub telemetry_overrides: TelemetrySettings, pub staff_mode: bool, } +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct TelemetrySettings { + diagnostics: Option, + metrics: Option, +} + #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct FeatureFlags { pub experimental_themes: bool, @@ -302,6 +310,8 @@ pub struct SettingsFileContent { #[serde(default)] pub theme: Option, #[serde(default)] + pub telemetry: TelemetrySettings, + #[serde(default)] pub staff_mode: Option, } @@ -312,6 +322,7 @@ pub struct LspSettings { } impl Settings { + /// Fill out the settings corresponding to the default.json file, overrides will be set later pub fn defaults( assets: impl AssetSource, font_cache: &FontCache, @@ -363,11 +374,13 @@ impl Settings { language_overrides: Default::default(), lsp: defaults.lsp.clone(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), - + telemetry_defaults: defaults.telemetry, + telemetry_overrides: Default::default(), staff_mode: false, } } + // Fill out the overrride and etc. settings from the user's settings.json pub fn set_user_settings( &mut self, data: SettingsFileContent, @@ -419,6 +432,7 @@ impl Settings { self.terminal_overrides.copy_on_select = data.terminal.copy_on_select; self.terminal_overrides = data.terminal; self.language_overrides = data.languages; + self.telemetry_overrides = data.telemetry; self.lsp = data.lsp; } @@ -489,6 +503,20 @@ impl Settings { .unwrap_or_else(|| R::default()) } + pub fn telemetry_diagnostics(&self) -> bool { + self.telemetry_overrides + .diagnostics + .or(self.telemetry_defaults.diagnostics) + .expect("missing default") + } + + pub fn telemetry_metrics(&self) -> bool { + self.telemetry_overrides + .metrics + .or(self.telemetry_defaults.metrics) + .expect("missing default") + } + pub fn terminal_scroll(&self) -> AlternateScroll { self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.as_ref()) } @@ -540,6 +568,8 @@ impl Settings { lsp: Default::default(), projects_online_by_default: true, theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), + telemetry_defaults: Default::default(), + telemetry_overrides: Default::default(), staff_mode: false, } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 3861c53e79..72115022d7 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -18,7 +18,7 @@ use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, }; -use gpui::{executor::Background, App, AssetSource, AsyncAppContext, Task, ViewContext}; +use gpui::{App, AssetSource, AsyncAppContext, MutableAppContext, Task, ViewContext}; use isahc::{config::Configurable, Request}; use language::LanguageRegistry; use log::LevelFilter; @@ -50,10 +50,13 @@ fn main() { log::info!("========== starting zed =========="); let mut app = gpui::App::new(Assets).unwrap(); + let app_version = ZED_APP_VERSION .or_else(|| app.platform().app_version().ok()) .map_or("dev".to_string(), |v| v.to_string()); - init_panic_hook(app_version, http.clone(), app.background()); + init_panic_hook(app_version); + + app.background(); load_embedded_fonts(&app); @@ -61,7 +64,6 @@ fn main() { let themes = ThemeRegistry::new(Assets, app.font_cache()); let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes); - let config_files = load_config_files(&app, fs.clone()); let login_shell_env_loaded = if stdout_is_a_pty() { @@ -88,6 +90,18 @@ fn main() { cx.set_global(*RELEASE_CHANNEL); cx.set_global(HomeDir(paths::HOME.to_path_buf())); + let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap(); + + //Setup settings global before binding actions + cx.set_global(SettingsFile::new( + &paths::SETTINGS, + settings_file_content.clone(), + fs.clone(), + )); + + watch_settings_file(default_settings, settings_file_content, themes.clone(), cx); + upload_previous_panics(http.clone(), cx); + let client = client::Client::new(http.clone(), cx); let mut languages = LanguageRegistry::new(login_shell_env_loaded); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); @@ -97,15 +111,6 @@ fn main() { .spawn(languages::init(languages.clone(), cx.background().clone())); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); - let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap(); - - //Setup settings global before binding actions - cx.set_global(SettingsFile::new( - &paths::SETTINGS, - settings_file_content.clone(), - fs.clone(), - )); - watch_settings_file(default_settings, settings_file_content, themes.clone(), cx); watch_keymap_file(keymap_file, cx); context_menu::init(cx); @@ -251,65 +256,7 @@ fn init_logger() { } } -fn init_panic_hook(app_version: String, http: Arc, background: Arc) { - background - .spawn({ - async move { - let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); - let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; - while let Some(child) = children.next().await { - let child = child?; - let child_path = child.path(); - if child_path.extension() != Some(OsStr::new("panic")) { - continue; - } - let filename = if let Some(filename) = child_path.file_name() { - filename.to_string_lossy() - } else { - continue; - }; - - let mut components = filename.split('-'); - if components.next() != Some("zed") { - continue; - } - let version = if let Some(version) = components.next() { - version - } else { - continue; - }; - - let text = smol::fs::read_to_string(&child_path) - .await - .context("error reading panic file")?; - let body = serde_json::to_string(&json!({ - "text": text, - "version": version, - "token": ZED_SECRET_CLIENT_TOKEN, - })) - .unwrap(); - let request = Request::post(&panic_report_url) - .redirect_policy(isahc::config::RedirectPolicy::Follow) - .header("Content-Type", "application/json") - .body(body.into())?; - let response = http.send(request).await.context("error sending panic")?; - if response.status().is_success() { - std::fs::remove_file(child_path) - .context("error removing panic after sending it successfully") - .log_err(); - } else { - return Err(anyhow!( - "error uploading panic to server: {}", - response.status() - )); - } - } - Ok::<_, anyhow::Error>(()) - } - .log_err() - }) - .detach(); - +fn init_panic_hook(app_version: String) { let is_pty = stdout_is_a_pty(); panic::set_hook(Box::new(move |info| { let backtrace = Backtrace::new(); @@ -358,6 +305,69 @@ fn init_panic_hook(app_version: String, http: Arc, background: A })); } +fn upload_previous_panics(http: Arc, cx: &mut MutableAppContext) { + let diagnostics_telemetry = cx.global::().telemetry_diagnostics(); + + cx.background() + .spawn({ + async move { + let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); + let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; + while let Some(child) = children.next().await { + let child = child?; + let child_path = child.path(); + + if child_path.extension() != Some(OsStr::new("panic")) { + continue; + } + let filename = if let Some(filename) = child_path.file_name() { + filename.to_string_lossy() + } else { + continue; + }; + + let mut components = filename.split('-'); + if components.next() != Some("zed") { + continue; + } + let version = if let Some(version) = components.next() { + version + } else { + continue; + }; + + if diagnostics_telemetry { + let text = smol::fs::read_to_string(&child_path) + .await + .context("error reading panic file")?; + let body = serde_json::to_string(&json!({ + "text": text, + "version": version, + "token": ZED_SECRET_CLIENT_TOKEN, + })) + .unwrap(); + let request = Request::post(&panic_report_url) + .redirect_policy(isahc::config::RedirectPolicy::Follow) + .header("Content-Type", "application/json") + .body(body.into())?; + let response = http.send(request).await.context("error sending panic")?; + if !response.status().is_success() { + log::error!("Error uploading panic to server: {}", response.status()); + } + } + + // We've done what we can, delete the file + std::fs::remove_file(child_path) + .context("error removing panic") + .log_err(); + } + Ok::<_, anyhow::Error>(()) + } + .log_err() + }) + .detach(); +} + async fn load_login_shell_environment() -> Result<()> { let marker = "ZED_LOGIN_SHELL_START"; let shell = env::var("SHELL").context( From 551dc1f3187daea5e2b2ee4b581d4e9135730934 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Jan 2023 15:32:14 -0800 Subject: [PATCH 017/108] Fix crash when activating prev/next pane while dock is active Co-authored-by: Antonio Scandurra --- crates/workspace/src/dock.rs | 14 ++++++++++++++ crates/workspace/src/workspace.rs | 31 ++++++++++++------------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 19fed4bf59..ecc8d3f7b3 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -623,6 +623,20 @@ mod tests { cx.assert_dock_pane_active(); } + #[gpui::test] + async fn test_activate_next_and_prev_pane(cx: &mut TestAppContext) { + let mut cx = DockTestContext::new(cx).await; + + cx.move_dock(DockAnchor::Right); + cx.assert_dock_pane_active(); + + cx.update_workspace(|workspace, cx| workspace.activate_next_pane(cx)); + cx.assert_dock_pane_active(); + + cx.update_workspace(|workspace, cx| workspace.activate_previous_pane(cx)); + cx.assert_dock_pane_active(); + } + struct DockTestContext<'a> { pub cx: &'a mut TestAppContext, pub window_id: usize, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ac6a9f38ed..027b9ba86a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -44,6 +44,7 @@ use language::LanguageRegistry; use std::{ any::TypeId, borrow::Cow, + cmp, future::Future, path::{Path, PathBuf}, sync::Arc, @@ -1415,29 +1416,21 @@ impl Workspace { } pub fn activate_next_pane(&mut self, cx: &mut ViewContext) { - let next_pane = { - let panes = self.center.panes(); - let ix = panes - .iter() - .position(|pane| **pane == self.active_pane) - .unwrap(); + let panes = self.center.panes(); + if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { let next_ix = (ix + 1) % panes.len(); - panes[next_ix].clone() - }; - cx.focus(next_pane); + let next_pane = panes[next_ix].clone(); + cx.focus(next_pane); + } } pub fn activate_previous_pane(&mut self, cx: &mut ViewContext) { - let prev_pane = { - let panes = self.center.panes(); - let ix = panes - .iter() - .position(|pane| **pane == self.active_pane) - .unwrap(); - let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 }; - panes[prev_ix].clone() - }; - cx.focus(prev_pane); + let panes = self.center.panes(); + if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { + let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1); + let prev_pane = panes[prev_ix].clone(); + cx.focus(prev_pane); + } } fn handle_pane_focused(&mut self, pane: ViewHandle, cx: &mut ViewContext) { From 37a4de1a848ab9e2b0e3e0f8174b49f5454a5371 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 15:49:54 -0800 Subject: [PATCH 018/108] Add opt-out for metric reporting co-authored-by: kay --- crates/client/src/client.rs | 31 ++++++++++++++++++++++++++----- crates/client/src/telemetry.rs | 19 ++++++++++++++++++- crates/client/src/user.rs | 14 ++++++-------- crates/editor/src/editor.rs | 9 +++++---- crates/settings/src/settings.rs | 16 ++++++++++++++++ crates/zed/src/main.rs | 6 +++++- 6 files changed, 76 insertions(+), 19 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index aa46d64fcb..213b2cf042 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -25,6 +25,7 @@ use postage::watch; use rand::prelude::*; use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage}; use serde::Deserialize; +use settings::{Settings, TelemetrySettings}; use std::{ any::TypeId, collections::HashMap, @@ -423,7 +424,9 @@ impl Client { })); } Status::SignedOut | Status::UpgradeRequired => { - self.telemetry.set_authenticated_user_info(None, false); + let telemetry_settings = cx.read(|cx| cx.global::().telemetry()); + self.telemetry + .set_authenticated_user_info(None, false, telemetry_settings); state._reconnect_task.take(); } _ => {} @@ -706,7 +709,13 @@ impl Client { credentials = read_credentials_from_keychain(cx); read_from_keychain = credentials.is_some(); if read_from_keychain { - self.report_event("read credentials from keychain", Default::default()); + cx.read(|cx| { + self.report_event( + "read credentials from keychain", + Default::default(), + cx.global::().telemetry(), + ); + }); } } if credentials.is_none() { @@ -997,6 +1006,8 @@ impl Client { let executor = cx.background(); let telemetry = self.telemetry.clone(); let http = self.http.clone(); + let metrics_enabled = cx.read(|cx| cx.global::().telemetry()); + executor.clone().spawn(async move { // Generate a pair of asymmetric encryption keys. The public key will be used by the // zed server to encrypt the user's access token, so that it can'be intercepted by @@ -1079,7 +1090,11 @@ impl Client { .context("failed to decrypt access token")?; platform.activate(true); - telemetry.report_event("authenticate with browser", Default::default()); + telemetry.report_event( + "authenticate with browser", + Default::default(), + metrics_enabled, + ); Ok(Credentials { user_id: user_id.parse()?, @@ -1287,8 +1302,14 @@ impl Client { self.telemetry.start(); } - pub fn report_event(&self, kind: &str, properties: Value) { - self.telemetry.report_event(kind, properties.clone()); + pub fn report_event( + &self, + kind: &str, + properties: Value, + telemetry_settings: TelemetrySettings, + ) { + self.telemetry + .report_event(kind, properties.clone(), telemetry_settings); } pub fn telemetry_log_file_path(&self) -> Option { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index ce8b713996..e5d2dad41e 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -10,6 +10,7 @@ use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; use serde_json::json; +use settings::TelemetrySettings; use std::{ io::Write, mem, @@ -184,11 +185,18 @@ impl Telemetry { .detach(); } + /// This method takes the entire TelemetrySettings struct in order to force client code + /// to pull the struct out of the settings global. Do not remove! pub fn set_authenticated_user_info( self: &Arc, metrics_id: Option, is_staff: bool, + telemetry_settings: TelemetrySettings, ) { + if !telemetry_settings.metrics() { + return; + } + let this = self.clone(); let mut state = self.state.lock(); let device_id = state.device_id.clone(); @@ -221,7 +229,16 @@ impl Telemetry { } } - pub fn report_event(self: &Arc, kind: &str, properties: Value) { + pub fn report_event( + self: &Arc, + kind: &str, + properties: Value, + telemetry_settings: TelemetrySettings, + ) { + if !telemetry_settings.metrics() { + return; + } + let mut state = self.state.lock(); let event = MixpanelEvent { event: kind.to_string(), diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 4d29669c2f..1201665571 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -5,6 +5,7 @@ use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt}; use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task}; use postage::{sink::Sink, watch}; use rpc::proto::{RequestMessage, UsersResponse}; +use settings::Settings; use std::sync::{Arc, Weak}; use util::TryFutureExt as _; @@ -141,14 +142,11 @@ impl UserStore { let fetch_metrics_id = client.request(proto::GetPrivateUserInfo {}).log_err(); let (user, info) = futures::join!(fetch_user, fetch_metrics_id); - if let Some(info) = info { - client.telemetry.set_authenticated_user_info( - Some(info.metrics_id.clone()), - info.staff, - ); - } else { - client.telemetry.set_authenticated_user_info(None, false); - } + client.telemetry.set_authenticated_user_info( + info.as_ref().map(|info| info.metrics_id.clone()), + info.as_ref().map(|info| info.staff).unwrap_or(false), + cx.read(|cx| cx.global::().telemetry()), + ); current_user_tx.send(user).await.ok(); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f11061cb84..8847c01af7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6087,10 +6087,11 @@ impl Editor { let extension = Path::new(file.file_name(cx)) .extension() .and_then(|e| e.to_str()); - project - .read(cx) - .client() - .report_event(name, json!({ "File Extension": extension })); + project.read(cx).client().report_event( + name, + json!({ "File Extension": extension }), + cx.global::().telemetry(), + ); } } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index dd2be6e318..e3e8516508 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -62,6 +62,15 @@ pub struct TelemetrySettings { metrics: Option, } +impl TelemetrySettings { + pub fn metrics(&self) -> bool { + self.metrics.unwrap() + } + pub fn diagnostics(&self) -> bool { + self.diagnostics.unwrap() + } +} + #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct FeatureFlags { pub experimental_themes: bool, @@ -503,6 +512,13 @@ impl Settings { .unwrap_or_else(|| R::default()) } + pub fn telemetry(&self) -> TelemetrySettings { + TelemetrySettings { + diagnostics: Some(self.telemetry_diagnostics()), + metrics: Some(self.telemetry_metrics()), + } + } + pub fn telemetry_diagnostics(&self) -> bool { self.telemetry_overrides .diagnostics diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 72115022d7..bf86bb9652 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -148,7 +148,11 @@ fn main() { .detach(); client.start_telemetry(); - client.report_event("start app", Default::default()); + client.report_event( + "start app", + Default::default(), + cx.global::().telemetry(), + ); let app_state = Arc::new(AppState { languages, From 41ff42ddec3936306d7bdfc3e11310e7a55e0130 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Jan 2023 16:27:13 -0800 Subject: [PATCH 019/108] Fix crash when restarting a language server after it reports an unknown buffer version Co-authored-by: Antonio Scandurra --- crates/project/src/project.rs | 33 +++++++++---------- crates/project/src/project_tests.rs | 49 +++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 608abc5815..478271118c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2081,6 +2081,7 @@ impl Project { .buffer_snapshots .entry(buffer.remote_id()) .or_insert_with(|| vec![(0, buffer.text_snapshot())]); + let (version, initial_snapshot) = versions.last().unwrap(); let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); language_server @@ -2617,6 +2618,7 @@ impl Project { worktree_id: worktree.read(cx).id(), path: relative_path.into(), }; + if let Some(buffer) = self.get_open_buffer(&project_path, cx) { self.update_buffer_diagnostics(&buffer, diagnostics.clone(), version, cx)?; } @@ -6124,25 +6126,20 @@ impl Project { .buffer_snapshots .get_mut(&buffer_id) .ok_or_else(|| anyhow!("no snapshot found for buffer {}", buffer_id))?; - let mut found_snapshot = None; - snapshots.retain(|(snapshot_version, snapshot)| { - if snapshot_version + OLD_VERSIONS_TO_RETAIN < version { - false - } else { - if *snapshot_version == version { - found_snapshot = Some(snapshot.clone()); - } - true - } + let found_snapshot = snapshots + .binary_search_by_key(&version, |e| e.0) + .map(|ix| snapshots[ix].1.clone()) + .map_err(|_| { + anyhow!( + "snapshot not found for buffer {} at version {}", + buffer_id, + version + ) + })?; + snapshots.retain(|(snapshot_version, _)| { + snapshot_version + OLD_VERSIONS_TO_RETAIN >= version }); - - found_snapshot.ok_or_else(|| { - anyhow!( - "snapshot not found for buffer {} at version {}", - buffer_id, - version - ) - }) + Ok(found_snapshot) } else { Ok((buffer.read(cx)).text_snapshot()) } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index a36831857f..c9e159f391 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -806,6 +806,55 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC }); } +#[gpui::test] +async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let mut language = Language::new( + LanguageConfig { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-lsp", + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.background()); + fs.insert_tree("/dir", json!({ "a.rs": "" })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // Before restarting the server, report diagnostics with an unknown buffer version. + let fake_server = fake_servers.next().await.unwrap(); + fake_server.notify::(lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), + version: Some(10000), + diagnostics: Vec::new(), + }); + cx.foreground().run_until_parked(); + + project.update(cx, |project, cx| { + project.restart_language_servers_for_buffers([buffer.clone()], cx); + }); + let mut fake_server = fake_servers.next().await.unwrap(); + let notification = fake_server + .receive_notification::() + .await + .text_document; + assert_eq!(notification.version, 0); +} + #[gpui::test] async fn test_toggling_enable_language_server( deterministic: Arc, From a3da41bfad3a6df94d1a93f37cfb120193bce5f0 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 16:38:07 -0800 Subject: [PATCH 020/108] Fix test failures due to dependency on Settings global in client for telemetry co-authored-by: kay --- crates/collab/src/tests/randomized_integration_tests.rs | 5 +++++ crates/settings/src/settings.rs | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index b067cac5ff..11c62005c2 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -18,6 +18,7 @@ use rand::{ distributions::{Alphanumeric, DistString}, prelude::*, }; +use settings::Settings; use std::{env, ffi::OsStr, path::PathBuf, sync::Arc}; #[gpui::test(iterations = 100)] @@ -104,6 +105,8 @@ async fn test_random_collaboration( cx.function_name.clone(), ); + client_cx.update(|cx| cx.set_global(Settings::test(cx))); + let op_start_signal = futures::channel::mpsc::unbounded(); let client = server.create_client(&mut client_cx, &username).await; user_ids.push(client.current_user_id(&client_cx)); @@ -173,6 +176,7 @@ async fn test_random_collaboration( available_users.push((removed_user_id, client.username.clone())); client_cx.update(|cx| { cx.clear_globals(); + cx.set_global(Settings::test(cx)); drop(client); }); @@ -401,6 +405,7 @@ async fn test_random_collaboration( for (client, mut cx) in clients { cx.update(|cx| { cx.clear_globals(); + cx.set_global(Settings::test(cx)); drop(client); }); } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index e3e8516508..e29a98e775 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -584,7 +584,10 @@ impl Settings { lsp: Default::default(), projects_online_by_default: true, theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), - telemetry_defaults: Default::default(), + telemetry_defaults: TelemetrySettings { + diagnostics: Some(true), + metrics: Some(true), + }, telemetry_overrides: Default::default(), staff_mode: false, } From bfb43c67f84c99c2a40ab7f6e35b8a8bb449306c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 16:50:54 -0800 Subject: [PATCH 021/108] Silence spurious log error co-authored-by: Kay --- crates/workspace/src/dock.rs | 6 +++--- crates/workspace/src/item.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 19fed4bf59..d830f34744 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -470,7 +470,7 @@ mod tests { use super::*; use crate::{ dock, - item::test::TestItem, + item::{self, test::TestItem}, persistence::model::{ SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, }, @@ -492,7 +492,7 @@ mod tests { Settings::test_async(cx); cx.update(|cx| { - register_deserializable_item::(cx); + register_deserializable_item::(cx); }); let serialized_workspace = SerializedWorkspace { @@ -508,7 +508,7 @@ mod tests { children: vec![SerializedItem { active: true, item_id: 0, - kind: "test".into(), + kind: "TestItem".into(), }], }, left_sidebar_open: false, diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 0a8311fd5c..19385038e4 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -919,7 +919,7 @@ pub(crate) mod test { } fn serialized_item_kind() -> Option<&'static str> { - None + Some("TestItem") } fn deserialize( From db831c3fbb8a2fdc2d25ce23f5f7696bcdf6cf07 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Jan 2023 17:38:34 -0800 Subject: [PATCH 022/108] Remove roadmap from readme --- README.md | 53 ----------------------------------------------------- 1 file changed, 53 deletions(-) diff --git a/README.md b/README.md index 9153d12bbb..24614e97c2 100644 --- a/README.md +++ b/README.md @@ -83,56 +83,3 @@ rustup target add wasm32-wasi ``` Plugins can be found in the `plugins` folder in the root. For more information about how plugins work, check the [Plugin Guide](./crates/plugin_runtime/README.md) in `crates/plugin_runtime/README.md`. - -## Roadmap - -We will organize our efforts around the following major milestones. We'll create tracking issues for each of these milestones to detail the individual tasks that comprise them. - -### Minimal text editor - -[Tracking issue](https://github.com/zed-industries/zed/issues/2) - -Ship a minimal text editor to investors and other insiders. It should be extremely fast and stable, but all it can do is open, edit, and save text files, making it potentially useful for basic editing but not for real coding. - -Establish basic infrastructure for building the app bundle and uploading an artifact. Once this is released, we should regularly distribute updates as features land. - -### Collaborative code editor for internal use - -[Tracking issue](https://github.com/zed-industries/zed/issues/6) - -Turn the minimal text editor into a collaborative _code_ editor. This will include the minimal features that the Zed team needs to collaborate in Zed to build Zed without net loss in developer productivity. This includes productivity-critical features such as: - -- Syntax highlighting and syntax-aware editing and navigation -- The ability to see and edit non-local working copies of a repository -- Language server support for Rust code navigation, refactoring, diagnostics, etc. -- Project browsing and project-wide search and replace - -We want to tackle collaboration fairly early so that the rest of the design of the product can flow around that assumption. We could probably produce a single-player code editor more quickly, but at the risk of having collaboration feel more "bolted on" when we eventually add it. - -### Private alpha for Rust teams on macOS - -The "minimal" milestones were about getting Zed to a point where the Zed team could use Zed productively to build Zed. What features are required for someone outside the company to use Zed to productively work on another project that is also written in Rust? - -This includes infrastructure like auto-updates, error reporting, and metrics collection. It also includes some amount of polish to make the tool more discoverable for someone that didn't write it, such as a UI for updating settings and key bindings. We may also need to enhance the server to support user authentication and related concerns. - -The initial target audience is like us. A small team working in Rust that's potentially interested in collaborating. As the alpha proceeds, we can work with teams of different sizes. - -### Private beta for Rust teams on macOS - -Once we're getting sufficiently positive feedback from our initial alpha users, we widen the audience by letting people share invites. Now may be a good time to get Zed running on the web, so that it's extremely easy for a Zed user to share a link and be collaborating in seconds. Once someone is using Zed on the Web, we'll let them register for the private beta and download the native binary if they're on macOS. - -### Expand to other languages - -Depending on how the Rust beta is going, focus hard on dominating another niche language such as Elixr or getting a foothold within a niche of a larger language, such as React/Typescript. Alternatively, go wide at this point and add decent support several widely-used languages such as Python, Ruby, Typescript, etc. This would entail taking 1-2 weeks per language and making sure we ship a solid experience based on a publicly-available language server. Each language has slightly different development practices, so we need to make sure Zed's UX meshes well with those practices. - -### Future directions - -Each of these sections could probably broken into multiple milestones, but this part of the roadmap is too far in the future to go into that level of detail at this point. - -#### Expand to other platforms - -Support Linux and Windows. We'll probably want to hire at least one person that prefers to work on each respective platform and have them spearhead the effort to port Zed to that platform. Once they've done so, they can join the general development effort while ensuring the user experience stays good on that platform. - -#### Expand on collaboration - -To start with, we'll focus on synchronous collaboration because that's where we're most differentiated, but there's no reason we have to limit ourselves to that. How can our tool facilitate collaboration generally, whether it's sync or async? What would it take for a team to go 100% Zed and collaborate fully within the tool? If we haven't added it already, basic Git support would be nice. From 9c627e82a09d5a652347c4710d9f94baa72dafb5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 11 Jan 2023 10:34:11 -0800 Subject: [PATCH 023/108] v0.70.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14574cc19a..d615546104 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8187,7 +8187,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.69.0" +version = "0.70.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d295deea71..38fa2ad4be 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.69.0" +version = "0.70.0" [lib] name = "zed" From 048da9ddce8dc8fd60c8be3ac425dc1afc842c14 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 11 Jan 2023 10:50:16 -0800 Subject: [PATCH 024/108] collab 0.5.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d615546104..fb090ac835 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.4.2" +version = "0.5.0" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 261289b9b3..1e26e6f527 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.4.2" +version = "0.5.0" [[bin]] name = "collab" From 14eec66e38b634fad59ce878e35b4cf8f90865a1 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Mon, 9 Jan 2023 15:44:40 -0800 Subject: [PATCH 025/108] in progress --- assets/keymaps/vim.json | 10 +++++++++- crates/vim/src/motion.rs | 23 +---------------------- crates/vim/src/normal.rs | 19 +++++++++++++++++++ crates/vim/src/state.rs | 6 +++++- crates/vim/src/vim.rs | 35 ++++++++++++++++++++++++++++++++++- crates/vim/src/visual.rs | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 102 insertions(+), 25 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index bef6f48cb4..12873a3e4e 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -209,6 +209,10 @@ "ctrl-e": [ "vim::Scroll", "LineDown" + ], + "r": [ + "vim::PushOperator", + "Replace" ] } }, @@ -294,7 +298,11 @@ "d": "vim::VisualDelete", "x": "vim::VisualDelete", "y": "vim::VisualYank", - "p": "vim::VisualPaste" + "p": "vim::VisualPaste", + "r": [ + "vim::PushOperator", + "Replace" + ] } }, { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 9089eebcb5..62b30730e8 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -3,7 +3,7 @@ use editor::{ display_map::{DisplaySnapshot, ToDisplayPoint}, movement, Bias, CharKind, DisplayPoint, }; -use gpui::{actions, impl_actions, keymap_matcher::KeyPressed, MutableAppContext}; +use gpui::{actions, impl_actions, MutableAppContext}; use language::{Point, Selection, SelectionGoal}; use serde::Deserialize; use workspace::Workspace; @@ -109,27 +109,6 @@ pub fn init(cx: &mut MutableAppContext) { &PreviousWordStart { ignore_punctuation }: &PreviousWordStart, cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) }, ); - cx.add_action( - |_: &mut Workspace, KeyPressed { keystroke }: &KeyPressed, cx| match Vim::read(cx) - .active_operator() - { - Some(Operator::FindForward { before }) => motion( - Motion::FindForward { - before, - character: keystroke.key.chars().next().unwrap(), - }, - cx, - ), - Some(Operator::FindBackward { after }) => motion( - Motion::FindBackward { - after, - character: keystroke.key.chars().next().unwrap(), - }, - cx, - ), - _ => cx.propagate_action(), - }, - ) } pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) { diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index d88d496ee9..d2ab40c772 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -424,6 +424,25 @@ fn scroll(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext "c", Operator::Delete => "d", Operator::Yank => "y", + Operator::Replace => "r", Operator::FindForward { before: false } => "f", Operator::FindForward { before: true } => "t", Operator::FindBackward { after: false } => "F", @@ -127,7 +129,9 @@ impl Operator { pub fn context_flags(&self) -> &'static [&'static str] { match self { Operator::Object { .. } => &["VimObject"], - Operator::FindForward { .. } | Operator::FindBackward { .. } => &["VimWaiting"], + Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => { + &["VimWaiting"] + } _ => &[], } } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 4d582fea6b..8c5dae1e01 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -13,11 +13,18 @@ mod visual; use collections::HashMap; use command_palette::CommandPaletteFilter; use editor::{Bias, Cancel, Editor}; -use gpui::{impl_actions, MutableAppContext, Subscription, ViewContext, WeakViewHandle}; +use gpui::{ + impl_actions, + keymap_matcher::{KeyPressed, Keystroke}, + MutableAppContext, Subscription, ViewContext, WeakViewHandle, +}; use language::CursorShape; +use motion::Motion; +use normal::normal_replace; use serde::Deserialize; use settings::Settings; use state::{Mode, Operator, VimState}; +use visual::visual_replace; use workspace::{self, Workspace}; #[derive(Clone, Deserialize, PartialEq)] @@ -51,6 +58,11 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(|_: &mut Workspace, n: &Number, cx: _| { Vim::update(cx, |vim, cx| vim.push_number(n, cx)); }); + cx.add_action( + |_: &mut Workspace, KeyPressed { keystroke }: &KeyPressed, cx| { + Vim::key_pressed(keystroke, cx); + }, + ); // Editor Actions cx.add_action(|_: &mut Editor, _: &Cancel, cx| { @@ -208,6 +220,27 @@ impl Vim { self.state.operator_stack.last().copied() } + fn key_pressed(keystroke: &Keystroke, cx: &mut ViewContext) { + match Vim::read(cx).active_operator() { + Some(Operator::FindForward { before }) => { + if let Some(character) = keystroke.key.chars().next() { + motion::motion(Motion::FindForward { before, character }, cx) + } + } + Some(Operator::FindBackward { after }) => { + if let Some(character) = keystroke.key.chars().next() { + motion::motion(Motion::FindBackward { after, character }, cx) + } + } + Some(Operator::Replace) => match Vim::read(cx).state.mode { + Mode::Normal => normal_replace(&keystroke.key, cx), + Mode::Visual { line } => visual_replace(&keystroke.key, line, cx), + _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)), + }, + _ => cx.propagate_action(), + } + } + fn set_enabled(&mut self, enabled: bool, cx: &mut MutableAppContext) { if self.enabled != enabled { self.enabled = enabled; diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index ef5bb6ddd8..3eba8699ef 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -313,6 +313,40 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext }); } +pub(crate) fn visual_replace(text: &str, line: bool, cx: &mut MutableAppContext) { + Vim::update(cx, |vim, cx| { + vim.update_active_editor(cx, |editor, cx| { + editor.transact(cx, |editor, cx| { + let (display_map, selections) = editor.selections.all_adjusted_display(cx); + let mut new_selections = Vec::new(); + editor.buffer().update(cx, |buffer, cx| { + let mut edits = Vec::new(); + for selection in selections.iter() { + let mut selection = selection.clone(); + if !line && !selection.reversed { + // Head is at the end of the selection. Adjust the end position to + // to include the character under the cursor. + *selection.end.column_mut() = selection.end.column() + 1; + selection.end = display_map.clip_point(selection.end, Bias::Right); + } + + let range = selection + .map(|p| p.to_offset(&display_map, Bias::Right)) + .range(); + new_selections.push(range.start..range.start); + let text = text.repeat(range.len()); + edits.push((range, text)); + } + + buffer.edit(edits, None, cx); + }); + editor.change_selections(None, cx, |s| s.select_ranges(new_selections)); + }); + }); + vim.pop_operator(cx) + }); +} + #[cfg(test)] mod test { use indoc::indoc; From 49379924cb9a53a65d7a8140ed46f8232e09add5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 13:25:02 -0800 Subject: [PATCH 026/108] Avoid dropping is_complete column for backward compatibility Co-authored-by: Antonio Scandurra --- crates/collab/migrations.sqlite/20221109000000_test_schema.sql | 1 + ...230103200902_replace_is_completed_with_completed_scan_id.sql | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index f89e46f8c9..32254d5757 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -57,6 +57,7 @@ CREATE TABLE "worktrees" ( "abs_path" VARCHAR NOT NULL, "visible" BOOL NOT NULL, "scan_id" INTEGER NOT NULL, + "is_complete" BOOL NOT NULL DEFAULT FALSE, "completed_scan_id" INTEGER NOT NULL, PRIMARY KEY(project_id, id) ); diff --git a/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql b/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql index e0f301b2e0..1894d888b9 100644 --- a/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql +++ b/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql @@ -1,3 +1,3 @@ ALTER TABLE worktrees - DROP COLUMN is_complete, + ALTER COLUMN is_complete SET DEFAULT FALSE, ADD COLUMN completed_scan_id INT8; From 74f8b493b23fe7df75174f8816ec74b53d8a0505 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 13:25:28 -0800 Subject: [PATCH 027/108] collab 0.5.1 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb090ac835..9a51f872a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.0" +version = "0.5.1" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 1e26e6f527..dbb5a1cc99 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.0" +version = "0.5.1" [[bin]] name = "collab" From fe27f135c0a00cf2f545350f65866d94fd4fec73 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 14:00:16 -0800 Subject: [PATCH 028/108] Bump protocol version after reconnect support --- crates/rpc/src/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 7fc59d86fb..39cdea7165 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 44; +pub const PROTOCOL_VERSION: u32 = 45; From 02f6928328e69ffceef9cd77dd7013ba11f044b6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 14:00:44 -0800 Subject: [PATCH 029/108] collab 0.5.2 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a51f872a6..3e79a68fc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.1" +version = "0.5.2" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index dbb5a1cc99..9c3f0f61c4 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.1" +version = "0.5.2" [[bin]] name = "collab" From 216b1aec08f687e3e84cc4a973d88c040b0d2df4 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 11 Jan 2023 14:57:40 -0800 Subject: [PATCH 030/108] fix replace in normal and visual modes --- crates/editor/src/movement.rs | 23 ++++++++++++++++ crates/vim/src/normal.rs | 50 +++++++++++++++++++++++++++++----- crates/vim/src/visual.rs | 51 ++++++++++++++++++++++------------- 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 96b2065823..0ede6186da 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -352,6 +352,29 @@ pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range< start..end } +pub fn split_display_range_by_lines( + map: &DisplaySnapshot, + range: Range, +) -> Vec> { + let mut result = Vec::new(); + + let mut start = range.start; + // Loop over all the covered rows until the one containing the range end + for row in range.start.row()..range.end.row() { + let row_end_column = map.line_len(row); + let end = map.clip_point(DisplayPoint::new(row, row_end_column), Bias::Left); + if start != end { + result.push(start..end); + } + start = map.clip_point(DisplayPoint::new(row + 1, 0), Bias::Left); + } + + // Add the final range from the start of the last end to the original range end. + result.push(start..range.end); + + result +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index d2ab40c772..d6391353cf 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -429,14 +429,42 @@ pub(crate) fn normal_replace(text: &str, cx: &mut MutableAppContext) { vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(None, cx, |s| { - s.move_with(|map, selection| { - *selection.end.column_mut() += 1; - selection.end = map.clip_point(selection.end, Bias::Right); - }); + let (map, display_selections) = editor.selections.all_display(cx); + // Selections are biased right at the start. So we need to store + // anchors that are biased left so that we can restore the selections + // after the change + let stable_anchors = editor + .selections + .disjoint_anchors() + .into_iter() + .map(|selection| { + let start = selection.start.bias_left(&map.buffer_snapshot); + start..start + }) + .collect::>(); + + let edits = display_selections + .into_iter() + .map(|selection| { + let mut range = selection.range(); + *range.end.column_mut() += 1; + range.end = map.clip_point(range.end, Bias::Right); + + ( + range.start.to_offset(&map, Bias::Left) + ..range.end.to_offset(&map, Bias::Left), + text, + ) + }) + .collect::>(); + + editor.buffer().update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); }); - editor.insert(text, cx); editor.set_clip_at_line_ends(true, cx); + editor.change_selections(None, cx, |s| { + s.select_anchor_ranges(stable_anchors); + }); }); }); vim.pop_operator(cx) @@ -487,6 +515,16 @@ mod test { .await; } + // #[gpui::test] + // async fn test_enter(cx: &mut gpui::TestAppContext) { + // let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]); + // cx.assert_all(indoc! {" + // ˇThe qˇuick broˇwn + // ˇfox jumps" + // }) + // .await; + // } + #[gpui::test] async fn test_k(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await.binding(["k"]); diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 3eba8699ef..ac8771f969 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use collections::HashMap; use editor::{ - display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection, + display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection, }; use gpui::{actions, MutableAppContext, ViewContext}; use language::{AutoindentMode, SelectionGoal}; @@ -318,32 +318,47 @@ pub(crate) fn visual_replace(text: &str, line: bool, cx: &mut MutableAppContext) vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { let (display_map, selections) = editor.selections.all_adjusted_display(cx); - let mut new_selections = Vec::new(); - editor.buffer().update(cx, |buffer, cx| { - let mut edits = Vec::new(); - for selection in selections.iter() { - let mut selection = selection.clone(); - if !line && !selection.reversed { - // Head is at the end of the selection. Adjust the end position to - // to include the character under the cursor. - *selection.end.column_mut() = selection.end.column() + 1; - selection.end = display_map.clip_point(selection.end, Bias::Right); - } - let range = selection - .map(|p| p.to_offset(&display_map, Bias::Right)) - .range(); - new_selections.push(range.start..range.start); + // Selections are biased right at the start. So we need to store + // anchors that are biased left so that we can restore the selections + // after the change + let stable_anchors = editor + .selections + .disjoint_anchors() + .into_iter() + .map(|selection| { + let start = selection.start.bias_left(&display_map.buffer_snapshot); + start..start + }) + .collect::>(); + + let mut edits = Vec::new(); + for selection in selections.iter() { + let mut selection = selection.clone(); + if !line && !selection.reversed { + // Head is at the end of the selection. Adjust the end position to + // to include the character under the cursor. + *selection.end.column_mut() = selection.end.column() + 1; + selection.end = display_map.clip_point(selection.end, Bias::Right); + } + + for row_range in + movement::split_display_range_by_lines(&display_map, selection.range()) + { + let range = row_range.start.to_offset(&display_map, Bias::Right) + ..row_range.end.to_offset(&display_map, Bias::Right); let text = text.repeat(range.len()); edits.push((range, text)); } + } + editor.buffer().update(cx, |buffer, cx| { buffer.edit(edits, None, cx); }); - editor.change_selections(None, cx, |s| s.select_ranges(new_selections)); + editor.change_selections(None, cx, |s| s.select_ranges(stable_anchors)); }); }); - vim.pop_operator(cx) + vim.switch_mode(Mode::Normal, false, cx); }); } From febf992a430e496f195b6bb7f8a17f59b80af9d2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 11 Jan 2023 16:35:49 -0800 Subject: [PATCH 031/108] Fix bug where keybindings would not show in command palette --- crates/gpui/src/keymap_matcher/keymap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui/src/keymap_matcher/keymap.rs b/crates/gpui/src/keymap_matcher/keymap.rs index 2f33164690..6f358aad39 100644 --- a/crates/gpui/src/keymap_matcher/keymap.rs +++ b/crates/gpui/src/keymap_matcher/keymap.rs @@ -43,7 +43,7 @@ impl Keymap { pub(crate) fn add_bindings>(&mut self, bindings: T) { for binding in bindings { self.binding_indices_by_action_type - .entry(binding.action().type_id()) + .entry(binding.action().as_any().type_id()) .or_default() .push(self.bindings.len()); self.bindings.push(binding); From 8e02266d0769b60d6b2eec541514a09d9923fc22 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Sat, 14 Jan 2023 02:30:21 -0500 Subject: [PATCH 032/108] Add Discourse release action --- .github/workflows/release_actions.yml | 9 +++++++ script/discourse_release | 38 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100755 script/discourse_release diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 3db4f7b467..a5949127f5 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -21,6 +21,15 @@ jobs: ${{ github.event.release.body }} ``` + discourse_release: + runs-on: ubuntu-latest + steps: + - 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 }} mixpanel_release: runs-on: ubuntu-latest steps: diff --git a/script/discourse_release b/script/discourse_release new file mode 100755 index 0000000000..c233bf1872 --- /dev/null +++ b/script/discourse_release @@ -0,0 +1,38 @@ +#!/usr/bin/env node --redirect-warnings=/dev/null + +main(); + +async function main() { + const apiKey = process.argv[2] + const zedVersion = process.argv[3] + const releaseNotes = process.argv[4] + const postBody = ` + 📣 Zed ${zedVersion} was just released! + + Restart your Zed or head to the [releases page](https://zed.dev/releases/latest) to grab it. + + --- + + ${releaseNotes} + ` + + const title = `${zedVersion} Release Notes` + + const options = { + method: "POST", + headers: { + "Api-Key": apiKey, + "Api-Username": "system" + }, + body: new URLSearchParams({ + title: title, + raw: postBody, + category: "8" + }) + }; + + fetch("https://forum.zed.dev/posts.json", options) + .then(response => response.json()) + .then(response => console.log(response)) + .catch(err => console.error(err)); +} \ No newline at end of file From 9779663c6b18fa35ecf9742c92c3f32a2a8d955d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Jan 2023 16:50:30 +0100 Subject: [PATCH 033/108] Use `cli.mjs` when available in TypeScript language server Otherwise, fall back to using `cli.js`. --- crates/zed/src/languages/typescript.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index f54b09ceda..01b62577ad 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -12,7 +12,8 @@ use util::ResultExt; pub struct TypeScriptLspAdapter; impl TypeScriptLspAdapter { - const BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js"; + const OLD_BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js"; + const NEW_BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs"; } struct Versions { @@ -57,7 +58,7 @@ impl LspAdapter for TypeScriptLspAdapter { fs::create_dir_all(&version_dir) .await .context("failed to create version directory")?; - let binary_path = version_dir.join(Self::BIN_PATH); + let binary_path = version_dir.join(Self::NEW_BIN_PATH); if fs::metadata(&binary_path).await.is_err() { npm_install_packages( @@ -98,9 +99,12 @@ impl LspAdapter for TypeScriptLspAdapter { } } let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let bin_path = last_version_dir.join(Self::BIN_PATH); - if bin_path.exists() { - Ok(bin_path) + let old_bin_path = last_version_dir.join(Self::OLD_BIN_PATH); + let new_bin_path = last_version_dir.join(Self::NEW_BIN_PATH); + if new_bin_path.exists() { + Ok(new_bin_path) + } else if old_bin_path.exists() { + Ok(old_bin_path) } else { Err(anyhow!( "missing executable in directory {:?}", From 2c1fd7b0bf635107e37d5d3ef41f64d7b83ed9a9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Jan 2023 16:51:45 +0100 Subject: [PATCH 034/108] Add a 5s timeout when running `npm info` and `npm install` This prevents those two commands from getting stuck when there is no internet connection. --- crates/zed/src/languages/installation.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/zed/src/languages/installation.rs b/crates/zed/src/languages/installation.rs index 40edbb88d7..c5aff17e56 100644 --- a/crates/zed/src/languages/installation.rs +++ b/crates/zed/src/languages/installation.rs @@ -37,6 +37,8 @@ pub(crate) struct GithubReleaseAsset { pub async fn npm_package_latest_version(name: &str) -> Result { let output = smol::process::Command::new("npm") + .args(["-fetch-retry-mintimeout", "2000"]) + .args(["-fetch-retry-maxtimeout", "5000"]) .args(["info", name, "--json"]) .output() .await @@ -60,6 +62,8 @@ pub async fn npm_install_packages( directory: &Path, ) -> Result<()> { let output = smol::process::Command::new("npm") + .args(["-fetch-retry-mintimeout", "2000"]) + .args(["-fetch-retry-maxtimeout", "5000"]) .arg("install") .arg("--prefix") .arg(directory) From b90e1012bf504f6d9320f6d365bfce3ab45d0cf3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 10:24:17 -0800 Subject: [PATCH 035/108] Don't render split drag targets in the dock --- crates/workspace/src/pane.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 618c650e02..bd355b8b3e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1452,7 +1452,11 @@ impl View for Pane { 0, self.active_item_index + 1, false, - Some(100.), + if self.docked.is_some() { + None + } else { + Some(100.) + }, cx, { let toolbar = self.toolbar.clone(); From aa9710f7c3184ca58676bb841aacdf8b485b92b7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 11:46:43 -0800 Subject: [PATCH 036/108] Avoid unwrapping pane split in SplitWithProjectEntry Also, implement pane-splitting operations more consistently. --- crates/workspace/src/workspace.rs | 134 +++++++++++++----------------- 1 file changed, 57 insertions(+), 77 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 027b9ba86a..eefce67371 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -231,54 +231,8 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { workspace.toggle_sidebar(SidebarSide::Right, cx); }); cx.add_action(Workspace::activate_pane_at_index); - cx.add_action( - |workspace: &mut Workspace, - SplitWithItem { - from, - pane_to_split, - item_id_to_move, - split_direction, - }: &_, - cx| { - workspace.split_pane_with_item( - from.clone(), - pane_to_split.clone(), - *item_id_to_move, - *split_direction, - cx, - ) - }, - ); - - cx.add_async_action( - |workspace: &mut Workspace, - SplitWithProjectEntry { - pane_to_split, - split_direction, - project_entry, - }: &_, - cx| { - pane_to_split.upgrade(cx).and_then(|pane_to_split| { - let new_pane = workspace.add_pane(cx); - workspace - .center - .split(&pane_to_split, &new_pane, *split_direction) - .unwrap(); - - workspace - .project - .read(cx) - .path_for_entry(*project_entry, cx) - .map(|path| { - let task = workspace.open_path(path, Some(new_pane.downgrade()), true, cx); - cx.foreground().spawn(async move { - task.await?; - Ok(()) - }) - }) - }) - }, - ); + cx.add_action(Workspace::split_pane_with_item); + cx.add_action(Workspace::split_pane_with_project_entry); cx.add_async_action( |workspace: &mut Workspace, @@ -1525,38 +1479,64 @@ impl Workspace { return None; } - pane.read(cx).active_item().map(|item| { - let new_pane = self.add_pane(cx); - if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) { - Pane::add_item(self, &new_pane, clone, true, true, None, cx); - } - self.center.split(&pane, &new_pane, direction).unwrap(); - cx.notify(); - new_pane - }) + let item = pane.read(cx).active_item()?; + let new_pane = self.add_pane(cx); + if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) { + Pane::add_item(self, &new_pane, clone, true, true, None, cx); + } + self.center.split(&pane, &new_pane, direction).unwrap(); + cx.notify(); + Some(new_pane) } - pub fn split_pane_with_item( - &mut self, - from: WeakViewHandle, - pane_to_split: WeakViewHandle, - item_id_to_move: usize, - split_direction: SplitDirection, - cx: &mut ViewContext, - ) { - if let Some((pane_to_split, from)) = pane_to_split.upgrade(cx).zip(from.upgrade(cx)) { - if &pane_to_split == self.dock_pane() { - warn!("Can't split dock pane."); - return; - } - - let new_pane = self.add_pane(cx); - Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx); - self.center - .split(&pane_to_split, &new_pane, split_direction) - .unwrap(); - cx.notify(); + pub fn split_pane_with_item(&mut self, action: &SplitWithItem, cx: &mut ViewContext) { + let Some(pane_to_split) = action.pane_to_split.upgrade(cx) else { return; }; + let Some(from) = action.from.upgrade(cx) else { return; }; + if &pane_to_split == self.dock_pane() { + warn!("Can't split dock pane."); + return; } + + let new_pane = self.add_pane(cx); + Pane::move_item( + self, + from.clone(), + new_pane.clone(), + action.item_id_to_move, + 0, + cx, + ); + self.center + .split(&pane_to_split, &new_pane, action.split_direction) + .unwrap(); + cx.notify(); + } + + pub fn split_pane_with_project_entry( + &mut self, + action: &SplitWithProjectEntry, + cx: &mut ViewContext, + ) -> Option>> { + let pane_to_split = action.pane_to_split.upgrade(cx)?; + if &pane_to_split == self.dock_pane() { + warn!("Can't split dock pane."); + return None; + } + + let new_pane = self.add_pane(cx); + self.center + .split(&pane_to_split, &new_pane, action.split_direction) + .unwrap(); + + let path = self + .project + .read(cx) + .path_for_entry(action.project_entry, cx)?; + let task = self.open_path(path, Some(new_pane.downgrade()), true, cx); + Some(cx.foreground().spawn(async move { + task.await?; + Ok(()) + })) } fn remove_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { From 765773cfe68e6022746b980263c567d2117b0ca8 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 16 Jan 2023 16:34:10 -0500 Subject: [PATCH 037/108] Make Finder "Open With" work correctly --- crates/zed/src/main.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index bf86bb9652..6baf08b01e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -75,6 +75,7 @@ fn main() { }; let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded(); + let (open_paths_tx, open_paths_rx) = mpsc::unbounded(); app.on_open_urls(move |urls, _| { if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { if let Some(cli_connection) = connect_to_cli(server_name).log_err() { @@ -83,6 +84,16 @@ fn main() { .map_err(|_| anyhow!("no listener for cli connections")) .log_err(); }; + } else { + let paths: Vec<_> = urls + .iter() + .flat_map(|url| url.strip_prefix("file://")) + .map(|path| PathBuf::from(path)) + .collect(); + open_paths_tx + .unbounded_send(paths) + .map_err(|_| anyhow!("no listener for open urls requests")) + .log_err(); } }); @@ -175,6 +186,9 @@ fn main() { cx.set_menus(menus::menus()); + cx.spawn(|cx| handle_open_paths(open_paths_rx, app_state.clone(), cx)) + .detach(); + if stdout_is_a_pty() { cx.platform().activate(true); let paths = collect_path_args(); @@ -500,6 +514,17 @@ fn load_config_files( rx } +async fn handle_open_paths( + mut rx: mpsc::UnboundedReceiver>, + app_state: Arc, + mut cx: AsyncAppContext, +) { + while let Some(paths) = rx.next().await { + cx.update(|cx| workspace::open_paths(&paths, &app_state, cx)) + .detach(); + } +} + fn connect_to_cli( server_name: &str, ) -> Result<(mpsc::Receiver, IpcSender)> { From f62d13de2119f600fbd2be63ce3d1dc097a3805a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 14:41:24 -0800 Subject: [PATCH 038/108] Use a hand-coded parser for keymap context predicates --- crates/gpui/build.rs | 12 - .../grammars/context-predicate/.gitignore | 2 - .../grammars/context-predicate/Cargo.toml | 20 - .../grammars/context-predicate/binding.gyp | 18 - .../bindings/node/binding.cc | 30 - .../context-predicate/bindings/node/index.js | 19 - .../context-predicate/bindings/rust/build.rs | 40 -- .../context-predicate/bindings/rust/lib.rs | 52 -- .../context-predicate/corpus/expressions.txt | 49 -- .../grammars/context-predicate/grammar.js | 31 - .../context-predicate/package-lock.json | 44 -- .../grammars/context-predicate/package.json | 10 - .../context-predicate/src/grammar.json | 208 ------- .../context-predicate/src/node-types.json | 353 ----------- .../grammars/context-predicate/src/parser.c | 584 ------------------ .../src/tree_sitter/parser.h | 223 ------- .../gpui/src/keymap_matcher/keymap_context.rs | 311 +++++++--- 17 files changed, 240 insertions(+), 1766 deletions(-) delete mode 100644 crates/gpui/grammars/context-predicate/.gitignore delete mode 100644 crates/gpui/grammars/context-predicate/Cargo.toml delete mode 100644 crates/gpui/grammars/context-predicate/binding.gyp delete mode 100644 crates/gpui/grammars/context-predicate/bindings/node/binding.cc delete mode 100644 crates/gpui/grammars/context-predicate/bindings/node/index.js delete mode 100644 crates/gpui/grammars/context-predicate/bindings/rust/build.rs delete mode 100644 crates/gpui/grammars/context-predicate/bindings/rust/lib.rs delete mode 100644 crates/gpui/grammars/context-predicate/corpus/expressions.txt delete mode 100644 crates/gpui/grammars/context-predicate/grammar.js delete mode 100644 crates/gpui/grammars/context-predicate/package-lock.json delete mode 100644 crates/gpui/grammars/context-predicate/package.json delete mode 100644 crates/gpui/grammars/context-predicate/src/grammar.json delete mode 100644 crates/gpui/grammars/context-predicate/src/node-types.json delete mode 100644 crates/gpui/grammars/context-predicate/src/parser.c delete mode 100644 crates/gpui/grammars/context-predicate/src/tree_sitter/parser.h diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index 23f6f04992..095d5065d9 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -6,7 +6,6 @@ use std::{ fn main() { generate_dispatch_bindings(); - compile_context_predicate_parser(); compile_metal_shaders(); generate_shader_bindings(); } @@ -30,17 +29,6 @@ fn generate_dispatch_bindings() { .expect("couldn't write dispatch bindings"); } -fn compile_context_predicate_parser() { - let dir = PathBuf::from("./grammars/context-predicate/src"); - let parser_c = dir.join("parser.c"); - - println!("cargo:rerun-if-changed={}", &parser_c.to_str().unwrap()); - cc::Build::new() - .include(&dir) - .file(parser_c) - .compile("tree_sitter_context_predicate"); -} - const SHADER_HEADER_PATH: &str = "./src/platform/mac/shaders/shaders.h"; fn compile_metal_shaders() { diff --git a/crates/gpui/grammars/context-predicate/.gitignore b/crates/gpui/grammars/context-predicate/.gitignore deleted file mode 100644 index 563ba71918..0000000000 --- a/crates/gpui/grammars/context-predicate/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/node_modules -/build \ No newline at end of file diff --git a/crates/gpui/grammars/context-predicate/Cargo.toml b/crates/gpui/grammars/context-predicate/Cargo.toml deleted file mode 100644 index a3cb197996..0000000000 --- a/crates/gpui/grammars/context-predicate/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "tree-sitter-context-predicate" -description = "context-predicate grammar for the tree-sitter parsing library" -version = "0.0.1" -keywords = ["incremental", "parsing", "context-predicate"] -categories = ["parsing", "text-editors"] -repository = "https://github.com/tree-sitter/tree-sitter-javascript" -edition = "2021" -license = "MIT" -build = "bindings/rust/build.rs" -include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*"] - -[lib] -path = "bindings/rust/lib.rs" - -[dependencies] -tree-sitter = "0.20" - -[build-dependencies] -cc = "1.0" diff --git a/crates/gpui/grammars/context-predicate/binding.gyp b/crates/gpui/grammars/context-predicate/binding.gyp deleted file mode 100644 index 16f3d1af27..0000000000 --- a/crates/gpui/grammars/context-predicate/binding.gyp +++ /dev/null @@ -1,18 +0,0 @@ -{ - "targets": [ - { - "target_name": "tree_sitter_context_predicate_binding", - "include_dirs": [ - " - -using namespace v8; - -extern "C" TSLanguage *tree_sitter_context_predicate(); - -namespace { - -NAN_METHOD(New) {} - -void Init(Local exports, Local module) { - Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New("Language").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - Local constructor = Nan::GetFunction(tpl).ToLocalChecked(); - Local instance = - constructor->NewInstance(Nan::GetCurrentContext()).ToLocalChecked(); - Nan::SetInternalFieldPointer(instance, 0, tree_sitter_context_predicate()); - - Nan::Set(instance, Nan::New("name").ToLocalChecked(), - Nan::New("context_predicate").ToLocalChecked()); - Nan::Set(module, Nan::New("exports").ToLocalChecked(), instance); -} - -NODE_MODULE(tree_sitter_context_predicate_binding, Init) - -} // namespace diff --git a/crates/gpui/grammars/context-predicate/bindings/node/index.js b/crates/gpui/grammars/context-predicate/bindings/node/index.js deleted file mode 100644 index 3bad018a56..0000000000 --- a/crates/gpui/grammars/context-predicate/bindings/node/index.js +++ /dev/null @@ -1,19 +0,0 @@ -try { - module.exports = require("../../build/Release/tree_sitter_context_predicate_binding"); -} catch (error1) { - if (error1.code !== 'MODULE_NOT_FOUND') { - throw error1; - } - try { - module.exports = require("../../build/Debug/tree_sitter_context_predicate_binding"); - } catch (error2) { - if (error2.code !== 'MODULE_NOT_FOUND') { - throw error2; - } - throw error1 - } -} - -try { - module.exports.nodeTypeInfo = require("../../src/node-types.json"); -} catch (_) {} diff --git a/crates/gpui/grammars/context-predicate/bindings/rust/build.rs b/crates/gpui/grammars/context-predicate/bindings/rust/build.rs deleted file mode 100644 index c6061f0995..0000000000 --- a/crates/gpui/grammars/context-predicate/bindings/rust/build.rs +++ /dev/null @@ -1,40 +0,0 @@ -fn main() { - let src_dir = std::path::Path::new("src"); - - let mut c_config = cc::Build::new(); - c_config.include(&src_dir); - c_config - .flag_if_supported("-Wno-unused-parameter") - .flag_if_supported("-Wno-unused-but-set-variable") - .flag_if_supported("-Wno-trigraphs"); - let parser_path = src_dir.join("parser.c"); - c_config.file(&parser_path); - - // If your language uses an external scanner written in C, - // then include this block of code: - - /* - let scanner_path = src_dir.join("scanner.c"); - c_config.file(&scanner_path); - println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); - */ - - c_config.compile("parser"); - println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); - - // If your language uses an external scanner written in C++, - // then include this block of code: - - /* - let mut cpp_config = cc::Build::new(); - cpp_config.cpp(true); - cpp_config.include(&src_dir); - cpp_config - .flag_if_supported("-Wno-unused-parameter") - .flag_if_supported("-Wno-unused-but-set-variable"); - let scanner_path = src_dir.join("scanner.cc"); - cpp_config.file(&scanner_path); - cpp_config.compile("scanner"); - println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); - */ -} diff --git a/crates/gpui/grammars/context-predicate/bindings/rust/lib.rs b/crates/gpui/grammars/context-predicate/bindings/rust/lib.rs deleted file mode 100644 index 41962c960d..0000000000 --- a/crates/gpui/grammars/context-predicate/bindings/rust/lib.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! This crate provides context_predicate language support for the [tree-sitter][] parsing library. -//! -//! Typically, you will use the [language][language func] function to add this language to a -//! tree-sitter [Parser][], and then use the parser to parse some code: -//! -//! ``` -//! let code = ""; -//! let mut parser = tree_sitter::Parser::new(); -//! parser.set_language(tree_sitter_context_predicate::language()).expect("Error loading context_predicate grammar"); -//! let tree = parser.parse(code, None).unwrap(); -//! ``` -//! -//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html -//! [language func]: fn.language.html -//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html -//! [tree-sitter]: https://tree-sitter.github.io/ - -use tree_sitter::Language; - -extern "C" { - fn tree_sitter_context_predicate() -> Language; -} - -/// Get the tree-sitter [Language][] for this grammar. -/// -/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html -pub fn language() -> Language { - unsafe { tree_sitter_context_predicate() } -} - -/// The content of the [`node-types.json`][] file for this grammar. -/// -/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types -pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json"); - -// Uncomment these to include any queries that this grammar contains - -// pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm"); -// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm"); -// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm"); -// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm"); - -#[cfg(test)] -mod tests { - #[test] - fn test_can_load_grammar() { - let mut parser = tree_sitter::Parser::new(); - parser - .set_language(super::language()) - .expect("Error loading context_predicate language"); - } -} diff --git a/crates/gpui/grammars/context-predicate/corpus/expressions.txt b/crates/gpui/grammars/context-predicate/corpus/expressions.txt deleted file mode 100644 index b53bb105a8..0000000000 --- a/crates/gpui/grammars/context-predicate/corpus/expressions.txt +++ /dev/null @@ -1,49 +0,0 @@ -================== -Identifiers -================== - -abc12 - ---- - -(source (identifier)) - -================== -Negation -================== - -!abc - ---- - -(source (not (identifier))) - -================== -And/Or -================== - -a || b && c && d - ---- - -(source - (or - (identifier) - (and - (and (identifier) (identifier)) - (identifier)))) - -================== -Expressions -================== - -a && (b == c || d != e) - ---- - -(source - (and - (identifier) - (parenthesized (or - (equal (identifier) (identifier)) - (not_equal (identifier) (identifier)))))) diff --git a/crates/gpui/grammars/context-predicate/grammar.js b/crates/gpui/grammars/context-predicate/grammar.js deleted file mode 100644 index 9a64935917..0000000000 --- a/crates/gpui/grammars/context-predicate/grammar.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = grammar({ - name: 'context_predicate', - - rules: { - source: $ => $._expression, - - _expression: $ => choice( - $.identifier, - $.not, - $.and, - $.or, - $.equal, - $.not_equal, - $.parenthesized, - ), - - identifier: $ => /[A-Za-z0-9_-]+/, - - not: $ => prec(3, seq("!", field("expression", $._expression))), - - and: $ => prec.left(2, seq(field("left", $._expression), "&&", field("right", $._expression))), - - or: $ => prec.left(1, seq(field("left", $._expression), "||", field("right", $._expression))), - - equal: $ => seq(field("left", $.identifier), "==", field("right", $.identifier)), - - not_equal: $ => seq(field("left", $.identifier), "!=", field("right", $.identifier)), - - parenthesized: $ => seq("(", field("expression", $._expression), ")"), - } -}); diff --git a/crates/gpui/grammars/context-predicate/package-lock.json b/crates/gpui/grammars/context-predicate/package-lock.json deleted file mode 100644 index 1da584a856..0000000000 --- a/crates/gpui/grammars/context-predicate/package-lock.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "tree-sitter-context-predicate", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "tree-sitter-context-predicate", - "dependencies": { - "nan": "^2.14.0" - }, - "devDependencies": { - "tree-sitter-cli": "^0.19.5" - } - }, - "node_modules/nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" - }, - "node_modules/tree-sitter-cli": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.19.5.tgz", - "integrity": "sha512-kRzKrUAwpDN9AjA3b0tPBwT1hd8N2oQvvvHup2OEsX6mdsSMLmAvR+NSqK9fe05JrRbVvG8mbteNUQsxlMQohQ==", - "dev": true, - "hasInstallScript": true, - "bin": { - "tree-sitter": "cli.js" - } - } - }, - "dependencies": { - "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" - }, - "tree-sitter-cli": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.19.5.tgz", - "integrity": "sha512-kRzKrUAwpDN9AjA3b0tPBwT1hd8N2oQvvvHup2OEsX6mdsSMLmAvR+NSqK9fe05JrRbVvG8mbteNUQsxlMQohQ==", - "dev": true - } - } -} diff --git a/crates/gpui/grammars/context-predicate/package.json b/crates/gpui/grammars/context-predicate/package.json deleted file mode 100644 index 298e34a6b5..0000000000 --- a/crates/gpui/grammars/context-predicate/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "tree-sitter-context-predicate", - "main": "bindings/node", - "devDependencies": { - "tree-sitter-cli": "^0.19.5" - }, - "dependencies": { - "nan": "^2.14.0" - } -} diff --git a/crates/gpui/grammars/context-predicate/src/grammar.json b/crates/gpui/grammars/context-predicate/src/grammar.json deleted file mode 100644 index 9ccb50fe79..0000000000 --- a/crates/gpui/grammars/context-predicate/src/grammar.json +++ /dev/null @@ -1,208 +0,0 @@ -{ - "name": "context_predicate", - "rules": { - "source": { - "type": "SYMBOL", - "name": "_expression" - }, - "_expression": { - "type": "CHOICE", - "members": [ - { - "type": "SYMBOL", - "name": "identifier" - }, - { - "type": "SYMBOL", - "name": "not" - }, - { - "type": "SYMBOL", - "name": "and" - }, - { - "type": "SYMBOL", - "name": "or" - }, - { - "type": "SYMBOL", - "name": "equal" - }, - { - "type": "SYMBOL", - "name": "not_equal" - }, - { - "type": "SYMBOL", - "name": "parenthesized" - } - ] - }, - "identifier": { - "type": "PATTERN", - "value": "[A-Za-z0-9_-]+" - }, - "not": { - "type": "PREC", - "value": 3, - "content": { - "type": "SEQ", - "members": [ - { - "type": "STRING", - "value": "!" - }, - { - "type": "FIELD", - "name": "expression", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - } - ] - } - }, - "and": { - "type": "PREC_LEFT", - "value": 2, - "content": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - }, - { - "type": "STRING", - "value": "&&" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - } - ] - } - }, - "or": { - "type": "PREC_LEFT", - "value": 1, - "content": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - }, - { - "type": "STRING", - "value": "||" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - } - ] - } - }, - "equal": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - }, - { - "type": "STRING", - "value": "==" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - } - ] - }, - "not_equal": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - }, - { - "type": "STRING", - "value": "!=" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - } - ] - }, - "parenthesized": { - "type": "SEQ", - "members": [ - { - "type": "STRING", - "value": "(" - }, - { - "type": "FIELD", - "name": "expression", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - }, - { - "type": "STRING", - "value": ")" - } - ] - } - }, - "extras": [ - { - "type": "PATTERN", - "value": "\\s" - } - ], - "conflicts": [], - "precedences": [], - "externals": [], - "inline": [], - "supertypes": [] -} - diff --git a/crates/gpui/grammars/context-predicate/src/node-types.json b/crates/gpui/grammars/context-predicate/src/node-types.json deleted file mode 100644 index 2e8868b1f1..0000000000 --- a/crates/gpui/grammars/context-predicate/src/node-types.json +++ /dev/null @@ -1,353 +0,0 @@ -[ - { - "type": "and", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "equal", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - } - } - }, - { - "type": "not", - "named": true, - "fields": { - "expression": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "not_equal", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - } - } - }, - { - "type": "or", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "parenthesized", - "named": true, - "fields": { - "expression": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "source", - "named": true, - "fields": {}, - "children": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - }, - { - "type": "!", - "named": false - }, - { - "type": "!=", - "named": false - }, - { - "type": "&&", - "named": false - }, - { - "type": "(", - "named": false - }, - { - "type": ")", - "named": false - }, - { - "type": "==", - "named": false - }, - { - "type": "identifier", - "named": true - }, - { - "type": "||", - "named": false - } -] \ No newline at end of file diff --git a/crates/gpui/grammars/context-predicate/src/parser.c b/crates/gpui/grammars/context-predicate/src/parser.c deleted file mode 100644 index 57a7364a43..0000000000 --- a/crates/gpui/grammars/context-predicate/src/parser.c +++ /dev/null @@ -1,584 +0,0 @@ -#include - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - -#define LANGUAGE_VERSION 13 -#define STATE_COUNT 18 -#define LARGE_STATE_COUNT 6 -#define SYMBOL_COUNT 17 -#define ALIAS_COUNT 0 -#define TOKEN_COUNT 9 -#define EXTERNAL_TOKEN_COUNT 0 -#define FIELD_COUNT 3 -#define MAX_ALIAS_SEQUENCE_LENGTH 3 -#define PRODUCTION_ID_COUNT 3 - -enum { - sym_identifier = 1, - anon_sym_BANG = 2, - anon_sym_AMP_AMP = 3, - anon_sym_PIPE_PIPE = 4, - anon_sym_EQ_EQ = 5, - anon_sym_BANG_EQ = 6, - anon_sym_LPAREN = 7, - anon_sym_RPAREN = 8, - sym_source = 9, - sym__expression = 10, - sym_not = 11, - sym_and = 12, - sym_or = 13, - sym_equal = 14, - sym_not_equal = 15, - sym_parenthesized = 16, -}; - -static const char *const ts_symbol_names[] = { - [ts_builtin_sym_end] = "end", - [sym_identifier] = "identifier", - [anon_sym_BANG] = "!", - [anon_sym_AMP_AMP] = "&&", - [anon_sym_PIPE_PIPE] = "||", - [anon_sym_EQ_EQ] = "==", - [anon_sym_BANG_EQ] = "!=", - [anon_sym_LPAREN] = "(", - [anon_sym_RPAREN] = ")", - [sym_source] = "source", - [sym__expression] = "_expression", - [sym_not] = "not", - [sym_and] = "and", - [sym_or] = "or", - [sym_equal] = "equal", - [sym_not_equal] = "not_equal", - [sym_parenthesized] = "parenthesized", -}; - -static const TSSymbol ts_symbol_map[] = { - [ts_builtin_sym_end] = ts_builtin_sym_end, - [sym_identifier] = sym_identifier, - [anon_sym_BANG] = anon_sym_BANG, - [anon_sym_AMP_AMP] = anon_sym_AMP_AMP, - [anon_sym_PIPE_PIPE] = anon_sym_PIPE_PIPE, - [anon_sym_EQ_EQ] = anon_sym_EQ_EQ, - [anon_sym_BANG_EQ] = anon_sym_BANG_EQ, - [anon_sym_LPAREN] = anon_sym_LPAREN, - [anon_sym_RPAREN] = anon_sym_RPAREN, - [sym_source] = sym_source, - [sym__expression] = sym__expression, - [sym_not] = sym_not, - [sym_and] = sym_and, - [sym_or] = sym_or, - [sym_equal] = sym_equal, - [sym_not_equal] = sym_not_equal, - [sym_parenthesized] = sym_parenthesized, -}; - -static const TSSymbolMetadata ts_symbol_metadata[] = { - [ts_builtin_sym_end] = - { - .visible = false, - .named = true, - }, - [sym_identifier] = - { - .visible = true, - .named = true, - }, - [anon_sym_BANG] = - { - .visible = true, - .named = false, - }, - [anon_sym_AMP_AMP] = - { - .visible = true, - .named = false, - }, - [anon_sym_PIPE_PIPE] = - { - .visible = true, - .named = false, - }, - [anon_sym_EQ_EQ] = - { - .visible = true, - .named = false, - }, - [anon_sym_BANG_EQ] = - { - .visible = true, - .named = false, - }, - [anon_sym_LPAREN] = - { - .visible = true, - .named = false, - }, - [anon_sym_RPAREN] = - { - .visible = true, - .named = false, - }, - [sym_source] = - { - .visible = true, - .named = true, - }, - [sym__expression] = - { - .visible = false, - .named = true, - }, - [sym_not] = - { - .visible = true, - .named = true, - }, - [sym_and] = - { - .visible = true, - .named = true, - }, - [sym_or] = - { - .visible = true, - .named = true, - }, - [sym_equal] = - { - .visible = true, - .named = true, - }, - [sym_not_equal] = - { - .visible = true, - .named = true, - }, - [sym_parenthesized] = - { - .visible = true, - .named = true, - }, -}; - -enum { - field_expression = 1, - field_left = 2, - field_right = 3, -}; - -static const char *const ts_field_names[] = { - [0] = NULL, - [field_expression] = "expression", - [field_left] = "left", - [field_right] = "right", -}; - -static const TSFieldMapSlice ts_field_map_slices[PRODUCTION_ID_COUNT] = { - [1] = {.index = 0, .length = 1}, - [2] = {.index = 1, .length = 2}, -}; - -static const TSFieldMapEntry ts_field_map_entries[] = { - [0] = {field_expression, 1}, - [1] = {field_left, 0}, - {field_right, 2}, -}; - -static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT] - [MAX_ALIAS_SEQUENCE_LENGTH] = { - [0] = {0}, -}; - -static const uint16_t ts_non_terminal_alias_map[] = { - 0, -}; - -static bool ts_lex(TSLexer *lexer, TSStateId state) { - START_LEXER(); - eof = lexer->eof(lexer); - switch (state) { - case 0: - if (eof) - ADVANCE(7); - if (lookahead == '!') - ADVANCE(10); - if (lookahead == '&') - ADVANCE(2); - if (lookahead == '(') - ADVANCE(15); - if (lookahead == ')') - ADVANCE(16); - if (lookahead == '=') - ADVANCE(4); - if (lookahead == '|') - ADVANCE(5); - if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' || - lookahead == ' ') - SKIP(0) - if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || - ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) - ADVANCE(8); - END_STATE(); - case 1: - if (lookahead == '!') - ADVANCE(9); - if (lookahead == '(') - ADVANCE(15); - if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' || - lookahead == ' ') - SKIP(1) - if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || - ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) - ADVANCE(8); - END_STATE(); - case 2: - if (lookahead == '&') - ADVANCE(11); - END_STATE(); - case 3: - if (lookahead == '=') - ADVANCE(14); - END_STATE(); - case 4: - if (lookahead == '=') - ADVANCE(13); - END_STATE(); - case 5: - if (lookahead == '|') - ADVANCE(12); - END_STATE(); - case 6: - if (eof) - ADVANCE(7); - if (lookahead == '!') - ADVANCE(3); - if (lookahead == '&') - ADVANCE(2); - if (lookahead == ')') - ADVANCE(16); - if (lookahead == '=') - ADVANCE(4); - if (lookahead == '|') - ADVANCE(5); - if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' || - lookahead == ' ') - SKIP(6) - END_STATE(); - case 7: - ACCEPT_TOKEN(ts_builtin_sym_end); - END_STATE(); - case 8: - ACCEPT_TOKEN(sym_identifier); - if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || - ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) - ADVANCE(8); - END_STATE(); - case 9: - ACCEPT_TOKEN(anon_sym_BANG); - END_STATE(); - case 10: - ACCEPT_TOKEN(anon_sym_BANG); - if (lookahead == '=') - ADVANCE(14); - END_STATE(); - case 11: - ACCEPT_TOKEN(anon_sym_AMP_AMP); - END_STATE(); - case 12: - ACCEPT_TOKEN(anon_sym_PIPE_PIPE); - END_STATE(); - case 13: - ACCEPT_TOKEN(anon_sym_EQ_EQ); - END_STATE(); - case 14: - ACCEPT_TOKEN(anon_sym_BANG_EQ); - END_STATE(); - case 15: - ACCEPT_TOKEN(anon_sym_LPAREN); - END_STATE(); - case 16: - ACCEPT_TOKEN(anon_sym_RPAREN); - END_STATE(); - default: - return false; - } -} - -static const TSLexMode ts_lex_modes[STATE_COUNT] = { - [0] = {.lex_state = 0}, [1] = {.lex_state = 1}, [2] = {.lex_state = 1}, - [3] = {.lex_state = 1}, [4] = {.lex_state = 1}, [5] = {.lex_state = 1}, - [6] = {.lex_state = 6}, [7] = {.lex_state = 0}, [8] = {.lex_state = 0}, - [9] = {.lex_state = 0}, [10] = {.lex_state = 0}, [11] = {.lex_state = 0}, - [12] = {.lex_state = 0}, [13] = {.lex_state = 0}, [14] = {.lex_state = 0}, - [15] = {.lex_state = 0}, [16] = {.lex_state = 0}, [17] = {.lex_state = 0}, -}; - -static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { - [0] = - { - [ts_builtin_sym_end] = ACTIONS(1), - [sym_identifier] = ACTIONS(1), - [anon_sym_BANG] = ACTIONS(1), - [anon_sym_AMP_AMP] = ACTIONS(1), - [anon_sym_PIPE_PIPE] = ACTIONS(1), - [anon_sym_EQ_EQ] = ACTIONS(1), - [anon_sym_BANG_EQ] = ACTIONS(1), - [anon_sym_LPAREN] = ACTIONS(1), - [anon_sym_RPAREN] = ACTIONS(1), - }, - [1] = - { - [sym_source] = STATE(15), - [sym__expression] = STATE(13), - [sym_not] = STATE(13), - [sym_and] = STATE(13), - [sym_or] = STATE(13), - [sym_equal] = STATE(13), - [sym_not_equal] = STATE(13), - [sym_parenthesized] = STATE(13), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [2] = - { - [sym__expression] = STATE(7), - [sym_not] = STATE(7), - [sym_and] = STATE(7), - [sym_or] = STATE(7), - [sym_equal] = STATE(7), - [sym_not_equal] = STATE(7), - [sym_parenthesized] = STATE(7), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [3] = - { - [sym__expression] = STATE(14), - [sym_not] = STATE(14), - [sym_and] = STATE(14), - [sym_or] = STATE(14), - [sym_equal] = STATE(14), - [sym_not_equal] = STATE(14), - [sym_parenthesized] = STATE(14), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [4] = - { - [sym__expression] = STATE(11), - [sym_not] = STATE(11), - [sym_and] = STATE(11), - [sym_or] = STATE(11), - [sym_equal] = STATE(11), - [sym_not_equal] = STATE(11), - [sym_parenthesized] = STATE(11), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [5] = - { - [sym__expression] = STATE(12), - [sym_not] = STATE(12), - [sym_and] = STATE(12), - [sym_or] = STATE(12), - [sym_equal] = STATE(12), - [sym_not_equal] = STATE(12), - [sym_parenthesized] = STATE(12), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, -}; - -static const uint16_t ts_small_parse_table[] = { - [0] = 3, - ACTIONS(11), - 1, - anon_sym_EQ_EQ, - ACTIONS(13), - 1, - anon_sym_BANG_EQ, - ACTIONS(9), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [13] = 1, - ACTIONS(15), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [20] = 1, - ACTIONS(17), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [27] = 1, - ACTIONS(19), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [34] = 1, - ACTIONS(21), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [41] = 1, - ACTIONS(23), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [48] = 2, - ACTIONS(27), - 1, - anon_sym_AMP_AMP, - ACTIONS(25), - 3, - ts_builtin_sym_end, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [57] = 3, - ACTIONS(27), - 1, - anon_sym_AMP_AMP, - ACTIONS(29), - 1, - ts_builtin_sym_end, - ACTIONS(31), - 1, - anon_sym_PIPE_PIPE, - [67] = 3, - ACTIONS(27), - 1, - anon_sym_AMP_AMP, - ACTIONS(31), - 1, - anon_sym_PIPE_PIPE, - ACTIONS(33), - 1, - anon_sym_RPAREN, - [77] = 1, - ACTIONS(35), - 1, - ts_builtin_sym_end, - [81] = 1, - ACTIONS(37), - 1, - sym_identifier, - [85] = 1, - ACTIONS(39), - 1, - sym_identifier, -}; - -static const uint32_t ts_small_parse_table_map[] = { - [SMALL_STATE(6)] = 0, [SMALL_STATE(7)] = 13, [SMALL_STATE(8)] = 20, - [SMALL_STATE(9)] = 27, [SMALL_STATE(10)] = 34, [SMALL_STATE(11)] = 41, - [SMALL_STATE(12)] = 48, [SMALL_STATE(13)] = 57, [SMALL_STATE(14)] = 67, - [SMALL_STATE(15)] = 77, [SMALL_STATE(16)] = 81, [SMALL_STATE(17)] = 85, -}; - -static const TSParseActionEntry ts_parse_actions[] = { - [0] = {.entry = {.count = 0, .reusable = false}}, - [1] = {.entry = {.count = 1, .reusable = false}}, - RECOVER(), - [3] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(6), - [5] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(2), - [7] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(3), - [9] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym__expression, 1), - [11] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(16), - [13] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(17), - [15] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_not, 2, .production_id = 1), - [17] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_equal, 3, .production_id = 2), - [19] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_not_equal, 3, .production_id = 2), - [21] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_parenthesized, 3, .production_id = 1), - [23] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_and, 3, .production_id = 2), - [25] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_or, 3, .production_id = 2), - [27] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(4), - [29] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_source, 1), - [31] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(5), - [33] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(10), - [35] = {.entry = {.count = 1, .reusable = true}}, - ACCEPT_INPUT(), - [37] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(8), - [39] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(9), -}; - -#ifdef __cplusplus -extern "C" { -#endif -#ifdef _WIN32 -#define extern __declspec(dllexport) -#endif - -extern const TSLanguage *tree_sitter_context_predicate(void) { - static const TSLanguage language = { - .version = LANGUAGE_VERSION, - .symbol_count = SYMBOL_COUNT, - .alias_count = ALIAS_COUNT, - .token_count = TOKEN_COUNT, - .external_token_count = EXTERNAL_TOKEN_COUNT, - .state_count = STATE_COUNT, - .large_state_count = LARGE_STATE_COUNT, - .production_id_count = PRODUCTION_ID_COUNT, - .field_count = FIELD_COUNT, - .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH, - .parse_table = &ts_parse_table[0][0], - .small_parse_table = ts_small_parse_table, - .small_parse_table_map = ts_small_parse_table_map, - .parse_actions = ts_parse_actions, - .symbol_names = ts_symbol_names, - .field_names = ts_field_names, - .field_map_slices = ts_field_map_slices, - .field_map_entries = ts_field_map_entries, - .symbol_metadata = ts_symbol_metadata, - .public_symbol_map = ts_symbol_map, - .alias_map = ts_non_terminal_alias_map, - .alias_sequences = &ts_alias_sequences[0][0], - .lex_modes = ts_lex_modes, - .lex_fn = ts_lex, - }; - return &language; -} -#ifdef __cplusplus -} -#endif diff --git a/crates/gpui/grammars/context-predicate/src/tree_sitter/parser.h b/crates/gpui/grammars/context-predicate/src/tree_sitter/parser.h deleted file mode 100644 index cbbc7b4ee3..0000000000 --- a/crates/gpui/grammars/context-predicate/src/tree_sitter/parser.h +++ /dev/null @@ -1,223 +0,0 @@ -#ifndef TREE_SITTER_PARSER_H_ -#define TREE_SITTER_PARSER_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#define ts_builtin_sym_error ((TSSymbol)-1) -#define ts_builtin_sym_end 0 -#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 - -typedef uint16_t TSStateId; - -#ifndef TREE_SITTER_API_H_ -typedef uint16_t TSSymbol; -typedef uint16_t TSFieldId; -typedef struct TSLanguage TSLanguage; -#endif - -typedef struct { - TSFieldId field_id; - uint8_t child_index; - bool inherited; -} TSFieldMapEntry; - -typedef struct { - uint16_t index; - uint16_t length; -} TSFieldMapSlice; - -typedef struct { - bool visible; - bool named; - bool supertype; -} TSSymbolMetadata; - -typedef struct TSLexer TSLexer; - -struct TSLexer { - int32_t lookahead; - TSSymbol result_symbol; - void (*advance)(TSLexer *, bool); - void (*mark_end)(TSLexer *); - uint32_t (*get_column)(TSLexer *); - bool (*is_at_included_range_start)(const TSLexer *); - bool (*eof)(const TSLexer *); -}; - -typedef enum { - TSParseActionTypeShift, - TSParseActionTypeReduce, - TSParseActionTypeAccept, - TSParseActionTypeRecover, -} TSParseActionType; - -typedef union { - struct { - uint8_t type; - TSStateId state; - bool extra; - bool repetition; - } shift; - struct { - uint8_t type; - uint8_t child_count; - TSSymbol symbol; - int16_t dynamic_precedence; - uint16_t production_id; - } reduce; - uint8_t type; -} TSParseAction; - -typedef struct { - uint16_t lex_state; - uint16_t external_lex_state; -} TSLexMode; - -typedef union { - TSParseAction action; - struct { - uint8_t count; - bool reusable; - } entry; -} TSParseActionEntry; - -struct TSLanguage { - uint32_t version; - uint32_t symbol_count; - uint32_t alias_count; - uint32_t token_count; - uint32_t external_token_count; - uint32_t state_count; - uint32_t large_state_count; - uint32_t production_id_count; - uint32_t field_count; - uint16_t max_alias_sequence_length; - const uint16_t *parse_table; - const uint16_t *small_parse_table; - const uint32_t *small_parse_table_map; - const TSParseActionEntry *parse_actions; - const char * const *symbol_names; - const char * const *field_names; - const TSFieldMapSlice *field_map_slices; - const TSFieldMapEntry *field_map_entries; - const TSSymbolMetadata *symbol_metadata; - const TSSymbol *public_symbol_map; - const uint16_t *alias_map; - const TSSymbol *alias_sequences; - const TSLexMode *lex_modes; - bool (*lex_fn)(TSLexer *, TSStateId); - bool (*keyword_lex_fn)(TSLexer *, TSStateId); - TSSymbol keyword_capture_token; - struct { - const bool *states; - const TSSymbol *symbol_map; - void *(*create)(void); - void (*destroy)(void *); - bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); - unsigned (*serialize)(void *, char *); - void (*deserialize)(void *, const char *, unsigned); - } external_scanner; -}; - -/* - * Lexer Macros - */ - -#define START_LEXER() \ - bool result = false; \ - bool skip = false; \ - bool eof = false; \ - int32_t lookahead; \ - goto start; \ - next_state: \ - lexer->advance(lexer, skip); \ - start: \ - skip = false; \ - lookahead = lexer->lookahead; - -#define ADVANCE(state_value) \ - { \ - state = state_value; \ - goto next_state; \ - } - -#define SKIP(state_value) \ - { \ - skip = true; \ - state = state_value; \ - goto next_state; \ - } - -#define ACCEPT_TOKEN(symbol_value) \ - result = true; \ - lexer->result_symbol = symbol_value; \ - lexer->mark_end(lexer); - -#define END_STATE() return result; - -/* - * Parse Table Macros - */ - -#define SMALL_STATE(id) id - LARGE_STATE_COUNT - -#define STATE(id) id - -#define ACTIONS(id) id - -#define SHIFT(state_value) \ - {{ \ - .shift = { \ - .type = TSParseActionTypeShift, \ - .state = state_value \ - } \ - }} - -#define SHIFT_REPEAT(state_value) \ - {{ \ - .shift = { \ - .type = TSParseActionTypeShift, \ - .state = state_value, \ - .repetition = true \ - } \ - }} - -#define SHIFT_EXTRA() \ - {{ \ - .shift = { \ - .type = TSParseActionTypeShift, \ - .extra = true \ - } \ - }} - -#define REDUCE(symbol_val, child_count_val, ...) \ - {{ \ - .reduce = { \ - .type = TSParseActionTypeReduce, \ - .symbol = symbol_val, \ - .child_count = child_count_val, \ - __VA_ARGS__ \ - }, \ - }} - -#define RECOVER() \ - {{ \ - .type = TSParseActionTypeRecover \ - }} - -#define ACCEPT_INPUT() \ - {{ \ - .type = TSParseActionTypeAccept \ - }} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_PARSER_H_ diff --git a/crates/gpui/src/keymap_matcher/keymap_context.rs b/crates/gpui/src/keymap_matcher/keymap_context.rs index ad7ce929fe..b1dbd0e92d 100644 --- a/crates/gpui/src/keymap_matcher/keymap_context.rs +++ b/crates/gpui/src/keymap_matcher/keymap_context.rs @@ -1,11 +1,5 @@ -use anyhow::anyhow; - +use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet}; -use tree_sitter::{Language, Node, Parser}; - -extern "C" { - fn tree_sitter_context_predicate() -> Language; -} #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct KeymapContext { @@ -35,70 +29,13 @@ pub enum KeymapContextPredicate { } impl KeymapContextPredicate { - pub fn parse(source: &str) -> anyhow::Result { - let mut parser = Parser::new(); - let language = unsafe { tree_sitter_context_predicate() }; - parser.set_language(language).unwrap(); - let source = source.as_bytes(); - let tree = parser.parse(source, None).unwrap(); - Self::from_node(tree.root_node(), source) - } - - fn from_node(node: Node, source: &[u8]) -> anyhow::Result { - let parse_error = "error parsing context predicate"; - let kind = node.kind(); - - match kind { - "source" => Self::from_node(node.child(0).ok_or_else(|| anyhow!(parse_error))?, source), - "identifier" => Ok(Self::Identifier(node.utf8_text(source)?.into())), - "not" => { - let child = Self::from_node( - node.child_by_field_name("expression") - .ok_or_else(|| anyhow!(parse_error))?, - source, - )?; - Ok(Self::Not(Box::new(child))) - } - "and" | "or" => { - let left = Box::new(Self::from_node( - node.child_by_field_name("left") - .ok_or_else(|| anyhow!(parse_error))?, - source, - )?); - let right = Box::new(Self::from_node( - node.child_by_field_name("right") - .ok_or_else(|| anyhow!(parse_error))?, - source, - )?); - if kind == "and" { - Ok(Self::And(left, right)) - } else { - Ok(Self::Or(left, right)) - } - } - "equal" | "not_equal" => { - let left = node - .child_by_field_name("left") - .ok_or_else(|| anyhow!(parse_error))? - .utf8_text(source)? - .into(); - let right = node - .child_by_field_name("right") - .ok_or_else(|| anyhow!(parse_error))? - .utf8_text(source)? - .into(); - if kind == "equal" { - Ok(Self::Equal(left, right)) - } else { - Ok(Self::NotEqual(left, right)) - } - } - "parenthesized" => Self::from_node( - node.child_by_field_name("expression") - .ok_or_else(|| anyhow!(parse_error))?, - source, - ), - _ => Err(anyhow!(parse_error)), + pub fn parse(source: &str) -> Result { + let source = Self::skip_whitespace(source); + let (predicate, rest) = Self::parse_expr(source, 0)?; + if let Some(next) = rest.chars().next() { + Err(anyhow!("unexpected character {next:?}")) + } else { + Ok(predicate) } } @@ -120,4 +57,236 @@ impl KeymapContextPredicate { Self::Or(left, right) => left.eval(context) || right.eval(context), } } + + fn parse_expr( + mut source: &str, + min_precedence: u32, + ) -> anyhow::Result<(KeymapContextPredicate, &str)> { + type Op = + fn(KeymapContextPredicate, KeymapContextPredicate) -> Result; + + let (mut predicate, rest) = Self::parse_primary(source)?; + source = rest; + + 'parse: loop { + for (operator, precedence, constructor) in [ + ("&&", PRECEDENCE_AND, KeymapContextPredicate::new_and as Op), + ("||", PRECEDENCE_OR, KeymapContextPredicate::new_or as Op), + ("==", PRECEDENCE_EQ, KeymapContextPredicate::new_eq as Op), + ("!=", PRECEDENCE_EQ, KeymapContextPredicate::new_neq as Op), + ] { + if source.starts_with(operator) && precedence >= min_precedence { + source = Self::skip_whitespace(&source[operator.len()..]); + let (right, rest) = Self::parse_expr(source, precedence + 1)?; + predicate = constructor(predicate, right)?; + source = rest; + continue 'parse; + } + } + break; + } + + Ok((predicate, source)) + } + + fn parse_primary(mut source: &str) -> anyhow::Result<(KeymapContextPredicate, &str)> { + let next = source + .chars() + .next() + .ok_or_else(|| anyhow!("unexpected eof"))?; + match next { + '(' => { + source = Self::skip_whitespace(&source[1..]); + let (predicate, rest) = Self::parse_expr(source, 0)?; + if rest.starts_with(')') { + source = Self::skip_whitespace(&rest[1..]); + Ok((predicate, source)) + } else { + Err(anyhow!("expected a ')'")) + } + } + '!' => { + let source = Self::skip_whitespace(&source[1..]); + let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?; + Ok((KeymapContextPredicate::Not(Box::new(predicate)), source)) + } + _ if next.is_alphanumeric() || next == '_' => { + let len = source + .find(|c: char| !(c.is_alphanumeric() || c == '_')) + .unwrap_or(source.len()); + let (identifier, rest) = source.split_at(len); + source = Self::skip_whitespace(rest); + Ok(( + KeymapContextPredicate::Identifier(identifier.into()), + source, + )) + } + _ => Err(anyhow!("unexpected character {next:?}")), + } + } + + fn skip_whitespace(source: &str) -> &str { + let len = source + .find(|c: char| !c.is_whitespace()) + .unwrap_or(source.len()); + &source[len..] + } + + fn new_or(self, other: Self) -> Result { + Ok(Self::Or(Box::new(self), Box::new(other))) + } + + fn new_and(self, other: Self) -> Result { + Ok(Self::And(Box::new(self), Box::new(other))) + } + + fn new_eq(self, other: Self) -> Result { + if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) { + Ok(Self::Equal(left, right)) + } else { + Err(anyhow!("operands must be identifiers")) + } + } + + fn new_neq(self, other: Self) -> Result { + if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) { + Ok(Self::NotEqual(left, right)) + } else { + Err(anyhow!("operands must be identifiers")) + } + } +} + +const PRECEDENCE_OR: u32 = 1; +const PRECEDENCE_AND: u32 = 2; +const PRECEDENCE_EQ: u32 = 3; +const PRECEDENCE_NOT: u32 = 4; + +#[cfg(test)] +mod tests { + use super::KeymapContextPredicate::{self, *}; + + #[test] + fn test_parse_identifiers() { + // Identifiers + assert_eq!( + KeymapContextPredicate::parse("abc12").unwrap(), + Identifier("abc12".into()) + ); + assert_eq!( + KeymapContextPredicate::parse("_1a").unwrap(), + Identifier("_1a".into()) + ); + } + + #[test] + fn test_parse_negations() { + assert_eq!( + KeymapContextPredicate::parse("!abc").unwrap(), + Not(Box::new(Identifier("abc".into()))) + ); + assert_eq!( + KeymapContextPredicate::parse(" ! ! abc").unwrap(), + Not(Box::new(Not(Box::new(Identifier("abc".into()))))) + ); + } + + #[test] + fn test_parse_equality_operators() { + assert_eq!( + KeymapContextPredicate::parse("a == b").unwrap(), + Equal("a".into(), "b".into()) + ); + assert_eq!( + KeymapContextPredicate::parse("c!=d").unwrap(), + NotEqual("c".into(), "d".into()) + ); + assert_eq!( + KeymapContextPredicate::parse("c == !d") + .unwrap_err() + .to_string(), + "operands must be identifiers" + ); + } + + #[test] + fn test_parse_boolean_operators() { + assert_eq!( + KeymapContextPredicate::parse("a || b").unwrap(), + Or( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a || !b && c").unwrap(), + Or( + Box::new(Identifier("a".into())), + Box::new(And( + Box::new(Not(Box::new(Identifier("b".into())))), + Box::new(Identifier("c".into())) + )) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a && b || c&&d").unwrap(), + Or( + Box::new(And( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())) + )), + Box::new(And( + Box::new(Identifier("c".into())), + Box::new(Identifier("d".into())) + )) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a == b && c || d == e && f").unwrap(), + Or( + Box::new(And( + Box::new(Equal("a".into(), "b".into())), + Box::new(Identifier("c".into())) + )), + Box::new(And( + Box::new(Equal("d".into(), "e".into())), + Box::new(Identifier("f".into())) + )) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a && b && c && d").unwrap(), + And( + Box::new(And( + Box::new(And( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())) + )), + Box::new(Identifier("c".into())), + )), + Box::new(Identifier("d".into())) + ), + ); + } + + #[test] + fn test_parse_parenthesized_expressions() { + assert_eq!( + KeymapContextPredicate::parse("a && (b == c || d != e)").unwrap(), + And( + Box::new(Identifier("a".into())), + Box::new(Or( + Box::new(Equal("b".into(), "c".into())), + Box::new(NotEqual("d".into(), "e".into())), + )), + ), + ); + assert_eq!( + KeymapContextPredicate::parse(" ( a || b ) ").unwrap(), + Or( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())), + ) + ); + } } From 373902d9333ccecba5eafcd9e45b695f488ec099 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 16:00:46 -0800 Subject: [PATCH 039/108] Add '>' child operator in keymap context predicates --- crates/gpui/src/app.rs | 54 ++++++++---- crates/gpui/src/keymap_matcher.rs | 84 +++++++++++++++---- crates/gpui/src/keymap_matcher/binding.rs | 12 +-- .../gpui/src/keymap_matcher/keymap_context.rs | 40 +++++---- crates/gpui/src/presenter.rs | 2 +- 5 files changed, 136 insertions(+), 56 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 6b784833c7..6a4086c5eb 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1349,21 +1349,24 @@ impl MutableAppContext { /// Return keystrokes that would dispatch the given action closest to the focused view, if there are any. pub(crate) fn keystrokes_for_action( - &self, + &mut self, window_id: usize, - dispatch_path: &[usize], + view_stack: &[usize], action: &dyn Action, ) -> Option> { - for view_id in dispatch_path.iter().rev() { + 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"); - let keymap_context = view.keymap_context(self.as_ref()); + self.keystroke_matcher + .contexts + .push(view.keymap_context(self.as_ref())); let keystrokes = self .keystroke_matcher - .keystrokes_for_action(action, &keymap_context); + .keystrokes_for_action(action, &self.keystroke_matcher.contexts); if keystrokes.is_some() { return keystrokes; } @@ -6681,7 +6684,7 @@ mod tests { view_3 }); - // This keymap's only binding dispatches an action on view 2 because that view will have + // This binding only dispatches an action on view 2 because that view will have // "a" and "b" in its context, but not "c". cx.add_bindings(vec![Binding::new( "a", @@ -6691,16 +6694,31 @@ mod tests { cx.add_bindings(vec![Binding::new("b", Action("b".to_string()), None)]); + // This binding only dispatches an action on views 2 and 3, because they have + // a parent view with a in its context + cx.add_bindings(vec![Binding::new( + "c", + Action("c".to_string()), + Some("b > c"), + )]); + + // This binding only dispatches an action on view 2, because they have + // a parent view with a in its context + cx.add_bindings(vec![Binding::new( + "d", + Action("d".to_string()), + Some("a && !b > b"), + )]); + let actions = Rc::new(RefCell::new(Vec::new())); cx.add_action({ let actions = actions.clone(); move |view: &mut View, action: &Action, cx| { - if action.0 == "a" { - actions.borrow_mut().push(format!("{} a", view.id)); - } else { - actions - .borrow_mut() - .push(format!("{} {}", view.id, action.0)); + actions + .borrow_mut() + .push(format!("{} {}", view.id, action.0)); + + if action.0 == "b" { cx.propagate_action(); } } @@ -6714,14 +6732,20 @@ mod tests { }); cx.dispatch_keystroke(window_id, &Keystroke::parse("a").unwrap()); - assert_eq!(&*actions.borrow(), &["2 a"]); - actions.borrow_mut().clear(); cx.dispatch_keystroke(window_id, &Keystroke::parse("b").unwrap()); - assert_eq!(&*actions.borrow(), &["3 b", "2 b", "1 b", "global b"]); + actions.borrow_mut().clear(); + + cx.dispatch_keystroke(window_id, &Keystroke::parse("c").unwrap()); + assert_eq!(&*actions.borrow(), &["3 c"]); + actions.borrow_mut().clear(); + + cx.dispatch_keystroke(window_id, &Keystroke::parse("d").unwrap()); + assert_eq!(&*actions.borrow(), &["2 d"]); + actions.borrow_mut().clear(); } #[crate::test(self)] diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index e007605cff..c7de035232 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -25,6 +25,7 @@ pub struct KeyPressed { impl_actions!(gpui, [KeyPressed]); pub struct KeymapMatcher { + pub contexts: Vec, pending_views: HashMap, pending_keystrokes: Vec, keymap: Keymap, @@ -33,6 +34,7 @@ pub struct KeymapMatcher { impl KeymapMatcher { pub fn new(keymap: Keymap) -> Self { Self { + contexts: Vec::new(), pending_views: Default::default(), pending_keystrokes: Vec::new(), keymap, @@ -70,7 +72,7 @@ impl KeymapMatcher { pub fn push_keystroke( &mut self, keystroke: Keystroke, - dispatch_path: Vec<(usize, KeymapContext)>, + mut dispatch_path: Vec<(usize, KeymapContext)>, ) -> MatchResult { let mut any_pending = false; let mut matched_bindings: Vec<(usize, Box)> = Vec::new(); @@ -78,7 +80,11 @@ impl KeymapMatcher { let first_keystroke = self.pending_keystrokes.is_empty(); self.pending_keystrokes.push(keystroke.clone()); - for (view_id, context) in dispatch_path { + self.contexts.clear(); + self.contexts + .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1))); + + for (i, (view_id, _)) in dispatch_path.into_iter().enumerate() { // Don't require pending view entry if there are no pending keystrokes if !first_keystroke && !self.pending_views.contains_key(&view_id) { continue; @@ -87,14 +93,15 @@ impl KeymapMatcher { // If there is a previous view context, invalidate that view if it // has changed if let Some(previous_view_context) = self.pending_views.remove(&view_id) { - if previous_view_context != context { + if previous_view_context != self.contexts[i] { continue; } } // Find the bindings which map the pending keystrokes and current context for binding in self.keymap.bindings().iter().rev() { - match binding.match_keys_and_context(&self.pending_keystrokes, &context) { + match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..]) + { BindingMatchResult::Complete(mut action) => { // Swap in keystroke for special KeyPressed action if action.name() == "KeyPressed" && action.namespace() == "gpui" { @@ -105,7 +112,7 @@ impl KeymapMatcher { matched_bindings.push((view_id, action)) } BindingMatchResult::Partial => { - self.pending_views.insert(view_id, context.clone()); + self.pending_views.insert(view_id, self.contexts[i].clone()); any_pending = true; } _ => {} @@ -129,13 +136,13 @@ impl KeymapMatcher { pub fn keystrokes_for_action( &self, action: &dyn Action, - context: &KeymapContext, + contexts: &[KeymapContext], ) -> Option> { self.keymap .bindings() .iter() .rev() - .find_map(|binding| binding.keystrokes_for_action(action, context)) + .find_map(|binding| binding.keystrokes_for_action(action, contexts)) } } @@ -349,27 +356,70 @@ mod tests { } #[test] - fn test_context_predicate_eval() -> Result<()> { - let predicate = KeymapContextPredicate::parse("a && b || c == d")?; + fn test_context_predicate_eval() { + let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap(); let mut context = KeymapContext::default(); context.set.insert("a".into()); - assert!(!predicate.eval(&context)); + assert!(!predicate.eval(&[context])); + let mut context = KeymapContext::default(); + context.set.insert("a".into()); context.set.insert("b".into()); - assert!(predicate.eval(&context)); + assert!(predicate.eval(&[context])); - context.set.remove("b"); + let mut context = KeymapContext::default(); + context.set.insert("a".into()); context.map.insert("c".into(), "x".into()); - assert!(!predicate.eval(&context)); + assert!(!predicate.eval(&[context])); + let mut context = KeymapContext::default(); + context.set.insert("a".into()); context.map.insert("c".into(), "d".into()); - assert!(predicate.eval(&context)); + assert!(predicate.eval(&[context])); - let predicate = KeymapContextPredicate::parse("!a")?; - assert!(predicate.eval(&KeymapContext::default())); + let predicate = KeymapContextPredicate::parse("!a").unwrap(); + assert!(predicate.eval(&[KeymapContext::default()])); + } - Ok(()) + #[test] + fn test_context_child_predicate_eval() { + let predicate = KeymapContextPredicate::parse("a && b > c").unwrap(); + let contexts = [ + context_set(&["e", "f"]), + context_set(&["c", "d"]), // match this context + context_set(&["a", "b"]), + ]; + + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); + + let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap(); + let contexts = [ + context_set(&["f"]), + context_set(&["e"]), // only match this context + context_set(&["c"]), + context_set(&["a", "b"]), + context_set(&["e"]), + context_set(&["c", "d"]), + context_set(&["a", "b"]), + ]; + + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); + assert!(!predicate.eval(&contexts[3..])); + assert!(!predicate.eval(&contexts[4..])); + assert!(!predicate.eval(&contexts[5..])); + assert!(!predicate.eval(&contexts[6..])); + + fn context_set(names: &[&str]) -> KeymapContext { + KeymapContext { + set: names.iter().copied().map(str::to_string).collect(), + ..Default::default() + } + } } #[test] diff --git a/crates/gpui/src/keymap_matcher/binding.rs b/crates/gpui/src/keymap_matcher/binding.rs index b16b7f1552..afd65d4f04 100644 --- a/crates/gpui/src/keymap_matcher/binding.rs +++ b/crates/gpui/src/keymap_matcher/binding.rs @@ -41,24 +41,24 @@ impl Binding { }) } - fn match_context(&self, context: &KeymapContext) -> bool { + fn match_context(&self, contexts: &[KeymapContext]) -> bool { self.context_predicate .as_ref() - .map(|predicate| predicate.eval(context)) + .map(|predicate| predicate.eval(contexts)) .unwrap_or(true) } pub fn match_keys_and_context( &self, pending_keystrokes: &Vec, - context: &KeymapContext, + contexts: &[KeymapContext], ) -> BindingMatchResult { if self .keystrokes .as_ref() .map(|keystrokes| keystrokes.starts_with(&pending_keystrokes)) .unwrap_or(true) - && self.match_context(context) + && self.match_context(contexts) { // If the binding is completed, push it onto the matches list if self @@ -79,9 +79,9 @@ impl Binding { pub fn keystrokes_for_action( &self, action: &dyn Action, - context: &KeymapContext, + contexts: &[KeymapContext], ) -> Option> { - if self.action.eq(action) && self.match_context(context) { + if self.action.eq(action) && self.match_context(contexts) { self.keystrokes.clone() } else { None diff --git a/crates/gpui/src/keymap_matcher/keymap_context.rs b/crates/gpui/src/keymap_matcher/keymap_context.rs index b1dbd0e92d..28f5f80c83 100644 --- a/crates/gpui/src/keymap_matcher/keymap_context.rs +++ b/crates/gpui/src/keymap_matcher/keymap_context.rs @@ -23,6 +23,7 @@ pub enum KeymapContextPredicate { Identifier(String), Equal(String, String), NotEqual(String, String), + Child(Box, Box), Not(Box), And(Box, Box), Or(Box, Box), @@ -39,7 +40,8 @@ impl KeymapContextPredicate { } } - pub fn eval(&self, context: &KeymapContext) -> bool { + 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::Equal(left, right) => context @@ -52,16 +54,14 @@ impl KeymapContextPredicate { .get(left) .map(|value| value != right) .unwrap_or(true), - Self::Not(pred) => !pred.eval(context), - Self::And(left, right) => left.eval(context) && right.eval(context), - Self::Or(left, right) => left.eval(context) || right.eval(context), + Self::Not(pred) => !pred.eval(contexts), + Self::Child(parent, child) => parent.eval(&contexts[1..]) && child.eval(contexts), + Self::And(left, right) => left.eval(contexts) && right.eval(contexts), + Self::Or(left, right) => left.eval(contexts) || right.eval(contexts), } } - fn parse_expr( - mut source: &str, - min_precedence: u32, - ) -> anyhow::Result<(KeymapContextPredicate, &str)> { + fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> { type Op = fn(KeymapContextPredicate, KeymapContextPredicate) -> Result; @@ -70,10 +70,11 @@ impl KeymapContextPredicate { 'parse: loop { for (operator, precedence, constructor) in [ - ("&&", PRECEDENCE_AND, KeymapContextPredicate::new_and as Op), - ("||", PRECEDENCE_OR, KeymapContextPredicate::new_or as Op), - ("==", PRECEDENCE_EQ, KeymapContextPredicate::new_eq as Op), - ("!=", PRECEDENCE_EQ, KeymapContextPredicate::new_neq as Op), + (">", PRECEDENCE_CHILD, Self::new_child as Op), + ("&&", PRECEDENCE_AND, Self::new_and as Op), + ("||", PRECEDENCE_OR, Self::new_or as Op), + ("==", PRECEDENCE_EQ, Self::new_eq as Op), + ("!=", PRECEDENCE_EQ, Self::new_neq as Op), ] { if source.starts_with(operator) && precedence >= min_precedence { source = Self::skip_whitespace(&source[operator.len()..]); @@ -89,7 +90,7 @@ impl KeymapContextPredicate { Ok((predicate, source)) } - fn parse_primary(mut source: &str) -> anyhow::Result<(KeymapContextPredicate, &str)> { + fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> { let next = source .chars() .next() @@ -140,6 +141,10 @@ impl KeymapContextPredicate { Ok(Self::And(Box::new(self), Box::new(other))) } + fn new_child(self, other: Self) -> Result { + Ok(Self::Child(Box::new(self), Box::new(other))) + } + fn new_eq(self, other: Self) -> Result { if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) { Ok(Self::Equal(left, right)) @@ -157,10 +162,11 @@ impl KeymapContextPredicate { } } -const PRECEDENCE_OR: u32 = 1; -const PRECEDENCE_AND: u32 = 2; -const PRECEDENCE_EQ: u32 = 3; -const PRECEDENCE_NOT: u32 = 4; +const PRECEDENCE_CHILD: u32 = 1; +const PRECEDENCE_OR: u32 = 2; +const PRECEDENCE_AND: u32 = 3; +const PRECEDENCE_EQ: u32 = 4; +const PRECEDENCE_NOT: u32 = 5; #[cfg(test)] mod tests { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 5c13f7467a..bbaf1ed0bb 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -573,7 +573,7 @@ pub struct LayoutContext<'a> { impl<'a> LayoutContext<'a> { pub(crate) fn keystrokes_for_action( - &self, + &mut self, action: &dyn Action, ) -> Option> { self.app From 3312a063688f047566cf95f7f5c9972e10f5da5b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 16:01:15 -0800 Subject: [PATCH 040/108] Move focus back from buffer search using tab, not cmd-f --- assets/keymaps/default.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index fbbb517c93..a0f437cf91 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -186,10 +186,10 @@ } }, { - "context": "BufferSearchBar", + "context": "BufferSearchBar > Editor", "bindings": { "escape": "buffer_search::Dismiss", - "cmd-f": "buffer_search::FocusEditor", + "tab": "buffer_search::FocusEditor", "enter": "search::SelectNextMatch", "shift-enter": "search::SelectPrevMatch" } From 6810490bf4f856f187e53ca2af7cf1a0c04299d4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 16:11:13 -0800 Subject: [PATCH 041/108] Remove tree-sitter dependency from gpui --- Cargo.lock | 1 - crates/gpui/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e79a68fc4..d9976fbba7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2563,7 +2563,6 @@ dependencies = [ "sum_tree", "time 0.3.17", "tiny-skia", - "tree-sitter", "usvg", "util", "waker-fn", diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 683e3bdfcd..5153c1f7c1 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -45,7 +45,6 @@ smallvec = { version = "1.6", features = ["union"] } smol = "1.2" time = { version = "0.3", features = ["serde", "serde-well-known"] } tiny-skia = "0.5" -tree-sitter = "0.20" usvg = "0.14" waker-fn = "1.1.0" From 244f2593314e6c83c2c890af6c5351fea0ec037b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 17:42:00 -0800 Subject: [PATCH 042/108] Always auto-indent in block-wise mode when pasting If the text was copied outside of Zed, so the original indent column is unknown, then act as if the first line was copied in its entirety. --- crates/editor/src/editor.rs | 4 +++- crates/language/src/buffer.rs | 30 ++++++++++++++++-------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8847c01af7..fb8e37077e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2020,7 +2020,9 @@ impl Editor { old_selections .iter() .map(|s| (s.start..s.end, text.clone())), - Some(AutoindentMode::EachLine), + Some(AutoindentMode::Block { + original_indent_columns: Vec::new(), + }), cx, ); anchors diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 9ceb6b32a9..d1e7316f1b 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1332,13 +1332,6 @@ impl Buffer { let edit_id = edit_operation.local_timestamp(); if let Some((before_edit, mode)) = autoindent_request { - let (start_columns, is_block_mode) = match mode { - AutoindentMode::Block { - original_indent_columns: start_columns, - } => (start_columns, true), - AutoindentMode::EachLine => (Default::default(), false), - }; - let mut delta = 0isize; let entries = edits .into_iter() @@ -1352,7 +1345,7 @@ impl Buffer { let mut range_of_insertion_to_indent = 0..new_text_len; let mut first_line_is_new = false; - let mut start_column = None; + let mut original_indent_column = None; // When inserting an entire line at the beginning of an existing line, // treat the insertion as new. @@ -1364,14 +1357,23 @@ impl Buffer { // When inserting text starting with a newline, avoid auto-indenting the // previous line. - if new_text[range_of_insertion_to_indent.clone()].starts_with('\n') { + if new_text.starts_with('\n') { range_of_insertion_to_indent.start += 1; first_line_is_new = true; } // Avoid auto-indenting after the insertion. - if is_block_mode { - start_column = start_columns.get(ix).copied(); + if let AutoindentMode::Block { + original_indent_columns, + } = &mode + { + original_indent_column = + Some(original_indent_columns.get(ix).copied().unwrap_or_else(|| { + indent_size_for_text( + new_text[range_of_insertion_to_indent.clone()].chars(), + ) + .len + })); if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') { range_of_insertion_to_indent.end -= 1; } @@ -1379,7 +1381,7 @@ impl Buffer { AutoindentRequestEntry { first_line_is_new, - original_indent_column: start_column, + original_indent_column, indent_size: before_edit.language_indent_size_at(range.start, cx), range: self.anchor_before(new_start + range_of_insertion_to_indent.start) ..self.anchor_after(new_start + range_of_insertion_to_indent.end), @@ -1390,7 +1392,7 @@ impl Buffer { self.autoindent_requests.push(Arc::new(AutoindentRequest { before_edit, entries, - is_block_mode, + is_block_mode: matches!(mode, AutoindentMode::Block { .. }), })); } @@ -2397,7 +2399,7 @@ impl BufferSnapshot { } } -pub fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize { +fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize { indent_size_for_text(text.chars_at(Point::new(row, 0))) } From 0bd6f9b6ceb09d639e8bb9aec886f5acb2008305 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 18:06:58 -0800 Subject: [PATCH 043/108] Add a test for block-wise auto-indent without original indent info --- crates/language/src/buffer_tests.rs | 95 +++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 6 deletions(-) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 09ccc5d621..b7f35e9ad5 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -995,12 +995,17 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { .unindent(); let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + // When this text was copied, both of the quotation marks were at the same + // indent level, but the indentation of the first line was not included in + // the copied text. This information is retained in the + // 'original_indent_columns' vector. + let original_indent_columns = vec![4]; let inserted_text = r#" " - c - d - e - " + c + d + e + " "# .unindent(); @@ -1009,7 +1014,7 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { buffer.edit( [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())], Some(AutoindentMode::Block { - original_indent_columns: vec![0], + original_indent_columns: original_indent_columns.clone(), }), cx, ); @@ -1037,7 +1042,7 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { buffer.edit( [(Point::new(2, 8)..Point::new(2, 8), inserted_text)], Some(AutoindentMode::Block { - original_indent_columns: vec![0], + original_indent_columns: original_indent_columns.clone(), }), cx, ); @@ -1060,6 +1065,84 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { }); } +#[gpui::test] +fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut MutableAppContext) { + cx.set_global(Settings::test(cx)); + cx.add_model(|cx| { + let text = r#" + fn a() { + if b() { + + } + } + "# + .unindent(); + let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + + // The original indent columns are not known, so this text is + // auto-indented in a block as if the first line was copied in + // its entirety. + let original_indent_columns = Vec::new(); + let inserted_text = " c\n .d()\n .e();"; + + // Insert the block at column zero. The entire block is indented + // so that the first line matches the previous line's indentation. + buffer.edit( + [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())], + Some(AutoindentMode::Block { + original_indent_columns: original_indent_columns.clone(), + }), + cx, + ); + assert_eq!( + buffer.text(), + r#" + fn a() { + if b() { + c + .d() + .e(); + } + } + "# + .unindent() + ); + + // Grouping is disabled in tests, so we need 2 undos + buffer.undo(cx); // Undo the auto-indent + buffer.undo(cx); // Undo the original edit + + // Insert the block at a deeper indent level. The entire block is outdented. + buffer.edit( + [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))], + None, + cx, + ); + buffer.edit( + [(Point::new(2, 12)..Point::new(2, 12), inserted_text)], + Some(AutoindentMode::Block { + original_indent_columns: Vec::new(), + }), + cx, + ); + assert_eq!( + buffer.text(), + r#" + fn a() { + if b() { + c + .d() + .e(); + } + } + "# + .unindent() + ); + + buffer + }); +} + #[gpui::test] fn test_autoindent_language_without_indents_query(cx: &mut MutableAppContext) { cx.set_global(Settings::test(cx)); From 467e5691b968134e8d29ee6c4a2ad2c73fcf134c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Jan 2023 16:22:47 +0100 Subject: [PATCH 044/108] Include saved mtime and fingerprint when serializing buffers This still doesn't include: - An assertion in the randomized test to ensure buffers are not spuriously marked as modified - Sending an update when synchronizing buffers after a reconnection --- crates/language/src/buffer.rs | 7 +++++++ crates/rpc/proto/zed.proto | 2 ++ 2 files changed, 9 insertions(+) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 9ceb6b32a9..b92f0a7c3e 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -385,6 +385,11 @@ impl Buffer { rpc::proto::LineEnding::from_i32(message.line_ending) .ok_or_else(|| anyhow!("missing line_ending"))?, )); + this.saved_version_fingerprint = message.saved_version_fingerprint; + this.saved_mtime = message + .saved_mtime + .ok_or_else(|| anyhow!("invalid saved_mtime"))? + .into(); Ok(this) } @@ -395,6 +400,8 @@ impl Buffer { base_text: self.base_text().to_string(), diff_base: self.diff_base.as_ref().map(|h| h.to_string()), line_ending: proto::serialize_line_ending(self.line_ending()) as i32, + saved_version_fingerprint: self.saved_version_fingerprint.clone(), + saved_mtime: Some(self.saved_mtime.into()), } } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 8626806abe..86494df05b 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -975,6 +975,8 @@ message BufferState { string base_text = 3; optional string diff_base = 4; LineEnding line_ending = 5; + string saved_version_fingerprint = 6; + Timestamp saved_mtime = 7; } message BufferChunk { From 2cd9db1cfeb4dd787d88a806aca1301d69060136 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:20:09 +0100 Subject: [PATCH 045/108] Ensure `Buffer::{is_dirty,has_conflict}` converge in randomized test --- .../src/tests/randomized_integration_tests.rs | 99 ++++++++++++++----- 1 file changed, 77 insertions(+), 22 deletions(-) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index 11c62005c2..8517385798 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -10,7 +10,7 @@ use collections::BTreeMap; use fs::{FakeFs, Fs as _}; use futures::StreamExt as _; use gpui::{executor::Deterministic, ModelHandle, TestAppContext}; -use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16}; +use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16, Rope}; use lsp::FakeLanguageServer; use parking_lot::Mutex; use project::{search::SearchQuery, Project}; @@ -19,7 +19,12 @@ use rand::{ prelude::*, }; use settings::Settings; -use std::{env, ffi::OsStr, path::PathBuf, sync::Arc}; +use std::{ + env, + ffi::OsStr, + path::{Path, PathBuf}, + sync::Arc, +}; #[gpui::test(iterations = 100)] async fn test_random_collaboration( @@ -398,6 +403,14 @@ async fn test_random_collaboration( let guest_diff_base = guest_buffer .read_with(client_cx, |b, _| b.diff_base().map(ToString::to_string)); assert_eq!(guest_diff_base, host_diff_base); + + let host_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); + let guest_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); + assert_eq!(guest_is_dirty, host_is_dirty); + + let host_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); + let guest_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); + assert_eq!(guest_has_conflict, host_has_conflict); } } } @@ -638,14 +651,7 @@ async fn randomly_mutate_git(client: &mut TestClient, rng: &Mutex) { client.fs.create_dir(&git_dir_path).await.unwrap(); } - let mut child_paths = client.fs.read_dir(&dir_path).await.unwrap(); - let mut child_file_paths = Vec::new(); - while let Some(child_path) = child_paths.next().await { - let child_path = child_path.unwrap(); - if client.fs.is_file(&child_path).await { - child_file_paths.push(child_path); - } - } + let mut child_file_paths = child_file_paths(client, &dir_path).await; let count = rng.lock().gen_range(0..=child_file_paths.len()); child_file_paths.shuffle(&mut *rng.lock()); child_file_paths.truncate(count); @@ -669,26 +675,63 @@ async fn randomly_mutate_git(client: &mut TestClient, rng: &Mutex) { } async fn randomly_mutate_fs(client: &mut TestClient, rng: &Mutex) { - let is_dir = rng.lock().gen::(); - let mut new_path = client + let parent_dir_path = client .fs .directories() .await .choose(&mut *rng.lock()) .unwrap() .clone(); - new_path.push(gen_file_name(rng)); + + let is_dir = rng.lock().gen::(); if is_dir { - log::info!("{}: creating local dir at {:?}", client.username, new_path); - client.fs.create_dir(&new_path).await.unwrap(); + let mut dir_path = parent_dir_path.clone(); + dir_path.push(gen_file_name(rng)); + log::info!("{}: creating local dir at {:?}", client.username, dir_path); + client.fs.create_dir(&dir_path).await.unwrap(); } else { - new_path.set_extension("rs"); - log::info!("{}: creating local file at {:?}", client.username, new_path); - client - .fs - .create_file(&new_path, Default::default()) - .await - .unwrap(); + let child_file_paths = child_file_paths(client, &parent_dir_path).await; + let create_new_file = child_file_paths.is_empty() || rng.lock().gen(); + let text = Alphanumeric.sample_string(&mut *rng.lock(), 16); + if create_new_file { + let mut file_path = parent_dir_path.clone(); + file_path.push(gen_file_name(rng)); + file_path.set_extension("rs"); + log::info!( + "{}: creating local file at {:?}", + client.username, + file_path + ); + client + .fs + .create_file(&file_path, Default::default()) + .await + .unwrap(); + log::info!( + "{}: setting local file {:?} text to {:?}", + client.username, + file_path, + text + ); + client + .fs + .save(&file_path, &Rope::from(text.as_str()), fs::LineEnding::Unix) + .await + .unwrap(); + } else { + let file_path = child_file_paths.choose(&mut *rng.lock()).unwrap(); + log::info!( + "{}: setting local file {:?} text to {:?}", + client.username, + file_path, + text + ); + client + .fs + .save(file_path, &Rope::from(text.as_str()), fs::LineEnding::Unix) + .await + .unwrap(); + } } } @@ -1154,3 +1197,15 @@ fn gen_file_name(rng: &Mutex) -> String { } name } + +async fn child_file_paths(client: &TestClient, dir_path: &Path) -> Vec { + let mut child_paths = client.fs.read_dir(dir_path).await.unwrap(); + let mut child_file_paths = Vec::new(); + while let Some(child_path) = child_paths.next().await { + let child_path = child_path.unwrap(); + if client.fs.is_file(&child_path).await { + child_file_paths.push(child_path); + } + } + child_file_paths +} From bb200aa082f7bb760463fd55eebb562dc86d1601 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:20:50 +0100 Subject: [PATCH 046/108] Relay saved version metadata to ensure buffers modified state converges --- crates/language/src/buffer.rs | 10 ++++++++++ crates/project/src/project.rs | 13 +++++++++++++ crates/rpc/proto/zed.proto | 5 +++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index b92f0a7c3e..0e546d4ba0 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -385,6 +385,7 @@ impl Buffer { rpc::proto::LineEnding::from_i32(message.line_ending) .ok_or_else(|| anyhow!("missing line_ending"))?, )); + this.saved_version = proto::deserialize_version(message.saved_version); this.saved_version_fingerprint = message.saved_version_fingerprint; this.saved_mtime = message .saved_mtime @@ -400,6 +401,7 @@ impl Buffer { base_text: self.base_text().to_string(), diff_base: self.diff_base.as_ref().map(|h| h.to_string()), line_ending: proto::serialize_line_ending(self.line_ending()) as i32, + saved_version: proto::serialize_version(&self.saved_version), saved_version_fingerprint: self.saved_version_fingerprint.clone(), saved_mtime: Some(self.saved_mtime.into()), } @@ -556,6 +558,14 @@ impl Buffer { &self.saved_version } + pub fn saved_version_fingerprint(&self) -> &str { + &self.saved_version_fingerprint + } + + pub fn saved_mtime(&self) -> SystemTime { + self.saved_mtime + } + pub fn set_language(&mut self, language: Option>, cx: &mut ModelContext) { self.syntax_map.lock().clear(); self.language = language; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 478271118c..d3b3c11f2e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -5209,6 +5209,19 @@ impl Project { }) .log_err(); + client + .send(proto::BufferReloaded { + project_id, + buffer_id, + version: language::proto::serialize_version(buffer.saved_version()), + mtime: Some(buffer.saved_mtime().into()), + fingerprint: buffer.saved_version_fingerprint().into(), + line_ending: language::proto::serialize_line_ending( + buffer.line_ending(), + ) as i32, + }) + .log_err(); + cx.background() .spawn( async move { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 86494df05b..ba481ce45b 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -975,8 +975,9 @@ message BufferState { string base_text = 3; optional string diff_base = 4; LineEnding line_ending = 5; - string saved_version_fingerprint = 6; - Timestamp saved_mtime = 7; + repeated VectorClockEntry saved_version = 6; + string saved_version_fingerprint = 7; + Timestamp saved_mtime = 8; } message BufferChunk { From fcf97ab41eabfa6719138a368e0d0301e3daa030 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:21:20 +0100 Subject: [PATCH 047/108] Bump protocol version --- crates/rpc/src/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 39cdea7165..b05bc17906 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 45; +pub const PROTOCOL_VERSION: u32 = 46; From 7726a9ec3d8e2390f55fb0c10585c8861626d5f7 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 17 Jan 2023 10:42:53 -0500 Subject: [PATCH 048/108] Utilize fit autoscroll for various go-to actions --- crates/editor/src/editor.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8847c01af7..e2bf3c775b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4799,7 +4799,7 @@ impl Editor { if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() { let (group_id, jump_to) = popover.activation_info(); if self.activate_diagnostics(group_id, cx) { - self.change_selections(Some(Autoscroll::center()), cx, |s| { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { let mut new_selection = s.newest_anchor().clone(); new_selection.collapse_to(jump_to, SelectionGoal::None); s.select_anchors(vec![new_selection.clone()]); @@ -4845,7 +4845,7 @@ impl Editor { if let Some((primary_range, group_id)) = group { if self.activate_diagnostics(group_id, cx) { - self.change_selections(Some(Autoscroll::center()), cx, |s| { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select(vec![Selection { id: selection.id, start: primary_range.start, @@ -4920,7 +4920,7 @@ impl Editor { .dedup(); if let Some(hunk) = hunks.next() { - this.change_selections(Some(Autoscroll::center()), cx, |s| { + this.change_selections(Some(Autoscroll::fit()), cx, |s| { let row = hunk.start_display_row(); let point = DisplayPoint::new(row, 0); s.select_display_ranges([point..point]); @@ -5018,7 +5018,7 @@ impl Editor { if editor_handle != target_editor_handle { pane.update(cx, |pane, _| pane.disable_history()); } - target_editor.change_selections(Some(Autoscroll::center()), cx, |s| { + target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges([range]); }); From cc788dc5f74164a9bab5eeefed015e7be165157f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:46:06 +0100 Subject: [PATCH 049/108] Verify `saved_version`, `saved_version_fingerprint` and `saved_mtime` --- .../src/tests/randomized_integration_tests.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index 8517385798..34346e2128 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -404,6 +404,25 @@ async fn test_random_collaboration( .read_with(client_cx, |b, _| b.diff_base().map(ToString::to_string)); assert_eq!(guest_diff_base, host_diff_base); + let host_saved_version = + host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); + let guest_saved_version = + host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); + assert_eq!(guest_saved_version, host_saved_version); + + let host_saved_version_fingerprint = host_buffer + .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); + let guest_saved_version_fingerprint = host_buffer + .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); + assert_eq!( + guest_saved_version_fingerprint, + host_saved_version_fingerprint + ); + + let host_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); + let guest_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); + assert_eq!(guest_saved_mtime, host_saved_mtime); + let host_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); let guest_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); assert_eq!(guest_is_dirty, host_is_dirty); From dc88a67f502dc77273b0170d7358ce7c45f79453 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 18:09:45 +0100 Subject: [PATCH 050/108] Fix assertions --- .../collab/src/tests/randomized_integration_tests.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index 34346e2128..ed8f984d27 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -407,28 +407,28 @@ async fn test_random_collaboration( let host_saved_version = host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); let guest_saved_version = - host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); + guest_buffer.read_with(client_cx, |b, _| b.saved_version().clone()); assert_eq!(guest_saved_version, host_saved_version); let host_saved_version_fingerprint = host_buffer .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); - let guest_saved_version_fingerprint = host_buffer - .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); + let guest_saved_version_fingerprint = guest_buffer + .read_with(client_cx, |b, _| b.saved_version_fingerprint().to_string()); assert_eq!( guest_saved_version_fingerprint, host_saved_version_fingerprint ); let host_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); - let guest_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); + let guest_saved_mtime = guest_buffer.read_with(client_cx, |b, _| b.saved_mtime()); assert_eq!(guest_saved_mtime, host_saved_mtime); let host_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); - let guest_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); + let guest_is_dirty = guest_buffer.read_with(client_cx, |b, _| b.is_dirty()); assert_eq!(guest_is_dirty, host_is_dirty); let host_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); - let guest_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); + let guest_has_conflict = guest_buffer.read_with(client_cx, |b, _| b.has_conflict()); assert_eq!(guest_has_conflict, host_has_conflict); } } From c3b102f5a82efa010b6d0fd9824b76f562a198ca Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 17 Jan 2023 16:46:01 -0500 Subject: [PATCH 051/108] Add users to mailing list when using an invite link --- crates/collab/src/api.rs | 3 +++ crates/collab/src/db.rs | 2 ++ crates/collab/src/db/tests.rs | 40 ++++++++++++++++++++++++++--------- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 921b4189e8..235ed66424 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -353,6 +353,8 @@ pub struct CreateInviteFromCodeParams { invite_code: String, email_address: String, device_id: Option, + #[serde(default)] + added_to_mailing_list: bool, } async fn create_invite_from_code( @@ -365,6 +367,7 @@ async fn create_invite_from_code( ¶ms.invite_code, ¶ms.email_address, params.device_id.as_deref(), + params.added_to_mailing_list, ) .await?, )) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 1bbfac85d3..63ea7fdd9e 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -882,6 +882,7 @@ impl Database { code: &str, email_address: &str, device_id: Option<&str>, + added_to_mailing_list: bool, ) -> Result { self.transaction(|tx| async move { let existing_user = user::Entity::find() @@ -933,6 +934,7 @@ impl Database { platform_windows: ActiveValue::set(false), platform_unknown: ActiveValue::set(true), device_id: ActiveValue::set(device_id.map(|device_id| device_id.into())), + added_to_mailing_list: ActiveValue::set(added_to_mailing_list), ..Default::default() }) .on_conflict( diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index 9d42c11f8b..1e27167545 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -567,7 +567,12 @@ async fn test_invite_codes() { // User 2 redeems the invite code and becomes a contact of user 1. let user2_invite = db - .create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id")) + .create_invite_from_code( + &invite_code, + "user2@example.com", + Some("user-2-device-id"), + true, + ) .await .unwrap(); let NewUserResult { @@ -617,7 +622,7 @@ async fn test_invite_codes() { // User 3 redeems the invite code and becomes a contact of user 1. let user3_invite = db - .create_invite_from_code(&invite_code, "user3@example.com", None) + .create_invite_from_code(&invite_code, "user3@example.com", None, true) .await .unwrap(); let NewUserResult { @@ -672,9 +677,14 @@ async fn test_invite_codes() { ); // Trying to reedem the code for the third time results in an error. - db.create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id")) - .await - .unwrap_err(); + db.create_invite_from_code( + &invite_code, + "user4@example.com", + Some("user-4-device-id"), + true, + ) + .await + .unwrap_err(); // Invite count can be updated after the code has been created. db.set_invite_count_for_user(user1, 2).await.unwrap(); @@ -684,7 +694,12 @@ async fn test_invite_codes() { // User 4 can now redeem the invite code and becomes a contact of user 1. let user4_invite = db - .create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id")) + .create_invite_from_code( + &invite_code, + "user4@example.com", + Some("user-4-device-id"), + true, + ) .await .unwrap(); let user4 = db @@ -739,9 +754,14 @@ async fn test_invite_codes() { ); // An existing user cannot redeem invite codes. - db.create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id")) - .await - .unwrap_err(); + db.create_invite_from_code( + &invite_code, + "user2@example.com", + Some("user-2-device-id"), + true, + ) + .await + .unwrap_err(); let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap(); assert_eq!(invite_count, 1); @@ -763,7 +783,7 @@ async fn test_invite_codes() { db.set_invite_count_for_user(user5, 5).await.unwrap(); let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap(); let user5_invite_to_user1 = db - .create_invite_from_code(&user5_invite_code, "user1@different.com", None) + .create_invite_from_code(&user5_invite_code, "user1@different.com", None, true) .await .unwrap(); let user1_2 = db From 292708573fc2e85602e452bd4551acaeb7f64c7d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 17 Jan 2023 16:13:26 -0800 Subject: [PATCH 052/108] Replace MultiBuffer::files with ::for_each_buffer --- crates/editor/src/items.rs | 14 ++++++++------ crates/editor/src/multi_buffer.rs | 12 +++++------- crates/workspace/src/pane.rs | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index f8fa3c9df0..1fe0329405 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -565,12 +565,14 @@ impl Item for Editor { } fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - self.buffer - .read(cx) - .files(cx) - .into_iter() - .filter_map(|file| File::from_dyn(Some(file))?.project_entry_id(cx)) - .collect() + let mut result = SmallVec::new(); + self.buffer.read(cx).for_each_buffer(|buffer| { + let buffer = buffer.read(cx); + if let Some(file) = File::from_dyn(buffer.file()) { + result.extend(file.project_entry_id(cx)); + } + }); + result } fn is_singleton(&self, cx: &AppContext) -> bool { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 2347d9a63d..c8a62c6a57 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -9,11 +9,10 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, - DiagnosticEntry, File, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, + DiagnosticEntry, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; -use smallvec::SmallVec; use std::{ borrow::Cow, cell::{Ref, RefCell}, @@ -1311,12 +1310,11 @@ impl MultiBuffer { .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset)) } - pub fn files<'a>(&'a self, cx: &'a AppContext) -> SmallVec<[&'a Arc; 2]> { - let buffers = self.buffers.borrow(); - buffers + pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle)) { + self.buffers + .borrow() .values() - .filter_map(|buffer| buffer.buffer.read(cx).file()) - .collect() + .for_each(|state| f(&state.buffer)) } pub fn title<'a>(&'a self, cx: &'a AppContext) -> Cow<'a, str> { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index bd355b8b3e..735e165c7d 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -482,7 +482,7 @@ impl Pane { ) -> Box { let existing_item = pane.update(cx, |pane, cx| { for (index, item) in pane.items.iter().enumerate() { - if item.project_path(cx).is_some() + if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [project_entry_id] { let item = item.boxed_clone(); From c9a306b4ac0995aa27d3f2ff8f984bf462446b1f Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 17 Jan 2023 16:58:55 -0800 Subject: [PATCH 053/108] Change sidebars to use the window width as a max width rather than participating in the flex co-authored-by: Mikayla --- crates/workspace/src/dock.rs | 26 ++++++++++++++++++++++++-- crates/workspace/src/workspace.rs | 27 +++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 69c6db51ab..747541f87d 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -2,8 +2,10 @@ use collections::HashMap; use gpui::{ actions, elements::{ChildView, Container, Empty, MouseEventHandler, ParentElement, Side, Stack, Svg}, + geometry::vector::Vector2F, impl_internal_actions, Border, CursorStyle, Element, ElementBox, Entity, MouseButton, - MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle, + MutableAppContext, RenderContext, SizeConstraint, View, ViewContext, ViewHandle, + WeakViewHandle, }; use serde::Deserialize; use settings::{DockAnchor, Settings}; @@ -312,7 +314,27 @@ impl Dock { } }); - resizable.flex(5., false).boxed() + if anchor == DockAnchor::Right { + resizable + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new(cx.window_size.x() * 0.8, constraint.max.y()), + ) + }) + .boxed() + } else { + resizable + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(constraint.min.x(), 50.), + Vector2F::new(constraint.max.x(), cx.window_size.y() * 0.8), + ) + }) + .boxed() + } } DockAnchor::Expanded => { enum ExpandedDockWash {} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index eefce67371..de5b50b468 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -32,12 +32,13 @@ use futures::{ use gpui::{ actions, elements::*, + geometry::vector::Vector2F, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, - MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -2471,7 +2472,16 @@ impl View for Workspace { if self.left_sidebar.read(cx).active_item().is_some() { Some( ChildView::new(&self.left_sidebar, cx) - .flex(0.8, false) + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new( + cx.window_size.x() * 0.8, + constraint.max.y(), + ), + ) + }) .boxed(), ) } else { @@ -2508,7 +2518,16 @@ impl View for Workspace { if self.right_sidebar.read(cx).active_item().is_some() { Some( ChildView::new(&self.right_sidebar, cx) - .flex(0.8, false) + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new( + cx.window_size.x() * 0.8, + constraint.max.y(), + ), + ) + }) .boxed(), ) } else { From 8651320c9f129ea2bf12f26ea425d36f439e9313 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 17 Jan 2023 17:21:06 -0800 Subject: [PATCH 054/108] Make workspace items expose their underlying models, remove file-related methods --- crates/collab/src/tests/integration_tests.rs | 6 +- crates/diagnostics/src/diagnostics.rs | 9 +- crates/editor/src/items.rs | 34 ++---- crates/project/src/project.rs | 10 +- crates/search/src/project_search.rs | 9 +- crates/terminal_view/src/terminal_view.rs | 11 +- crates/theme_testbench/src/theme_testbench.rs | 21 ++-- crates/workspace/src/item.rs | 115 +++++++++++++----- crates/workspace/src/pane.rs | 45 +++---- crates/workspace/src/shared_screen.rs | 19 +-- crates/workspace/src/workspace.rs | 115 ++++++++---------- 11 files changed, 198 insertions(+), 196 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 729da6d109..3f2a777f87 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -32,7 +32,9 @@ use std::{ sync::Arc, }; use unindent::Unindent as _; -use workspace::{item::Item, shared_screen::SharedScreen, SplitDirection, ToggleFollow, Workspace}; +use workspace::{ + item::ItemHandle as _, shared_screen::SharedScreen, SplitDirection, ToggleFollow, Workspace, +}; #[ctor::ctor] fn init_logger() { @@ -5602,7 +5604,7 @@ async fn test_following( }); assert!(cx_b.read(|cx| editor_b2.is_focused(cx))); assert_eq!( - editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)), + cx_b.read(|cx| editor_b2.project_path(cx)), Some((worktree_id, "2.txt").into()) ); assert_eq!( diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index e98aa645d5..d3078bce81 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -21,7 +21,6 @@ use language::{ use project::{DiagnosticSummary, Project, ProjectPath}; use serde_json::json; use settings::Settings; -use smallvec::SmallVec; use std::{ any::{Any, TypeId}, cmp::Ordering, @@ -521,12 +520,8 @@ impl Item for ProjectDiagnosticsEditor { ) } - fn project_path(&self, _: &AppContext) -> Option { - None - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - self.editor.project_entry_ids(cx) + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.editor.for_each_project_item(cx, f) } fn is_singleton(&self, _: &AppContext) -> bool { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 1fe0329405..6568fe78dd 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -14,11 +14,10 @@ use gpui::{ RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::proto::serialize_anchor as serialize_text_anchor; -use language::{Bias, Buffer, File as _, OffsetRangeExt, Point, SelectionGoal}; -use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath}; +use language::{Bias, Buffer, OffsetRangeExt, Point, SelectionGoal}; +use project::{FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; -use smallvec::SmallVec; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -555,24 +554,10 @@ impl Item for Editor { .boxed() } - fn project_path(&self, cx: &AppContext) -> Option { - let buffer = self.buffer.read(cx).as_singleton()?; - let file = buffer.read(cx).file(); - File::from_dyn(file).map(|file| ProjectPath { - worktree_id: file.worktree_id(cx), - path: file.path().clone(), - }) - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - let mut result = SmallVec::new(); - self.buffer.read(cx).for_each_buffer(|buffer| { - let buffer = buffer.read(cx); - if let Some(file) = File::from_dyn(buffer.file()) { - result.extend(file.project_entry_id(cx)); - } - }); - result + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.buffer + .read(cx) + .for_each_buffer(|buffer| f(buffer.id(), buffer.read(cx))); } fn is_singleton(&self, cx: &AppContext) -> bool { @@ -609,7 +594,12 @@ impl Item for Editor { } fn can_save(&self, cx: &AppContext) -> bool { - !self.buffer().read(cx).is_singleton() || self.project_path(cx).is_some() + let buffer = &self.buffer().read(cx); + if let Some(buffer) = buffer.as_singleton() { + buffer.read(cx).project_path(cx).is_some() + } else { + true + } } fn save( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d3b3c11f2e..66b016e27c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -67,8 +67,9 @@ use util::{debug_panic, defer, post_inc, ResultExt, TryFutureExt as _}; pub use fs::*; pub use worktree::*; -pub trait Item: Entity { +pub trait Item { fn entry_id(&self, cx: &AppContext) -> Option; + fn project_path(&self, cx: &AppContext) -> Option; } // Language server state is stored across 3 collections: @@ -6401,4 +6402,11 @@ impl Item for Buffer { fn entry_id(&self, cx: &AppContext) -> Option { File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx)) } + + fn project_path(&self, cx: &AppContext) -> Option { + File::from_dyn(self.file()).map(|file| ProjectPath { + worktree_id: file.worktree_id(cx), + path: file.path().clone(), + }) + } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 9d2f31e269..d30f4d804c 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -15,7 +15,6 @@ use gpui::{ use menu::Confirm; use project::{search::SearchQuery, Project}; use settings::Settings; -use smallvec::SmallVec; use std::{ any::{Any, TypeId}, ops::Range, @@ -264,12 +263,8 @@ impl Item for ProjectSearchView { .boxed() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - None - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - self.results_editor.project_entry_ids(cx) + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.results_editor.for_each_project_item(cx, f) } fn is_singleton(&self, _: &AppContext) -> bool { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index e02a9758dd..847dfc5ee5 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -18,10 +18,9 @@ use gpui::{ AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use project::{LocalWorktree, Project, ProjectPath}; +use project::{LocalWorktree, Project}; use serde::Deserialize; use settings::{Settings, TerminalBlink, WorkingDirectory}; -use smallvec::SmallVec; use smol::Timer; use terminal::{ alacritty_terminal::{ @@ -616,13 +615,7 @@ impl Item for TerminalView { None } - fn project_path(&self, _cx: &gpui::AppContext) -> Option { - None - } - - fn project_entry_ids(&self, _cx: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - SmallVec::new() - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} fn is_singleton(&self, _cx: &gpui::AppContext) -> bool { false diff --git a/crates/theme_testbench/src/theme_testbench.rs b/crates/theme_testbench/src/theme_testbench.rs index cf9f03de45..3cda5d3e51 100644 --- a/crates/theme_testbench/src/theme_testbench.rs +++ b/crates/theme_testbench/src/theme_testbench.rs @@ -6,12 +6,11 @@ use gpui::{ Padding, ParentElement, }, fonts::TextStyle, - Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + AppContext, Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task, + View, ViewContext, ViewHandle, WeakViewHandle, }; -use project::{Project, ProjectEntryId, ProjectPath}; +use project::Project; use settings::Settings; -use smallvec::SmallVec; use theme::{ColorScheme, Layer, Style, StyleSet}; use workspace::{ item::{Item, ItemEvent}, @@ -306,7 +305,7 @@ impl Item for ThemeTestbench { &self, _: Option, style: &theme::Tab, - _: &gpui::AppContext, + _: &AppContext, ) -> gpui::ElementBox { Label::new("Theme Testbench".into(), style.label.clone()) .aligned() @@ -314,21 +313,15 @@ impl Item for ThemeTestbench { .boxed() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - None - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> { - SmallVec::new() - } - - fn is_singleton(&self, _: &gpui::AppContext) -> bool { + fn is_singleton(&self, _: &AppContext) -> bool { false } fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} - fn can_save(&self, _: &gpui::AppContext) -> bool { + fn can_save(&self, _: &AppContext) -> bool { false } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 19385038e4..9d5b94bddb 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -49,8 +49,7 @@ pub trait Item: View { } fn tab_content(&self, detail: Option, style: &theme::Tab, cx: &AppContext) -> ElementBox; - fn project_path(&self, cx: &AppContext) -> Option; - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)); fn is_singleton(&self, cx: &AppContext) -> bool; fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext); fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option @@ -147,6 +146,8 @@ pub trait ItemHandle: 'static + fmt::Debug { -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; + fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>; + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)); fn is_singleton(&self, cx: &AppContext) -> bool; fn boxed_clone(&self) -> Box; fn clone_on_split( @@ -240,11 +241,36 @@ impl ItemHandle for ViewHandle { } fn project_path(&self, cx: &AppContext) -> Option { - self.read(cx).project_path(cx) + let this = self.read(cx); + let mut result = None; + if this.is_singleton(cx) { + this.for_each_project_item(cx, &mut |_, item| { + result = item.project_path(cx); + }); + } + result } fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - self.read(cx).project_entry_ids(cx) + let mut result = SmallVec::new(); + self.read(cx).for_each_project_item(cx, &mut |_, item| { + if let Some(id) = item.entry_id(cx) { + result.push(id); + } + }); + result + } + + fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]> { + let mut result = SmallVec::new(); + self.read(cx).for_each_project_item(cx, &mut |id, _| { + result.push(id); + }); + result + } + + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.read(cx).for_each_project_item(cx, f) } fn is_singleton(&self, cx: &AppContext) -> bool { @@ -582,7 +608,7 @@ impl WeakItemHandle for WeakViewHandle { } pub trait ProjectItem: Item { - type Item: project::Item; + type Item: project::Item + gpui::Entity; fn for_project_item( project: ModelHandle, @@ -690,18 +716,18 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { - use std::{any::Any, borrow::Cow, cell::Cell}; - + use super::{Item, ItemEvent}; + use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ - elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext, Task, - View, ViewContext, ViewHandle, WeakViewHandle, + elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, + RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use project::{Project, ProjectEntryId, ProjectPath}; - use smallvec::SmallVec; + use std::{any::Any, borrow::Cow, cell::Cell}; - use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; - - use super::{Item, ItemEvent}; + pub struct TestProjectItem { + pub entry_id: Option, + } pub struct TestItem { pub workspace_id: WorkspaceId, @@ -713,13 +739,27 @@ pub(crate) mod test { pub is_dirty: bool, pub is_singleton: bool, pub has_conflict: bool, - pub project_entry_ids: Vec, + pub project_items: Vec>, pub project_path: Option, pub nav_history: Option, pub tab_descriptions: Option>, pub tab_detail: Cell>, } + impl Entity for TestProjectItem { + type Event = (); + } + + impl project::Item for TestProjectItem { + fn entry_id(&self, _: &AppContext) -> Option { + self.entry_id + } + + fn project_path(&self, _: &AppContext) -> Option { + None + } + } + pub enum TestItemEvent { Edit, } @@ -735,7 +775,7 @@ pub(crate) mod test { is_dirty: self.is_dirty, is_singleton: self.is_singleton, has_conflict: self.has_conflict, - project_entry_ids: self.project_entry_ids.clone(), + project_items: self.project_items.clone(), project_path: self.project_path.clone(), nav_history: None, tab_descriptions: None, @@ -755,7 +795,7 @@ pub(crate) mod test { reload_count: 0, is_dirty: false, has_conflict: false, - project_entry_ids: Vec::new(), + project_items: Vec::new(), project_path: None, is_singleton: true, nav_history: None, @@ -781,13 +821,26 @@ pub(crate) mod test { self } - pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self { - self.project_entry_ids.extend( - project_entry_ids - .iter() - .copied() - .map(ProjectEntryId::from_proto), - ); + pub fn with_dirty(mut self, dirty: bool) -> Self { + self.is_dirty = dirty; + self + } + + pub fn with_conflict(mut self, has_conflict: bool) -> Self { + self.has_conflict = has_conflict; + self + } + + pub fn with_project_entry_ids( + mut self, + project_entry_ids: &[u64], + cx: &mut MutableAppContext, + ) -> Self { + self.project_items + .extend(project_entry_ids.iter().copied().map(|id| { + let id = ProjectEntryId::from_proto(id); + cx.add_model(|_| TestProjectItem { entry_id: Some(id) }) + })); self } @@ -830,12 +883,14 @@ pub(crate) mod test { Empty::new().boxed() } - fn project_path(&self, _: &AppContext) -> Option { - self.project_path.clone() - } - - fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - self.project_entry_ids.iter().copied().collect() + fn for_each_project_item( + &self, + cx: &AppContext, + f: &mut dyn FnMut(usize, &dyn project::Item), + ) { + self.project_items + .iter() + .for_each(|item| f(item.id(), item.read(cx))) } fn is_singleton(&self, _: &AppContext) -> bool { @@ -880,7 +935,7 @@ pub(crate) mod test { } fn can_save(&self, _: &AppContext) -> bool { - !self.project_entry_ids.is_empty() + !self.project_items.is_empty() } fn save( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 735e165c7d..4d5a73b2b3 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -809,8 +809,8 @@ impl Pane { // Find the item's current index and its set of project entries. Avoid // storing these in advance, in case they have changed since this task // was started. - let (item_ix, mut project_entry_ids) = pane.read_with(&cx, |pane, cx| { - (pane.index_for_item(&*item), item.project_entry_ids(cx)) + let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { + (pane.index_for_item(&*item), item.project_item_model_ids(cx)) }); let item_ix = if let Some(ix) = item_ix { ix @@ -823,25 +823,20 @@ impl Pane { // any project entries that are not open anywhere else in the workspace, // AND that the user has not already been prompted to save. If there are // any such project entries, prompt the user to save this item. - let should_save = if project_entry_ids.is_empty() { - true - } else { - workspace.read_with(&cx, |workspace, cx| { - for item in workspace.items(cx) { - if !items_to_close - .iter() - .any(|item_to_close| item_to_close.id() == item.id()) - { - let other_project_entry_ids = item.project_entry_ids(cx); - project_entry_ids - .retain(|id| !other_project_entry_ids.contains(id)); - } + workspace.read_with(&cx, |workspace, cx| { + for item in workspace.items(cx) { + if !items_to_close + .iter() + .any(|item_to_close| item_to_close.id() == item.id()) + { + let other_project_item_ids = item.project_item_model_ids(cx); + project_item_ids.retain(|id| !other_project_item_ids.contains(id)); } - }); - project_entry_ids - .iter() - .any(|id| saved_project_entry_ids.insert(*id)) - }; + } + }); + let should_save = project_item_ids + .iter() + .any(|id| saved_project_entry_ids.insert(*id)); if should_save && !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx) @@ -1866,7 +1861,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, @@ -1885,7 +1880,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, @@ -1904,7 +1899,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 2") - .with_project_entry_ids(&[2]); + .with_project_entry_ids(&[2], cx); Pane::add_item( workspace, @@ -1923,7 +1918,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, @@ -1942,7 +1937,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1b") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index d292ece3d5..b76535f6ed 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -8,12 +8,11 @@ use futures::StreamExt; use gpui::{ elements::*, geometry::{rect::RectF, vector::vec2f}, - Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext, ViewHandle, - WeakViewHandle, + AppContext, Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext, + ViewHandle, WeakViewHandle, }; use project::Project; use settings::Settings; -use smallvec::SmallVec; use std::{ path::PathBuf, sync::{Arc, Weak}, @@ -106,7 +105,7 @@ impl Item for SharedScreen { &self, _: Option, style: &theme::Tab, - _: &gpui::AppContext, + _: &AppContext, ) -> gpui::ElementBox { Flex::row() .with_child( @@ -130,15 +129,9 @@ impl Item for SharedScreen { .boxed() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - Default::default() - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - Default::default() - } - - fn is_singleton(&self, _: &gpui::AppContext) -> bool { + fn is_singleton(&self, _: &AppContext) -> bool { false } @@ -155,7 +148,7 @@ impl Item for SharedScreen { Some(Self::new(&track, self.peer_id, self.user.clone(), cx)) } - fn can_save(&self, _: &gpui::AppContext) -> bool { + fn can_save(&self, _: &AppContext) -> bool { false } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index eefce67371..c7aa0798a3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2943,16 +2943,11 @@ mod tests { // When there are dirty untitled items, prompt to save each one. If the user // cancels any prompt, then abort. - let item2 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item - }); - let item3 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item2 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); + let item3 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_project_entry_ids(&[1], cx) }); workspace.update(cx, |w, cx| { w.add_item(Box::new(item2.clone()), cx); @@ -2977,31 +2972,24 @@ mod tests { Workspace::new(Default::default(), 0, project, default_item_factory, cx) }); - let item1 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item1 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_project_entry_ids(&[1], cx) }); - let item2 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.has_conflict = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(2)]; - item + let item2 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_conflict(true) + .with_project_entry_ids(&[2], cx) }); - let item3 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.has_conflict = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(3)]; - item - }); - let item4 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item + let item3 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_conflict(true) + .with_project_entry_ids(&[3], cx) }); + let item4 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); let pane = workspace.update(cx, |workspace, cx| { workspace.add_item(Box::new(item1.clone()), cx); workspace.add_item(Box::new(item2.clone()), cx); @@ -3078,29 +3066,26 @@ mod tests { // workspace items with multiple project entries. let single_entry_items = (0..=4) .map(|project_entry_id| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)]; - item.is_singleton = true; - item + cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_singleton(true) + .with_project_entry_ids(&[project_entry_id], cx) + }) }) .collect::>(); - let item_2_3 = { - let mut item = TestItem::new(); - item.is_dirty = true; - item.is_singleton = false; - item.project_entry_ids = - vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)]; - item - }; - let item_3_4 = { - let mut item = TestItem::new(); - item.is_dirty = true; - item.is_singleton = false; - item.project_entry_ids = - vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)]; - item - }; + let item_2_3 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_singleton(false) + .with_project_entry_ids(&[2, 3], cx) + }); + let item_3_4 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_singleton(false) + .with_project_entry_ids(&[3, 4], cx) + }); // Create two panes that contain the following project entries: // left pane: @@ -3111,9 +3096,9 @@ mod tests { // multi-entry items: (3, 4) let left_pane = workspace.update(cx, |workspace, cx| { let left_pane = workspace.active_pane().clone(); - workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx); - for item in &single_entry_items { - workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx); + workspace.add_item(Box::new(item_2_3.clone()), cx); + for item in single_entry_items { + workspace.add_item(Box::new(item), cx); } left_pane.update(cx, |pane, cx| { pane.activate_item(2, true, true, cx); @@ -3128,7 +3113,7 @@ mod tests { //Need to cause an effect flush in order to respect new focus workspace.update(cx, |workspace, cx| { - workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx); + workspace.add_item(Box::new(item_3_4.clone()), cx); cx.focus(left_pane.clone()); }); @@ -3177,10 +3162,8 @@ mod tests { Workspace::new(Default::default(), 0, project, default_item_factory, cx) }); - let item = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_entry_ids(&[1], cx) }); let item_id = item.id(); workspace.update(cx, |workspace, cx| { @@ -3265,7 +3248,9 @@ mod tests { workspace.add_item(Box::new(item.clone()), cx); }); item.update(cx, |item, cx| { - item.project_entry_ids = Default::default(); + item.project_items[0].update(cx, |item, _| { + item.entry_id = None; + }); item.is_dirty = true; cx.blur(); }); @@ -3296,10 +3281,8 @@ mod tests { Workspace::new(Default::default(), 0, project, default_item_factory, cx) }); - let item = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_entry_ids(&[1], cx) }); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); From c3518cefe832535a8ba2a3cd7c8a7a8ebb9a0b27 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 17 Jan 2023 17:32:10 -0800 Subject: [PATCH 055/108] disable vim mode in non full editors --- crates/vim/src/editor_events.rs | 4 +--- crates/vim/src/vim.rs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index 7b777a50ed..c526e3b1dc 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -37,9 +37,7 @@ fn editor_focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppCont let editor_mode = editor.mode(); let newest_selection_empty = editor.selections.newest::(cx).is_empty(); - if editor_mode != EditorMode::Full { - vim.switch_mode(Mode::Insert, true, cx); - } else if !newest_selection_empty { + if editor_mode == EditorMode::Full && !newest_selection_empty { vim.switch_mode(Mode::Visual { line: false }, true, cx); } }); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 8c5dae1e01..9f799ef37f 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -12,7 +12,7 @@ mod visual; use collections::HashMap; use command_palette::CommandPaletteFilter; -use editor::{Bias, Cancel, Editor}; +use editor::{Bias, Cancel, Editor, EditorMode}; use gpui::{ impl_actions, keymap_matcher::{KeyPressed, Keystroke}, @@ -267,7 +267,7 @@ impl Vim { for editor in self.editors.values() { if let Some(editor) = editor.upgrade(cx) { editor.update(cx, |editor, cx| { - if self.enabled { + if self.enabled && editor.mode() == EditorMode::Full { editor.set_cursor_shape(cursor_shape, cx); editor.set_clip_at_line_ends(state.clip_at_line_end(), cx); editor.set_input_enabled(!state.vim_controlled()); From cf193154e10266c35dfc5539702d04a0cc1c7979 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 17 Jan 2023 17:35:39 -0800 Subject: [PATCH 056/108] fix broken test --- crates/vim/src/test.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index e320962cfa..0214806e11 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -84,9 +84,6 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) { ); cx.simulate_keystroke("/"); - // We now use a weird insert mode with selection when jumping to a single line editor - assert_eq!(cx.mode(), Mode::Insert); - let search_bar = cx.workspace(|workspace, cx| { workspace .active_pane() From a0a50cb412ba23e97f010013153719132582676a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 17 Jan 2023 17:37:52 -0800 Subject: [PATCH 057/108] Set up fake project paths correctly in tests --- crates/workspace/src/item.rs | 24 ++++++++++++--------- crates/workspace/src/pane.rs | 10 ++++----- crates/workspace/src/workspace.rs | 36 ++++++++++++++----------------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 9d5b94bddb..1bc713b5fb 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -722,11 +722,12 @@ pub(crate) mod test { elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; - use project::{Project, ProjectEntryId, ProjectPath}; - use std::{any::Any, borrow::Cow, cell::Cell}; + use project::{Project, ProjectEntryId, ProjectPath, WorktreeId}; + use std::{any::Any, borrow::Cow, cell::Cell, path::Path}; pub struct TestProjectItem { pub entry_id: Option, + pub project_path: Option, } pub struct TestItem { @@ -740,7 +741,6 @@ pub(crate) mod test { pub is_singleton: bool, pub has_conflict: bool, pub project_items: Vec>, - pub project_path: Option, pub nav_history: Option, pub tab_descriptions: Option>, pub tab_detail: Cell>, @@ -756,7 +756,7 @@ pub(crate) mod test { } fn project_path(&self, _: &AppContext) -> Option { - None + self.project_path.clone() } } @@ -776,7 +776,6 @@ pub(crate) mod test { is_singleton: self.is_singleton, has_conflict: self.has_conflict, project_items: self.project_items.clone(), - project_path: self.project_path.clone(), nav_history: None, tab_descriptions: None, tab_detail: Default::default(), @@ -796,7 +795,6 @@ pub(crate) mod test { is_dirty: false, has_conflict: false, project_items: Vec::new(), - project_path: None, is_singleton: true, nav_history: None, tab_descriptions: None, @@ -831,15 +829,21 @@ pub(crate) mod test { self } - pub fn with_project_entry_ids( + pub fn with_project_items( mut self, - project_entry_ids: &[u64], + items: &[(u64, &str)], cx: &mut MutableAppContext, ) -> Self { self.project_items - .extend(project_entry_ids.iter().copied().map(|id| { + .extend(items.iter().copied().map(|(id, path)| { let id = ProjectEntryId::from_proto(id); - cx.add_model(|_| TestProjectItem { entry_id: Some(id) }) + cx.add_model(|_| TestProjectItem { + entry_id: Some(id), + project_path: Some(ProjectPath { + worktree_id: WorktreeId::from_usize(0), + path: Path::new(path).into(), + }), + }) })); self } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 4d5a73b2b3..de298e7be1 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1861,7 +1861,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "one.txt")], cx); Pane::add_item( workspace, @@ -1880,7 +1880,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "1.txt")], cx); Pane::add_item( workspace, @@ -1899,7 +1899,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 2") - .with_project_entry_ids(&[2], cx); + .with_project_items(&[(2, "2.txt")], cx); Pane::add_item( workspace, @@ -1918,7 +1918,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "1.txt")], cx); Pane::add_item( workspace, @@ -1937,7 +1937,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1b") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "1.txt")], cx); Pane::add_item( workspace, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c7aa0798a3..a521d02b5f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2834,15 +2834,11 @@ mod tests { project.worktrees(cx).next().unwrap().read(cx).id() }); - let item1 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_path = Some((worktree_id, "one.txt").into()); - item + let item1 = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_items(&[(1, "one.txt")], cx) }); - let item2 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_path = Some((worktree_id, "two.txt").into()); - item + let item2 = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_items(&[(2, "two.txt")], cx) }); // Add an item to an empty pane @@ -2947,7 +2943,7 @@ mod tests { let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_entry_ids(&[1], cx) + .with_project_items(&[(1, "1.txt")], cx) }); workspace.update(cx, |w, cx| { w.add_item(Box::new(item2.clone()), cx); @@ -2975,19 +2971,19 @@ mod tests { let item1 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_entry_ids(&[1], cx) + .with_project_items(&[(1, "1.txt")], cx) }); let item2 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_entry_ids(&[2], cx) + .with_project_items(&[(2, "2.txt")], cx) }); let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_entry_ids(&[3], cx) + .with_project_items(&[(3, "3.txt")], cx) }); let item4 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); let pane = workspace.update(cx, |workspace, cx| { @@ -3067,10 +3063,10 @@ mod tests { let single_entry_items = (0..=4) .map(|project_entry_id| { cx.add_view(&workspace, |cx| { - TestItem::new() - .with_dirty(true) - .with_singleton(true) - .with_project_entry_ids(&[project_entry_id], cx) + TestItem::new().with_dirty(true).with_project_items( + &[(project_entry_id, &format!("{project_entry_id}.txt"))], + cx, + ) }) }) .collect::>(); @@ -3078,13 +3074,13 @@ mod tests { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_entry_ids(&[2, 3], cx) + .with_project_items(&[(2, "2.txt"), (3, "3.txt")], cx) }); let item_3_4 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_entry_ids(&[3, 4], cx) + .with_project_items(&[(3, "3.txt"), (4, "4.txt")], cx) }); // Create two panes that contain the following project entries: @@ -3163,7 +3159,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_entry_ids(&[1], cx) + TestItem::new().with_project_items(&[(1, "1.txt")], cx) }); let item_id = item.id(); workspace.update(cx, |workspace, cx| { @@ -3282,7 +3278,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_entry_ids(&[1], cx) + TestItem::new().with_project_items(&[(1, "1.txt")], cx) }); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); From bec03dc882a734241f8091b0f1ce3cb0d388ad13 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 18 Jan 2023 00:12:52 -0500 Subject: [PATCH 058/108] WIP --- Cargo.lock | 3 + crates/feedback/Cargo.toml | 3 + crates/feedback/src/feedback.rs | 3 + crates/feedback/src/feedback_popover.rs | 370 +++++++++++++++--------- crates/feedback/src/system_specs.rs | 2 +- crates/theme/src/theme.rs | 16 +- crates/workspace/src/workspace.rs | 3 +- crates/zed/src/zed.rs | 2 +- 8 files changed, 251 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6bd88e507..66a3e3ee72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2034,9 +2034,12 @@ dependencies = [ "human_bytes", "isahc", "lazy_static", + "project", "serde", "settings", + "smallvec", "sysinfo", + "theme", "urlencoding", "util", "workspace", diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index afb43f501f..c6f139e36e 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -18,9 +18,12 @@ gpui = { path = "../gpui" } human_bytes = "0.4.1" isahc = "1.7" lazy_static = "1.4.0" +project = { path = "../project" } serde = { version = "1.0", features = ["derive", "rc"] } settings = { path = "../settings" } +smallvec = { version = "1.6", features = ["union"] } sysinfo = "0.27.1" +theme = { path = "../theme" } urlencoding = "2.1.2" util = { path = "../util" } workspace = { path = "../workspace" } \ No newline at end of file diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 58cb682f3b..03379d655c 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -59,4 +59,7 @@ pub fn init(cx: &mut gpui::MutableAppContext) { }); }, ); + + // TODO FEEDBACK: Should I put Give Feedback action here? + // TODO FEEDBACK: Disble buffer search? } diff --git a/crates/feedback/src/feedback_popover.rs b/crates/feedback/src/feedback_popover.rs index b9a9312952..6beaea83f0 100644 --- a/crates/feedback/src/feedback_popover.rs +++ b/crates/feedback/src/feedback_popover.rs @@ -2,24 +2,30 @@ use std::{ops::Range, sync::Arc}; use anyhow::bail; use client::{Client, ZED_SECRET_CLIENT_TOKEN}; -use editor::Editor; +use editor::{Editor, MultiBuffer}; use futures::AsyncReadExt; use gpui::{ actions, - elements::{ - AnchorCorner, ChildView, Flex, MouseEventHandler, Overlay, OverlayFitMode, ParentElement, - Stack, Text, - }, - serde_json, CursorStyle, Element, ElementBox, Entity, MouseButton, MutableAppContext, - RenderContext, View, ViewContext, ViewHandle, + elements::{ChildView, Flex, Label, MouseEventHandler, ParentElement, Stack, Text}, + serde_json, CursorStyle, Element, ElementBox, Entity, ModelHandle, MouseButton, + MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use isahc::Request; + use lazy_static::lazy_static; +use project::{Project, ProjectEntryId, ProjectPath}; use serde::Serialize; use settings::Settings; -use workspace::{item::ItemHandle, StatusItemView}; +use smallvec::SmallVec; +use workspace::{ + item::{Item, ItemHandle}, + StatusItemView, Workspace, +}; -use crate::{feedback_popover, system_specs::SystemSpecs}; +use crate::system_specs::SystemSpecs; + +// TODO FEEDBACK: Rename this file to feedback editor? +// TODO FEEDBACK: Where is the backend code for air table? lazy_static! { pub static ref ZED_SERVER_URL: String = @@ -31,36 +37,14 @@ const FEEDBACK_CHAR_COUNT_RANGE: Range = Range { end: 1000, }; -actions!(feedback, [ToggleFeedbackPopover, SubmitFeedback]); +actions!(feedback, [SubmitFeedback, GiveFeedback, DeployFeedback]); pub fn init(cx: &mut MutableAppContext) { - cx.add_action(FeedbackButton::toggle_feedback); - cx.add_action(FeedbackPopover::submit_feedback); + // cx.add_action(FeedbackView::submit_feedback); + cx.add_action(FeedbackEditor::deploy); } -pub struct FeedbackButton { - feedback_popover: Option>, -} - -impl FeedbackButton { - pub fn new() -> Self { - Self { - feedback_popover: None, - } - } - - pub fn toggle_feedback(&mut self, _: &ToggleFeedbackPopover, cx: &mut ViewContext) { - match self.feedback_popover.take() { - Some(_) => {} - None => { - let popover_view = cx.add_view(|_cx| FeedbackPopover::new(_cx)); - self.feedback_popover = Some(popover_view.clone()); - } - } - - cx.notify(); - } -} +pub struct FeedbackButton; impl Entity for FeedbackButton { type Event = (); @@ -80,27 +64,81 @@ impl View for FeedbackButton { Text::new( "Give Feedback".to_string(), - theme - .style_for(state, self.feedback_popover.is_some()) - .clone(), + theme.style_for(state, true).clone(), ) .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, cx| { - cx.dispatch_action(ToggleFeedbackPopover) - }) + .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(GiveFeedback)) .boxed(), ) - .with_children(self.feedback_popover.as_ref().map(|popover| { - Overlay::new(ChildView::new(popover, cx).contained().boxed()) - .with_fit_mode(OverlayFitMode::SwitchAnchor) - .with_anchor_corner(AnchorCorner::TopLeft) - .with_z_index(999) - .boxed() - })) .boxed() } + + fn focus_in(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) {} + + fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) {} + + fn key_down(&mut self, _: &gpui::KeyDownEvent, _: &mut ViewContext) -> bool { + false + } + + fn key_up(&mut self, _: &gpui::KeyUpEvent, _: &mut ViewContext) -> bool { + false + } + + fn modifiers_changed( + &mut self, + _: &gpui::ModifiersChangedEvent, + _: &mut ViewContext, + ) -> bool { + false + } + + fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap_matcher::KeymapContext { + Self::default_keymap_context() + } + + fn default_keymap_context() -> gpui::keymap_matcher::KeymapContext { + let mut cx = gpui::keymap_matcher::KeymapContext::default(); + cx.set.insert(Self::ui_name().into()); + cx + } + + fn debug_json(&self, _: &gpui::AppContext) -> gpui::serde_json::Value { + gpui::serde_json::Value::Null + } + + fn text_for_range(&self, _: Range, _: &gpui::AppContext) -> Option { + None + } + + fn selected_text_range(&self, _: &gpui::AppContext) -> Option> { + None + } + + fn marked_text_range(&self, _: &gpui::AppContext) -> Option> { + None + } + + fn unmark_text(&mut self, _: &mut ViewContext) {} + + fn replace_text_in_range( + &mut self, + _: Option>, + _: &str, + _: &mut ViewContext, + ) { + } + + fn replace_and_mark_text_in_range( + &mut self, + _: Option>, + _: &str, + _: Option>, + _: &mut ViewContext, + ) { + } } impl StatusItemView for FeedbackButton { @@ -113,14 +151,9 @@ impl StatusItemView for FeedbackButton { } } -pub struct FeedbackPopover { - feedback_editor: ViewHandle, - // _subscriptions: Vec, -} - -impl Entity for FeedbackPopover { - type Event = (); -} +// impl Entity for FeedbackView { +// type Event = (); +// } #[derive(Serialize)] struct FeedbackRequestBody<'a> { @@ -130,40 +163,34 @@ struct FeedbackRequestBody<'a> { token: &'a str, } -impl FeedbackPopover { - pub fn new(cx: &mut ViewContext) -> Self { - let feedback_editor = cx.add_view(|cx| { - let editor = Editor::multi_line( - Some(Arc::new(|theme| theme.feedback.feedback_editor.clone())), - cx, - ); +struct FeedbackEditor { + editor: ViewHandle, +} + +impl FeedbackEditor { + fn new( + project_handle: ModelHandle, + _: WeakViewHandle, + cx: &mut ViewContext, + ) -> Self { + // TODO FEEDBACK: Get rid of this expect + let buffer = project_handle + .update(cx, |project, cx| project.create_buffer("", None, cx)) + .expect("Could not open feedback window"); + + let editor = cx.add_view(|cx| { + let mut editor = Editor::for_buffer(buffer, Some(project_handle.clone()), cx); + editor.set_vertical_scroll_margin(5, cx); + editor.set_placeholder_text("Enter your feedback here, save to submit feedback", cx); editor }); - cx.focus(&feedback_editor); - - cx.subscribe(&feedback_editor, |this, _, event, cx| { - if let editor::Event::BufferEdited = event { - let buffer_len = this.feedback_editor.read(cx).buffer().read(cx).len(cx); - let feedback_chars_remaining = FEEDBACK_CHAR_COUNT_RANGE.end - buffer_len; - dbg!(feedback_chars_remaining); - } - }) - .detach(); - - // let active_call = ActiveCall::global(cx); - // let mut subscriptions = Vec::new(); - // subscriptions.push(cx.observe(&user_store, |this, _, cx| this.update_entries(cx))); - // subscriptions.push(cx.observe(&active_call, |this, _, cx| this.update_entries(cx))); - let this = Self { - feedback_editor, // _subscriptions: subscriptions, - }; - // this.update_entries(cx); + let this = Self { editor }; this } - fn submit_feedback(&mut self, _: &SubmitFeedback, cx: &mut ViewContext<'_, Self>) { - let feedback_text = self.feedback_editor.read(cx).text(cx); + fn submit_feedback(&mut self, cx: &mut ViewContext<'_, Self>) { + let feedback_text = self.editor.read(cx).text(cx); let zed_client = cx.global::>(); let system_specs = SystemSpecs::new(cx); let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL); @@ -222,66 +249,129 @@ impl FeedbackPopover { } } -impl View for FeedbackPopover { +impl FeedbackEditor { + pub fn deploy(workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext) { + // if let Some(existing) = workspace.item_of_type::(cx) { + // workspace.activate_item(&existing, cx); + // } else { + let workspace_handle = cx.weak_handle(); + let feedback_editor = cx + .add_view(|cx| FeedbackEditor::new(workspace.project().clone(), workspace_handle, cx)); + workspace.add_item(Box::new(feedback_editor), cx); + } + // } +} + +// struct FeedbackView { +// editor: Editor, +// } + +impl View for FeedbackEditor { fn ui_name() -> &'static str { - "FeedbackPopover" + "Feedback" } fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - enum SubmitFeedback {} + // let theme = cx.global::().theme.clone(); + // let submit_feedback_text_button_height = 20.0; - // I'd like to just define: - - // 1. Overall popover width x height dimensions (done) - // 2. Submit Feedback button height dimensions - // 3. Allow editor to dynamically fill in the remaining space - - let theme = cx.global::().theme.clone(); - let submit_feedback_text_button_height = 20.0; - - Flex::column() - .with_child( - Flex::row() - .with_child( - ChildView::new(self.feedback_editor.clone(), cx) - .contained() - .with_style(theme.feedback.feedback_editor.container) - .flex(1., true) - .boxed(), - ) - .constrained() - .with_width(theme.feedback.feedback_popover.width) - .with_height( - theme.feedback.feedback_popover.height - submit_feedback_text_button_height, - ) - .boxed(), - ) - .with_child( - MouseEventHandler::::new(0, cx, |state, _| { - let theme = &theme.workspace.status_bar.feedback; - - Text::new( - "Submit Feedback".to_string(), - theme.style_for(state, true).clone(), - ) - .constrained() - .with_height(submit_feedback_text_button_height) - .boxed() - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, cx| { - cx.dispatch_action(feedback_popover::SubmitFeedback) - }) - .on_click(MouseButton::Left, |_, cx| { - cx.dispatch_action(feedback_popover::ToggleFeedbackPopover) - }) - .boxed(), - ) - .contained() - .with_style(theme.feedback.feedback_popover.container) - .constrained() - .with_width(theme.feedback.feedback_popover.width) - .with_height(theme.feedback.feedback_popover.height) - .boxed() + ChildView::new(&self.editor, cx).boxed() } } + +impl Entity for FeedbackEditor { + type Event = (); +} + +impl Item for FeedbackEditor { + fn tab_content( + &self, + _: Option, + style: &theme::Tab, + _: &gpui::AppContext, + ) -> ElementBox { + Flex::row() + .with_child( + Label::new("Feedback".to_string(), style.label.clone()) + .aligned() + .contained() + .boxed(), + ) + .boxed() + } + + fn to_item_events(_: &Self::Event) -> Vec { + Vec::new() + } + + fn project_path(&self, _: &gpui::AppContext) -> Option { + None + } + + fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> { + SmallVec::new() + } + + fn is_singleton(&self, _: &gpui::AppContext) -> bool { + true + } + + fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} + + fn can_save(&self, _: &gpui::AppContext) -> bool { + true + } + + fn save( + &mut self, + _: gpui::ModelHandle, + cx: &mut ViewContext, + ) -> Task> { + cx.prompt( + gpui::PromptLevel::Info, + &format!("You are trying to to submit this feedbac"), + &["OK"], + ); + + self.submit_feedback(cx); + Task::ready(Ok(())) + } + + fn save_as( + &mut self, + _: gpui::ModelHandle, + _: std::path::PathBuf, + _: &mut ViewContext, + ) -> Task> { + unreachable!("save_as should not have been called"); + } + + fn reload( + &mut self, + _: gpui::ModelHandle, + _: &mut ViewContext, + ) -> Task> { + unreachable!("should not have been called") + } + + fn serialized_item_kind() -> Option<&'static str> { + None + } + + fn deserialize( + _: gpui::ModelHandle, + _: gpui::WeakViewHandle, + _: workspace::WorkspaceId, + _: workspace::ItemId, + _: &mut ViewContext, + ) -> Task>> { + unreachable!() + } +} + +// TODO FEEDBACK: Add placeholder text +// TODO FEEDBACK: act_as_type (max mentionedt this) +// TODO FEEDBACK: focus +// TODO FEEDBACK: markdown highlighting +// TODO FEEDBACK: save prompts and accepting closes +// TODO FEEDBACK: multiple tabs? diff --git a/crates/feedback/src/system_specs.rs b/crates/feedback/src/system_specs.rs index fc55f76f35..17e51a6815 100644 --- a/crates/feedback/src/system_specs.rs +++ b/crates/feedback/src/system_specs.rs @@ -42,7 +42,7 @@ impl Display for SystemSpecs { None => format!("OS: {}", self.os_name), }; let system_specs = [ - format!("Zed: {} ({})", self.app_version, self.release_channel), + format!("Zed: v{} ({})", self.app_version, self.release_channel), os_information, format!("Memory: {}", human_bytes(self.memory as f64)), format!("Architecture: {}", self.architecture), diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 3b118bbef9..ed8fb1bb12 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -122,17 +122,17 @@ pub struct ContactList { #[derive(Deserialize, Default)] pub struct Feedback { - pub feedback_popover: FeedbackPopover, + // pub feedback_popover: FeedbackPopover, pub feedback_editor: FieldEditor, } -#[derive(Deserialize, Default)] -pub struct FeedbackPopover { - #[serde(flatten)] - pub container: ContainerStyle, - pub height: f32, - pub width: f32, -} +// #[derive(Deserialize, Default)] +// pub struct FeedbackPopover { +// #[serde(flatten)] +// pub container: ContainerStyle, +// pub height: f32, +// pub width: f32, +// } #[derive(Deserialize, Default)] pub struct ProjectRow { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8bf4cbce32..ff5748f240 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -96,7 +96,8 @@ actions!( ToggleLeftSidebar, ToggleRightSidebar, NewTerminal, - NewSearch + NewSearch, + Feedback ] ); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 21f5b5d342..8f597ebe79 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -327,7 +327,7 @@ pub fn initialize_workspace( let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - let feedback_button = cx.add_view(|_| feedback::feedback_popover::FeedbackButton::new()); + let feedback_button = cx.add_view(|_| feedback::feedback_popover::FeedbackButton {}); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); From a653e87658618b9315bd96d975c341cd5a6f56a5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Jan 2023 12:22:08 +0100 Subject: [PATCH 059/108] WIP: Avoid converting RopeFingerprint into a string Co-Authored-By: Petros Amoiridis --- Cargo.lock | 3 +-- crates/language/src/buffer.rs | 25 +++++++++++++------------ crates/language/src/proto.rs | 9 +++++++++ crates/project/src/project.rs | 16 ++++++++++------ crates/rope/Cargo.toml | 2 +- crates/rope/src/rope.rs | 8 +++++--- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9976fbba7..66b0d88b22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -739,8 +739,7 @@ dependencies = [ [[package]] name = "bromberg_sl2" version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ed88064f69518b7e3ea50ecfc1b61d43f19248618a377b95ae5c8b611134d4d" +source = "git+https://github.com/zed-industries/bromberg_sl2?rev=dac565a90e8f9245f48ff46225c915dc50f76920#dac565a90e8f9245f48ff46225c915dc50f76920" dependencies = [ "digest 0.9.0", "lazy_static", diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 4b79009a68..972a33c0b7 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -60,7 +60,7 @@ pub struct Buffer { git_diff_status: GitDiffStatus, file: Option>, saved_version: clock::Global, - saved_version_fingerprint: String, + saved_version_fingerprint: RopeFingerprint, saved_mtime: SystemTime, transaction_depth: usize, was_dirty_before_starting_transaction: Option, @@ -221,7 +221,7 @@ pub trait File: Send + Sync { version: clock::Global, line_ending: LineEnding, cx: &mut MutableAppContext, - ) -> Task>; + ) -> Task>; fn as_any(&self) -> &dyn Any; @@ -238,7 +238,7 @@ pub trait LocalFile: File { &self, buffer_id: u64, version: &clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, line_ending: LineEnding, mtime: SystemTime, cx: &mut MutableAppContext, @@ -386,7 +386,8 @@ impl Buffer { .ok_or_else(|| anyhow!("missing line_ending"))?, )); this.saved_version = proto::deserialize_version(message.saved_version); - this.saved_version_fingerprint = message.saved_version_fingerprint; + this.saved_version_fingerprint = + proto::deserialize_fingerprint(&message.saved_version_fingerprint)?; this.saved_mtime = message .saved_mtime .ok_or_else(|| anyhow!("invalid saved_mtime"))? @@ -402,7 +403,7 @@ impl Buffer { diff_base: self.diff_base.as_ref().map(|h| h.to_string()), line_ending: proto::serialize_line_ending(self.line_ending()) as i32, saved_version: proto::serialize_version(&self.saved_version), - saved_version_fingerprint: self.saved_version_fingerprint.clone(), + saved_version_fingerprint: proto::serialize_fingerprint(self.saved_version_fingerprint), saved_mtime: Some(self.saved_mtime.into()), } } @@ -530,7 +531,7 @@ impl Buffer { pub fn save( &mut self, cx: &mut ModelContext, - ) -> Task> { + ) -> Task> { let file = if let Some(file) = self.file.as_ref() { file } else { @@ -548,7 +549,7 @@ impl Buffer { cx.spawn(|this, mut cx| async move { let (version, fingerprint, mtime) = save.await?; this.update(&mut cx, |this, cx| { - this.did_save(version.clone(), fingerprint.clone(), mtime, None, cx); + this.did_save(version.clone(), fingerprint, mtime, None, cx); }); Ok((version, fingerprint, mtime)) }) @@ -558,8 +559,8 @@ impl Buffer { &self.saved_version } - pub fn saved_version_fingerprint(&self) -> &str { - &self.saved_version_fingerprint + pub fn saved_version_fingerprint(&self) -> RopeFingerprint { + self.saved_version_fingerprint } pub fn saved_mtime(&self) -> SystemTime { @@ -581,7 +582,7 @@ impl Buffer { pub fn did_save( &mut self, version: clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, mtime: SystemTime, new_file: Option>, cx: &mut ModelContext, @@ -630,7 +631,7 @@ impl Buffer { pub fn did_reload( &mut self, version: clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, line_ending: LineEnding, mtime: SystemTime, cx: &mut ModelContext, @@ -643,7 +644,7 @@ impl Buffer { file.buffer_reloaded( self.remote_id(), &self.saved_version, - self.saved_version_fingerprint.clone(), + self.saved_version_fingerprint, self.line_ending(), self.saved_mtime, cx, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 9612deb5bd..1b95e3ace9 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -11,6 +11,15 @@ use text::*; pub use proto::{BufferState, Operation}; +pub fn serialize_fingerprint(fingerprint: RopeFingerprint) -> String { + fingerprint.to_hex() +} + +pub fn deserialize_fingerprint(fingerprint: &str) -> Result { + RopeFingerprint::from_hex(fingerprint) + .map_err(|error| anyhow!("invalid fingerprint: {}", error)) +} + pub fn deserialize_line_ending(message: proto::LineEnding) -> fs::LineEnding { match message { proto::LineEnding::Unix => fs::LineEnding::Unix, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d3b3c11f2e..ce7296994a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -22,8 +22,8 @@ use gpui::{ use language::{ point_to_lsp, proto::{ - deserialize_anchor, deserialize_line_ending, deserialize_version, serialize_anchor, - serialize_version, + deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, + serialize_anchor, serialize_version, }, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, @@ -5123,7 +5123,7 @@ impl Project { buffer_id, version: serialize_version(&saved_version), mtime: Some(mtime.into()), - fingerprint, + fingerprint: language::proto::serialize_fingerprint(fingerprint), }) } @@ -5215,7 +5215,9 @@ impl Project { buffer_id, version: language::proto::serialize_version(buffer.saved_version()), mtime: Some(buffer.saved_mtime().into()), - fingerprint: buffer.saved_version_fingerprint().into(), + fingerprint: language::proto::serialize_fingerprint( + buffer.saved_version_fingerprint(), + ), line_ending: language::proto::serialize_line_ending( buffer.line_ending(), ) as i32, @@ -5970,6 +5972,7 @@ impl Project { _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { + let fingerprint = deserialize_fingerprint(&envelope.payload.fingerprint)?; let version = deserialize_version(envelope.payload.version); let mtime = envelope .payload @@ -5984,7 +5987,7 @@ impl Project { .and_then(|buffer| buffer.upgrade(cx)); if let Some(buffer) = buffer { buffer.update(cx, |buffer, cx| { - buffer.did_save(version, envelope.payload.fingerprint, mtime, None, cx); + buffer.did_save(version, fingerprint, mtime, None, cx); }); } Ok(()) @@ -5999,6 +6002,7 @@ impl Project { ) -> Result<()> { let payload = envelope.payload; let version = deserialize_version(payload.version); + let fingerprint = deserialize_fingerprint(&payload.fingerprint)?; let line_ending = deserialize_line_ending( proto::LineEnding::from_i32(payload.line_ending) .ok_or_else(|| anyhow!("missing line ending"))?, @@ -6014,7 +6018,7 @@ impl Project { .and_then(|buffer| buffer.upgrade(cx)); if let Some(buffer) = buffer { buffer.update(cx, |buffer, cx| { - buffer.did_reload(version, payload.fingerprint, line_ending, mtime, cx); + buffer.did_reload(version, fingerprint, line_ending, mtime, cx); }); } Ok(()) diff --git a/crates/rope/Cargo.toml b/crates/rope/Cargo.toml index bd1dc690db..e9ddcc8195 100644 --- a/crates/rope/Cargo.toml +++ b/crates/rope/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" path = "src/rope.rs" [dependencies] -bromberg_sl2 = "0.6" +bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "dac565a90e8f9245f48ff46225c915dc50f76920" } smallvec = { version = "1.6", features = ["union"] } sum_tree = { path = "../sum_tree" } arrayvec = "0.7.1" diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 53713e3f7a..74cc9a6789 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -4,7 +4,7 @@ mod point_utf16; mod unclipped; use arrayvec::ArrayString; -use bromberg_sl2::{DigestString, HashMatrix}; +use bromberg_sl2::HashMatrix; use smallvec::SmallVec; use std::{ cmp, fmt, io, mem, @@ -25,6 +25,8 @@ const CHUNK_BASE: usize = 6; #[cfg(not(test))] const CHUNK_BASE: usize = 16; +pub type RopeFingerprint = HashMatrix; + #[derive(Clone, Default, Debug)] pub struct Rope { chunks: SumTree, @@ -361,8 +363,8 @@ impl Rope { .column } - pub fn fingerprint(&self) -> String { - self.chunks.summary().fingerprint.to_hex() + pub fn fingerprint(&self) -> RopeFingerprint { + self.chunks.summary().fingerprint } } From 8ca0f9ac99457095e3a0a6a181a65c1a34fea579 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Jan 2023 13:53:48 +0100 Subject: [PATCH 060/108] Fix compile errors --- .../src/tests/randomized_integration_tests.rs | 8 ++++---- crates/editor/src/items.rs | 12 ++++++++---- crates/project/src/worktree.rs | 19 +++++++++++-------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index ed8f984d27..e0170f6648 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -410,10 +410,10 @@ async fn test_random_collaboration( guest_buffer.read_with(client_cx, |b, _| b.saved_version().clone()); assert_eq!(guest_saved_version, host_saved_version); - let host_saved_version_fingerprint = host_buffer - .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); - let guest_saved_version_fingerprint = guest_buffer - .read_with(client_cx, |b, _| b.saved_version_fingerprint().to_string()); + let host_saved_version_fingerprint = + host_buffer.read_with(host_cx, |b, _| b.saved_version_fingerprint()); + let guest_saved_version_fingerprint = + guest_buffer.read_with(client_cx, |b, _| b.saved_version_fingerprint()); assert_eq!( guest_saved_version_fingerprint, host_saved_version_fingerprint diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index f8fa3c9df0..cce6d338f0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -13,8 +13,10 @@ use gpui::{ elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use language::proto::serialize_anchor as serialize_text_anchor; -use language::{Bias, Buffer, File as _, OffsetRangeExt, Point, SelectionGoal}; +use language::{ + proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, File as _, OffsetRangeExt, + Point, SelectionGoal, +}; use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; @@ -1165,9 +1167,11 @@ fn path_for_file<'a>( mod tests { use super::*; use gpui::MutableAppContext; + use language::RopeFingerprint; use std::{ path::{Path, PathBuf}, sync::Arc, + time::SystemTime, }; #[gpui::test] @@ -1197,7 +1201,7 @@ mod tests { todo!() } - fn mtime(&self) -> std::time::SystemTime { + fn mtime(&self) -> SystemTime { todo!() } @@ -1216,7 +1220,7 @@ mod tests { _: clock::Global, _: project::LineEnding, _: &mut MutableAppContext, - ) -> gpui::Task> { + ) -> gpui::Task> { todo!() } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index f22e915785..b65cf9e39b 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -20,10 +20,12 @@ use gpui::{ executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, }; -use language::Unclipped; use language::{ - proto::{deserialize_version, serialize_line_ending, serialize_version}, - Buffer, DiagnosticEntry, PointUtf16, Rope, + proto::{ + deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending, + serialize_version, + }, + Buffer, DiagnosticEntry, PointUtf16, Rope, RopeFingerprint, Unclipped, }; use parking_lot::Mutex; use postage::{ @@ -1863,7 +1865,7 @@ impl language::File for File { version: clock::Global, line_ending: LineEnding, cx: &mut MutableAppContext, - ) -> Task> { + ) -> Task> { self.worktree.update(cx, |worktree, cx| match worktree { Worktree::Local(worktree) => { let rpc = worktree.client.clone(); @@ -1878,7 +1880,7 @@ impl language::File for File { buffer_id, version: serialize_version(&version), mtime: Some(entry.mtime.into()), - fingerprint: fingerprint.clone(), + fingerprint: serialize_fingerprint(fingerprint), })?; } Ok((version, fingerprint, entry.mtime)) @@ -1896,11 +1898,12 @@ impl language::File for File { }) .await?; let version = deserialize_version(response.version); + let fingerprint = deserialize_fingerprint(&response.fingerprint)?; let mtime = response .mtime .ok_or_else(|| anyhow!("missing mtime"))? .into(); - Ok((version, response.fingerprint, mtime)) + Ok((version, fingerprint, mtime)) }) } }) @@ -1943,7 +1946,7 @@ impl language::LocalFile for File { &self, buffer_id: u64, version: &clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, line_ending: LineEnding, mtime: SystemTime, cx: &mut MutableAppContext, @@ -1957,7 +1960,7 @@ impl language::LocalFile for File { buffer_id, version: serialize_version(version), mtime: Some(mtime.into()), - fingerprint, + fingerprint: serialize_fingerprint(fingerprint), line_ending: serialize_line_ending(line_ending) as i32, }) .log_err(); From f8d092fdc6dd0059fe61d5397b18fce6a0da8071 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 18 Jan 2023 15:22:20 +0200 Subject: [PATCH 061/108] Fix mouse interrupting file/dir editing in project panel Co-Authored-By: Antonio Scandurra --- crates/project_panel/src/project_panel.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 0042695950..73b558bd5b 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1176,13 +1176,15 @@ impl ProjectPanel { ) }) .on_click(MouseButton::Left, move |e, cx| { - if kind == EntryKind::Dir { - cx.dispatch_action(ToggleExpanded(entry_id)) - } else { - cx.dispatch_action(Open { - entry_id, - change_focus: e.click_count > 1, - }) + if !show_editor { + if kind == EntryKind::Dir { + cx.dispatch_action(ToggleExpanded(entry_id)) + } else { + cx.dispatch_action(Open { + entry_id, + change_focus: e.click_count > 1, + }) + } } }) .on_down(MouseButton::Right, move |e, cx| { From a8f466b42269bf507ec8600d69608bdc7c0a1d99 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Jan 2023 14:22:23 +0100 Subject: [PATCH 062/108] Don't starve the main thread adding too many search excerpts at once --- Cargo.lock | 1 + crates/search/Cargo.toml | 1 + crates/search/src/project_search.rs | 45 +++++++++++++++++++++-------- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66b0d88b22..bbe77d8c75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5520,6 +5520,7 @@ dependencies = [ "serde_json", "settings", "smallvec", + "smol", "theme", "unindent", "util", diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 1f6c9582be..30802801a6 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -23,6 +23,7 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] } postage = { version = "0.4.1", features = ["futures-traits"] } serde = { version = "1.0", features = ["derive", "rc"] } smallvec = { version = "1.6", features = ["union"] } +smol = "1.2" [dev-dependencies] editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 9d2f31e269..a0f29738b0 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -18,6 +18,7 @@ use settings::Settings; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, + mem, ops::Range, path::PathBuf, sync::Arc, @@ -67,6 +68,7 @@ struct ProjectSearch { pending_search: Option>>, match_ranges: Vec>, active_query: Option, + search_id: usize, } pub struct ProjectSearchView { @@ -78,6 +80,7 @@ pub struct ProjectSearchView { regex: bool, query_contains_error: bool, active_match_index: Option, + search_id: usize, } pub struct ProjectSearchBar { @@ -98,6 +101,7 @@ impl ProjectSearch { pending_search: Default::default(), match_ranges: Default::default(), active_query: None, + search_id: 0, } } @@ -110,6 +114,7 @@ impl ProjectSearch { pending_search: Default::default(), match_ranges: self.match_ranges.clone(), active_query: self.active_query.clone(), + search_id: self.search_id, }) } @@ -117,21 +122,25 @@ impl ProjectSearch { let search = self .project .update(cx, |project, cx| project.search(query.clone(), cx)); + self.search_id += 1; self.active_query = Some(query); self.match_ranges.clear(); self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move { let matches = search.await.log_err()?; - if let Some(this) = this.upgrade(&cx) { + let this = this.upgrade(&cx)?; + let mut matches = matches.into_iter().collect::>(); + this.update(&mut cx, |this, cx| { + this.match_ranges.clear(); + matches.sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); + this.excerpts.update(cx, |excerpts, cx| excerpts.clear(cx)); + }); + + for matches_chunk in matches.chunks(100) { this.update(&mut cx, |this, cx| { - this.match_ranges.clear(); - let mut matches = matches.into_iter().collect::>(); - matches - .sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); this.excerpts.update(cx, |excerpts, cx| { - excerpts.clear(cx); - for (buffer, buffer_matches) in matches { + for (buffer, buffer_matches) in matches_chunk { let ranges_to_highlight = excerpts.push_excerpts_with_context_lines( - buffer, + buffer.clone(), buffer_matches.clone(), 1, cx, @@ -139,10 +148,19 @@ impl ProjectSearch { this.match_ranges.extend(ranges_to_highlight); } }); - this.pending_search.take(); + cx.notify(); }); + + // Don't starve the main thread when adding lots of excerpts. + smol::future::yield_now().await; } + + this.update(&mut cx, |this, cx| { + this.pending_search.take(); + cx.notify(); + }); + None })); cx.notify(); @@ -398,7 +416,7 @@ impl ProjectSearchView { whole_word = active_query.whole_word(); } } - cx.observe(&model, |this, _, cx| this.model_changed(true, cx)) + cx.observe(&model, |this, _, cx| this.model_changed(cx)) .detach(); let query_editor = cx.add_view(|cx| { @@ -433,6 +451,7 @@ impl ProjectSearchView { .detach(); let mut this = ProjectSearchView { + search_id: model.read(cx).search_id, model, query_editor, results_editor, @@ -442,7 +461,7 @@ impl ProjectSearchView { query_contains_error: false, active_match_index: None, }; - this.model_changed(false, cx); + this.model_changed(cx); this } @@ -562,11 +581,13 @@ impl ProjectSearchView { cx.focus(&self.results_editor); } - fn model_changed(&mut self, reset_selections: bool, cx: &mut ViewContext) { + fn model_changed(&mut self, cx: &mut ViewContext) { let match_ranges = self.model.read(cx).match_ranges.clone(); if match_ranges.is_empty() { 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; self.results_editor.update(cx, |editor, cx| { if reset_selections { editor.change_selections(Some(Autoscroll::fit()), cx, |s| { From 27a6951403cd11234edaa62c339e52678b9a2b86 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 18 Jan 2023 17:35:21 +0200 Subject: [PATCH 063/108] Allow pasting the same entry more than once in project panel Co-Authored-By: Antonio Scandurra --- crates/project_panel/src/project_panel.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 73b558bd5b..e59353aae4 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -763,7 +763,6 @@ impl ProjectPanel { ix += 1; } - self.clipboard_entry.take(); if clipboard_entry.is_cut() { if let Some(task) = self.project.update(cx, |project, cx| { project.rename_entry(clipboard_entry.entry_id(), new_path, cx) From b0fb5913b6aa9fd734f1ee40a1cfa5d322b19d1e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 18 Jan 2023 12:39:38 -0800 Subject: [PATCH 064/108] v0.71.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9976fbba7..62674acf3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8186,7 +8186,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.70.0" +version = "0.71.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 38fa2ad4be..63719b7003 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.70.0" +version = "0.71.0" [lib] name = "zed" From 203f569f2eda03cb1179f600ea2e6eb321bdbe52 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 18 Jan 2023 12:52:58 -0800 Subject: [PATCH 065/108] collab 0.5.3 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62674acf3d..c0b97a6b3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.2" +version = "0.5.3" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 9c3f0f61c4..8c21af7273 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.2" +version = "0.5.3" [[bin]] name = "collab" From 06c31a0daad2a3a8b6017a5843e1e4174808b5bb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Jan 2023 15:00:40 -0800 Subject: [PATCH 066/108] Fix workspace tests after changing Item trait --- crates/workspace/src/item.rs | 46 ++++++++++++++--------- crates/workspace/src/pane.rs | 16 ++++---- crates/workspace/src/workspace.rs | 62 ++++++++++++++++++++++--------- 3 files changed, 82 insertions(+), 42 deletions(-) diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 1bc713b5fb..b1888bb243 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -784,6 +784,27 @@ pub(crate) mod test { } } + impl TestProjectItem { + pub fn new(id: u64, path: &str, cx: &mut MutableAppContext) -> ModelHandle { + let entry_id = Some(ProjectEntryId::from_proto(id)); + let project_path = Some(ProjectPath { + worktree_id: WorktreeId::from_usize(0), + path: Path::new(path).into(), + }); + cx.add_model(|_| Self { + entry_id, + project_path, + }) + } + + pub fn new_untitled(cx: &mut MutableAppContext) -> ModelHandle { + cx.add_model(|_| Self { + project_path: None, + entry_id: None, + }) + } + } + impl TestItem { pub fn new() -> Self { Self { @@ -829,22 +850,9 @@ pub(crate) mod test { self } - pub fn with_project_items( - mut self, - items: &[(u64, &str)], - cx: &mut MutableAppContext, - ) -> Self { - self.project_items - .extend(items.iter().copied().map(|(id, path)| { - let id = ProjectEntryId::from_proto(id); - cx.add_model(|_| TestProjectItem { - entry_id: Some(id), - project_path: Some(ProjectPath { - worktree_id: WorktreeId::from_usize(0), - path: Path::new(path).into(), - }), - }) - })); + pub fn with_project_items(mut self, items: &[ModelHandle]) -> Self { + self.project_items.clear(); + self.project_items.extend(items.iter().cloned()); self } @@ -938,8 +946,12 @@ pub(crate) mod test { self.has_conflict } - fn can_save(&self, _: &AppContext) -> bool { + fn can_save(&self, cx: &AppContext) -> bool { !self.project_items.is_empty() + && self + .project_items + .iter() + .all(|item| item.read(cx).entry_id.is_some()) } fn save( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index de298e7be1..05d6de1ee2 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -804,7 +804,7 @@ impl Pane { items_to_close.sort_by_key(|item| !item.is_singleton(cx)); cx.spawn(|workspace, mut cx| async move { - let mut saved_project_entry_ids = HashSet::default(); + let mut saved_project_items_ids = HashSet::default(); for item in items_to_close.clone() { // Find the item's current index and its set of project entries. Avoid // storing these in advance, in case they have changed since this task @@ -836,7 +836,7 @@ impl Pane { }); let should_save = project_item_ids .iter() - .any(|id| saved_project_entry_ids.insert(*id)); + .any(|id| saved_project_items_ids.insert(*id)); if should_save && !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx) @@ -1672,7 +1672,7 @@ mod tests { use std::sync::Arc; use super::*; - use crate::item::test::TestItem; + use crate::item::test::{TestItem, TestProjectItem}; use gpui::{executor::Deterministic, TestAppContext}; use project::FakeFs; @@ -1861,7 +1861,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_items(&[(1, "one.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]); Pane::add_item( workspace, @@ -1880,7 +1880,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, @@ -1899,7 +1899,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 2") - .with_project_items(&[(2, "2.txt")], cx); + .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]); Pane::add_item( workspace, @@ -1918,7 +1918,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, @@ -1937,7 +1937,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1b") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a521d02b5f..47e2be29df 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2727,7 +2727,7 @@ pub fn open_new(app_state: &Arc, cx: &mut MutableAppContext) -> Task<( mod tests { use std::{cell::RefCell, rc::Rc}; - use crate::item::test::{TestItem, TestItemEvent}; + use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; use super::*; use fs::FakeFs; @@ -2835,10 +2835,10 @@ mod tests { }); let item1 = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "one.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) }); let item2 = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(2, "two.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)]) }); // Add an item to an empty pane @@ -2943,7 +2943,7 @@ mod tests { let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_items(&[(1, "1.txt")], cx) + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); workspace.update(cx, |w, cx| { w.add_item(Box::new(item2.clone()), cx); @@ -2971,21 +2971,25 @@ mod tests { let item1 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_items(&[(1, "1.txt")], cx) + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let item2 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_items(&[(2, "2.txt")], cx) + .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]) }); let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_items(&[(3, "3.txt")], cx) + .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)]) + }); + let item4 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_project_items(&[TestProjectItem::new_untitled(cx)]) }); - let item4 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); let pane = workspace.update(cx, |workspace, cx| { workspace.add_item(Box::new(item1.clone()), cx); workspace.add_item(Box::new(item2.clone()), cx); @@ -3007,15 +3011,20 @@ mod tests { [item1_id, item3_id, item4_id].contains(&id) }) }); - cx.foreground().run_until_parked(); + + // There's a prompt to save item 1. pane.read_with(cx, |pane, _| { assert_eq!(pane.items_len(), 4); assert_eq!(pane.active_item().unwrap().id(), item1.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Confirm saving item 1. cx.simulate_prompt_answer(window_id, 0); cx.foreground().run_until_parked(); + + // Item 1 is saved. There's a prompt to save item 3. pane.read_with(cx, |pane, cx| { assert_eq!(item1.read(cx).save_count, 1); assert_eq!(item1.read(cx).save_as_count, 0); @@ -3023,9 +3032,13 @@ mod tests { assert_eq!(pane.items_len(), 3); assert_eq!(pane.active_item().unwrap().id(), item3.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Cancel saving item 3. cx.simulate_prompt_answer(window_id, 1); cx.foreground().run_until_parked(); + + // Item 3 is reloaded. There's a prompt to save item 4. pane.read_with(cx, |pane, cx| { assert_eq!(item3.read(cx).save_count, 0); assert_eq!(item3.read(cx).save_as_count, 0); @@ -3033,11 +3046,17 @@ mod tests { assert_eq!(pane.items_len(), 2); assert_eq!(pane.active_item().unwrap().id(), item4.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Confirm saving item 4. cx.simulate_prompt_answer(window_id, 0); cx.foreground().run_until_parked(); + + // There's a prompt for a path for item 4. cx.simulate_new_path_selection(|_| Some(Default::default())); close_items.await.unwrap(); + + // The requested items are closed. pane.read_with(cx, |pane, cx| { assert_eq!(item4.read(cx).save_count, 0); assert_eq!(item4.read(cx).save_as_count, 1); @@ -3063,10 +3082,13 @@ mod tests { let single_entry_items = (0..=4) .map(|project_entry_id| { cx.add_view(&workspace, |cx| { - TestItem::new().with_dirty(true).with_project_items( - &[(project_entry_id, &format!("{project_entry_id}.txt"))], - cx, - ) + TestItem::new() + .with_dirty(true) + .with_project_items(&[TestProjectItem::new( + project_entry_id, + &format!("{project_entry_id}.txt"), + cx, + )]) }) }) .collect::>(); @@ -3074,13 +3096,19 @@ mod tests { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_items(&[(2, "2.txt"), (3, "3.txt")], cx) + .with_project_items(&[ + single_entry_items[2].read(cx).project_items[0].clone(), + single_entry_items[3].read(cx).project_items[0].clone(), + ]) }); let item_3_4 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_items(&[(3, "3.txt"), (4, "4.txt")], cx) + .with_project_items(&[ + single_entry_items[3].read(cx).project_items[0].clone(), + single_entry_items[4].read(cx).project_items[0].clone(), + ]) }); // Create two panes that contain the following project entries: @@ -3159,7 +3187,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "1.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let item_id = item.id(); workspace.update(cx, |workspace, cx| { @@ -3278,7 +3306,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "1.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); From 062e7a03a9604c39ec8ead4542d0493b469f9a19 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Jan 2023 15:17:44 -0800 Subject: [PATCH 067/108] Update comments in Pane::close_items --- crates/workspace/src/pane.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 05d6de1ee2..8d0d731551 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -806,7 +806,7 @@ impl Pane { cx.spawn(|workspace, mut cx| async move { let mut saved_project_items_ids = HashSet::default(); for item in items_to_close.clone() { - // Find the item's current index and its set of project entries. Avoid + // Find the item's current index and its set of project item models. Avoid // storing these in advance, in case they have changed since this task // was started. let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { @@ -818,11 +818,9 @@ impl Pane { continue; }; - // If an item hasn't yet been associated with a project entry, then always - // prompt to save it before closing it. Otherwise, check if the item has - // any project entries that are not open anywhere else in the workspace, - // AND that the user has not already been prompted to save. If there are - // any such project entries, prompt the user to save this item. + // Check if this view has any project items that are not open anywhere else + // in the workspace, AND that the user has not already been prompted to save. + // If there are any such project entries, prompt the user to save this item. workspace.read_with(&cx, |workspace, cx| { for item in workspace.items(cx) { if !items_to_close From d9948bf77273df94f92d0cca7cb8f38ea0ef46b5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Jan 2023 16:43:18 -0800 Subject: [PATCH 068/108] Prevent outline items from accidentally spanning multiple lines --- crates/language/src/buffer.rs | 10 +++++++--- crates/language/src/buffer_tests.rs | 26 +++++++++++++++++++++++++ crates/zed/src/languages/go/outline.scm | 3 +-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 4b79009a68..a52ee34962 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2168,8 +2168,6 @@ impl BufferSnapshot { continue; } - // TODO - move later, after processing captures - let mut text = String::new(); let mut name_ranges = Vec::new(); let mut highlight_ranges = Vec::new(); @@ -2183,7 +2181,13 @@ impl BufferSnapshot { continue; } - let range = capture.node.start_byte()..capture.node.end_byte(); + let mut range = capture.node.start_byte()..capture.node.end_byte(); + let start = capture.node.start_position(); + if capture.node.end_position().row > start.row { + range.end = + range.start + self.line_len(start.row as u32) as usize - start.column; + } + if !text.is_empty() { text.push(' '); } diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index b7f35e9ad5..5aa9152205 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -455,6 +455,32 @@ async fn test_outline(cx: &mut gpui::TestAppContext) { } } +#[gpui::test] +async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) { + let text = r#" + impl A for B< + C + > { + }; + "# + .unindent(); + + let buffer = + cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx)); + let outline = buffer + .read_with(cx, |buffer, _| buffer.snapshot().outline(None)) + .unwrap(); + + assert_eq!( + outline + .items + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect::>(), + &[("impl A for B<", 0)] + ); +} + #[gpui::test] async fn test_symbols_containing(cx: &mut gpui::TestAppContext) { let text = r#" diff --git a/crates/zed/src/languages/go/outline.scm b/crates/zed/src/languages/go/outline.scm index 849d2bdebf..2ff7ef25a0 100644 --- a/crates/zed/src/languages/go/outline.scm +++ b/crates/zed/src/languages/go/outline.scm @@ -40,5 +40,4 @@ ")" @context)) @item (field_declaration - name: (_) @name - type: (_) @context) @item \ No newline at end of file + name: (_) @name) @item \ No newline at end of file From 5ce065ac92381ad629dcd8e6bd4ee5ab2a1155bd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 15:42:14 +0100 Subject: [PATCH 069/108] Introduce `MultiBuffer::stream_excerpts_with_context_lines` This allows us to push excerpts in a streaming fashion without blocking the main thread. --- Cargo.lock | 1 + crates/editor/src/multi_buffer.rs | 134 +++++++++++++++++++++------- crates/search/Cargo.toml | 1 + crates/search/src/project_search.rs | 29 +++--- 4 files changed, 114 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbe77d8c75..7be9d45293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5510,6 +5510,7 @@ dependencies = [ "anyhow", "collections", "editor", + "futures 0.3.25", "gpui", "language", "log", diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 2347d9a63d..4d576883d3 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -4,6 +4,7 @@ pub use anchor::{Anchor, AnchorRangeExt}; use anyhow::Result; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet}; +use futures::{channel::mpsc, SinkExt}; use git::diff::DiffHunk; use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; @@ -764,6 +765,63 @@ impl MultiBuffer { None } + pub fn stream_excerpts_with_context_lines( + &mut self, + excerpts: Vec<(ModelHandle, Vec>)>, + context_line_count: u32, + cx: &mut ModelContext, + ) -> (Task<()>, mpsc::Receiver>) { + let (mut tx, rx) = mpsc::channel(256); + let task = cx.spawn(|this, mut cx| async move { + for (buffer, ranges) in excerpts { + let buffer_id = buffer.id(); + let buffer_snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot()); + + let mut excerpt_ranges = Vec::new(); + let mut range_counts = Vec::new(); + cx.background() + .scoped(|scope| { + scope.spawn(async { + let (ranges, counts) = + build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); + excerpt_ranges = ranges; + range_counts = counts; + }); + }) + .await; + + let mut ranges = ranges.into_iter(); + let mut range_counts = range_counts.into_iter(); + for excerpt_ranges in excerpt_ranges.chunks(100) { + let excerpt_ids = this.update(&mut cx, |this, cx| { + this.push_excerpts(buffer.clone(), excerpt_ranges.iter().cloned(), cx) + }); + + for (excerpt_id, range_count) in + excerpt_ids.into_iter().zip(range_counts.by_ref()) + { + for range in ranges.by_ref().take(range_count) { + let start = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: range.start, + }; + let end = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: range.end, + }; + if tx.send(start..end).await.is_err() { + break; + } + } + } + } + } + }); + (task, rx) + } + pub fn push_excerpts( &mut self, buffer: ModelHandle, @@ -788,39 +846,8 @@ impl MultiBuffer { { let buffer_id = buffer.id(); let buffer_snapshot = buffer.read(cx).snapshot(); - let max_point = buffer_snapshot.max_point(); - - let mut range_counts = Vec::new(); - let mut excerpt_ranges = Vec::new(); - let mut range_iter = ranges - .iter() - .map(|range| { - range.start.to_point(&buffer_snapshot)..range.end.to_point(&buffer_snapshot) - }) - .peekable(); - while let Some(range) = range_iter.next() { - let excerpt_start = Point::new(range.start.row.saturating_sub(context_line_count), 0); - let mut excerpt_end = - Point::new(range.end.row + 1 + context_line_count, 0).min(max_point); - let mut ranges_in_excerpt = 1; - - while let Some(next_range) = range_iter.peek() { - if next_range.start.row <= excerpt_end.row + context_line_count { - excerpt_end = - Point::new(next_range.end.row + 1 + context_line_count, 0).min(max_point); - ranges_in_excerpt += 1; - range_iter.next(); - } else { - break; - } - } - - excerpt_ranges.push(ExcerptRange { - context: excerpt_start..excerpt_end, - primary: Some(range), - }); - range_counts.push(ranges_in_excerpt); - } + let (excerpt_ranges, range_counts) = + build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); let excerpt_ids = self.push_excerpts(buffer, excerpt_ranges, cx); @@ -3605,6 +3632,47 @@ impl ToPointUtf16 for PointUtf16 { } } +fn build_excerpt_ranges( + buffer: &BufferSnapshot, + ranges: &[Range], + context_line_count: u32, +) -> (Vec>, Vec) +where + T: text::ToPoint, +{ + let max_point = buffer.max_point(); + let mut range_counts = Vec::new(); + let mut excerpt_ranges = Vec::new(); + let mut range_iter = ranges + .iter() + .map(|range| range.start.to_point(buffer)..range.end.to_point(buffer)) + .peekable(); + while let Some(range) = range_iter.next() { + let excerpt_start = Point::new(range.start.row.saturating_sub(context_line_count), 0); + let mut excerpt_end = Point::new(range.end.row + 1 + context_line_count, 0).min(max_point); + let mut ranges_in_excerpt = 1; + + while let Some(next_range) = range_iter.peek() { + if next_range.start.row <= excerpt_end.row + context_line_count { + excerpt_end = + Point::new(next_range.end.row + 1 + context_line_count, 0).min(max_point); + ranges_in_excerpt += 1; + range_iter.next(); + } else { + break; + } + } + + excerpt_ranges.push(ExcerptRange { + context: excerpt_start..excerpt_end, + primary: Some(range), + }); + range_counts.push(ranges_in_excerpt); + } + + (excerpt_ranges, range_counts) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 30802801a6..3a5d9468fc 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -19,6 +19,7 @@ theme = { path = "../theme" } util = { path = "../util" } workspace = { path = "../workspace" } anyhow = "1.0" +futures = "0.3" log = { version = "0.4.16", features = ["kv_unstable_serde"] } postage = { version = "0.4.1", features = ["futures-traits"] } serde = { version = "1.0", features = ["derive", "rc"] } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a0f29738b0..11318b1c43 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -7,6 +7,7 @@ use editor::{ items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN, }; +use futures::StreamExt; use gpui::{ actions, elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, @@ -129,31 +130,23 @@ impl ProjectSearch { let matches = search.await.log_err()?; let this = this.upgrade(&cx)?; let mut matches = matches.into_iter().collect::>(); - this.update(&mut cx, |this, cx| { + let (_rebuild, mut match_ranges) = this.update(&mut cx, |this, cx| { this.match_ranges.clear(); matches.sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); - this.excerpts.update(cx, |excerpts, cx| excerpts.clear(cx)); + this.excerpts.update(cx, |excerpts, cx| { + excerpts.clear(cx); + excerpts.stream_excerpts_with_context_lines(matches, 1, cx) + }) }); - for matches_chunk in matches.chunks(100) { + while let Some(match_range) = match_ranges.next().await { this.update(&mut cx, |this, cx| { - this.excerpts.update(cx, |excerpts, cx| { - for (buffer, buffer_matches) in matches_chunk { - let ranges_to_highlight = excerpts.push_excerpts_with_context_lines( - buffer.clone(), - buffer_matches.clone(), - 1, - cx, - ); - this.match_ranges.extend(ranges_to_highlight); - } - }); - + this.match_ranges.push(match_range); + while let Ok(Some(match_range)) = match_ranges.try_next() { + this.match_ranges.push(match_range); + } cx.notify(); }); - - // Don't starve the main thread when adding lots of excerpts. - smol::future::yield_now().await; } this.update(&mut cx, |this, cx| { From c124caeb0dccad8073f6e25413f6cb54143f5ca7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 15:52:59 +0100 Subject: [PATCH 070/108] Add test for `stream_excerpts_with_context_lines` --- crates/editor/src/multi_buffer.rs | 39 +++++++++++++++++++++++++++++ crates/search/src/project_search.rs | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 4d576883d3..7812c34781 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -3676,6 +3676,7 @@ where #[cfg(test)] mod tests { use super::*; + use futures::StreamExt; use gpui::{MutableAppContext, TestAppContext}; use language::{Buffer, Rope}; use rand::prelude::*; @@ -4080,6 +4081,44 @@ mod tests { ); } + #[gpui::test] + async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) { + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(20, 3, 'a'), cx)); + let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); + let (task, anchor_ranges) = multibuffer.update(cx, |multibuffer, cx| { + let snapshot = buffer.read(cx); + let ranges = vec![ + snapshot.anchor_before(Point::new(3, 2))..snapshot.anchor_before(Point::new(4, 2)), + snapshot.anchor_before(Point::new(7, 1))..snapshot.anchor_before(Point::new(7, 3)), + snapshot.anchor_before(Point::new(15, 0)) + ..snapshot.anchor_before(Point::new(15, 0)), + ]; + multibuffer.stream_excerpts_with_context_lines(vec![(buffer.clone(), ranges)], 2, cx) + }); + + let anchor_ranges = anchor_ranges.collect::>().await; + // Ensure task is finished when stream completes. + task.await; + + let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx)); + assert_eq!( + snapshot.text(), + "bbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\n\nnnn\nooo\nppp\nqqq\nrrr\n" + ); + + assert_eq!( + anchor_ranges + .iter() + .map(|range| range.to_point(&snapshot)) + .collect::>(), + vec![ + Point::new(2, 2)..Point::new(3, 2), + Point::new(6, 1)..Point::new(6, 3), + Point::new(12, 0)..Point::new(12, 0) + ] + ); + } + #[gpui::test] fn test_empty_multibuffer(cx: &mut MutableAppContext) { let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 11318b1c43..68c2f33a75 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -130,7 +130,7 @@ impl ProjectSearch { let matches = search.await.log_err()?; let this = this.upgrade(&cx)?; let mut matches = matches.into_iter().collect::>(); - let (_rebuild, mut match_ranges) = this.update(&mut cx, |this, cx| { + let (_task, mut match_ranges) = this.update(&mut cx, |this, cx| { this.match_ranges.clear(); matches.sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); this.excerpts.update(cx, |excerpts, cx| { From 0a7111d21607cbf780cef0159729602265ac429f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 16:26:27 +0100 Subject: [PATCH 071/108] Fix tests --- crates/search/src/project_search.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a315cf0c5f..d3d5c437c5 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -949,13 +949,13 @@ impl ToolbarItemView for ProjectSearchBar { mod tests { use super::*; use editor::DisplayPoint; - use gpui::{color::Color, TestAppContext}; + use gpui::{color::Color, executor::Deterministic, TestAppContext}; use project::FakeFs; use serde_json::json; use std::sync::Arc; #[gpui::test] - async fn test_project_search(cx: &mut TestAppContext) { + async fn test_project_search(deterministic: Arc, cx: &mut TestAppContext) { let fonts = cx.font_cache(); let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default); theme.search.match_background = Color::red(); @@ -987,7 +987,7 @@ mod tests { .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx)); search_view.search(cx); }); - search_view.next_notification(cx).await; + deterministic.run_until_parked(); search_view.update(cx, |search_view, cx| { assert_eq!( search_view From 1f649e52dedd1d46fb034749f111d31ab60f71c4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 17:25:59 +0100 Subject: [PATCH 072/108] Document RopeFingerprint --- crates/rope/src/rope.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 74cc9a6789..d0796cfc6a 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -25,6 +25,10 @@ const CHUNK_BASE: usize = 6; #[cfg(not(test))] const CHUNK_BASE: usize = 16; +/// Type alias to [HashMatrix], an implementation of a homomorphic hash function. Two [Rope] instances +/// containing the same text will produce the same fingerprint. This hash function is special in that +/// it allows us to hash individual chunks and aggregate them up the [Rope]'s tree, with the resulting +/// hash being equivalent to hashing all the text contained in the [Rope] at once. pub type RopeFingerprint = HashMatrix; #[derive(Clone, Default, Debug)] @@ -858,7 +862,7 @@ impl sum_tree::Item for Chunk { #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct ChunkSummary { text: TextSummary, - fingerprint: HashMatrix, + fingerprint: RopeFingerprint, } impl<'a> From<&'a str> for ChunkSummary { From f088de5947f6b467171a1f1950239761d525b544 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Thu, 19 Jan 2023 19:05:17 +0200 Subject: [PATCH 073/108] Add date to the log format Co-Authored-By: Mikayla --- crates/zed/src/main.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 6baf08b01e..16fd0c1eea 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -29,6 +29,7 @@ use settings::{ self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent, WorkingDirectory, }; +use simplelog::ConfigBuilder; use smol::process::Command; use std::fs::OpenOptions; use std::{env, ffi::OsStr, panic, path::PathBuf, sync::Arc, thread, time::Duration}; @@ -269,8 +270,12 @@ fn init_logger() { .append(true) .open(&*paths::LOG) .expect("could not open logfile"); - simplelog::WriteLogger::init(level, simplelog::Config::default(), log_file) - .expect("could not initialize logger"); + + let config = ConfigBuilder::new() + .set_time_format_str("%Y-%m-%dT%T") //All timestamps are UTC + .build(); + + simplelog::WriteLogger::init(level, config, log_file).expect("could not initialize logger"); } } From ad1947fa50d1561fd9741dafb1e9744480941c8f Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 19 Jan 2023 12:34:13 -0500 Subject: [PATCH 074/108] Add in-window on-move-out mouse handler concept --- crates/editor/src/element.rs | 3 +++ .../gpui/src/elements/mouse_event_handler.rs | 10 ++++++- crates/gpui/src/presenter.rs | 17 ++++++++++-- crates/gpui/src/scene/mouse_event.rs | 13 +++++++++ crates/gpui/src/scene/mouse_region.rs | 27 ++++++++++++++++++- 5 files changed, 66 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7d69d3833c..5c34b98380 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -190,6 +190,9 @@ impl EditorElement { } } }) + .on_move_out(|e, cx| { + println!("on move out"); + }) .on_scroll({ let position_map = position_map.clone(); move |e, cx| { diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 132a915f0b..761e134262 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -7,7 +7,7 @@ use crate::{ platform::CursorStyle, scene::{ CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover, - MouseMove, MouseScrollWheel, MouseUp, MouseUpOut, + MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, }, DebugContext, Element, ElementBox, EventContext, LayoutContext, MeasurementContext, MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View, @@ -82,6 +82,14 @@ impl MouseEventHandler { self } + pub fn on_move_out( + mut self, + handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_move_out(handler); + self + } + pub fn on_down( mut self, button: MouseButton, diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index bbaf1ed0bb..1573b10a10 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -8,7 +8,7 @@ use crate::{ platform::{CursorStyle, Event}, scene::{ CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, - MouseMove, MouseScrollWheel, MouseUp, MouseUpOut, Scene, + MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene, }, text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, Appearance, @@ -245,8 +245,11 @@ impl Presenter { // -> Also updates mouse-related state match &event { Event::KeyDown(e) => return cx.dispatch_key_down(self.window_id, e), + Event::KeyUp(e) => return cx.dispatch_key_up(self.window_id, e), + Event::ModifiersChanged(e) => return cx.dispatch_modifiers_changed(self.window_id, e), + Event::MouseDown(e) => { // Click events are weird because they can be fired after a drag event. // MDN says that browsers handle this by starting from 'the most @@ -279,6 +282,7 @@ impl Presenter { platform_event: e.clone(), })); } + Event::MouseUp(e) => { // NOTE: The order of event pushes is important! MouseUp events MUST be fired // before click events, and so the MouseUp events need to be pushed before @@ -296,6 +300,7 @@ impl Presenter { platform_event: e.clone(), })); } + Event::MouseMoved( e @ MouseMovedEvent { position, @@ -347,9 +352,13 @@ impl Presenter { platform_event: e.clone(), started: false, })); + mouse_events.push(MouseEvent::MoveOut(MouseMoveOut { + region: Default::default(), + })); self.last_mouse_moved_event = Some(event.clone()); } + Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel { region: Default::default(), platform_event: e.clone(), @@ -407,6 +416,7 @@ impl Presenter { } } } + MouseEvent::Down(_) | MouseEvent::Up(_) => { for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(self.mouse_position) { @@ -417,6 +427,7 @@ impl Presenter { } } } + MouseEvent::Click(e) => { // Only raise click events if the released button is the same as the one stored if self @@ -439,6 +450,7 @@ impl Presenter { } } } + MouseEvent::Drag(_) => { for (mouse_region, _) in self.mouse_regions.iter().rev() { if self.clicked_region_ids.contains(&mouse_region.id()) { @@ -447,7 +459,7 @@ impl Presenter { } } - MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => { + MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => { for (mouse_region, _) in self.mouse_regions.iter().rev() { // NOT contains if !mouse_region.bounds.contains_point(self.mouse_position) { @@ -455,6 +467,7 @@ impl Presenter { } } } + _ => { for (mouse_region, _) in self.mouse_regions.iter().rev() { // Contains diff --git a/crates/gpui/src/scene/mouse_event.rs b/crates/gpui/src/scene/mouse_event.rs index 00d1ddbf8b..2effb8ab53 100644 --- a/crates/gpui/src/scene/mouse_event.rs +++ b/crates/gpui/src/scene/mouse_event.rs @@ -21,6 +21,11 @@ impl Deref for MouseMove { } } +#[derive(Debug, Default, Clone)] +pub struct MouseMoveOut { + pub region: RectF, +} + #[derive(Debug, Default, Clone)] pub struct MouseDrag { pub region: RectF, @@ -138,6 +143,7 @@ impl Deref for MouseScrollWheel { #[derive(Debug, Clone)] pub enum MouseEvent { Move(MouseMove), + MoveOut(MouseMoveOut), Drag(MouseDrag), Hover(MouseHover), Down(MouseDown), @@ -152,6 +158,7 @@ impl MouseEvent { pub fn set_region(&mut self, region: RectF) { match self { MouseEvent::Move(r) => r.region = region, + MouseEvent::MoveOut(r) => r.region = region, MouseEvent::Drag(r) => r.region = region, MouseEvent::Hover(r) => r.region = region, MouseEvent::Down(r) => r.region = region, @@ -168,6 +175,7 @@ impl MouseEvent { pub fn is_capturable(&self) -> bool { match self { MouseEvent::Move(_) => true, + MouseEvent::MoveOut(_) => false, MouseEvent::Drag(_) => true, MouseEvent::Hover(_) => false, MouseEvent::Down(_) => true, @@ -185,6 +193,10 @@ impl MouseEvent { discriminant(&MouseEvent::Move(Default::default())) } + pub fn move_out_disc() -> Discriminant { + discriminant(&MouseEvent::MoveOut(Default::default())) + } + pub fn drag_disc() -> Discriminant { discriminant(&MouseEvent::Drag(Default::default())) } @@ -220,6 +232,7 @@ impl MouseEvent { pub fn handler_key(&self) -> HandlerKey { match self { MouseEvent::Move(_) => HandlerKey::new(Self::move_disc(), None), + MouseEvent::MoveOut(_) => HandlerKey::new(Self::move_out_disc(), None), MouseEvent::Drag(e) => HandlerKey::new(Self::drag_disc(), e.pressed_button), MouseEvent::Hover(_) => HandlerKey::new(Self::hover_disc(), None), MouseEvent::Down(e) => HandlerKey::new(Self::down_disc(), Some(e.button)), diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 0fdc76ebbf..7cbbc5b897 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -12,7 +12,7 @@ use super::{ MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, MouseMove, MouseUp, MouseUpOut, }, - MouseScrollWheel, + MouseMoveOut, MouseScrollWheel, }; #[derive(Clone)] @@ -124,6 +124,14 @@ impl MouseRegion { self } + pub fn on_move_out( + mut self, + handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_move_out(handler); + self + } + pub fn on_scroll( mut self, handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static, @@ -289,6 +297,23 @@ impl HandlerSet { self } + pub fn on_move_out( + mut self, + handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, + ) -> Self { + self.insert(MouseEvent::move_out_disc(), None, + Rc::new(move |region_event, cx| { + if let MouseEvent::MoveOut(e) = region_event { + handler(e, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::MoveOut, found {:?}", + region_event); + } + })); + self + } + pub fn on_down( mut self, button: MouseButton, From d30e129d63d7438e717fdc958dd2473dea05fd3b Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Thu, 19 Jan 2023 19:38:05 +0200 Subject: [PATCH 075/108] Create files passed as args to CLI Co-Authored-by: Mikayla --- crates/cli/src/main.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 4bc2d6e73d..a31e59587f 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -9,7 +9,13 @@ use core_foundation::{ use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType}; use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender}; use serde::Deserialize; -use std::{ffi::OsStr, fs, path::PathBuf, ptr}; +use std::{ + ffi::OsStr, + fs::{self, OpenOptions}, + io, + path::{Path, PathBuf}, + ptr, +}; #[derive(Parser)] #[clap(name = "zed", global_setting(clap::AppSettings::NoAutoVersion))] @@ -54,6 +60,12 @@ fn main() -> Result<()> { return Ok(()); } + for path in args.paths.iter() { + if !path.exists() { + touch(path.as_path())?; + } + } + let (tx, rx) = launch_app(bundle_path)?; tx.send(CliRequest::Open { @@ -77,6 +89,13 @@ fn main() -> Result<()> { Ok(()) } +fn touch(path: &Path) -> io::Result<()> { + match OpenOptions::new().create(true).write(true).open(path) { + Ok(_) => Ok(()), + Err(e) => Err(e), + } +} + fn locate_bundle() -> Result { let cli_path = std::env::current_exe()?.canonicalize()?; let mut app_path = cli_path.clone(); From 38476f5429562246a7e293e1a9ee22d45e6a0b4a Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Thu, 19 Jan 2023 13:45:05 -0500 Subject: [PATCH 076/108] Disable soft wrap in single line editors Co-Authored-By: Mikayla Maki --- crates/editor/src/editor.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 07c0bce6b6..a6ce562631 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1086,6 +1086,8 @@ impl Editor { let blink_manager = cx.add_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx)); + let soft_wrap_mode_override = + (mode == EditorMode::SingleLine).then(|| settings::SoftWrap::None); let mut this = Self { handle: cx.weak_handle(), buffer: buffer.clone(), @@ -1101,7 +1103,7 @@ impl Editor { select_larger_syntax_node_stack: Vec::new(), ime_transaction: Default::default(), active_diagnostics: None, - soft_wrap_mode_override: None, + soft_wrap_mode_override, get_field_editor_theme, project, focused: false, From 4a46227909820fe6f5014f72b7758a3242d67e6c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 11:43:46 -0800 Subject: [PATCH 077/108] Change incoming call notification to only require one click --- crates/collab_ui/src/incoming_call_notification.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index a51fb4891d..daf93ce6a2 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -48,6 +48,7 @@ pub fn init(cx: &mut MutableAppContext) { }, |_| IncomingCallNotification::new(incoming_call.clone()), ); + cx.activate_window(window_id); notification_windows.push(window_id); } } From 1851e2e77cd0a1e59ec9c1487000d01311714f05 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 19 Jan 2023 12:31:34 -0800 Subject: [PATCH 078/108] Start work on language config overrides Co-authored-by: Julia Risley --- crates/editor/src/editor.rs | 9 +- crates/editor/src/multi_buffer.rs | 11 +- crates/language/src/buffer.rs | 23 +++- crates/language/src/language.rs | 123 +++++++++++++++--- crates/language/src/syntax_map.rs | 35 +++++ .../zed/src/languages/javascript/contexts.scm | 0 crates/zed/src/languages/tsx/config.toml | 9 ++ crates/zed/src/languages/tsx/overrides.scm | 2 + 8 files changed, 189 insertions(+), 23 deletions(-) create mode 100644 crates/zed/src/languages/javascript/contexts.scm create mode 100644 crates/zed/src/languages/tsx/overrides.scm diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a6ce562631..81ab73e24e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1737,7 +1737,7 @@ impl Editor { for (selection, autoclose_region) in self.selections_with_autoclose_regions(selections, &snapshot) { - if let Some(language) = snapshot.language_at(selection.head()) { + if let Some(language) = snapshot.language_config_at(selection.head()) { // Determine if the inserted text matches the opening or closing // bracket of any of this language's bracket pairs. let mut bracket_pair = None; @@ -1898,7 +1898,7 @@ impl Editor { let end = selection.end; let mut insert_extra_newline = false; - if let Some(language) = buffer.language_at(start) { + if let Some(language) = buffer.language_config_at(start) { let leading_whitespace_len = buffer .reversed_chars_at(start) .take_while(|c| c.is_whitespace() && *c != '\n') @@ -4533,7 +4533,10 @@ impl Editor { // TODO: Handle selections that cross excerpts for selection in &mut selections { - let language = if let Some(language) = snapshot.language_at(selection.start) { + let start_column = snapshot.indent_size_for_line(selection.start.row).len; + let language = if let Some(language) = + snapshot.language_config_at(Point::new(selection.start.row, start_column)) + { language } else { continue; diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 6b5262c16a..57fe5411cd 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -10,9 +10,9 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, - DiagnosticEntry, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, - Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, - ToPointUtf16 as _, TransactionId, Unclipped, + DiagnosticEntry, IndentSize, Language, LanguageConfigYeet, OffsetRangeExt, OffsetUtf16, + Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, + ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; use std::{ borrow::Cow, @@ -2691,6 +2691,11 @@ impl MultiBufferSnapshot { .and_then(|(buffer, offset)| buffer.language_at(offset)) } + pub fn language_config_at<'a, T: ToOffset>(&'a self, point: T) -> Option { + self.point_to_buffer_offset(point) + .and_then(|(buffer, offset)| buffer.language_config_at(offset)) + } + pub fn is_dirty(&self) -> bool { self.is_dirty } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index ad3bdb6821..5593241e70 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -9,7 +9,7 @@ use crate::{ syntax_map::{ SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint, }, - CodeLabel, Outline, + CodeLabel, LanguageConfigYeet, Outline, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -2015,6 +2015,27 @@ impl BufferSnapshot { .or(self.language.as_ref()) } + pub fn language_config_at(&self, position: D) -> Option { + let offset = position.to_offset(self); + + if let Some(layer_info) = self + .syntax + .layers_for_range(offset..offset, &self.text) + .filter(|l| l.node.end_byte() > offset) + .last() + { + Some(LanguageConfigYeet { + language: layer_info.language.clone(), + override_id: layer_info.override_id(offset, &self.text), + }) + } else { + self.language.clone().map(|language| LanguageConfigYeet { + language, + override_id: None, + }) + } + } + pub fn surrounding_word(&self, start: T) -> (Range, Option) { let mut start = start.to_offset(self); let mut end = start; diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c3f2c3716b..3c8b2b987b 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -22,7 +22,10 @@ use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; -use serde::{de, Deserialize, Deserializer}; +use serde::{ + de::{self}, + Deserialize, Deserializer, +}; use serde_json::Value; use std::{ any::Any, @@ -243,6 +246,45 @@ pub struct LanguageConfig { pub line_comment: Option>, #[serde(default)] pub block_comment: Option<(Arc, Arc)>, + #[serde(default)] + pub overrides: HashMap, +} + +#[derive(Clone)] +pub struct LanguageConfigYeet { + language: Arc, + override_id: Option, +} + +#[derive(Deserialize)] +pub struct LanguageConfigOverride { + #[serde(default)] + pub line_comment: Override>, + #[serde(default)] + pub block_comment: Override<(Arc, Arc)>, +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum Override { + Remove { remove: bool }, + Set(T), +} + +impl Default for Override { + fn default() -> Self { + Override::Remove { remove: false } + } +} + +impl Override { + fn as_option<'a>(this: Option<&'a Self>, original: &'a Option) -> Option<&'a T> { + match this { + Some(Self::Set(value)) => Some(value), + Some(Self::Remove { remove: true }) => None, + Some(Self::Remove { remove: false }) | None => original.as_ref(), + } + } } impl Default for LanguageConfig { @@ -257,6 +299,7 @@ impl Default for LanguageConfig { autoclose_before: Default::default(), line_comment: Default::default(), block_comment: Default::default(), + overrides: Default::default(), } } } @@ -311,6 +354,7 @@ pub struct Grammar { pub(crate) indents_config: Option, pub(crate) outline_config: Option, pub(crate) injection_config: Option, + pub(crate) override_config: Option, pub(crate) highlight_map: Mutex, } @@ -336,6 +380,11 @@ struct InjectionConfig { patterns: Vec, } +struct OverrideConfig { + query: Query, + values: HashMap, +} + #[derive(Default, Clone)] struct InjectionPatternConfig { language: Option>, @@ -635,6 +684,7 @@ impl Language { outline_config: None, indents_config: None, injection_config: None, + override_config: None, ts_language, highlight_map: Default::default(), }) @@ -775,6 +825,25 @@ impl Language { Ok(self) } + pub fn with_override_query(mut self, source: &str) -> Result { + let query = Query::new(self.grammar_mut().ts_language, source)?; + + let mut values = HashMap::default(); + for (ix, name) in query.capture_names().iter().enumerate() { + if let Some(override_name) = name.strip_prefix("override.") { + let value = self + .config + .overrides + .remove(override_name) + .ok_or_else(|| anyhow!("no such override {override_name}"))?; + values.insert(ix as u32, value); + } + } + + self.grammar_mut().override_config = Some(OverrideConfig { query, values }); + Ok(self) + } + fn grammar_mut(&mut self) -> &mut Grammar { Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap() } @@ -800,17 +869,6 @@ impl Language { self.config.name.clone() } - pub fn line_comment_prefix(&self) -> Option<&Arc> { - self.config.line_comment.as_ref() - } - - pub fn block_comment_delimiters(&self) -> Option<(&Arc, &Arc)> { - self.config - .block_comment - .as_ref() - .map(|(start, end)| (start, end)) - } - pub async fn disk_based_diagnostic_sources(&self) -> &[String] { match self.adapter.as_ref() { Some(adapter) => &adapter.disk_based_diagnostic_sources, @@ -886,10 +944,6 @@ impl Language { result } - pub fn brackets(&self) -> &[BracketPair] { - &self.config.brackets - } - pub fn path_suffixes(&self) -> &[String] { &self.config.path_suffixes } @@ -912,6 +966,43 @@ impl Language { } } +impl LanguageConfigYeet { + pub fn line_comment_prefix(&self) -> Option<&Arc> { + Override::as_option( + self.over_ride().map(|o| &o.line_comment), + &self.language.config.line_comment, + ) + } + + pub fn block_comment_delimiters(&self) -> Option<(&Arc, &Arc)> { + Override::as_option( + self.over_ride().map(|o| &o.block_comment), + &self.language.config.block_comment, + ) + .map(|e| (&e.0, &e.1)) + } + + pub fn brackets(&self) -> &[BracketPair] { + &self.language.config.brackets + } + + pub fn should_autoclose_before(&self, c: char) -> bool { + c.is_whitespace() || self.language.config.autoclose_before.contains(c) + } + + fn over_ride(&self) -> Option<&LanguageConfigOverride> { + self.override_id.and_then(|id| { + self.language + .grammar + .as_ref()? + .override_config + .as_ref()? + .values + .get(&id) + }) + } +} + impl Hash for Language { fn hash(&self, state: &mut H) { self.id().hash(state) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 65d01e9493..458ffd8bc2 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1127,6 +1127,41 @@ fn splice_included_ranges( ranges } +impl<'a> SyntaxLayerInfo<'a> { + pub(crate) fn override_id(&self, offset: usize, text: &text::BufferSnapshot) -> Option { + let text = TextProvider(text.as_rope()); + let config = self.language.grammar.as_ref()?.override_config.as_ref()?; + + let mut query_cursor = QueryCursorHandle::new(); + query_cursor.set_byte_range(offset..offset); + + let mut smallest_match: Option<(u32, Range)> = None; + for mat in query_cursor.matches(&config.query, self.node, text) { + for capture in mat.captures { + if !config.values.contains_key(&capture.index) { + continue; + } + + let range = capture.node.byte_range(); + if offset <= range.start || offset >= range.end { + continue; + } + + if let Some((_, smallest_range)) = &smallest_match { + if range.len() < smallest_range.len() { + smallest_match = Some((capture.index, range)) + } + continue; + } + + smallest_match = Some((capture.index, range)); + } + } + + smallest_match.map(|(index, _)| index) + } +} + impl std::ops::Deref for SyntaxMap { type Target = SyntaxSnapshot; diff --git a/crates/zed/src/languages/javascript/contexts.scm b/crates/zed/src/languages/javascript/contexts.scm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/zed/src/languages/tsx/config.toml b/crates/zed/src/languages/tsx/config.toml index 7baa12385c..4f466cc961 100644 --- a/crates/zed/src/languages/tsx/config.toml +++ b/crates/zed/src/languages/tsx/config.toml @@ -12,3 +12,12 @@ brackets = [ { start = "`", end = "`", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.element] +line_comment = { remove = true } +block_comment = ["{/* ", " */}"] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/tsx/overrides.scm b/crates/zed/src/languages/tsx/overrides.scm new file mode 100644 index 0000000000..aa617b3086 --- /dev/null +++ b/crates/zed/src/languages/tsx/overrides.scm @@ -0,0 +1,2 @@ +(jsx_element) @override.element +(string) @override.string From 4eeb1aec50f45a07ce3101bfa12eacc950235ee2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 14:22:12 -0800 Subject: [PATCH 079/108] Adds UI for showing the followed-by status to collaboration --- crates/collab_ui/src/collab_titlebar_item.rs | 12 ++++++++++-- crates/workspace/src/workspace.rs | 11 ++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 3351fb9eb9..ffafa2d142 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -417,9 +417,13 @@ impl CollabTitlebarItem { theme: &Theme, cx: &mut RenderContext, ) -> ElementBox { - let is_followed = peer.map_or(false, |(peer_id, _, _)| { - workspace.read(cx).is_following(peer_id) + let (is_followed, is_following) = peer.map_or((false, false), |(peer_id, _, _)| { + ( + workspace.read(cx).is_following(peer_id), + workspace.read(cx).is_followed(peer_id), + ) }); + // my color, around their avatar. let mut avatar_style; if let Some((_, _, location)) = peer.as_ref() { @@ -442,6 +446,10 @@ impl CollabTitlebarItem { replica_color = Some(color); if is_followed { avatar_style.border = Border::all(1.0, color); + } else if is_following { + let our_id = workspace.read(cx).project().read(cx).replica_id(); + let our_color = theme.editor.replica_selection_style(our_id).cursor; + avatar_style.border = Border::all(1.0, our_color); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5851734609..6a8fdaa17b 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1623,6 +1623,7 @@ impl Workspace { project_id, leader_id: Some(leader_id), }); + Some(cx.spawn_weak(|this, mut cx| async move { let response = request.await?; if let Some(this) = this.upgrade(&cx) { @@ -1719,6 +1720,10 @@ impl Workspace { self.follower_states_by_leader.contains_key(&peer_id) } + pub fn is_followed(&self, peer_id: PeerId) -> bool { + self.leader_state.followers.contains(&peer_id) + } + fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { let project = &self.project.read(cx); let mut worktree_root_names = String::new(); @@ -1896,6 +1901,9 @@ impl Workspace { .to_proto(), ) }); + + cx.notify(); + Ok(proto::FollowResponse { active_view_id, views: this @@ -1928,10 +1936,11 @@ impl Workspace { _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { - this.update(&mut cx, |this, _| { + this.update(&mut cx, |this, cx| { this.leader_state .followers .remove(&envelope.original_sender_id()?); + cx.notify(); Ok(()) }) } From 2967b46a17b9e5bb64dda54fe6fb5a68dee0b539 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 19 Jan 2023 15:04:27 -0800 Subject: [PATCH 080/108] Implement scope-specific bracket matching and comment toggling Co-authored-by: Julia Risley --- crates/editor/src/editor.rs | 6 +- crates/editor/src/multi_buffer.rs | 10 +-- crates/language/src/buffer.rs | 8 +-- crates/language/src/buffer_tests.rs | 83 ++++++++++++++++++++++ crates/language/src/language.rs | 48 ++++++------- crates/zed/src/languages.rs | 5 ++ crates/zed/src/languages/tsx/overrides.scm | 7 +- 7 files changed, 129 insertions(+), 38 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 81ab73e24e..0d62c1f625 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1737,7 +1737,7 @@ impl Editor { for (selection, autoclose_region) in self.selections_with_autoclose_regions(selections, &snapshot) { - if let Some(language) = snapshot.language_config_at(selection.head()) { + if let Some(language) = snapshot.language_scope_at(selection.head()) { // Determine if the inserted text matches the opening or closing // bracket of any of this language's bracket pairs. let mut bracket_pair = None; @@ -1898,7 +1898,7 @@ impl Editor { let end = selection.end; let mut insert_extra_newline = false; - if let Some(language) = buffer.language_config_at(start) { + if let Some(language) = buffer.language_scope_at(start) { let leading_whitespace_len = buffer .reversed_chars_at(start) .take_while(|c| c.is_whitespace() && *c != '\n') @@ -4535,7 +4535,7 @@ impl Editor { for selection in &mut selections { let start_column = snapshot.indent_size_for_line(selection.start.row).len; let language = if let Some(language) = - snapshot.language_config_at(Point::new(selection.start.row, start_column)) + snapshot.language_scope_at(Point::new(selection.start.row, start_column)) { language } else { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 57fe5411cd..7079d197f9 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -10,9 +10,9 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, - DiagnosticEntry, IndentSize, Language, LanguageConfigYeet, OffsetRangeExt, OffsetUtf16, - Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, - ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, + DiagnosticEntry, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, + OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, + ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; use std::{ borrow::Cow, @@ -2691,9 +2691,9 @@ impl MultiBufferSnapshot { .and_then(|(buffer, offset)| buffer.language_at(offset)) } - pub fn language_config_at<'a, T: ToOffset>(&'a self, point: T) -> Option { + pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option { self.point_to_buffer_offset(point) - .and_then(|(buffer, offset)| buffer.language_config_at(offset)) + .and_then(|(buffer, offset)| buffer.language_scope_at(offset)) } pub fn is_dirty(&self) -> bool { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5593241e70..7ef7845952 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -9,7 +9,7 @@ use crate::{ syntax_map::{ SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint, }, - CodeLabel, LanguageConfigYeet, Outline, + CodeLabel, LanguageScope, Outline, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -2015,7 +2015,7 @@ impl BufferSnapshot { .or(self.language.as_ref()) } - pub fn language_config_at(&self, position: D) -> Option { + pub fn language_scope_at(&self, position: D) -> Option { let offset = position.to_offset(self); if let Some(layer_info) = self @@ -2024,12 +2024,12 @@ impl BufferSnapshot { .filter(|l| l.node.end_byte() > offset) .last() { - Some(LanguageConfigYeet { + Some(LanguageScope { language: layer_info.language.clone(), override_id: layer_info.override_id(offset, &self.text), }) } else { - self.language.clone().map(|language| LanguageConfigYeet { + self.language.clone().map(|language| LanguageScope { language, override_id: None, }) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 5aa9152205..07198e8090 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1369,6 +1369,89 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut MutableAppContext) { }); } +#[gpui::test] +fn test_language_config_at(cx: &mut MutableAppContext) { + cx.set_global(Settings::test(cx)); + cx.add_model(|cx| { + let language = Language::new( + LanguageConfig { + name: "JavaScript".into(), + line_comment: Some("// ".into()), + brackets: vec![ + BracketPair { + start: "{".into(), + end: "}".into(), + close: true, + newline: false, + }, + BracketPair { + start: "'".into(), + end: "'".into(), + close: true, + newline: false, + }, + ], + overrides: [ + ( + "element".into(), + LanguageConfigOverride { + line_comment: Override::Remove { remove: true }, + block_comment: Override::Set(("{/*".into(), "*/}".into())), + ..Default::default() + }, + ), + ( + "string".into(), + LanguageConfigOverride { + brackets: Override::Set(vec![BracketPair { + start: "{".into(), + end: "}".into(), + close: true, + newline: false, + }]), + ..Default::default() + }, + ), + ] + .into_iter() + .collect(), + ..Default::default() + }, + Some(tree_sitter_javascript::language()), + ) + .with_override_query( + r#" + (jsx_element) @override.element + (string) @override.string + "#, + ) + .unwrap(); + + let text = r#"a["b"] = ;"#; + + let buffer = Buffer::new(0, text, cx).with_language(Arc::new(language), cx); + let snapshot = buffer.snapshot(); + + let config = snapshot.language_scope_at(0).unwrap(); + assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// "); + assert_eq!(config.brackets().len(), 2); + + let string_config = snapshot.language_scope_at(3).unwrap(); + assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// "); + assert_eq!(string_config.brackets().len(), 1); + + let element_config = snapshot.language_scope_at(10).unwrap(); + assert_eq!(element_config.line_comment_prefix(), None); + assert_eq!( + element_config.block_comment_delimiters(), + Some((&"{/*".into(), &"*/}".into())) + ); + assert_eq!(element_config.brackets().len(), 2); + + buffer + }); +} + #[gpui::test] fn test_serialization(cx: &mut gpui::MutableAppContext) { let mut now = Instant::now(); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 3c8b2b987b..928e208627 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -22,10 +22,7 @@ use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; -use serde::{ - de::{self}, - Deserialize, Deserializer, -}; +use serde::{de, Deserialize, Deserializer}; use serde_json::Value; use std::{ any::Any, @@ -251,20 +248,22 @@ pub struct LanguageConfig { } #[derive(Clone)] -pub struct LanguageConfigYeet { +pub struct LanguageScope { language: Arc, override_id: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, Default, Debug)] pub struct LanguageConfigOverride { #[serde(default)] pub line_comment: Override>, #[serde(default)] pub block_comment: Override<(Arc, Arc)>, + #[serde(default)] + pub brackets: Override>, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(untagged)] pub enum Override { Remove { remove: bool }, @@ -278,11 +277,11 @@ impl Default for Override { } impl Override { - fn as_option<'a>(this: Option<&'a Self>, original: &'a Option) -> Option<&'a T> { + fn as_option<'a>(this: Option<&'a Self>, original: Option<&'a T>) -> Option<&'a T> { match this { Some(Self::Set(value)) => Some(value), Some(Self::Remove { remove: true }) => None, - Some(Self::Remove { remove: false }) | None => original.as_ref(), + Some(Self::Remove { remove: false }) | None => original, } } } @@ -966,40 +965,39 @@ impl Language { } } -impl LanguageConfigYeet { +impl LanguageScope { pub fn line_comment_prefix(&self) -> Option<&Arc> { Override::as_option( - self.over_ride().map(|o| &o.line_comment), - &self.language.config.line_comment, + self.config_override().map(|o| &o.line_comment), + self.language.config.line_comment.as_ref(), ) } pub fn block_comment_delimiters(&self) -> Option<(&Arc, &Arc)> { Override::as_option( - self.over_ride().map(|o| &o.block_comment), - &self.language.config.block_comment, + self.config_override().map(|o| &o.block_comment), + self.language.config.block_comment.as_ref(), ) .map(|e| (&e.0, &e.1)) } pub fn brackets(&self) -> &[BracketPair] { - &self.language.config.brackets + Override::as_option( + self.config_override().map(|o| &o.brackets), + Some(&self.language.config.brackets), + ) + .map_or(&[], Vec::as_slice) } pub fn should_autoclose_before(&self, c: char) -> bool { c.is_whitespace() || self.language.config.autoclose_before.contains(c) } - fn over_ride(&self) -> Option<&LanguageConfigOverride> { - self.override_id.and_then(|id| { - self.language - .grammar - .as_ref()? - .override_config - .as_ref()? - .values - .get(&id) - }) + fn config_override(&self) -> Option<&LanguageConfigOverride> { + let id = self.override_id?; + let grammar = self.language.grammar.as_ref()?; + let override_config = grammar.override_config.as_ref()?; + override_config.values.get(&id) } } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 0250c53684..d7fa67b1d4 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -173,6 +173,11 @@ pub(crate) fn language( .with_injection_query(query.as_ref()) .expect("failed to load injection query"); } + if let Some(query) = load_query(name, "/overrides") { + language = language + .with_override_query(query.as_ref()) + .expect("failed to load override query"); + } if let Some(lsp_adapter) = lsp_adapter { language = language.with_lsp_adapter(lsp_adapter) } diff --git a/crates/zed/src/languages/tsx/overrides.scm b/crates/zed/src/languages/tsx/overrides.scm index aa617b3086..183f7b4f98 100644 --- a/crates/zed/src/languages/tsx/overrides.scm +++ b/crates/zed/src/languages/tsx/overrides.scm @@ -1,2 +1,7 @@ -(jsx_element) @override.element +[ + (jsx_element) + (jsx_fragment) + (jsx_self_closing_element) + (jsx_expression) +] @override.element (string) @override.string From 88170df7f0dfcb445f3c3dc3d34c3d34054ce89e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 15:21:26 -0800 Subject: [PATCH 081/108] Switched from active hover to NSViews acceptsFirstMouse API --- crates/collab_ui/src/collab_titlebar_item.rs | 12 ++---------- .../collab_ui/src/incoming_call_notification.rs | 4 +++- .../collab_ui/src/project_shared_notification.rs | 1 + crates/gpui/src/platform.rs | 2 ++ crates/gpui/src/platform/mac/window.rs | 15 +++++++++++++++ crates/zed/src/zed.rs | 1 + 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index ffafa2d142..3351fb9eb9 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -417,13 +417,9 @@ impl CollabTitlebarItem { theme: &Theme, cx: &mut RenderContext, ) -> ElementBox { - let (is_followed, is_following) = peer.map_or((false, false), |(peer_id, _, _)| { - ( - workspace.read(cx).is_following(peer_id), - workspace.read(cx).is_followed(peer_id), - ) + let is_followed = peer.map_or(false, |(peer_id, _, _)| { + workspace.read(cx).is_following(peer_id) }); - // my color, around their avatar. let mut avatar_style; if let Some((_, _, location)) = peer.as_ref() { @@ -446,10 +442,6 @@ impl CollabTitlebarItem { replica_color = Some(color); if is_followed { avatar_style.border = Border::all(1.0, color); - } else if is_following { - let our_id = workspace.read(cx).project().read(cx).replica_id(); - let our_color = theme.editor.replica_selection_style(our_id).cursor; - avatar_style.border = Border::all(1.0, our_color); } } diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index daf93ce6a2..f0daa3c7e8 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -45,10 +45,11 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), + accepts_first_mouse: true, }, |_| IncomingCallNotification::new(incoming_call.clone()), ); - cx.activate_window(window_id); + notification_windows.push(window_id); } } @@ -226,6 +227,7 @@ impl View for IncomingCallNotification { .theme .incoming_call_notification .background; + Flex::row() .with_child(self.render_caller(cx)) .with_child(self.render_buttons(cx)) diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 0815d9c8d8..858886a0e3 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -44,6 +44,7 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), + accepts_first_mouse: true, }, |_| { ProjectSharedNotification::new( diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index d027218040..ad4d7542d3 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -156,6 +156,7 @@ pub struct WindowOptions<'a> { pub kind: WindowKind, pub is_movable: bool, pub screen: Option>, + pub accepts_first_mouse: bool, } #[derive(Debug)] @@ -301,6 +302,7 @@ impl<'a> Default for WindowOptions<'a> { kind: WindowKind::Normal, is_movable: true, screen: None, + accepts_first_mouse: false, } } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 958ac2ebd6..a929ba08a3 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -252,6 +252,11 @@ unsafe fn build_classes() { do_command_by_selector as extern "C" fn(&Object, Sel, Sel), ); + decl.add_method( + sel!(acceptsFirstMouse:), + accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL, + ); + decl.register() }; } @@ -338,6 +343,7 @@ struct WindowState { ime_state: ImeState, //Retains the last IME Text ime_text: Option, + accepts_first_mouse: bool, } struct InsertText { @@ -444,6 +450,7 @@ impl Window { previous_modifiers_changed_event: None, ime_state: ImeState::None, ime_text: None, + accepts_first_mouse: options.accepts_first_mouse, }))); (*native_window).set_ivar( @@ -1404,6 +1411,14 @@ extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) { } } +extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL { + unsafe { + let state = get_window_state(this); + let state_borrow = state.as_ref().borrow(); + return state_borrow.accepts_first_mouse as BOOL; + } +} + async fn synthetic_drag( window_state: Weak>, drag_id: usize, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c3af91306d..248bab5fda 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -408,6 +408,7 @@ pub fn build_window_options() -> WindowOptions<'static> { kind: WindowKind::Normal, is_movable: true, screen: None, + accepts_first_mouse: false, } } From 131f3471fc5b16f3972ad0bac072b32c6fe17be2 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 20 Jan 2023 09:50:02 -0500 Subject: [PATCH 082/108] Don't dispatch mousemove without focus & avoid swallowing external moves Co-Authored-By: Antonio Scandurra --- crates/gpui/src/platform/mac/window.rs | 33 ++++++++++---------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 958ac2ebd6..ca233ccf65 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -66,12 +66,6 @@ const NSNormalWindowLevel: NSInteger = 0; #[allow(non_upper_case_globals)] const NSPopUpWindowLevel: NSInteger = 101; #[allow(non_upper_case_globals)] -const NSTrackingMouseMoved: NSUInteger = 0x02; -#[allow(non_upper_case_globals)] -const NSTrackingActiveAlways: NSUInteger = 0x80; -#[allow(non_upper_case_globals)] -const NSTrackingInVisibleRect: NSUInteger = 0x200; -#[allow(non_upper_case_globals)] const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4; #[repr(C)] @@ -469,15 +463,7 @@ impl Window { native_window.setTitlebarAppearsTransparent_(YES); } - let tracking_area: id = msg_send![class!(NSTrackingArea), alloc]; - let _: () = msg_send![ - tracking_area, - initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)) - options: NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect - owner: native_view - userInfo: nil - ]; - let _: () = msg_send![native_view, addTrackingArea: tracking_area.autorelease()]; + native_window.setAcceptsMouseMovedEvents_(YES); native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable); native_view.setWantsBestResolutionOpenGLSurface_(YES); @@ -873,11 +859,10 @@ extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) { extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL { let window_state = unsafe { get_window_state(this) }; - let mut window_state_borrow = window_state.as_ref().borrow_mut(); - let event = - unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) }; + let window_height = window_state_borrow.content_size().y(); + let event = unsafe { Event::from_native(native_event, Some(window_height)) }; if let Some(event) = event { if key_equivalent { @@ -902,6 +887,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: function_is_held = event.keystroke.function; Some((event, None)) } + _ => return NO, }; @@ -968,9 +954,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let window_state = unsafe { get_window_state(this) }; let weak_window_state = Rc::downgrade(&window_state); let mut window_state_borrow = window_state.as_ref().borrow_mut(); + let is_active = unsafe { window_state_borrow.native_window.isKeyWindow() == YES }; + + let window_height = window_state_borrow.content_size().y(); + let event = unsafe { Event::from_native(native_event, Some(window_height)) }; - let event = - unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) }; if let Some(event) = event { match &event { Event::MouseMoved( @@ -989,12 +977,16 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { )) .detach(); } + + Event::MouseMoved(_) if !is_active => return, + Event::MouseUp(MouseButtonEvent { button: MouseButton::Left, .. }) => { window_state_borrow.synthetic_drag_counter += 1; } + Event::ModifiersChanged(ModifiersChangedEvent { modifiers }) => { // Only raise modifiers changed event when they have actually changed if let Some(Event::ModifiersChanged(ModifiersChangedEvent { @@ -1008,6 +1000,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { window_state_borrow.previous_modifiers_changed_event = Some(event.clone()); } + _ => {} } From 467e3dc50ac846260bbf44a51d458e3028311a5b Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 20 Jan 2023 10:16:24 -0500 Subject: [PATCH 083/108] Hide editor hover on mouse move out & always notify when hiding hover Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 10 +++++----- crates/editor/src/element.rs | 10 +++++++--- crates/editor/src/hover_popover.rs | 16 ++++++++++++---- crates/editor/src/scroll.rs | 8 ++++---- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 07c0bce6b6..057a86e7f3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -44,7 +44,7 @@ use gpui::{ ViewContext, ViewHandle, WeakViewHandle, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; -use hover_popover::{hide_hover, HoverState}; +use hover_popover::{hide_hover, HideHover, HoverState}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -1319,7 +1319,7 @@ impl Editor { } } - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); if old_cursor_position.to_display_point(&display_map).row() != new_cursor_position.to_display_point(&display_map).row() @@ -1694,7 +1694,7 @@ impl Editor { return; } - if hide_hover(self, cx) { + if hide_hover(self, &HideHover, cx) { return; } @@ -6174,7 +6174,7 @@ impl View for Editor { cx.defer(move |cx| { if let Some(editor) = handle.upgrade(cx) { editor.update(cx, |editor, cx| { - hide_hover(editor, cx); + hide_hover(editor, &HideHover, cx); hide_link_definition(editor, cx); }) } @@ -6223,7 +6223,7 @@ impl View for Editor { self.buffer .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); self.hide_context_menu(cx); - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); cx.emit(Event::Blurred); cx.notify(); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 5c34b98380..bce63ca0cf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -7,7 +7,7 @@ use crate::{ display_map::{BlockStyle, DisplaySnapshot, TransformBlock}, git::{diff_hunk_to_display, DisplayDiffHunk}, hover_popover::{ - HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, + HideHover, HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, }, link_go_to_definition::{ GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink, @@ -114,6 +114,7 @@ impl EditorElement { fn attach_mouse_handlers( view: &WeakViewHandle, position_map: &Arc, + has_popovers: bool, visible_bounds: RectF, text_bounds: RectF, gutter_bounds: RectF, @@ -190,8 +191,10 @@ impl EditorElement { } } }) - .on_move_out(|e, cx| { - println!("on move out"); + .on_move_out(move |_, cx| { + if has_popovers { + cx.dispatch_action(HideHover); + } }) .on_scroll({ let position_map = position_map.clone(); @@ -1873,6 +1876,7 @@ impl Element for EditorElement { Self::attach_mouse_handlers( &self.view, &layout.position_map, + layout.hover_popovers.is_some(), visible_bounds, text_bounds, gutter_bounds, diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 7369b0a6f4..6d003cae5d 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -29,12 +29,16 @@ pub struct HoverAt { pub point: Option, } +#[derive(Copy, Clone, PartialEq)] +pub struct HideHover; + actions!(editor, [Hover]); -impl_internal_actions!(editor, [HoverAt]); +impl_internal_actions!(editor, [HoverAt, HideHover]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(hover); cx.add_action(hover_at); + cx.add_action(hide_hover); } /// Bindable action which uses the most recent selection head to trigger a hover @@ -50,7 +54,7 @@ pub fn hover_at(editor: &mut Editor, action: &HoverAt, cx: &mut ViewContext) -> bool { +pub fn hide_hover(editor: &mut Editor, _: &HideHover, cx: &mut ViewContext) -> bool { let did_hide = editor.hover_state.info_popover.take().is_some() | editor.hover_state.diagnostic_popover.take().is_some(); @@ -67,6 +71,10 @@ pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext) -> bool { editor.clear_background_highlights::(cx); + if did_hide { + cx.notify(); + } + did_hide } @@ -121,7 +129,7 @@ fn show_hover( // Hover triggered from same location as last time. Don't show again. return; } else { - hide_hover(editor, cx); + hide_hover(editor, &HideHover, cx); } } } diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 0ca837a843..1b3fd96dbd 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -17,7 +17,7 @@ use workspace::WorkspaceId; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, - hover_popover::hide_hover, + hover_popover::{hide_hover, HideHover}, persistence::DB, Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint, }; @@ -307,7 +307,7 @@ impl Editor { ) { let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); self.scroll_manager.set_scroll_position( scroll_position, &map, @@ -323,7 +323,7 @@ impl Editor { } pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext) { - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); let top_row = scroll_anchor .top_anchor .to_point(&self.buffer().read(cx).snapshot(cx)) @@ -337,7 +337,7 @@ impl Editor { scroll_anchor: ScrollAnchor, cx: &mut ViewContext, ) { - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); let top_row = scroll_anchor .top_anchor .to_point(&self.buffer().read(cx).snapshot(cx)) From 95e661a78c38947c83f59f69e82ba9497924db5a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 15:21:26 -0800 Subject: [PATCH 084/108] Switched from active hover to NSViews acceptsFirstMouse API Co-authored-by: Nathan --- crates/gpui/src/platform/mac/window.rs | 1 + crates/gpui/src/presenter.rs | 1 + crates/workspace/src/workspace.rs | 11 ++++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index a929ba08a3..f5a21c4644 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -443,6 +443,7 @@ impl Window { scene_to_render: Default::default(), renderer: Renderer::new(true, fonts), last_fresh_keydown: None, + accepts_first_mouse: matches!(options.kind, WindowKind::PopUp), traffic_light_position: options .titlebar .as_ref() diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index bbaf1ed0bb..4ccc319219 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -156,6 +156,7 @@ impl Presenter { self.cursor_regions = scene.cursor_regions(); self.mouse_regions = scene.mouse_regions(); + // window.is_topmost for the mouse moved event's postion? if cx.window_is_active(self.window_id) { if let Some(event) = self.last_mouse_moved_event.clone() { self.dispatch_event(event, true, cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6a8fdaa17b..9665db6be6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,14 +31,18 @@ use futures::{ }; use gpui::{ actions, + color::Color, elements::*, - geometry::vector::Vector2F, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, + Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -98,7 +102,8 @@ actions!( ToggleLeftSidebar, ToggleRightSidebar, NewTerminal, - NewSearch + NewSearch, + ShowNotif, ] ); From 97203e1e02fdf3c25d5ffd037d495947a74ac6d5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 09:19:58 -0800 Subject: [PATCH 085/108] Fix broken merge --- crates/collab_ui/src/incoming_call_notification.rs | 1 - crates/collab_ui/src/project_shared_notification.rs | 1 - crates/gpui/src/platform.rs | 4 +--- crates/gpui/src/platform/mac/window.rs | 3 +-- crates/workspace/src/workspace.rs | 8 ++------ crates/zed/src/zed.rs | 1 - 6 files changed, 4 insertions(+), 14 deletions(-) diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index f0daa3c7e8..6ad533665e 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -45,7 +45,6 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), - accepts_first_mouse: true, }, |_| IncomingCallNotification::new(incoming_call.clone()), ); diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 858886a0e3..0815d9c8d8 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -44,7 +44,6 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), - accepts_first_mouse: true, }, |_| { ProjectSharedNotification::new( diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index ad4d7542d3..99d607e407 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -156,7 +156,6 @@ pub struct WindowOptions<'a> { pub kind: WindowKind, pub is_movable: bool, pub screen: Option>, - pub accepts_first_mouse: bool, } #[derive(Debug)] @@ -180,7 +179,7 @@ impl Default for Appearance { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum WindowKind { Normal, PopUp, @@ -302,7 +301,6 @@ impl<'a> Default for WindowOptions<'a> { kind: WindowKind::Normal, is_movable: true, screen: None, - accepts_first_mouse: false, } } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index f5a21c4644..26305e86da 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -443,7 +443,7 @@ impl Window { scene_to_render: Default::default(), renderer: Renderer::new(true, fonts), last_fresh_keydown: None, - accepts_first_mouse: matches!(options.kind, WindowKind::PopUp), + accepts_first_mouse: options.kind == WindowKind::PopUp, traffic_light_position: options .titlebar .as_ref() @@ -451,7 +451,6 @@ impl Window { previous_modifiers_changed_event: None, ime_state: ImeState::None, ime_text: None, - accepts_first_mouse: options.accepts_first_mouse, }))); (*native_window).set_ivar( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9665db6be6..8735f33fbe 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,18 +31,14 @@ use futures::{ }; use gpui::{ actions, - color::Color, elements::*, - geometry::{ - rect::RectF, - vector::{vec2f, Vector2F}, - }, + geometry::vector::Vector2F, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 248bab5fda..c3af91306d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -408,7 +408,6 @@ pub fn build_window_options() -> WindowOptions<'static> { kind: WindowKind::Normal, is_movable: true, screen: None, - accepts_first_mouse: false, } } From 8af1294ba51d6c8b114d06d3f8a1aa7cfbcc20ae Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 09:37:09 -0800 Subject: [PATCH 086/108] Changed platform mouse moved handling to only fire on active or popup windows co-authored-by: Antonio --- crates/gpui/src/platform/mac/window.rs | 12 ++++++++---- crates/workspace/src/workspace.rs | 8 ++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 4104b0ebf8..0da2e4351e 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -316,6 +316,7 @@ enum ImeState { struct WindowState { id: usize, native_window: id, + kind: WindowKind, event_callback: Option bool>>, activate_callback: Option>, resize_callback: Option>, @@ -337,7 +338,6 @@ struct WindowState { ime_state: ImeState, //Retains the last IME Text ime_text: Option, - accepts_first_mouse: bool, } struct InsertText { @@ -422,6 +422,7 @@ impl Window { let window = Self(Rc::new(RefCell::new(WindowState { id, native_window, + kind: options.kind, event_callback: None, resize_callback: None, should_close_callback: None, @@ -437,7 +438,6 @@ impl Window { scene_to_render: Default::default(), renderer: Renderer::new(true, fonts), last_fresh_keydown: None, - accepts_first_mouse: options.kind == WindowKind::PopUp, traffic_light_position: options .titlebar .as_ref() @@ -985,7 +985,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { .detach(); } - Event::MouseMoved(_) if !is_active => return, + Event::MouseMoved(_) + if !(is_active || window_state_borrow.kind == WindowKind::PopUp) => + { + return + } Event::MouseUp(MouseButtonEvent { button: MouseButton::Left, @@ -1408,7 +1412,7 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL { unsafe { let state = get_window_state(this); let state_borrow = state.as_ref().borrow(); - return state_borrow.accepts_first_mouse as BOOL; + return state_borrow.kind == WindowKind::PopUp; } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8735f33fbe..9665db6be6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,14 +31,18 @@ use futures::{ }; use gpui::{ actions, + color::Color, elements::*, - geometry::vector::Vector2F, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, + Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; From 2aa7a9e95bcbab7533e8f9930d88980ac2f35027 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 10:39:31 -0800 Subject: [PATCH 087/108] Add overrides for all languages Co-authored-by: Julia Risley --- crates/language/src/language.rs | 19 ++++++++++++++----- crates/zed/src/languages.rs | 3 +++ crates/zed/src/languages/c/config.toml | 15 +++++++++++++++ crates/zed/src/languages/c/overrides.scm | 2 ++ crates/zed/src/languages/cpp/config.toml | 15 +++++++++++++++ crates/zed/src/languages/cpp/overrides.scm | 2 ++ crates/zed/src/languages/css/config.toml | 17 ++++++++++++++++- crates/zed/src/languages/css/overrides.scm | 2 ++ crates/zed/src/languages/elixir/config.toml | 17 ++++++++++++++++- crates/zed/src/languages/elixir/overrides.scm | 2 ++ crates/zed/src/languages/erb/config.toml | 3 +-- crates/zed/src/languages/go/config.toml | 15 +++++++++++++++ crates/zed/src/languages/go/overrides.scm | 6 ++++++ crates/zed/src/languages/html/config.toml | 16 +++++++++++++++- crates/zed/src/languages/html/overrides.scm | 2 ++ .../zed/src/languages/javascript/config.toml | 18 ++++++++++++++++++ .../src/languages/javascript/overrides.scm | 9 +++++++++ crates/zed/src/languages/json/config.toml | 6 ++++++ crates/zed/src/languages/json/overrides.scm | 2 ++ crates/zed/src/languages/python/config.toml | 16 +++++++++++++++- crates/zed/src/languages/python/overrides.scm | 2 ++ crates/zed/src/languages/ruby/config.toml | 18 ++++++++++++++++-- crates/zed/src/languages/ruby/overrides.scm | 2 ++ crates/zed/src/languages/rust/config.toml | 16 ++++++++++++++++ crates/zed/src/languages/rust/overrides.scm | 8 ++++++++ crates/zed/src/languages/scheme/config.toml | 12 ++++++++++++ crates/zed/src/languages/scheme/overrides.scm | 6 ++++++ crates/zed/src/languages/toml/config.toml | 13 +++++++++++++ crates/zed/src/languages/toml/overrides.scm | 2 ++ .../zed/src/languages/typescript/config.toml | 14 ++++++++++++++ .../src/languages/typescript/overrides.scm | 2 ++ 31 files changed, 269 insertions(+), 13 deletions(-) create mode 100644 crates/zed/src/languages/c/overrides.scm create mode 100644 crates/zed/src/languages/cpp/overrides.scm create mode 100644 crates/zed/src/languages/css/overrides.scm create mode 100644 crates/zed/src/languages/elixir/overrides.scm create mode 100644 crates/zed/src/languages/go/overrides.scm create mode 100644 crates/zed/src/languages/html/overrides.scm create mode 100644 crates/zed/src/languages/javascript/overrides.scm create mode 100644 crates/zed/src/languages/json/overrides.scm create mode 100644 crates/zed/src/languages/python/overrides.scm create mode 100644 crates/zed/src/languages/ruby/overrides.scm create mode 100644 crates/zed/src/languages/rust/overrides.scm create mode 100644 crates/zed/src/languages/scheme/overrides.scm create mode 100644 crates/zed/src/languages/toml/overrides.scm create mode 100644 crates/zed/src/languages/typescript/overrides.scm diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 928e208627..766e181671 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -830,15 +830,24 @@ impl Language { let mut values = HashMap::default(); for (ix, name) in query.capture_names().iter().enumerate() { if let Some(override_name) = name.strip_prefix("override.") { - let value = self - .config - .overrides - .remove(override_name) - .ok_or_else(|| anyhow!("no such override {override_name}"))?; + let value = self.config.overrides.remove(override_name).ok_or_else(|| { + anyhow!( + "language {:?} has override in query but not in config: {override_name:?}", + self.config.name + ) + })?; values.insert(ix as u32, value); } } + if !self.config.overrides.is_empty() { + let keys = self.config.overrides.keys().collect::>(); + Err(anyhow!( + "language {:?} has overrides in config not in query: {keys:?}", + self.config.name + ))?; + } + self.grammar_mut().override_config = Some(OverrideConfig { query, values }); Ok(self) } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index d7fa67b1d4..240d1dc49e 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use gpui::executor::Background; pub use language::*; use lazy_static::lazy_static; @@ -145,7 +146,9 @@ pub(crate) fn language( .unwrap() .data, ) + .with_context(|| format!("failed to load config.toml for language {name:?}")) .unwrap(); + let mut language = Language::new(config, Some(grammar)); if let Some(query) = load_query(name, "/highlights") { diff --git a/crates/zed/src/languages/c/config.toml b/crates/zed/src/languages/c/config.toml index 5b48415610..1a5fe9339a 100644 --- a/crates/zed/src/languages/c/config.toml +++ b/crates/zed/src/languages/c/config.toml @@ -7,5 +7,20 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/c/overrides.scm b/crates/zed/src/languages/c/overrides.scm new file mode 100644 index 0000000000..0d14e6c205 --- /dev/null +++ b/crates/zed/src/languages/c/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string_literal) @override.string diff --git a/crates/zed/src/languages/cpp/config.toml b/crates/zed/src/languages/cpp/config.toml index e9a793ec3c..c83adfb067 100644 --- a/crates/zed/src/languages/cpp/config.toml +++ b/crates/zed/src/languages/cpp/config.toml @@ -7,5 +7,20 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/cpp/overrides.scm b/crates/zed/src/languages/cpp/overrides.scm new file mode 100644 index 0000000000..0d14e6c205 --- /dev/null +++ b/crates/zed/src/languages/cpp/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string_literal) @override.string diff --git a/crates/zed/src/languages/css/config.toml b/crates/zed/src/languages/css/config.toml index 28def3abd5..cf8d4f1e2f 100644 --- a/crates/zed/src/languages/css/config.toml +++ b/crates/zed/src/languages/css/config.toml @@ -5,5 +5,20 @@ brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, - { start = "\"", end = "\"", close = true, newline = false } + { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, ] diff --git a/crates/zed/src/languages/css/overrides.scm b/crates/zed/src/languages/css/overrides.scm new file mode 100644 index 0000000000..9617249d3c --- /dev/null +++ b/crates/zed/src/languages/css/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string_value) @override.string diff --git a/crates/zed/src/languages/elixir/config.toml b/crates/zed/src/languages/elixir/config.toml index 4e1af93943..0185d8eec3 100644 --- a/crates/zed/src/languages/elixir/config.toml +++ b/crates/zed/src/languages/elixir/config.toml @@ -6,5 +6,20 @@ brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, - { start = "\"", end = "\"", close = true, newline = false } + { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, ] diff --git a/crates/zed/src/languages/elixir/overrides.scm b/crates/zed/src/languages/elixir/overrides.scm new file mode 100644 index 0000000000..ed1b20d44b --- /dev/null +++ b/crates/zed/src/languages/elixir/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +[(string) (charlist)] @override.string diff --git a/crates/zed/src/languages/erb/config.toml b/crates/zed/src/languages/erb/config.toml index 280219a119..9cfcef0c8b 100644 --- a/crates/zed/src/languages/erb/config.toml +++ b/crates/zed/src/languages/erb/config.toml @@ -4,5 +4,4 @@ autoclose_before = ">})" brackets = [ { start = "<", end = ">", close = true, newline = true }, ] - -block_comment = ["<%#", "%>"] \ No newline at end of file +block_comment = ["<%#", "%>"] diff --git a/crates/zed/src/languages/go/config.toml b/crates/zed/src/languages/go/config.toml index fc6167e311..3f9464594c 100644 --- a/crates/zed/src/languages/go/config.toml +++ b/crates/zed/src/languages/go/config.toml @@ -7,5 +7,20 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/go/overrides.scm b/crates/zed/src/languages/go/overrides.scm new file mode 100644 index 0000000000..fc32c47f68 --- /dev/null +++ b/crates/zed/src/languages/go/overrides.scm @@ -0,0 +1,6 @@ +(comment) @override.comment +[ + (interpreted_string_literal) + (raw_string_literal) + (rune_literal) +] @override.string diff --git a/crates/zed/src/languages/html/config.toml b/crates/zed/src/languages/html/config.toml index 3e618da25e..f4f0ba26ea 100644 --- a/crates/zed/src/languages/html/config.toml +++ b/crates/zed/src/languages/html/config.toml @@ -9,4 +9,18 @@ brackets = [ { start = "!--", end = " --", close = true, newline = false }, ] -block_comment = [""] \ No newline at end of file +block_comment = [""] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/html/overrides.scm b/crates/zed/src/languages/html/overrides.scm new file mode 100644 index 0000000000..8f6a1f9eea --- /dev/null +++ b/crates/zed/src/languages/html/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(quoted_attribute_value) @override.string \ No newline at end of file diff --git a/crates/zed/src/languages/javascript/config.toml b/crates/zed/src/languages/javascript/config.toml index d7612d13f0..a5a656393d 100644 --- a/crates/zed/src/languages/javascript/config.toml +++ b/crates/zed/src/languages/javascript/config.toml @@ -12,3 +12,21 @@ brackets = [ { start = "`", end = "`", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.element] +line_comment = { remove = true } +block_comment = ["{/* ", " */}"] diff --git a/crates/zed/src/languages/javascript/overrides.scm b/crates/zed/src/languages/javascript/overrides.scm new file mode 100644 index 0000000000..f19c1d9203 --- /dev/null +++ b/crates/zed/src/languages/javascript/overrides.scm @@ -0,0 +1,9 @@ +(comment) @override.comment +(string) @override.string + +[ + (jsx_element) + (jsx_fragment) + (jsx_self_closing_element) + (jsx_expression) +] @override.element diff --git a/crates/zed/src/languages/json/config.toml b/crates/zed/src/languages/json/config.toml index cb36279358..48a1fb0c99 100644 --- a/crates/zed/src/languages/json/config.toml +++ b/crates/zed/src/languages/json/config.toml @@ -7,3 +7,9 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, ] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/json/overrides.scm b/crates/zed/src/languages/json/overrides.scm new file mode 100644 index 0000000000..83911ae4a5 --- /dev/null +++ b/crates/zed/src/languages/json/overrides.scm @@ -0,0 +1,2 @@ +(comment) @comment +(string) @override.string \ No newline at end of file diff --git a/crates/zed/src/languages/python/config.toml b/crates/zed/src/languages/python/config.toml index c6b41ed700..a817de8e3b 100644 --- a/crates/zed/src/languages/python/config.toml +++ b/crates/zed/src/languages/python/config.toml @@ -12,4 +12,18 @@ brackets = [ auto_indent_using_last_non_empty_line = false increase_indent_pattern = ":$" -decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:" \ No newline at end of file +decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:" + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/python/overrides.scm b/crates/zed/src/languages/python/overrides.scm new file mode 100644 index 0000000000..3a7ad5f035 --- /dev/null +++ b/crates/zed/src/languages/python/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/ruby/config.toml b/crates/zed/src/languages/ruby/config.toml index 5600266de3..4fd21d8bd0 100644 --- a/crates/zed/src/languages/ruby/config.toml +++ b/crates/zed/src/languages/ruby/config.toml @@ -7,5 +7,19 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, - { start = "'", end = "'", close = false, newline = false }, -] \ No newline at end of file + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/ruby/overrides.scm b/crates/zed/src/languages/ruby/overrides.scm new file mode 100644 index 0000000000..3a7ad5f035 --- /dev/null +++ b/crates/zed/src/languages/ruby/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/rust/config.toml b/crates/zed/src/languages/rust/config.toml index 971ed27ebc..f9f6b59717 100644 --- a/crates/zed/src/languages/rust/config.toml +++ b/crates/zed/src/languages/rust/config.toml @@ -11,3 +11,19 @@ brackets = [ { start = "'", end = "'", close = false, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = false, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = false, newline = true }, +] diff --git a/crates/zed/src/languages/rust/overrides.scm b/crates/zed/src/languages/rust/overrides.scm new file mode 100644 index 0000000000..29128d98e7 --- /dev/null +++ b/crates/zed/src/languages/rust/overrides.scm @@ -0,0 +1,8 @@ +[ + (string_literal) + (raw_string_literal) +] @override.string +[ + (line_comment) + (block_comment) +] @override.comment diff --git a/crates/zed/src/languages/scheme/config.toml b/crates/zed/src/languages/scheme/config.toml index 7e63673834..46e11bdc11 100644 --- a/crates/zed/src/languages/scheme/config.toml +++ b/crates/zed/src/languages/scheme/config.toml @@ -7,3 +7,15 @@ brackets = [ { start = "(", end = ")", close = true, newline = false }, { start = "\"", end = "\"", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/scheme/overrides.scm b/crates/zed/src/languages/scheme/overrides.scm new file mode 100644 index 0000000000..28a8a569df --- /dev/null +++ b/crates/zed/src/languages/scheme/overrides.scm @@ -0,0 +1,6 @@ +[ + (comment) + (block_comment) + (directive) +] @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/toml/config.toml b/crates/zed/src/languages/toml/config.toml index cd624aecef..30797bf141 100644 --- a/crates/zed/src/languages/toml/config.toml +++ b/crates/zed/src/languages/toml/config.toml @@ -6,4 +6,17 @@ brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, ] diff --git a/crates/zed/src/languages/toml/overrides.scm b/crates/zed/src/languages/toml/overrides.scm new file mode 100644 index 0000000000..3a7ad5f035 --- /dev/null +++ b/crates/zed/src/languages/toml/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/typescript/config.toml b/crates/zed/src/languages/typescript/config.toml index 8e5886167c..3c2146e001 100644 --- a/crates/zed/src/languages/typescript/config.toml +++ b/crates/zed/src/languages/typescript/config.toml @@ -12,3 +12,17 @@ brackets = [ { start = "`", end = "`", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/typescript/overrides.scm b/crates/zed/src/languages/typescript/overrides.scm new file mode 100644 index 0000000000..3a7ad5f035 --- /dev/null +++ b/crates/zed/src/languages/typescript/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string From 8dcef46842ea329a1d47e5b6e2ba9be44fd2b1cf Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 10:44:33 -0800 Subject: [PATCH 088/108] Drop 'override.' prefix from capture names in override query Co-authored-by: Julia Risley --- crates/language/src/buffer_tests.rs | 4 ++-- crates/language/src/language.rs | 6 +++--- crates/zed/src/languages/c/overrides.scm | 4 ++-- crates/zed/src/languages/cpp/overrides.scm | 4 ++-- crates/zed/src/languages/css/overrides.scm | 4 ++-- crates/zed/src/languages/elixir/overrides.scm | 4 ++-- crates/zed/src/languages/go/overrides.scm | 4 ++-- crates/zed/src/languages/html/overrides.scm | 4 ++-- crates/zed/src/languages/javascript/overrides.scm | 6 +++--- crates/zed/src/languages/json/overrides.scm | 3 +-- crates/zed/src/languages/python/overrides.scm | 4 ++-- crates/zed/src/languages/ruby/overrides.scm | 4 ++-- crates/zed/src/languages/rust/overrides.scm | 4 ++-- crates/zed/src/languages/scheme/overrides.scm | 4 ++-- crates/zed/src/languages/toml/overrides.scm | 4 ++-- crates/zed/src/languages/tsx/overrides.scm | 4 ++-- crates/zed/src/languages/typescript/overrides.scm | 4 ++-- 17 files changed, 35 insertions(+), 36 deletions(-) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 07198e8090..382311e204 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1421,8 +1421,8 @@ fn test_language_config_at(cx: &mut MutableAppContext) { ) .with_override_query( r#" - (jsx_element) @override.element - (string) @override.string + (jsx_element) @element + (string) @string "#, ) .unwrap(); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 766e181671..e926a776bd 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -829,10 +829,10 @@ impl Language { let mut values = HashMap::default(); for (ix, name) in query.capture_names().iter().enumerate() { - if let Some(override_name) = name.strip_prefix("override.") { - let value = self.config.overrides.remove(override_name).ok_or_else(|| { + if !name.starts_with('_') { + let value = self.config.overrides.remove(name).ok_or_else(|| { anyhow!( - "language {:?} has override in query but not in config: {override_name:?}", + "language {:?} has override in query but not in config: {name:?}", self.config.name ) })?; diff --git a/crates/zed/src/languages/c/overrides.scm b/crates/zed/src/languages/c/overrides.scm index 0d14e6c205..178355c67c 100644 --- a/crates/zed/src/languages/c/overrides.scm +++ b/crates/zed/src/languages/c/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string_literal) @override.string +(comment) @comment +(string_literal) @string diff --git a/crates/zed/src/languages/cpp/overrides.scm b/crates/zed/src/languages/cpp/overrides.scm index 0d14e6c205..178355c67c 100644 --- a/crates/zed/src/languages/cpp/overrides.scm +++ b/crates/zed/src/languages/cpp/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string_literal) @override.string +(comment) @comment +(string_literal) @string diff --git a/crates/zed/src/languages/css/overrides.scm b/crates/zed/src/languages/css/overrides.scm index 9617249d3c..c0db9fe327 100644 --- a/crates/zed/src/languages/css/overrides.scm +++ b/crates/zed/src/languages/css/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string_value) @override.string +(comment) @comment +(string_value) @string diff --git a/crates/zed/src/languages/elixir/overrides.scm b/crates/zed/src/languages/elixir/overrides.scm index ed1b20d44b..1812540181 100644 --- a/crates/zed/src/languages/elixir/overrides.scm +++ b/crates/zed/src/languages/elixir/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -[(string) (charlist)] @override.string +(comment) @comment +[(string) (charlist)] @string diff --git a/crates/zed/src/languages/go/overrides.scm b/crates/zed/src/languages/go/overrides.scm index fc32c47f68..9eb287df3f 100644 --- a/crates/zed/src/languages/go/overrides.scm +++ b/crates/zed/src/languages/go/overrides.scm @@ -1,6 +1,6 @@ -(comment) @override.comment +(comment) @comment [ (interpreted_string_literal) (raw_string_literal) (rune_literal) -] @override.string +] @string diff --git a/crates/zed/src/languages/html/overrides.scm b/crates/zed/src/languages/html/overrides.scm index 8f6a1f9eea..97accffd67 100644 --- a/crates/zed/src/languages/html/overrides.scm +++ b/crates/zed/src/languages/html/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(quoted_attribute_value) @override.string \ No newline at end of file +(comment) @comment +(quoted_attribute_value) @string \ No newline at end of file diff --git a/crates/zed/src/languages/javascript/overrides.scm b/crates/zed/src/languages/javascript/overrides.scm index f19c1d9203..5e43c4a94a 100644 --- a/crates/zed/src/languages/javascript/overrides.scm +++ b/crates/zed/src/languages/javascript/overrides.scm @@ -1,9 +1,9 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string [ (jsx_element) (jsx_fragment) (jsx_self_closing_element) (jsx_expression) -] @override.element +] @element diff --git a/crates/zed/src/languages/json/overrides.scm b/crates/zed/src/languages/json/overrides.scm index 83911ae4a5..746dbc5cd9 100644 --- a/crates/zed/src/languages/json/overrides.scm +++ b/crates/zed/src/languages/json/overrides.scm @@ -1,2 +1 @@ -(comment) @comment -(string) @override.string \ No newline at end of file +(string) @string \ No newline at end of file diff --git a/crates/zed/src/languages/python/overrides.scm b/crates/zed/src/languages/python/overrides.scm index 3a7ad5f035..8a58e304e5 100644 --- a/crates/zed/src/languages/python/overrides.scm +++ b/crates/zed/src/languages/python/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string diff --git a/crates/zed/src/languages/ruby/overrides.scm b/crates/zed/src/languages/ruby/overrides.scm index 3a7ad5f035..8a58e304e5 100644 --- a/crates/zed/src/languages/ruby/overrides.scm +++ b/crates/zed/src/languages/ruby/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string diff --git a/crates/zed/src/languages/rust/overrides.scm b/crates/zed/src/languages/rust/overrides.scm index 29128d98e7..216a395147 100644 --- a/crates/zed/src/languages/rust/overrides.scm +++ b/crates/zed/src/languages/rust/overrides.scm @@ -1,8 +1,8 @@ [ (string_literal) (raw_string_literal) -] @override.string +] @string [ (line_comment) (block_comment) -] @override.comment +] @comment diff --git a/crates/zed/src/languages/scheme/overrides.scm b/crates/zed/src/languages/scheme/overrides.scm index 28a8a569df..8c0d41b046 100644 --- a/crates/zed/src/languages/scheme/overrides.scm +++ b/crates/zed/src/languages/scheme/overrides.scm @@ -2,5 +2,5 @@ (comment) (block_comment) (directive) -] @override.comment -(string) @override.string +] @comment +(string) @string diff --git a/crates/zed/src/languages/toml/overrides.scm b/crates/zed/src/languages/toml/overrides.scm index 3a7ad5f035..8a58e304e5 100644 --- a/crates/zed/src/languages/toml/overrides.scm +++ b/crates/zed/src/languages/toml/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string diff --git a/crates/zed/src/languages/tsx/overrides.scm b/crates/zed/src/languages/tsx/overrides.scm index 183f7b4f98..d86186a903 100644 --- a/crates/zed/src/languages/tsx/overrides.scm +++ b/crates/zed/src/languages/tsx/overrides.scm @@ -3,5 +3,5 @@ (jsx_fragment) (jsx_self_closing_element) (jsx_expression) -] @override.element -(string) @override.string +] @element +(string) @string diff --git a/crates/zed/src/languages/typescript/overrides.scm b/crates/zed/src/languages/typescript/overrides.scm index 3a7ad5f035..8a58e304e5 100644 --- a/crates/zed/src/languages/typescript/overrides.scm +++ b/crates/zed/src/languages/typescript/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string From 2f42af2ac3ef922cfa87a0fce930b6360e452edf Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 13:02:38 -0800 Subject: [PATCH 089/108] Add confirm_quit setting --- assets/settings/default.json | 2 ++ crates/collab/src/tests.rs | 5 +---- crates/gpui/src/app.rs | 2 +- crates/settings/src/settings.rs | 13 ++++++------- crates/zed/src/zed.rs | 22 +++++++++++++++++++++- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 3924e84d61..1ef2ac8a16 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -13,6 +13,8 @@ // Whether to show the informational hover box when moving the mouse // over symbols in the editor. "hover_popover_enabled": true, + // Whether to confirm before quitting Zed. + "confirm_quit": false, // Whether the cursor blinks in the editor. "cursor_blink": true, // Whether to pop the completions menu while typing in an editor without diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 8dc29f3d60..f257d95493 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -101,10 +101,7 @@ impl TestServer { async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient { cx.update(|cx| { cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf())); - - let mut settings = Settings::test(cx); - settings.projects_online_by_default = false; - cx.set_global(settings); + cx.set_global(Settings::test(cx)); }); let http = FakeHttpClient::with_404_response(); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 6a4086c5eb..ad1fad85b1 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -989,7 +989,7 @@ impl MutableAppContext { window.toggle_full_screen(); } - fn prompt( + pub fn prompt( &self, window_id: usize, level: PromptLevel, diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index e29a98e775..8b2c12a59b 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -28,12 +28,12 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; #[derive(Clone)] pub struct Settings { pub experiments: FeatureFlags, - pub projects_online_by_default: bool, pub buffer_font_family: FamilyId, pub default_buffer_font_size: f32, pub buffer_font_size: f32, pub active_pane_magnification: f32, pub cursor_blink: bool, + pub confirm_quit: bool, pub hover_popover_enabled: bool, pub show_completions_on_input: bool, pub vim_mode: bool, @@ -294,6 +294,8 @@ pub struct SettingsFileContent { #[serde(default)] pub cursor_blink: Option, #[serde(default)] + pub confirm_quit: Option, + #[serde(default)] pub hover_popover_enabled: Option, #[serde(default)] pub show_completions_on_input: Option, @@ -356,10 +358,10 @@ impl Settings { buffer_font_size: defaults.buffer_font_size.unwrap(), active_pane_magnification: defaults.active_pane_magnification.unwrap(), default_buffer_font_size: defaults.buffer_font_size.unwrap(), + confirm_quit: defaults.confirm_quit.unwrap(), cursor_blink: defaults.cursor_blink.unwrap(), hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), show_completions_on_input: defaults.show_completions_on_input.unwrap(), - projects_online_by_default: defaults.projects_online_by_default.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), default_dock_anchor: defaults.default_dock_anchor.unwrap(), @@ -407,10 +409,6 @@ impl Settings { } } - merge( - &mut self.projects_online_by_default, - data.projects_online_by_default, - ); merge(&mut self.buffer_font_size, data.buffer_font_size); merge( &mut self.active_pane_magnification, @@ -418,6 +416,7 @@ impl Settings { ); merge(&mut self.default_buffer_font_size, data.buffer_font_size); merge(&mut self.cursor_blink, data.cursor_blink); + merge(&mut self.confirm_quit, data.confirm_quit); merge(&mut self.hover_popover_enabled, data.hover_popover_enabled); merge( &mut self.show_completions_on_input, @@ -557,6 +556,7 @@ impl Settings { buffer_font_size: 14., active_pane_magnification: 1., default_buffer_font_size: 14., + confirm_quit: false, cursor_blink: true, hover_popover_enabled: true, show_completions_on_input: true, @@ -582,7 +582,6 @@ impl Settings { language_defaults: Default::default(), language_overrides: Default::default(), lsp: Default::default(), - projects_online_by_default: true, theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), telemetry_defaults: TelemetrySettings { diagnostics: Some(true), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c3af91306d..e765a9926b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -14,6 +14,7 @@ use collections::VecDeque; pub use editor; use editor::{Editor, MultiBuffer}; +use futures::StreamExt; use gpui::{ actions, geometry::{ @@ -22,7 +23,8 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, ClipboardItem, TitlebarOptions, ViewContext, WindowKind, + AssetSource, AsyncAppContext, ClipboardItem, PromptLevel, TitlebarOptions, ViewContext, + WindowKind, }; use language::Rope; use lazy_static::lazy_static; @@ -421,7 +423,25 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { // prompt in the active window before switching to a different window. workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id())); + let should_confirm = cx.global::().confirm_quit; cx.spawn(|mut cx| async move { + dbg!(should_confirm, workspaces.first()); + + if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { + let answer = cx + .prompt( + workspace.window_id(), + PromptLevel::Info, + "Are you sure you want to quit?", + &["Quit", "Cancel"], + ) + .next() + .await; + if answer != Some(0) { + return Ok(()); + } + } + // If the user cancels any save prompt, then keep the app open. for workspace in workspaces { if !workspace From 2e37c0ea4a8bcbd0629a42250a43d916e342b7e7 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 20 Jan 2023 13:28:13 -0800 Subject: [PATCH 090/108] Open multiple definitions in a multibuffer instead of opening the files directly --- crates/editor/src/editor.rs | 147 ++++++++++++++++++++++++------------ 1 file changed, 97 insertions(+), 50 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cef32ff98c..4fbaaa8058 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -62,7 +62,7 @@ pub use multi_buffer::{ }; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; -use project::{FormatTrigger, LocationLink, Project, ProjectPath, ProjectTransaction}; +use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -5012,13 +5012,15 @@ impl Editor { cx: &mut ViewContext, ) { let pane = workspace.active_pane().clone(); - for definition in definitions { + // If there is one definition, just open it directly + if let [definition] = definitions.as_slice() { let range = definition .target .range .to_offset(definition.target.buffer.read(cx)); - let target_editor_handle = workspace.open_project_item(definition.target.buffer, cx); + let target_editor_handle = + workspace.open_project_item(definition.target.buffer.clone(), cx); target_editor_handle.update(cx, |target_editor, cx| { // When selecting a definition in a different buffer, disable the nav history // to avoid creating a history entry at the previous cursor location. @@ -5031,6 +5033,28 @@ impl Editor { pane.update(cx, |pane, _| pane.enable_history()); }); + } else { + let replica_id = editor_handle.read(cx).replica_id(cx); + let title = definitions + .iter() + .find(|definition| definition.origin.is_some()) + .and_then(|definition| { + definition.origin.as_ref().map(|origin| { + let buffer = origin.buffer.read(cx); + format!( + "Definitions for {}", + buffer + .text_for_range(origin.range.clone()) + .collect::() + ) + }) + }) + .unwrap_or("Definitions".to_owned()); + let locations = definitions + .into_iter() + .map(|definition| definition.target) + .collect(); + Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx) } } @@ -5051,64 +5075,87 @@ impl Editor { let project = workspace.project().clone(); let references = project.update(cx, |project, cx| project.references(&buffer, head, cx)); Some(cx.spawn(|workspace, mut cx| async move { - let mut locations = references.await?; + let locations = references.await?; if locations.is_empty() { return Ok(()); } - locations.sort_by_key(|location| location.buffer.id()); - let mut locations = locations.into_iter().peekable(); - let mut ranges_to_highlight = Vec::new(); - - let excerpt_buffer = cx.add_model(|cx| { - let mut symbol_name = None; - let mut multibuffer = MultiBuffer::new(replica_id); - while let Some(location) = locations.next() { - let buffer = location.buffer.read(cx); - let mut ranges_for_buffer = Vec::new(); - let range = location.range.to_offset(buffer); - ranges_for_buffer.push(range.clone()); - if symbol_name.is_none() { - symbol_name = Some(buffer.text_for_range(range).collect::()); - } - - while let Some(next_location) = locations.peek() { - if next_location.buffer == location.buffer { - ranges_for_buffer.push(next_location.range.to_offset(buffer)); - locations.next(); - } else { - break; - } - } - - ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end))); - ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines( - location.buffer.clone(), - ranges_for_buffer, - 1, - cx, - )); - } - multibuffer.with_title(format!("References to `{}`", symbol_name.unwrap())) - }); - workspace.update(&mut cx, |workspace, cx| { - let editor = - cx.add_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx)); - editor.update(cx, |editor, cx| { - editor.highlight_background::( - ranges_to_highlight, - |theme| theme.editor.highlighted_line_background, - cx, - ); - }); - workspace.add_item(Box::new(editor), cx); + let title = locations + .first() + .as_ref() + .map(|location| { + let buffer = location.buffer.read(cx); + format!( + "References to `{}`", + buffer + .text_for_range(location.range.clone()) + .collect::() + ) + }) + .unwrap(); + Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx); }); Ok(()) })) } + /// Opens a multibuffer with the given project locations in it + pub fn open_locations_in_multibuffer( + workspace: &mut Workspace, + mut locations: Vec, + replica_id: ReplicaId, + title: String, + cx: &mut ViewContext, + ) { + // If there are multiple definitions, open them in a multibuffer + locations.sort_by_key(|location| location.buffer.id()); + let mut locations = locations.into_iter().peekable(); + let mut ranges_to_highlight = Vec::new(); + + let excerpt_buffer = cx.add_model(|cx| { + let mut multibuffer = MultiBuffer::new(replica_id); + while let Some(location) = locations.next() { + let buffer = location.buffer.read(cx); + let mut ranges_for_buffer = Vec::new(); + let range = location.range.to_offset(buffer); + ranges_for_buffer.push(range.clone()); + + while let Some(next_location) = locations.peek() { + if next_location.buffer == location.buffer { + ranges_for_buffer.push(next_location.range.to_offset(buffer)); + locations.next(); + } else { + break; + } + } + + ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end))); + ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines( + location.buffer.clone(), + ranges_for_buffer, + 1, + cx, + )) + } + + multibuffer.with_title(title) + }); + + let editor = cx.add_view(|cx| { + Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), cx) + }); + editor.update(cx, |editor, cx| { + editor.highlight_background::( + ranges_to_highlight, + |theme| theme.editor.highlighted_line_background, + cx, + ); + }); + workspace.add_item(Box::new(editor), cx); + } + pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext) -> Option>> { use language::ToOffset as _; From c1934d62328904f1f500e43477f8ce9996137132 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 20 Jan 2023 16:56:56 -0500 Subject: [PATCH 091/108] WIP --- Cargo.lock | 3 + crates/feedback/Cargo.toml | 3 + crates/feedback/src/feedback.rs | 7 +- ...feedback_popover.rs => feedback_editor.rs} | 183 ++++++++++++------ crates/zed/src/zed.rs | 2 +- 5 files changed, 137 insertions(+), 61 deletions(-) rename crates/feedback/src/{feedback_popover.rs => feedback_editor.rs} (66%) diff --git a/Cargo.lock b/Cargo.lock index 66a3e3ee72..10c67ee33b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2033,13 +2033,16 @@ dependencies = [ "gpui", "human_bytes", "isahc", + "language", "lazy_static", + "postage", "project", "serde", "settings", "smallvec", "sysinfo", "theme", + "tree-sitter-markdown", "urlencoding", "util", "workspace", diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index c6f139e36e..3eaa9448f9 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -13,17 +13,20 @@ test-support = [] anyhow = "1.0.38" client = { path = "../client" } editor = { path = "../editor" } +language = { path = "../language" } futures = "0.3" gpui = { path = "../gpui" } human_bytes = "0.4.1" isahc = "1.7" lazy_static = "1.4.0" +postage = { version = "0.4", features = ["futures-traits"] } project = { path = "../project" } serde = { version = "1.0", features = ["derive", "rc"] } settings = { path = "../settings" } smallvec = { version = "1.6", features = ["union"] } sysinfo = "0.27.1" theme = { path = "../theme" } +tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } urlencoding = "2.1.2" util = { path = "../util" } workspace = { path = "../workspace" } \ No newline at end of file diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 03379d655c..46c9c2efc2 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -pub mod feedback_popover; +pub mod feedback_editor; mod system_specs; use gpui::{actions, impl_actions, ClipboardItem, ViewContext}; use serde::Deserialize; @@ -21,7 +21,7 @@ actions!( ); pub fn init(cx: &mut gpui::MutableAppContext) { - feedback_popover::init(cx); + feedback_editor::init(cx); cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); @@ -59,7 +59,4 @@ pub fn init(cx: &mut gpui::MutableAppContext) { }); }, ); - - // TODO FEEDBACK: Should I put Give Feedback action here? - // TODO FEEDBACK: Disble buffer search? } diff --git a/crates/feedback/src/feedback_popover.rs b/crates/feedback/src/feedback_editor.rs similarity index 66% rename from crates/feedback/src/feedback_popover.rs rename to crates/feedback/src/feedback_editor.rs index 6beaea83f0..120431ab22 100644 --- a/crates/feedback/src/feedback_popover.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -2,15 +2,18 @@ use std::{ops::Range, sync::Arc}; use anyhow::bail; use client::{Client, ZED_SECRET_CLIENT_TOKEN}; -use editor::{Editor, MultiBuffer}; +use editor::Editor; use futures::AsyncReadExt; use gpui::{ actions, elements::{ChildView, Flex, Label, MouseEventHandler, ParentElement, Stack, Text}, - serde_json, CursorStyle, Element, ElementBox, Entity, ModelHandle, MouseButton, - MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, + serde_json, AnyViewHandle, CursorStyle, Element, ElementBox, Entity, ModelHandle, MouseButton, + MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, + WeakViewHandle, }; use isahc::Request; +use language::{Language, LanguageConfig}; +use postage::prelude::Stream; use lazy_static::lazy_static; use project::{Project, ProjectEntryId, ProjectPath}; @@ -24,14 +27,13 @@ use workspace::{ use crate::system_specs::SystemSpecs; -// TODO FEEDBACK: Rename this file to feedback editor? -// TODO FEEDBACK: Where is the backend code for air table? - lazy_static! { pub static ref ZED_SERVER_URL: String = std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string()); } +// TODO FEEDBACK: In the future, it would be nice to use this is some sort of live-rendering character counter thing +// Currently, we are just checking on submit that the the text exceeds the `start` value in this range const FEEDBACK_CHAR_COUNT_RANGE: Range = Range { start: 5, end: 1000, @@ -40,7 +42,6 @@ const FEEDBACK_CHAR_COUNT_RANGE: Range = Range { actions!(feedback, [SubmitFeedback, GiveFeedback, DeployFeedback]); pub fn init(cx: &mut MutableAppContext) { - // cx.add_action(FeedbackView::submit_feedback); cx.add_action(FeedbackEditor::deploy); } @@ -147,14 +148,9 @@ impl StatusItemView for FeedbackButton { _: Option<&dyn ItemHandle>, _: &mut gpui::ViewContext, ) { - // N/A } } -// impl Entity for FeedbackView { -// type Event = (); -// } - #[derive(Serialize)] struct FeedbackRequestBody<'a> { feedback_text: &'a str, @@ -163,6 +159,7 @@ struct FeedbackRequestBody<'a> { token: &'a str, } +#[derive(Clone)] struct FeedbackEditor { editor: ViewHandle, } @@ -173,15 +170,30 @@ impl FeedbackEditor { _: WeakViewHandle, cx: &mut ViewContext, ) -> Self { - // TODO FEEDBACK: Get rid of this expect + // TODO FEEDBACK: This doesn't work like I expected it would + // let markdown_language = Arc::new(Language::new( + // LanguageConfig::default(), + // Some(tree_sitter_markdown::language()), + // )); + + let markdown_language = project_handle + .read(cx) + .languages() + .get_language("Markdown") + .unwrap(); + let buffer = project_handle - .update(cx, |project, cx| project.create_buffer("", None, cx)) - .expect("Could not open feedback window"); + .update(cx, |project, cx| { + project.create_buffer("", Some(markdown_language), cx) + }) + .expect("creating buffers on a local workspace always succeeds"); + + const FEDBACK_PLACEHOLDER_TEXT: &str = "Thanks for spending time with Zed. Enter your feedback here in the form of Markdown. Save the tab to submit your feedback."; let editor = cx.add_view(|cx| { let mut editor = Editor::for_buffer(buffer, Some(project_handle.clone()), cx); editor.set_vertical_scroll_margin(5, cx); - editor.set_placeholder_text("Enter your feedback here, save to submit feedback", cx); + editor.set_placeholder_text(FEDBACK_PLACEHOLDER_TEXT, cx); editor }); @@ -189,7 +201,65 @@ impl FeedbackEditor { this } - fn submit_feedback(&mut self, cx: &mut ViewContext<'_, Self>) { + fn handle_save( + &mut self, + _: gpui::ModelHandle, + cx: &mut ViewContext, + ) -> Task> { + // TODO FEEDBACK: These don't look right + let feedback_text_length = self.editor.read(cx).buffer().read(cx).len(cx); + + if feedback_text_length <= FEEDBACK_CHAR_COUNT_RANGE.start { + cx.prompt( + PromptLevel::Critical, + &format!( + "Feedback must be longer than {} characters", + FEEDBACK_CHAR_COUNT_RANGE.start + ), + &["OK"], + ); + + return Task::ready(Ok(())); + } + + let mut answer = cx.prompt( + PromptLevel::Warning, + "Ready to submit your feedback?", + &["Yes, Submit!", "No"], + ); + + let this = cx.handle(); + cx.spawn(|_, mut cx| async move { + let answer = answer.recv().await; + + if answer == Some(0) { + cx.update(|cx| { + this.update(cx, |this, cx| match this.submit_feedback(cx) { + // TODO FEEDBACK + Ok(_) => { + // Close file after feedback sent successfully + // workspace + // .update(cx, |workspace, cx| { + // Pane::close_active_item(workspace, &Default::default(), cx) + // .unwrap() + // }) + // .await + // .unwrap(); + } + Err(error) => { + cx.prompt(PromptLevel::Critical, &error.to_string(), &["OK"]); + // Prompt that something failed (and to check the log for the exact error? and to try again?) + } + }) + }) + } + }) + .detach(); + + Task::ready(Ok(())) + } + + fn submit_feedback(&mut self, cx: &mut ViewContext<'_, Self>) -> anyhow::Result<()> { let feedback_text = self.editor.read(cx).text(cx); let zed_client = cx.global::>(); let system_specs = SystemSpecs::new(cx); @@ -198,13 +268,12 @@ impl FeedbackEditor { let metrics_id = zed_client.metrics_id(); let http_client = zed_client.http_client(); - cx.spawn(|_, _| { - async move { - // TODO FEEDBACK: Use or remove - // this.read_with(&async_cx, |this, cx| { - // // Now we have a &self and a &AppContext - // }); + // TODO FEEDBACK: how to get error out of the thread + let this = cx.handle(); + + cx.spawn(|_, async_cx| { + async move { let request = FeedbackRequestBody { feedback_text: &feedback_text, metrics_id, @@ -224,13 +293,14 @@ impl FeedbackEditor { let response_status = response.status(); - dbg!(response_status); - if !response_status.is_success() { - // TODO FEEDBACK: Do some sort of error reporting here for if store fails - bail!("Error") + bail!("Feedback API failed with: {}", response_status) } + this.read_with(&async_cx, |this, cx| -> anyhow::Result<()> { + bail!("Error") + })?; + // TODO FEEDBACK: Use or remove // Will need to handle error cases // async_cx.update(|cx| { @@ -246,6 +316,8 @@ impl FeedbackEditor { } }) .detach(); + + Ok(()) } } @@ -258,25 +330,24 @@ impl FeedbackEditor { let feedback_editor = cx .add_view(|cx| FeedbackEditor::new(workspace.project().clone(), workspace_handle, cx)); workspace.add_item(Box::new(feedback_editor), cx); + // } } - // } } -// struct FeedbackView { -// editor: Editor, -// } - impl View for FeedbackEditor { fn ui_name() -> &'static str { - "Feedback" + "FeedbackEditor" } fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - // let theme = cx.global::().theme.clone(); - // let submit_feedback_text_button_height = 20.0; - ChildView::new(&self.editor, cx).boxed() } + + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + if cx.is_self_focused() { + cx.focus(&self.editor); + } + } } impl Entity for FeedbackEditor { @@ -324,26 +395,19 @@ impl Item for FeedbackEditor { fn save( &mut self, - _: gpui::ModelHandle, + project_handle: gpui::ModelHandle, cx: &mut ViewContext, ) -> Task> { - cx.prompt( - gpui::PromptLevel::Info, - &format!("You are trying to to submit this feedbac"), - &["OK"], - ); - - self.submit_feedback(cx); - Task::ready(Ok(())) + self.handle_save(project_handle, cx) } fn save_as( &mut self, - _: gpui::ModelHandle, + project_handle: gpui::ModelHandle, _: std::path::PathBuf, - _: &mut ViewContext, + cx: &mut ViewContext, ) -> Task> { - unreachable!("save_as should not have been called"); + self.handle_save(project_handle, cx) } fn reload( @@ -351,7 +415,20 @@ impl Item for FeedbackEditor { _: gpui::ModelHandle, _: &mut ViewContext, ) -> Task> { - unreachable!("should not have been called") + unreachable!("reload should not have been called") + } + + fn clone_on_split( + &self, + _workspace_id: workspace::WorkspaceId, + cx: &mut ViewContext, + ) -> Option + where + Self: Sized, + { + // TODO FEEDBACK: split is busted + // Some(self.clone()) + None } fn serialized_item_kind() -> Option<&'static str> { @@ -369,9 +446,5 @@ impl Item for FeedbackEditor { } } -// TODO FEEDBACK: Add placeholder text -// TODO FEEDBACK: act_as_type (max mentionedt this) -// TODO FEEDBACK: focus -// TODO FEEDBACK: markdown highlighting -// TODO FEEDBACK: save prompts and accepting closes -// TODO FEEDBACK: multiple tabs? +// TODO FEEDBACK: search buffer? +// TODO FEEDBACK: warnings diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 8f597ebe79..167b57aaf2 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -327,7 +327,7 @@ pub fn initialize_workspace( let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - let feedback_button = cx.add_view(|_| feedback::feedback_popover::FeedbackButton {}); + let feedback_button = cx.add_view(|_| feedback::feedback_editor::FeedbackButton {}); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); From 083986dfae054237c2312135f4388364e6d1e031 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 20 Jan 2023 18:05:24 -0500 Subject: [PATCH 092/108] WIP Co-Authored-By: Mikayla Maki --- Cargo.lock | 1 + crates/feedback/Cargo.toml | 1 + crates/feedback/src/feedback_editor.rs | 122 ++++++++++++++++++------- 3 files changed, 90 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 10c67ee33b..fad1906e05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2037,6 +2037,7 @@ dependencies = [ "lazy_static", "postage", "project", + "search", "serde", "settings", "smallvec", diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index 3eaa9448f9..b623c9b68e 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -21,6 +21,7 @@ isahc = "1.7" lazy_static = "1.4.0" postage = { version = "0.4", features = ["futures-traits"] } project = { path = "../project" } +search = { path = "../search" } serde = { version = "1.0", features = ["derive", "rc"] } settings = { path = "../settings" } smallvec = { version = "1.6", features = ["union"] } diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 120431ab22..c5e90b7216 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -9,10 +9,9 @@ use gpui::{ elements::{ChildView, Flex, Label, MouseEventHandler, ParentElement, Stack, Text}, serde_json, AnyViewHandle, CursorStyle, Element, ElementBox, Entity, ModelHandle, MouseButton, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, - WeakViewHandle, }; use isahc::Request; -use language::{Language, LanguageConfig}; +use language::Buffer; use postage::prelude::Stream; use lazy_static::lazy_static; @@ -162,43 +161,44 @@ struct FeedbackRequestBody<'a> { #[derive(Clone)] struct FeedbackEditor { editor: ViewHandle, + project: ModelHandle, } impl FeedbackEditor { - fn new( - project_handle: ModelHandle, - _: WeakViewHandle, + fn new_with_buffer( + project: ModelHandle, + buffer: ModelHandle, cx: &mut ViewContext, ) -> Self { + const FEDBACK_PLACEHOLDER_TEXT: &str = "Thanks for spending time with Zed. Enter your feedback here in the form of Markdown. Save the tab to submit your feedback."; + + let editor = cx.add_view(|cx| { + let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx); + editor.set_vertical_scroll_margin(5, cx); + editor.set_placeholder_text(FEDBACK_PLACEHOLDER_TEXT, cx); + editor + }); + + let this = Self { editor, project }; + this + } + + fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { // TODO FEEDBACK: This doesn't work like I expected it would // let markdown_language = Arc::new(Language::new( // LanguageConfig::default(), // Some(tree_sitter_markdown::language()), // )); - let markdown_language = project_handle - .read(cx) - .languages() - .get_language("Markdown") - .unwrap(); + let markdown_language = project.read(cx).languages().get_language("Markdown"); - let buffer = project_handle + let buffer = project .update(cx, |project, cx| { - project.create_buffer("", Some(markdown_language), cx) + project.create_buffer("", markdown_language, cx) }) .expect("creating buffers on a local workspace always succeeds"); - const FEDBACK_PLACEHOLDER_TEXT: &str = "Thanks for spending time with Zed. Enter your feedback here in the form of Markdown. Save the tab to submit your feedback."; - - let editor = cx.add_view(|cx| { - let mut editor = Editor::for_buffer(buffer, Some(project_handle.clone()), cx); - editor.set_vertical_scroll_margin(5, cx); - editor.set_placeholder_text(FEDBACK_PLACEHOLDER_TEXT, cx); - editor - }); - - let this = Self { editor }; - this + Self::new_with_buffer(project, buffer, cx) } fn handle_save( @@ -297,7 +297,7 @@ impl FeedbackEditor { bail!("Feedback API failed with: {}", response_status) } - this.read_with(&async_cx, |this, cx| -> anyhow::Result<()> { + this.read_with(&async_cx, |_this, _cx| -> anyhow::Result<()> { bail!("Error") })?; @@ -326,9 +326,8 @@ impl FeedbackEditor { // if let Some(existing) = workspace.item_of_type::(cx) { // workspace.activate_item(&existing, cx); // } else { - let workspace_handle = cx.weak_handle(); - let feedback_editor = cx - .add_view(|cx| FeedbackEditor::new(workspace.project().clone(), workspace_handle, cx)); + let feedback_editor = + cx.add_view(|cx| FeedbackEditor::new(workspace.project().clone(), cx)); workspace.add_item(Box::new(feedback_editor), cx); // } } @@ -395,19 +394,19 @@ impl Item for FeedbackEditor { fn save( &mut self, - project_handle: gpui::ModelHandle, + project: gpui::ModelHandle, cx: &mut ViewContext, ) -> Task> { - self.handle_save(project_handle, cx) + self.handle_save(project, cx) } fn save_as( &mut self, - project_handle: gpui::ModelHandle, + project: gpui::ModelHandle, _: std::path::PathBuf, cx: &mut ViewContext, ) -> Task> { - self.handle_save(project_handle, cx) + self.handle_save(project, cx) } fn reload( @@ -426,9 +425,19 @@ impl Item for FeedbackEditor { where Self: Sized, { - // TODO FEEDBACK: split is busted - // Some(self.clone()) - None + let buffer = self + .editor + .read(cx) + .buffer() + .read(cx) + .as_singleton() + .expect("Feedback buffer is only ever singleton"); + + Some(Self::new_with_buffer( + self.project.clone(), + buffer.clone(), + cx, + )) } fn serialized_item_kind() -> Option<&'static str> { @@ -446,5 +455,50 @@ impl Item for FeedbackEditor { } } +// impl SearchableItem for FeedbackEditor { +// type Match = ::Match; + +// fn to_search_event(event: &Self::Event) -> Option { +// Editor::to_search_event(event) +// } + +// fn clear_matches(&mut self, cx: &mut ViewContext) { +// self. +// } + +// fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext) { +// todo!() +// } + +// fn query_suggestion(&mut self, cx: &mut ViewContext) -> String { +// todo!() +// } + +// fn activate_match( +// &mut self, +// index: usize, +// matches: Vec, +// cx: &mut ViewContext, +// ) { +// todo!() +// } + +// fn find_matches( +// &mut self, +// query: project::search::SearchQuery, +// cx: &mut ViewContext, +// ) -> Task> { +// todo!() +// } + +// fn active_match_index( +// &mut self, +// matches: Vec, +// cx: &mut ViewContext, +// ) -> Option { +// todo!() +// } +// } + // TODO FEEDBACK: search buffer? // TODO FEEDBACK: warnings From f7ceebfce30b242017705080bb94b61b70f3f341 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 15:56:45 -0800 Subject: [PATCH 093/108] Avoid adjusting indentation of lines inside newly-created errors --- crates/language/src/buffer.rs | 56 +++++++++++++++++++++++++++------ crates/language/src/language.rs | 2 ++ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 7ef7845952..5c966bbc72 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -282,6 +282,7 @@ struct AutoindentRequestEntry { struct IndentSuggestion { basis_row: u32, delta: Ordering, + within_error: bool, } struct BufferChunkHighlights<'a> { @@ -937,7 +938,7 @@ impl Buffer { // Build a map containing the suggested indentation for each of the edited lines // with respect to the state of the buffer before these edits. This map is keyed // by the rows for these lines in the current state of the buffer. - let mut old_suggestions = BTreeMap::::default(); + let mut old_suggestions = BTreeMap::::default(); let old_edited_ranges = contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields); let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable(); @@ -963,14 +964,17 @@ impl Buffer { let suggested_indent = old_to_new_rows .get(&suggestion.basis_row) - .and_then(|from_row| old_suggestions.get(from_row).copied()) + .and_then(|from_row| { + Some(old_suggestions.get(from_row).copied()?.0) + }) .unwrap_or_else(|| { request .before_edit .indent_size_for_line(suggestion.basis_row) }) .with_delta(suggestion.delta, language_indent_size); - old_suggestions.insert(new_row, suggested_indent); + old_suggestions + .insert(new_row, (suggested_indent, suggestion.within_error)); } } yield_now().await; @@ -1016,12 +1020,13 @@ impl Buffer { snapshot.indent_size_for_line(suggestion.basis_row) }) .with_delta(suggestion.delta, language_indent_size); - if old_suggestions - .get(&new_row) - .map_or(true, |old_indentation| { + if old_suggestions.get(&new_row).map_or( + true, + |(old_indentation, was_within_error)| { suggested_indent != *old_indentation - }) - { + && (!suggestion.within_error || *was_within_error) + }, + ) { indent_sizes.insert(new_row, suggested_indent); } } @@ -1779,7 +1784,7 @@ impl BufferSnapshot { let start = Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0); let end = Point::new(row_range.end, 0); let range = (start..end).to_offset(&self.text); - let mut matches = self.syntax.matches(range, &self.text, |grammar| { + let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| { Some(&grammar.indents_config.as_ref()?.query) }); let indent_configs = matches @@ -1825,6 +1830,30 @@ impl BufferSnapshot { } } + let mut error_ranges = Vec::>::new(); + let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| { + Some(&grammar.error_query) + }); + while let Some(mat) = matches.peek() { + let node = mat.captures[0].node; + let start = Point::from_ts_point(node.start_position()); + let end = Point::from_ts_point(node.end_position()); + let range = start..end; + let ix = match error_ranges.binary_search_by_key(&range.start, |r| r.start) { + Ok(ix) | Err(ix) => ix, + }; + let mut end_ix = ix; + while let Some(existing_range) = error_ranges.get(end_ix) { + if existing_range.end < end { + end_ix += 1; + } else { + break; + } + } + error_ranges.splice(ix..end_ix, [range]); + matches.advance(); + } + outdent_positions.sort(); for outdent_position in outdent_positions { // find the innermost indent range containing this outdent_position @@ -1902,33 +1931,42 @@ impl BufferSnapshot { } } + let within_error = error_ranges + .iter() + .any(|e| e.start.row < row && e.end > row_start); + let suggestion = if outdent_to_row == prev_row || (outdent_from_prev_row && indent_from_prev_row) { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Equal, + within_error, }) } else if indent_from_prev_row { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Greater, + within_error, }) } else if outdent_to_row < prev_row { Some(IndentSuggestion { basis_row: outdent_to_row, delta: Ordering::Equal, + within_error, }) } else if outdent_from_prev_row { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Less, + within_error, }) } else if config.auto_indent_using_last_non_empty_line || !self.is_line_blank(prev_row) { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Equal, + within_error, }) } else { None diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e926a776bd..045e8dcd6f 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -348,6 +348,7 @@ pub struct Language { pub struct Grammar { id: usize, pub(crate) ts_language: tree_sitter::Language, + pub(crate) error_query: Query, pub(crate) highlights_query: Option, pub(crate) brackets_config: Option, pub(crate) indents_config: Option, @@ -684,6 +685,7 @@ impl Language { indents_config: None, injection_config: None, override_config: None, + error_query: Query::new(ts_language, "(ERROR) @error").unwrap(), ts_language, highlight_map: Default::default(), }) From 9f74d6e4ac9f75ecf2c13ec3c3ac4897ae938f72 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 15:56:56 -0800 Subject: [PATCH 094/108] Highlight and auto-indent await expressions in rust --- crates/zed/src/languages/rust/highlights.scm | 1 + crates/zed/src/languages/rust/indents.scm | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/zed/src/languages/rust/highlights.scm b/crates/zed/src/languages/rust/highlights.scm index 98ea1ee40e..b52a7a8aff 100644 --- a/crates/zed/src/languages/rust/highlights.scm +++ b/crates/zed/src/languages/rust/highlights.scm @@ -52,6 +52,7 @@ [ "as" "async" + "await" "break" "const" "continue" diff --git a/crates/zed/src/languages/rust/indents.scm b/crates/zed/src/languages/rust/indents.scm index 504c235d10..9ab6b02908 100644 --- a/crates/zed/src/languages/rust/indents.scm +++ b/crates/zed/src/languages/rust/indents.scm @@ -5,6 +5,7 @@ (assignment_expression) (let_declaration) (let_chain) + (await_expression) ] @indent (_ "[" "]" @end) @indent From 310d867aab01e6b0cc8b7f0954d13c68d88123c4 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 10:32:44 -0800 Subject: [PATCH 095/108] Switch PopUp windows to use the NSTrackingArea API and add support for the mouseExited event Co-authored-by: Antonio --- crates/gpui/src/platform/event.rs | 17 +++++++++++++ crates/gpui/src/platform/mac/event.rs | 12 ++++++++- crates/gpui/src/platform/mac/window.rs | 34 +++++++++++++++++++++++--- crates/gpui/src/presenter.rs | 15 ++++++++++++ crates/workspace/src/workspace.rs | 8 ++---- 5 files changed, 75 insertions(+), 11 deletions(-) diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 0c08af4497..c39c76dc34 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -178,6 +178,21 @@ impl MouseMovedEvent { } } +#[derive(Clone, Copy, Debug, Default)] +pub struct MouseExitedEvent { + pub position: Vector2F, + pub pressed_button: Option, + pub modifiers: Modifiers, +} + +impl Deref for MouseExitedEvent { + type Target = Modifiers; + + fn deref(&self) -> &Self::Target { + &self.modifiers + } +} + #[derive(Clone, Debug)] pub enum Event { KeyDown(KeyDownEvent), @@ -186,6 +201,7 @@ pub enum Event { MouseDown(MouseButtonEvent), MouseUp(MouseButtonEvent), MouseMoved(MouseMovedEvent), + MouseExited(MouseExitedEvent), ScrollWheel(ScrollWheelEvent), } @@ -197,6 +213,7 @@ impl Event { Event::ModifiersChanged { .. } => None, Event::MouseDown(event) | Event::MouseUp(event) => Some(event.position), Event::MouseMoved(event) => Some(event.position), + Event::MouseExited(event) => Some(event.position), Event::ScrollWheel(event) => Some(event.position), } } diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index c527fe8d25..2f29898c26 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -3,7 +3,7 @@ use crate::{ keymap_matcher::Keystroke, platform::{Event, NavigationDirection}, KeyDownEvent, KeyUpEvent, Modifiers, ModifiersChangedEvent, MouseButton, MouseButtonEvent, - MouseMovedEvent, ScrollDelta, ScrollWheelEvent, TouchPhase, + MouseExitedEvent, MouseMovedEvent, ScrollDelta, ScrollWheelEvent, TouchPhase, }; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType}, @@ -221,6 +221,16 @@ impl Event { modifiers: read_modifiers(native_event), }) }), + NSEventType::NSMouseExited => window_height.map(|window_height| { + Self::MouseExited(MouseExitedEvent { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + pressed_button: None, + modifiers: read_modifiers(native_event), + }) + }), _ => None, } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 0da2e4351e..e93e63be92 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -66,6 +66,14 @@ const NSNormalWindowLevel: NSInteger = 0; #[allow(non_upper_case_globals)] const NSPopUpWindowLevel: NSInteger = 101; #[allow(non_upper_case_globals)] +const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01; +#[allow(non_upper_case_globals)] +const NSTrackingMouseMoved: NSUInteger = 0x02; +#[allow(non_upper_case_globals)] +const NSTrackingActiveAlways: NSUInteger = 0x80; +#[allow(non_upper_case_globals)] +const NSTrackingInVisibleRect: NSUInteger = 0x200; +#[allow(non_upper_case_globals)] const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4; #[repr(C)] @@ -164,6 +172,10 @@ unsafe fn build_classes() { sel!(mouseMoved:), handle_view_event as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(mouseExited:), + handle_view_event as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(mouseDragged:), handle_view_event as extern "C" fn(&Object, Sel, id), @@ -470,8 +482,6 @@ impl Window { native_window.setTitlebarAppearsTransparent_(YES); } - native_window.setAcceptsMouseMovedEvents_(YES); - native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable); native_view.setWantsBestResolutionOpenGLSurface_(YES); @@ -494,8 +504,25 @@ impl Window { } match options.kind { - WindowKind::Normal => native_window.setLevel_(NSNormalWindowLevel), + WindowKind::Normal => { + native_window.setLevel_(NSNormalWindowLevel); + native_window.setAcceptsMouseMovedEvents_(YES); + } WindowKind::PopUp => { + // Use a tracking area to allow receiving MouseMoved events even when + // the window or application aren't active, which is often the case + // e.g. for notification windows. + let tracking_area: id = msg_send![class!(NSTrackingArea), alloc]; + let _: () = msg_send![ + tracking_area, + initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)) + options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect + owner: native_view + userInfo: nil + ]; + let _: () = + msg_send![native_view, addTrackingArea: tracking_area.autorelease()]; + native_window.setLevel_(NSPopUpWindowLevel); let _: () = msg_send![ native_window, @@ -965,7 +992,6 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let window_height = window_state_borrow.content_size().y(); let event = unsafe { Event::from_native(native_event, Some(window_height)) }; - if let Some(event) = event { match &event { Event::MouseMoved( diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 73e371c50b..0909d95fd0 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -360,6 +360,21 @@ impl Presenter { self.last_mouse_moved_event = Some(event.clone()); } + Event::MouseExited(event) => { + // When the platform sends a MouseExited event, synthesize + // a MouseMoved event whose position is outside the window's + // bounds so that hover and cursor state can be updated. + return self.dispatch_event( + Event::MouseMoved(MouseMovedEvent { + position: event.position, + pressed_button: event.pressed_button, + modifiers: event.modifiers, + }), + event_reused, + cx, + ); + } + Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel { region: Default::default(), platform_event: e.clone(), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9665db6be6..8735f33fbe 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,18 +31,14 @@ use futures::{ }; use gpui::{ actions, - color::Color, elements::*, - geometry::{ - rect::RectF, - vector::{vec2f, Vector2F}, - }, + geometry::vector::Vector2F, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; From bb24f1142fd11754eeba40c800f2c9b280c23232 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 16:47:23 -0800 Subject: [PATCH 096/108] Removed dbg --- crates/zed/src/zed.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e765a9926b..b6b00e7869 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -425,8 +425,6 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { let should_confirm = cx.global::().confirm_quit; cx.spawn(|mut cx| async move { - dbg!(should_confirm, workspaces.first()); - if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { let answer = cx .prompt( From 56080771e61f95b387b5f737bab158ca28fa05f8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 17:02:38 -0800 Subject: [PATCH 097/108] Add test for avoiding indent adjustment inside newly-created errors --- crates/language/src/buffer.rs | 10 ++ crates/language/src/buffer_tests.rs | 207 ++++++++++++++++++++-------- crates/language/src/syntax_map.rs | 48 +------ crates/text/src/text.rs | 51 +++++++ 4 files changed, 212 insertions(+), 104 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5c966bbc72..110e10564c 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1666,6 +1666,16 @@ impl Buffer { #[cfg(any(test, feature = "test-support"))] impl Buffer { + pub fn edit_via_marked_text( + &mut self, + marked_string: &str, + autoindent_mode: Option, + cx: &mut ModelContext, + ) { + let edits = self.edits_for_marked_text(marked_string); + self.edit(edits, autoindent_mode, cx); + } + pub fn set_group_interval(&mut self, group_interval: Duration) { self.text.set_group_interval(group_interval); } diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 382311e204..0b2ef1d7a7 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -800,23 +800,29 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta cx.set_global(settings); cx.add_model(|cx| { - let text = " + let mut buffer = Buffer::new( + 0, + " fn a() { c; d; } - " - .unindent(); - - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); // Lines 2 and 3 don't match the indentation suggestion. When editing these lines, // their indentation is not adjusted. - buffer.edit( - [ - (empty(Point::new(1, 1)), "()"), - (empty(Point::new(2, 1)), "()"), - ], + buffer.edit_via_marked_text( + &" + fn a() { + c«()»; + d«()»; + } + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -833,14 +839,22 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta // When appending new content after these lines, the indentation is based on the // preceding lines' actual indentation. - buffer.edit( - [ - (empty(Point::new(1, 1)), "\n.f\n.g"), - (empty(Point::new(2, 1)), "\n.f\n.g"), - ], + buffer.edit_via_marked_text( + &" + fn a() { + c« + .f + .g()»; + d« + .f + .g()»; + } + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); + assert_eq!( buffer.text(), " @@ -859,20 +873,27 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta }); cx.add_model(|cx| { - let text = " + let mut buffer = Buffer::new( + 0, + " fn a() { - { - b()? - } - Ok(()) - } - " - .unindent(); - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + b(); + | + " + .replace("|", "") // marker to preserve trailing whitespace + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); - // Delete a closing curly brace changes the suggested indent for the line. - buffer.edit( - [(Point::new(3, 4)..Point::new(3, 5), "")], + // Insert a closing brace. It is outdented. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + «}» + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -880,19 +901,20 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta buffer.text(), " fn a() { - { - b()? - | - Ok(()) + b(); } " - .replace('|', "") // included in the string to preserve trailing whites .unindent() ); - // Manually editing the leading whitespace - buffer.edit( - [(Point::new(3, 0)..Point::new(3, 12), "")], + // Manually edit the leading whitespace. The edit is preserved. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + « »} + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -900,11 +922,8 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta buffer.text(), " fn a() { - { - b()? - - Ok(()) - } + b(); + } " .unindent() ); @@ -913,30 +932,108 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta } #[gpui::test] -fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) { - cx.set_global(Settings::test(cx)); +fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut MutableAppContext) { + let settings = Settings::test(cx); + cx.set_global(settings); + cx.add_model(|cx| { - let text = " - fn a() {} - " - .unindent(); + let mut buffer = Buffer::new( + 0, + " + fn a() { + i + } + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); - - buffer.edit([(5..5, "\nb")], Some(AutoindentMode::EachLine), cx); + // Regression test: line does not get outdented due to syntax error + buffer.edit_via_marked_text( + &" + fn a() { + i«f let Some(x) = y» + } + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); assert_eq!( buffer.text(), " - fn a( - b) {} + fn a() { + if let Some(x) = y + } + " + .unindent() + ); + + buffer.edit_via_marked_text( + &" + fn a() { + if let Some(x) = y« {» + } + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a() { + if let Some(x) = y { + } + " + .unindent() + ); + + buffer + }); +} + +#[gpui::test] +fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) { + cx.set_global(Settings::test(cx)); + cx.add_model(|cx| { + let mut buffer = Buffer::new( + 0, + " + fn a() {} + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); + + buffer.edit_via_marked_text( + &" + fn a(« + b») {} + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a( + b) {} " .unindent() ); // The indentation suggestion changed because `@end` node (a close paren) // is now at the beginning of the line. - buffer.edit( - [(Point::new(1, 4)..Point::new(1, 5), "")], + buffer.edit_via_marked_text( + &" + fn a( + ˇ) {} + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -1894,7 +1991,3 @@ fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> Str layers[0].node.to_sexp() }) } - -fn empty(point: Point) -> Range { - point..point -} diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 458ffd8bc2..8d66730854 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -2262,7 +2262,7 @@ mod tests { mutated_syntax_map.reparse(language.clone(), &buffer); for (i, marked_string) in steps.into_iter().enumerate() { - edit_buffer(&mut buffer, &marked_string.unindent()); + buffer.edit_via_marked_text(&marked_string.unindent()); // Reparse the syntax map mutated_syntax_map.interpolate(&buffer); @@ -2452,52 +2452,6 @@ mod tests { assert_eq!(actual_ranges, expected_ranges); } - fn edit_buffer(buffer: &mut Buffer, marked_string: &str) { - let old_text = buffer.text(); - let (new_text, mut ranges) = marked_text_ranges(marked_string, false); - if ranges.is_empty() { - ranges.push(0..new_text.len()); - } - - assert_eq!( - old_text[..ranges[0].start], - new_text[..ranges[0].start], - "invalid edit" - ); - - let mut delta = 0; - let mut edits = Vec::new(); - let mut ranges = ranges.into_iter().peekable(); - - while let Some(inserted_range) = ranges.next() { - let new_start = inserted_range.start; - let old_start = (new_start as isize - delta) as usize; - - let following_text = if let Some(next_range) = ranges.peek() { - &new_text[inserted_range.end..next_range.start] - } else { - &new_text[inserted_range.end..] - }; - - let inserted_len = inserted_range.len(); - let deleted_len = old_text[old_start..] - .find(following_text) - .expect("invalid edit"); - - let old_range = old_start..old_start + deleted_len; - edits.push((old_range, new_text[inserted_range].to_string())); - delta += inserted_len as isize - deleted_len as isize; - } - - assert_eq!( - old_text.len() as isize + delta, - new_text.len() as isize, - "invalid edit" - ); - - buffer.edit(edits); - } - pub fn string_contains_sequence(text: &str, parts: &[&str]) -> bool { let mut last_part_end = 0; for part in parts { diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 914023f305..c7d36e29de 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1372,6 +1372,57 @@ impl Buffer { #[cfg(any(test, feature = "test-support"))] impl Buffer { + pub fn edit_via_marked_text(&mut self, marked_string: &str) { + let edits = self.edits_for_marked_text(marked_string); + self.edit(edits); + } + + pub fn edits_for_marked_text(&self, marked_string: &str) -> Vec<(Range, String)> { + let old_text = self.text(); + let (new_text, mut ranges) = util::test::marked_text_ranges(marked_string, false); + if ranges.is_empty() { + ranges.push(0..new_text.len()); + } + + assert_eq!( + old_text[..ranges[0].start], + new_text[..ranges[0].start], + "invalid edit" + ); + + let mut delta = 0; + let mut edits = Vec::new(); + let mut ranges = ranges.into_iter().peekable(); + + while let Some(inserted_range) = ranges.next() { + let new_start = inserted_range.start; + let old_start = (new_start as isize - delta) as usize; + + let following_text = if let Some(next_range) = ranges.peek() { + &new_text[inserted_range.end..next_range.start] + } else { + &new_text[inserted_range.end..] + }; + + let inserted_len = inserted_range.len(); + let deleted_len = old_text[old_start..] + .find(following_text) + .expect("invalid edit"); + + let old_range = old_start..old_start + deleted_len; + edits.push((old_range, new_text[inserted_range].to_string())); + delta += inserted_len as isize - deleted_len as isize; + } + + assert_eq!( + old_text.len() as isize + delta, + new_text.len() as isize, + "invalid edit" + ); + + edits + } + pub fn check_invariants(&self) { // Ensure every fragment is ordered by locator in the fragment tree and corresponds // to an insertion fragment in the insertions tree. From 8dcaa81aadb31038ed5771ec3d5c8a43ca7949d9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 18:19:24 -0800 Subject: [PATCH 098/108] switch return type of accepts_first_mouse --- crates/gpui/src/platform/mac/window.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index e93e63be92..6126533644 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1438,7 +1438,11 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL { unsafe { let state = get_window_state(this); let state_borrow = state.as_ref().borrow(); - return state_borrow.kind == WindowKind::PopUp; + return if state_borrow.kind == WindowKind::PopUp { + YES + } else { + NO + }; } } From fb2278dc6d8c688a1e17d58d4fd46f81fc937081 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 23 Jan 2023 00:59:46 -0500 Subject: [PATCH 099/108] Complete first iteration of in-app feedback --- Cargo.lock | 1 + crates/feedback/Cargo.toml | 1 + crates/feedback/src/feedback.rs | 5 +- crates/feedback/src/feedback_editor.rs | 234 ++++++++++++------------- crates/zed/src/zed.rs | 2 +- 5 files changed, 113 insertions(+), 130 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fad1906e05..b503e1838a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2035,6 +2035,7 @@ dependencies = [ "isahc", "language", "lazy_static", + "log", "postage", "project", "search", diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index b623c9b68e..ec5aa52a1a 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -14,6 +14,7 @@ anyhow = "1.0.38" client = { path = "../client" } editor = { path = "../editor" } language = { path = "../language" } +log = "0.4" futures = "0.3" gpui = { path = "../gpui" } human_bytes = "0.4.1" diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 46c9c2efc2..4b0dfc4df9 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -7,10 +7,9 @@ use serde::Deserialize; use system_specs::SystemSpecs; use workspace::Workspace; -// TODO FEEDBACK: This open brownser code is duplicated from the zed crate, where should we refactor it to? #[derive(Deserialize, Clone, PartialEq)] -struct OpenBrowser { - url: Arc, +pub struct OpenBrowser { + pub url: Arc, } impl_actions!(zed, [OpenBrowser]); diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index c5e90b7216..564ac76203 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -2,7 +2,7 @@ use std::{ops::Range, sync::Arc}; use anyhow::bail; use client::{Client, ZED_SECRET_CLIENT_TOKEN}; -use editor::Editor; +use editor::{Anchor, Editor}; use futures::AsyncReadExt; use gpui::{ actions, @@ -21,6 +21,7 @@ use settings::Settings; use smallvec::SmallVec; use workspace::{ item::{Item, ItemHandle}, + searchable::{SearchableItem, SearchableItemHandle}, StatusItemView, Workspace, }; @@ -31,13 +32,15 @@ lazy_static! { std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string()); } -// TODO FEEDBACK: In the future, it would be nice to use this is some sort of live-rendering character counter thing -// Currently, we are just checking on submit that the the text exceeds the `start` value in this range const FEEDBACK_CHAR_COUNT_RANGE: Range = Range { - start: 5, + start: 10, end: 1000, }; +const FEEDBACK_PLACEHOLDER_TEXT: &str = "Thanks for spending time with Zed. Enter your feedback here in the form of Markdown. Save the tab to submit your feedback."; +const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = + "Feedback failed to submit, see error log for details."; + actions!(feedback, [SubmitFeedback, GiveFeedback, DeployFeedback]); pub fn init(cx: &mut MutableAppContext) { @@ -170,26 +173,21 @@ impl FeedbackEditor { buffer: ModelHandle, cx: &mut ViewContext, ) -> Self { - const FEDBACK_PLACEHOLDER_TEXT: &str = "Thanks for spending time with Zed. Enter your feedback here in the form of Markdown. Save the tab to submit your feedback."; - let editor = cx.add_view(|cx| { let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx); editor.set_vertical_scroll_margin(5, cx); - editor.set_placeholder_text(FEDBACK_PLACEHOLDER_TEXT, cx); + editor.set_placeholder_text(FEEDBACK_PLACEHOLDER_TEXT, cx); editor }); + cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone())) + .detach(); + let this = Self { editor, project }; this } fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { - // TODO FEEDBACK: This doesn't work like I expected it would - // let markdown_language = Arc::new(Language::new( - // LanguageConfig::default(), - // Some(tree_sitter_markdown::language()), - // )); - let markdown_language = project.read(cx).languages().get_language("Markdown"); let buffer = project @@ -206,7 +204,6 @@ impl FeedbackEditor { _: gpui::ModelHandle, cx: &mut ViewContext, ) -> Task> { - // TODO FEEDBACK: These don't look right let feedback_text_length = self.editor.read(cx).buffer().read(cx).len(cx); if feedback_text_length <= FEEDBACK_CHAR_COUNT_RANGE.start { @@ -223,35 +220,42 @@ impl FeedbackEditor { } let mut answer = cx.prompt( - PromptLevel::Warning, + PromptLevel::Info, "Ready to submit your feedback?", &["Yes, Submit!", "No"], ); let this = cx.handle(); + let client = cx.global::>().clone(); + let feedback_text = self.editor.read(cx).text(cx); + let specs = SystemSpecs::new(cx); + cx.spawn(|_, mut cx| async move { let answer = answer.recv().await; if answer == Some(0) { - cx.update(|cx| { - this.update(cx, |this, cx| match this.submit_feedback(cx) { - // TODO FEEDBACK - Ok(_) => { - // Close file after feedback sent successfully - // workspace - // .update(cx, |workspace, cx| { - // Pane::close_active_item(workspace, &Default::default(), cx) - // .unwrap() - // }) - // .await - // .unwrap(); - } - Err(error) => { - cx.prompt(PromptLevel::Critical, &error.to_string(), &["OK"]); - // Prompt that something failed (and to check the log for the exact error? and to try again?) - } - }) - }) + match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await { + Ok(_) => { + cx.update(|cx| { + this.update(cx, |_, cx| { + cx.dispatch_action(workspace::CloseActiveItem); + }) + }); + } + Err(error) => { + log::error!("{}", error); + + cx.update(|cx| { + this.update(cx, |_, cx| { + cx.prompt( + PromptLevel::Critical, + FEEDBACK_SUBMISSION_ERROR_TEXT, + &["OK"], + ); + }) + }); + } + } } }) .detach(); @@ -259,63 +263,38 @@ impl FeedbackEditor { Task::ready(Ok(())) } - fn submit_feedback(&mut self, cx: &mut ViewContext<'_, Self>) -> anyhow::Result<()> { - let feedback_text = self.editor.read(cx).text(cx); - let zed_client = cx.global::>(); - let system_specs = SystemSpecs::new(cx); + async fn submit_feedback( + feedback_text: &str, + zed_client: Arc, + system_specs: SystemSpecs, + ) -> anyhow::Result<()> { let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL); let metrics_id = zed_client.metrics_id(); let http_client = zed_client.http_client(); - // TODO FEEDBACK: how to get error out of the thread + let request = FeedbackRequestBody { + feedback_text: &feedback_text, + metrics_id, + system_specs, + token: ZED_SECRET_CLIENT_TOKEN, + }; - let this = cx.handle(); + let json_bytes = serde_json::to_vec(&request)?; - cx.spawn(|_, async_cx| { - async move { - let request = FeedbackRequestBody { - feedback_text: &feedback_text, - metrics_id, - system_specs, - token: ZED_SECRET_CLIENT_TOKEN, - }; + let request = Request::post(feedback_endpoint) + .header("content-type", "application/json") + .body(json_bytes.into())?; - let json_bytes = serde_json::to_vec(&request)?; + let mut response = http_client.send(request).await?; + let mut body = String::new(); + response.body_mut().read_to_string(&mut body).await?; - let request = Request::post(feedback_endpoint) - .header("content-type", "application/json") - .body(json_bytes.into())?; + let response_status = response.status(); - let mut response = http_client.send(request).await?; - let mut body = String::new(); - response.body_mut().read_to_string(&mut body).await?; - - let response_status = response.status(); - - if !response_status.is_success() { - bail!("Feedback API failed with: {}", response_status) - } - - this.read_with(&async_cx, |_this, _cx| -> anyhow::Result<()> { - bail!("Error") - })?; - - // TODO FEEDBACK: Use or remove - // Will need to handle error cases - // async_cx.update(|cx| { - // this.update(cx, |this, cx| { - // this.handle_error(error); - // cx.notify(); - // cx.dispatch_action(ShowErrorPopover); - // this.error_text = "Embedding failed" - // }) - // }); - - Ok(()) - } - }) - .detach(); + if !response_status.is_success() { + bail!("Feedback API failed with error: {}", response_status) + } Ok(()) } @@ -323,13 +302,9 @@ impl FeedbackEditor { impl FeedbackEditor { pub fn deploy(workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext) { - // if let Some(existing) = workspace.item_of_type::(cx) { - // workspace.activate_item(&existing, cx); - // } else { let feedback_editor = cx.add_view(|cx| FeedbackEditor::new(workspace.project().clone(), cx)); workspace.add_item(Box::new(feedback_editor), cx); - // } } } @@ -350,7 +325,7 @@ impl View for FeedbackEditor { } impl Entity for FeedbackEditor { - type Event = (); + type Event = editor::Event; } impl Item for FeedbackEditor { @@ -453,52 +428,59 @@ impl Item for FeedbackEditor { ) -> Task>> { unreachable!() } + + fn as_searchable(&self, handle: &ViewHandle) -> Option> { + Some(Box::new(handle.clone())) + } } -// impl SearchableItem for FeedbackEditor { -// type Match = ::Match; +impl SearchableItem for FeedbackEditor { + type Match = Range; -// fn to_search_event(event: &Self::Event) -> Option { -// Editor::to_search_event(event) -// } + fn to_search_event(event: &Self::Event) -> Option { + Editor::to_search_event(event) + } -// fn clear_matches(&mut self, cx: &mut ViewContext) { -// self. -// } + fn clear_matches(&mut self, cx: &mut ViewContext) { + self.editor + .update(cx, |editor, cx| editor.clear_matches(cx)) + } -// fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext) { -// todo!() -// } + fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + self.editor + .update(cx, |editor, cx| editor.update_matches(matches, cx)) + } -// fn query_suggestion(&mut self, cx: &mut ViewContext) -> String { -// todo!() -// } + fn query_suggestion(&mut self, cx: &mut ViewContext) -> String { + self.editor + .update(cx, |editor, cx| editor.query_suggestion(cx)) + } -// fn activate_match( -// &mut self, -// index: usize, -// matches: Vec, -// cx: &mut ViewContext, -// ) { -// todo!() -// } + fn activate_match( + &mut self, + index: usize, + matches: Vec, + cx: &mut ViewContext, + ) { + self.editor + .update(cx, |editor, cx| editor.activate_match(index, matches, cx)) + } -// fn find_matches( -// &mut self, -// query: project::search::SearchQuery, -// cx: &mut ViewContext, -// ) -> Task> { -// todo!() -// } + fn find_matches( + &mut self, + query: project::search::SearchQuery, + cx: &mut ViewContext, + ) -> Task> { + self.editor + .update(cx, |editor, cx| editor.find_matches(query, cx)) + } -// fn active_match_index( -// &mut self, -// matches: Vec, -// cx: &mut ViewContext, -// ) -> Option { -// todo!() -// } -// } - -// TODO FEEDBACK: search buffer? -// TODO FEEDBACK: warnings + fn active_match_index( + &mut self, + matches: Vec, + cx: &mut ViewContext, + ) -> Option { + self.editor + .update(cx, |editor, cx| editor.active_match_index(matches, cx)) + } +} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 167b57aaf2..4dca6e0de8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -36,7 +36,7 @@ pub use workspace; use workspace::{sidebar::SidebarSide, AppState, Workspace}; #[derive(Deserialize, Clone, PartialEq)] -struct OpenBrowser { +pub struct OpenBrowser { url: Arc, } From c118f9aabd6b226f9373232fcecea130ec6fdeb3 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 23 Jan 2023 01:31:02 -0500 Subject: [PATCH 100/108] Fix new errors after merge --- Cargo.lock | 1 - crates/feedback/Cargo.toml | 1 - crates/feedback/src/feedback_editor.rs | 20 ++++++++------------ crates/workspace/src/workspace.rs | 2 +- crates/zed/src/zed.rs | 3 +-- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be257b2425..f08a13902e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2040,7 +2040,6 @@ dependencies = [ "search", "serde", "settings", - "smallvec", "sysinfo", "theme", "tree-sitter-markdown", diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index ec5aa52a1a..ee6ec2f6ed 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -25,7 +25,6 @@ project = { path = "../project" } search = { path = "../search" } serde = { version = "1.0", features = ["derive", "rc"] } settings = { path = "../settings" } -smallvec = { version = "1.6", features = ["union"] } sysinfo = "0.27.1" theme = { path = "../theme" } tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 564ac76203..ca960a7c10 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -7,18 +7,18 @@ use futures::AsyncReadExt; use gpui::{ actions, elements::{ChildView, Flex, Label, MouseEventHandler, ParentElement, Stack, Text}, - serde_json, AnyViewHandle, CursorStyle, Element, ElementBox, Entity, ModelHandle, MouseButton, - MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, + serde_json, AnyViewHandle, AppContext, CursorStyle, Element, ElementBox, Entity, ModelHandle, + MouseButton, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, + ViewHandle, }; use isahc::Request; use language::Buffer; use postage::prelude::Stream; use lazy_static::lazy_static; -use project::{Project, ProjectEntryId, ProjectPath}; +use project::Project; use serde::Serialize; use settings::Settings; -use smallvec::SmallVec; use workspace::{ item::{Item, ItemHandle}, searchable::{SearchableItem, SearchableItemHandle}, @@ -345,18 +345,14 @@ impl Item for FeedbackEditor { .boxed() } + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.editor.for_each_project_item(cx, f) + } + fn to_item_events(_: &Self::Event) -> Vec { Vec::new() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - None - } - - fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> { - SmallVec::new() - } - fn is_singleton(&self, _: &gpui::AppContext) -> bool { true } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c1c90f2560..a212949511 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -99,7 +99,7 @@ actions!( ToggleRightSidebar, NewTerminal, NewSearch, - Feedback + Feedback, ShowNotif, ] ); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3b1d65c349..57219cf7aa 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -20,8 +20,7 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, ClipboardItem, PromptLevel, TitlebarOptions, ViewContext, - WindowKind, + AssetSource, AsyncAppContext, PromptLevel, TitlebarOptions, ViewContext, WindowKind, }; use language::Rope; use lazy_static::lazy_static; From 95b259b841898caf5f720b10e34115bb5b489bc8 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 23 Jan 2023 11:43:50 -0500 Subject: [PATCH 101/108] Avoid stomping on tab close icon's cursor style --- crates/workspace/src/pane.rs | 87 ++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 8d0d731551..f9124bfd83 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1150,7 +1150,7 @@ impl Pane { row.add_child({ enum Tab {} - dragged_item_receiver::(ix, ix, true, None, cx, { + let mut receiver = dragged_item_receiver::(ix, ix, true, None, cx, { let item = item.clone(); let pane = pane.clone(); let detail = detail.clone(); @@ -1162,50 +1162,51 @@ impl Pane { let hovered = mouse_state.hovered(); Self::render_tab(&item, pane, ix == 0, detail, hovered, tab_style, cx) } - }) - .with_cursor_style(if pane_active && tab_active { - CursorStyle::Arrow - } else { - CursorStyle::PointingHand - }) - .on_down(MouseButton::Left, move |_, cx| { - cx.dispatch_action(ActivateItem(ix)); - cx.propagate_event(); - }) - .on_click(MouseButton::Middle, { - let item = item.clone(); - let pane = pane.clone(); - move |_, cx: &mut EventContext| { - cx.dispatch_action(CloseItem { - item_id: item.id(), - pane: pane.clone(), - }) - } - }) - .as_draggable( - DraggedItem { - item, - pane: pane.clone(), - }, - { - let theme = cx.global::().theme.clone(); + }); - let detail = detail.clone(); - move |dragged_item, cx: &mut RenderContext| { - let tab_style = &theme.workspace.tab_bar.dragged_tab; - Self::render_tab( - &dragged_item.item, - dragged_item.pane.clone(), - false, - detail, - false, - &tab_style, - cx, - ) + if !pane_active || !tab_active { + receiver = receiver.with_cursor_style(CursorStyle::PointingHand); + } + + receiver + .on_down(MouseButton::Left, move |_, cx| { + cx.dispatch_action(ActivateItem(ix)); + cx.propagate_event(); + }) + .on_click(MouseButton::Middle, { + let item = item.clone(); + let pane = pane.clone(); + move |_, cx: &mut EventContext| { + cx.dispatch_action(CloseItem { + item_id: item.id(), + pane: pane.clone(), + }) } - }, - ) - .boxed() + }) + .as_draggable( + DraggedItem { + item, + pane: pane.clone(), + }, + { + let theme = cx.global::().theme.clone(); + + let detail = detail.clone(); + move |dragged_item, cx: &mut RenderContext| { + let tab_style = &theme.workspace.tab_bar.dragged_tab; + Self::render_tab( + &dragged_item.item, + dragged_item.pane.clone(), + false, + detail, + false, + &tab_style, + cx, + ) + } + }, + ) + .boxed() }) } From 4609be20deee44f61692dddd9900f3c05423a675 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 11 Jan 2023 17:52:18 -0800 Subject: [PATCH 102/108] WIP: Adding license compliance to CI --- licenses/test.tx | 1 + licenses/test.txt | 1 + script/collect-licenses.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 licenses/test.tx create mode 100644 licenses/test.txt create mode 100755 script/collect-licenses.js diff --git a/licenses/test.tx b/licenses/test.tx new file mode 100644 index 0000000000..9f27c62f59 --- /dev/null +++ b/licenses/test.tx @@ -0,0 +1 @@ +DATA DATA DATA \ No newline at end of file diff --git a/licenses/test.txt b/licenses/test.txt new file mode 100644 index 0000000000..b18c75c886 --- /dev/null +++ b/licenses/test.txt @@ -0,0 +1 @@ +Test test \ No newline at end of file diff --git a/script/collect-licenses.js b/script/collect-licenses.js new file mode 100755 index 0000000000..a0193a4899 --- /dev/null +++ b/script/collect-licenses.js @@ -0,0 +1,29 @@ +#!/usr/bin/env node + +const fs = require('fs'); + +const file_name = (path) => "./licenses/".concat(path); + +const writeFile = (path, data) => { + fs.writeFile(file_name(path), data, (err) => { + if (err) throw err; + console.log("Saved file") + }); +} + +main(); + +async function main() { + console.log("Here!"); + + writeFile("test.tx", "DATA DATA DATA") + + // Next steps: + // 1a. Add wiring in Zed to check for a licenses markdown file + // 1b. Add wiring in Zed.dev for builds to publish licenses alongside releases as well as licenses for Zed.dev itself + // 2. Figure out how to run those commands and get the license text for each MIT and Apache licensed software + // 3. Add in the configuration file: + // a. and refactor this script to have types of licenses + // b. add callback handlers for each type, + // c. check if the handler succeeds +} \ No newline at end of file From 9d5803206456f76abde64eb6d05ce3ac28202eda Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 13 Jan 2023 14:53:44 -0800 Subject: [PATCH 103/108] Add action to open licenses file --- assets/licenses.md | 1 + crates/workspace/src/workspace.rs | 1 + crates/zed/src/zed.rs | 25 +++++++++++++++++++++---- licenses/test.tx | 1 - licenses/test.txt | 1 - script/collect-licenses.js | 29 ----------------------------- script/collect-licenses.rs | 17 +++++++++++++++++ 7 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 assets/licenses.md delete mode 100644 licenses/test.tx delete mode 100644 licenses/test.txt delete mode 100755 script/collect-licenses.js create mode 100755 script/collect-licenses.rs diff --git a/assets/licenses.md b/assets/licenses.md new file mode 100644 index 0000000000..b4c705632b --- /dev/null +++ b/assets/licenses.md @@ -0,0 +1 @@ +TWSTS \ No newline at end of file diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8735f33fbe..e8af0d62fc 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -233,6 +233,7 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { workspace.toggle_sidebar(SidebarSide::Right, cx); }); cx.add_action(Workspace::activate_pane_at_index); + cx.add_action(Workspace::split_pane_with_item); cx.add_action(Workspace::split_pane_with_project_entry); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b6b00e7869..30f5bef53b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -62,6 +62,7 @@ actions!( DebugElements, OpenSettings, OpenLog, + OpenLicenses, OpenTelemetryLog, OpenKeymap, OpenDefaultSettings, @@ -184,6 +185,19 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { open_log_file(workspace, app_state.clone(), cx); } }); + cx.add_action({ + let app_state = app_state.clone(); + move |workspace: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext| { + open_bundled_file( + workspace, + app_state.clone(), + "licenses.md", + "Open Source License Attribution", + "Markdown", + cx, + ); + } + }); cx.add_action({ let app_state = app_state.clone(); move |workspace: &mut Workspace, _: &OpenTelemetryLog, cx: &mut ViewContext| { @@ -199,11 +213,12 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { cx.add_action({ let app_state = app_state.clone(); move |workspace: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext| { - open_bundled_config_file( + open_bundled_file( workspace, app_state.clone(), "keymaps/default.json", "Default Key Bindings", + "JSON", cx, ); } @@ -213,11 +228,12 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { move |workspace: &mut Workspace, _: &OpenDefaultSettings, cx: &mut ViewContext| { - open_bundled_config_file( + open_bundled_file( workspace, app_state.clone(), "settings/default.json", "Default Settings", + "JSON", cx, ); } @@ -656,11 +672,12 @@ fn open_telemetry_log_file( }).detach(); } -fn open_bundled_config_file( +fn open_bundled_file( workspace: &mut Workspace, app_state: Arc, asset_path: &'static str, title: &'static str, + language: &'static str, cx: &mut ViewContext, ) { workspace @@ -670,7 +687,7 @@ fn open_bundled_config_file( let text = Assets::get(asset_path).unwrap().data; let text = str::from_utf8(text.as_ref()).unwrap(); project - .create_buffer(text, project.languages().get_language("JSON"), cx) + .create_buffer(text, project.languages().get_language(language), cx) .expect("creating buffers on a local workspace always succeeds") }); let buffer = diff --git a/licenses/test.tx b/licenses/test.tx deleted file mode 100644 index 9f27c62f59..0000000000 --- a/licenses/test.tx +++ /dev/null @@ -1 +0,0 @@ -DATA DATA DATA \ No newline at end of file diff --git a/licenses/test.txt b/licenses/test.txt deleted file mode 100644 index b18c75c886..0000000000 --- a/licenses/test.txt +++ /dev/null @@ -1 +0,0 @@ -Test test \ No newline at end of file diff --git a/script/collect-licenses.js b/script/collect-licenses.js deleted file mode 100755 index a0193a4899..0000000000 --- a/script/collect-licenses.js +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); - -const file_name = (path) => "./licenses/".concat(path); - -const writeFile = (path, data) => { - fs.writeFile(file_name(path), data, (err) => { - if (err) throw err; - console.log("Saved file") - }); -} - -main(); - -async function main() { - console.log("Here!"); - - writeFile("test.tx", "DATA DATA DATA") - - // Next steps: - // 1a. Add wiring in Zed to check for a licenses markdown file - // 1b. Add wiring in Zed.dev for builds to publish licenses alongside releases as well as licenses for Zed.dev itself - // 2. Figure out how to run those commands and get the license text for each MIT and Apache licensed software - // 3. Add in the configuration file: - // a. and refactor this script to have types of licenses - // b. add callback handlers for each type, - // c. check if the handler succeeds -} \ No newline at end of file diff --git a/script/collect-licenses.rs b/script/collect-licenses.rs new file mode 100755 index 0000000000..2814feaa08 --- /dev/null +++ b/script/collect-licenses.rs @@ -0,0 +1,17 @@ +//usr/bin/env rustc $0 -o a.out && ./a.out ; rm -f ./a.out ; exit + +fn main() { + println!("Hello world"); + + +} + +// Next steps: +// 1a. Add wiring in Zed to check for a licenses markdown file +// 1b. Add wiring in Zed.dev for builds to publish licenses alongside releases as well as licenses for Zed.dev itself +// (e.g. https://github.com/zed-industries/zed.dev/tree/main/content/licenses) +// 2. Figure out how to run those commands and get the license text for each MIT and Apache licensed software +// 3. Add in the configuration file: +// a. and refactor this script to have types of licenses +// b. add callback handlers for each type, +// c. check if the handler succeeds From d060114f00c198f72f0e571464893cf55c100545 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 18 Jan 2023 12:28:02 -0800 Subject: [PATCH 104/108] Added complete scripts for generating third party license files --- assets/licenses.md | 2 +- crates/activity_indicator/Cargo.toml | 1 + crates/assets/Cargo.toml | 1 + crates/auto_update/Cargo.toml | 1 + crates/breadcrumbs/Cargo.toml | 1 + crates/call/Cargo.toml | 1 + crates/cli/Cargo.toml | 1 + crates/client/Cargo.toml | 1 + crates/clock/Cargo.toml | 1 + crates/collab/Cargo.toml | 1 + crates/collab_ui/Cargo.toml | 1 + crates/collections/Cargo.toml | 1 + crates/command_palette/Cargo.toml | 1 + crates/context_menu/Cargo.toml | 1 + crates/db/Cargo.toml | 1 + crates/diagnostics/Cargo.toml | 1 + crates/drag_and_drop/Cargo.toml | 1 + crates/editor/Cargo.toml | 1 + crates/file_finder/Cargo.toml | 1 + crates/fs/Cargo.toml | 1 + crates/fsevent/Cargo.toml | 1 + crates/fuzzy/Cargo.toml | 1 + crates/git/Cargo.toml | 1 + crates/go_to_line/Cargo.toml | 1 + crates/gpui/Cargo.toml | 1 + crates/gpui_macros/Cargo.toml | 1 + crates/journal/Cargo.toml | 1 + crates/language/Cargo.toml | 1 + crates/live_kit_client/Cargo.toml | 1 + crates/live_kit_server/Cargo.toml | 1 + crates/lsp/Cargo.toml | 1 + crates/media/Cargo.toml | 1 + crates/menu/Cargo.toml | 1 + crates/outline/Cargo.toml | 1 + crates/picker/Cargo.toml | 1 + crates/plugin/Cargo.toml | 1 + crates/plugin_macros/Cargo.toml | 1 + crates/plugin_runtime/Cargo.toml | 1 + crates/project/Cargo.toml | 1 + crates/project_panel/Cargo.toml | 1 + crates/project_symbols/Cargo.toml | 1 + crates/recent_projects/Cargo.toml | 1 + crates/rope/Cargo.toml | 1 + crates/rpc/Cargo.toml | 1 + crates/search/Cargo.toml | 1 + crates/settings/Cargo.toml | 1 + crates/snippet/Cargo.toml | 1 + crates/sqlez/Cargo.toml | 1 + crates/sqlez_macros/Cargo.toml | 1 + crates/sum_tree/Cargo.toml | 1 + crates/terminal/Cargo.toml | 1 + crates/terminal_view/Cargo.toml | 1 + crates/text/Cargo.toml | 1 + crates/theme/Cargo.toml | 1 + crates/theme_selector/Cargo.toml | 1 + crates/theme_testbench/Cargo.toml | 1 + crates/util/Cargo.toml | 1 + crates/vim/Cargo.toml | 1 + crates/workspace/Cargo.toml | 1 + crates/zed/Cargo.toml | 1 + script/collect-licenses.rs | 17 ------------- script/generate-licenses | 10 ++++++++ script/licenses/template.hbs.md | 27 ++++++++++++++++++++ script/licenses/zed-licenses.toml | 37 ++++++++++++++++++++++++++++ styles/src/themes/one-light.ts | 18 +++++++------- 65 files changed, 143 insertions(+), 27 deletions(-) delete mode 100755 script/collect-licenses.rs create mode 100755 script/generate-licenses create mode 100644 script/licenses/template.hbs.md create mode 100644 script/licenses/zed-licenses.toml diff --git a/assets/licenses.md b/assets/licenses.md index b4c705632b..5d70eab88b 100644 --- a/assets/licenses.md +++ b/assets/licenses.md @@ -1 +1 @@ -TWSTS \ No newline at end of file +Place holder \ No newline at end of file diff --git a/crates/activity_indicator/Cargo.toml b/crates/activity_indicator/Cargo.toml index 63998fa47b..78a4e752b2 100644 --- a/crates/activity_indicator/Cargo.toml +++ b/crates/activity_indicator/Cargo.toml @@ -2,6 +2,7 @@ name = "activity_indicator" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/activity_indicator.rs" diff --git a/crates/assets/Cargo.toml b/crates/assets/Cargo.toml index 71db57f320..ca96c92602 100644 --- a/crates/assets/Cargo.toml +++ b/crates/assets/Cargo.toml @@ -2,6 +2,7 @@ name = "assets" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/assets.rs" diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index b1ca061614..5f672e759f 100644 --- a/crates/auto_update/Cargo.toml +++ b/crates/auto_update/Cargo.toml @@ -2,6 +2,7 @@ name = "auto_update" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/auto_update.rs" diff --git a/crates/breadcrumbs/Cargo.toml b/crates/breadcrumbs/Cargo.toml index e5cae74e8f..99476fdc0a 100644 --- a/crates/breadcrumbs/Cargo.toml +++ b/crates/breadcrumbs/Cargo.toml @@ -2,6 +2,7 @@ name = "breadcrumbs" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/breadcrumbs.rs" diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index e6aa357bef..156925fb72 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -2,6 +2,7 @@ name = "call" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/call.rs" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index fafcc5ab68..f2bab22ea7 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -2,6 +2,7 @@ name = "cli" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/cli.rs" diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 74533fbc3b..347424d34e 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -2,6 +2,7 @@ name = "client" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/client.rs" diff --git a/crates/clock/Cargo.toml b/crates/clock/Cargo.toml index 8e17e15e5e..1705fdc6d5 100644 --- a/crates/clock/Cargo.toml +++ b/crates/clock/Cargo.toml @@ -2,6 +2,7 @@ name = "clock" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/clock.rs" diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 8c21af7273..456bcf6531 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -4,6 +4,7 @@ default-run = "collab" edition = "2021" name = "collab" version = "0.5.3" +publish = false [[bin]] name = "collab" diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index 20db066ce7..ac13e361fd 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -2,6 +2,7 @@ name = "collab_ui" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/collab_ui.rs" diff --git a/crates/collections/Cargo.toml b/crates/collections/Cargo.toml index 8e18cbd11d..dcbc642c4c 100644 --- a/crates/collections/Cargo.toml +++ b/crates/collections/Cargo.toml @@ -2,6 +2,7 @@ name = "collections" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/collections.rs" diff --git a/crates/command_palette/Cargo.toml b/crates/command_palette/Cargo.toml index 85f5b36ed6..555deff1ce 100644 --- a/crates/command_palette/Cargo.toml +++ b/crates/command_palette/Cargo.toml @@ -2,6 +2,7 @@ name = "command_palette" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/command_palette.rs" diff --git a/crates/context_menu/Cargo.toml b/crates/context_menu/Cargo.toml index 817893f43e..d764d4ddb8 100644 --- a/crates/context_menu/Cargo.toml +++ b/crates/context_menu/Cargo.toml @@ -2,6 +2,7 @@ name = "context_menu" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/context_menu.rs" diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index 8e12b06027..496e61b811 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -2,6 +2,7 @@ name = "db" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/db.rs" diff --git a/crates/diagnostics/Cargo.toml b/crates/diagnostics/Cargo.toml index 616f69117f..ebb57e0636 100644 --- a/crates/diagnostics/Cargo.toml +++ b/crates/diagnostics/Cargo.toml @@ -2,6 +2,7 @@ name = "diagnostics" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/diagnostics.rs" diff --git a/crates/drag_and_drop/Cargo.toml b/crates/drag_and_drop/Cargo.toml index 4ab54ad8e6..e378a5bc31 100644 --- a/crates/drag_and_drop/Cargo.toml +++ b/crates/drag_and_drop/Cargo.toml @@ -2,6 +2,7 @@ name = "drag_and_drop" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/drag_and_drop.rs" diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index f992ed5116..26dd371041 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -2,6 +2,7 @@ name = "editor" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/editor.rs" diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index 6fd792940d..1d1a4dfb1b 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -2,6 +2,7 @@ name = "file_finder" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/file_finder.rs" diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 5b9082d114..cd6c8f969c 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -2,6 +2,7 @@ name = "fs" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/fs.rs" diff --git a/crates/fsevent/Cargo.toml b/crates/fsevent/Cargo.toml index 3bf7ae885b..e71e247bf2 100644 --- a/crates/fsevent/Cargo.toml +++ b/crates/fsevent/Cargo.toml @@ -3,6 +3,7 @@ name = "fsevent" version = "2.0.2" license = "MIT" edition = "2021" +publish = false [lib] path = "src/fsevent.rs" diff --git a/crates/fuzzy/Cargo.toml b/crates/fuzzy/Cargo.toml index e36c22055a..553c0497a5 100644 --- a/crates/fuzzy/Cargo.toml +++ b/crates/fuzzy/Cargo.toml @@ -2,6 +2,7 @@ name = "fuzzy" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/fuzzy.rs" diff --git a/crates/git/Cargo.toml b/crates/git/Cargo.toml index 66202a489a..3e88d72313 100644 --- a/crates/git/Cargo.toml +++ b/crates/git/Cargo.toml @@ -2,6 +2,7 @@ name = "git" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/git.rs" diff --git a/crates/go_to_line/Cargo.toml b/crates/go_to_line/Cargo.toml index 93ae96f93e..def6361dc2 100644 --- a/crates/go_to_line/Cargo.toml +++ b/crates/go_to_line/Cargo.toml @@ -2,6 +2,7 @@ name = "go_to_line" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/go_to_line.rs" diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 5153c1f7c1..e1b6e11b46 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -4,6 +4,7 @@ edition = "2021" name = "gpui" version = "0.1.0" description = "A GPU-accelerated UI framework" +publish = false [lib] path = "src/gpui.rs" diff --git a/crates/gpui_macros/Cargo.toml b/crates/gpui_macros/Cargo.toml index e35e0b1d2b..76daeae2a8 100644 --- a/crates/gpui_macros/Cargo.toml +++ b/crates/gpui_macros/Cargo.toml @@ -2,6 +2,7 @@ name = "gpui_macros" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/gpui_macros.rs" diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml index 9622049a9c..b532397dd1 100644 --- a/crates/journal/Cargo.toml +++ b/crates/journal/Cargo.toml @@ -2,6 +2,7 @@ name = "journal" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/journal.rs" diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 6c074a2d75..ab6c687b7a 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -2,6 +2,7 @@ name = "language" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/language.rs" diff --git a/crates/live_kit_client/Cargo.toml b/crates/live_kit_client/Cargo.toml index d0f54782b9..0145737508 100644 --- a/crates/live_kit_client/Cargo.toml +++ b/crates/live_kit_client/Cargo.toml @@ -3,6 +3,7 @@ name = "live_kit_client" version = "0.1.0" edition = "2021" description = "Bindings to LiveKit Swift client SDK" +publish = false [lib] path = "src/live_kit_client.rs" diff --git a/crates/live_kit_server/Cargo.toml b/crates/live_kit_server/Cargo.toml index 64267f62d1..17ee3cd62e 100644 --- a/crates/live_kit_server/Cargo.toml +++ b/crates/live_kit_server/Cargo.toml @@ -3,6 +3,7 @@ name = "live_kit_server" version = "0.1.0" edition = "2021" description = "SDK for the LiveKit server API" +publish = false [lib] path = "src/live_kit_server.rs" diff --git a/crates/lsp/Cargo.toml b/crates/lsp/Cargo.toml index 94780a9472..eb6e02aac9 100644 --- a/crates/lsp/Cargo.toml +++ b/crates/lsp/Cargo.toml @@ -2,6 +2,7 @@ name = "lsp" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/lsp.rs" diff --git a/crates/media/Cargo.toml b/crates/media/Cargo.toml index aad2b74c02..4c230819e2 100644 --- a/crates/media/Cargo.toml +++ b/crates/media/Cargo.toml @@ -2,6 +2,7 @@ name = "media" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/media.rs" diff --git a/crates/menu/Cargo.toml b/crates/menu/Cargo.toml index cdcacd4416..c473df7ef0 100644 --- a/crates/menu/Cargo.toml +++ b/crates/menu/Cargo.toml @@ -2,6 +2,7 @@ name = "menu" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/menu.rs" diff --git a/crates/outline/Cargo.toml b/crates/outline/Cargo.toml index 5b4751e620..661c84c8cd 100644 --- a/crates/outline/Cargo.toml +++ b/crates/outline/Cargo.toml @@ -2,6 +2,7 @@ name = "outline" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/outline.rs" diff --git a/crates/picker/Cargo.toml b/crates/picker/Cargo.toml index 6438697910..e7a8079caa 100644 --- a/crates/picker/Cargo.toml +++ b/crates/picker/Cargo.toml @@ -2,6 +2,7 @@ name = "picker" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/picker.rs" diff --git a/crates/plugin/Cargo.toml b/crates/plugin/Cargo.toml index 6f37c458d9..7bf5510465 100644 --- a/crates/plugin/Cargo.toml +++ b/crates/plugin/Cargo.toml @@ -2,6 +2,7 @@ name = "plugin" version = "0.1.0" edition = "2021" +publish = false [dependencies] serde = "1.0" diff --git a/crates/plugin_macros/Cargo.toml b/crates/plugin_macros/Cargo.toml index b0a2c4e09b..32bfc6a01a 100644 --- a/crates/plugin_macros/Cargo.toml +++ b/crates/plugin_macros/Cargo.toml @@ -2,6 +2,7 @@ name = "plugin_macros" version = "0.1.0" edition = "2021" +publish = false [lib] proc-macro = true diff --git a/crates/plugin_runtime/Cargo.toml b/crates/plugin_runtime/Cargo.toml index a8c0a063a8..b5cfb9514f 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -2,6 +2,7 @@ name = "plugin_runtime" version = "0.1.0" edition = "2021" +publish = false [dependencies] wasmtime = "0.38" diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 6d6560ea38..949dbdf916 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -2,6 +2,7 @@ name = "project" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/project.rs" diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index 8704f57c8c..cc27f40954 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -2,6 +2,7 @@ name = "project_panel" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/project_panel.rs" diff --git a/crates/project_symbols/Cargo.toml b/crates/project_symbols/Cargo.toml index a426e2e0d4..e9283b14c9 100644 --- a/crates/project_symbols/Cargo.toml +++ b/crates/project_symbols/Cargo.toml @@ -2,6 +2,7 @@ name = "project_symbols" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/project_symbols.rs" diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index d633381365..037c6fd4fb 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -2,6 +2,7 @@ name = "recent_projects" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/recent_projects.rs" diff --git a/crates/rope/Cargo.toml b/crates/rope/Cargo.toml index e9ddcc8195..8257ef4a6e 100644 --- a/crates/rope/Cargo.toml +++ b/crates/rope/Cargo.toml @@ -2,6 +2,7 @@ name = "rope" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/rope.rs" diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index cd959e75a1..25c2ce1ca7 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -3,6 +3,7 @@ description = "Shared logic for communication between the Zed app and the zed.de edition = "2021" name = "rpc" version = "0.1.0" +publish = false [lib] path = "src/rpc.rs" diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 3a5d9468fc..f36865a89b 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -2,6 +2,7 @@ name = "search" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/search.rs" diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index a292358e75..c1ec2a6210 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -2,6 +2,7 @@ name = "settings" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/settings.rs" diff --git a/crates/snippet/Cargo.toml b/crates/snippet/Cargo.toml index 03a0f8314a..429f5d416e 100644 --- a/crates/snippet/Cargo.toml +++ b/crates/snippet/Cargo.toml @@ -2,6 +2,7 @@ name = "snippet" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/snippet.rs" diff --git a/crates/sqlez/Cargo.toml b/crates/sqlez/Cargo.toml index 78bf83dc30..8409a1dff5 100644 --- a/crates/sqlez/Cargo.toml +++ b/crates/sqlez/Cargo.toml @@ -2,6 +2,7 @@ name = "sqlez" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/sqlez_macros/Cargo.toml b/crates/sqlez_macros/Cargo.toml index 423b494500..8d650074c3 100644 --- a/crates/sqlez_macros/Cargo.toml +++ b/crates/sqlez_macros/Cargo.toml @@ -2,6 +2,7 @@ name = "sqlez_macros" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/sqlez_macros.rs" diff --git a/crates/sum_tree/Cargo.toml b/crates/sum_tree/Cargo.toml index 02cad4fb9d..3aab16cc3e 100644 --- a/crates/sum_tree/Cargo.toml +++ b/crates/sum_tree/Cargo.toml @@ -2,6 +2,7 @@ name = "sum_tree" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/sum_tree.rs" diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 0dea7bfbcf..c6f33a0fc7 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -2,6 +2,7 @@ name = "terminal" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/terminal.rs" diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index 05fda2c75f..f12e4be03b 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -2,6 +2,7 @@ name = "terminal_view" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/terminal_view.rs" diff --git a/crates/text/Cargo.toml b/crates/text/Cargo.toml index ad960ec93e..5fda4b613c 100644 --- a/crates/text/Cargo.toml +++ b/crates/text/Cargo.toml @@ -2,6 +2,7 @@ name = "text" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/text.rs" diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 36de158afe..1e9883860f 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -2,6 +2,7 @@ name = "theme" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/theme.rs" diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index 59cb5fbc2c..8f6fc74600 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -2,6 +2,7 @@ name = "theme_selector" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/theme_selector.rs" diff --git a/crates/theme_testbench/Cargo.toml b/crates/theme_testbench/Cargo.toml index 5fb263501f..763727fc68 100644 --- a/crates/theme_testbench/Cargo.toml +++ b/crates/theme_testbench/Cargo.toml @@ -2,6 +2,7 @@ name = "theme_testbench" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/theme_testbench.rs" diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 0a0bacf53c..4cbaa382e8 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -2,6 +2,7 @@ name = "util" version = "0.1.0" edition = "2021" +publish = false [lib] doctest = false diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index daefebdbdd..bd94e48b9e 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -2,6 +2,7 @@ name = "vim" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/vim.rs" diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 5894a2a44e..60680f82a2 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -2,6 +2,7 @@ name = "workspace" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/workspace.rs" diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 63719b7003..f2e82bd63f 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -4,6 +4,7 @@ description = "The fast, collaborative code editor." edition = "2021" name = "zed" version = "0.71.0" +publish = false [lib] name = "zed" diff --git a/script/collect-licenses.rs b/script/collect-licenses.rs deleted file mode 100755 index 2814feaa08..0000000000 --- a/script/collect-licenses.rs +++ /dev/null @@ -1,17 +0,0 @@ -//usr/bin/env rustc $0 -o a.out && ./a.out ; rm -f ./a.out ; exit - -fn main() { - println!("Hello world"); - - -} - -// Next steps: -// 1a. Add wiring in Zed to check for a licenses markdown file -// 1b. Add wiring in Zed.dev for builds to publish licenses alongside releases as well as licenses for Zed.dev itself -// (e.g. https://github.com/zed-industries/zed.dev/tree/main/content/licenses) -// 2. Figure out how to run those commands and get the license text for each MIT and Apache licensed software -// 3. Add in the configuration file: -// a. and refactor this script to have types of licenses -// b. add callback handlers for each type, -// c. check if the handler succeeds diff --git a/script/generate-licenses b/script/generate-licenses new file mode 100755 index 0000000000..31febf263f --- /dev/null +++ b/script/generate-licenses @@ -0,0 +1,10 @@ +#!/bin/bash +cargo about generate --workspace -o assets/licenses.md -c script/licenses/zed-licenses.toml script/licenses/template.hbs.md + +# cargo about automatically html-escapes all output, so we need to undo it here: +sed -i '' 's/"/"/g' assets/licenses.md +sed -i '' 's/'/'\''/g' assets/licenses.md # `'\''` ends the string, appends a single quote, and re-opens the string +sed -i '' 's/=/=/g' assets/licenses.md +sed -i '' 's/`/`/g' assets/licenses.md +sed -i '' 's/<//g' assets/licenses.md diff --git a/script/licenses/template.hbs.md b/script/licenses/template.hbs.md new file mode 100644 index 0000000000..a51b714dae --- /dev/null +++ b/script/licenses/template.hbs.md @@ -0,0 +1,27 @@ +# Third Party Licenses + +This page lists the licenses of the projects used in Zed. + +## Overview of licenses: + +{{#each overview}} +* {{name}} ({{count}}) +{{/each}} + +## All license texts: + +{{#each licenses}} + +### {{name}} + +#### Used by: + +{{#each used_by}} +* [{{crate.name}} {{crate.version}}]({{#if crate.repository}} {{crate.repository}} {{else}} https://crates.io/crates/{{crate.name}} {{/if}}) +{{/each}} + +{{text}} + +-------------------------------------------------------------------------------- + +{{/each}} \ No newline at end of file diff --git a/script/licenses/zed-licenses.toml b/script/licenses/zed-licenses.toml new file mode 100644 index 0000000000..2e294878d7 --- /dev/null +++ b/script/licenses/zed-licenses.toml @@ -0,0 +1,37 @@ +no-clearly-defined = true +private = { ignore = true } +accepted = [ + "Apache-2.0", + "MIT", + "Apache-2.0 WITH LLVM-exception", + "MPL-2.0", + "BSD-3-Clause", + "BSD-2-Clause", + "ISC", + "CC0-1.0", + "Unicode-DFS-2016", + "OpenSSL", + "Zlib", +] +workarounds = [ + "ring", + "wasmtime", +] + +[procinfo.clarify] +license = "MIT" +[[procinfo.clarify.git]] +path = 'LICENSE.md' +checksum = '37db33bbbd7348969eda397b89a16f252d56c1ca7481b6ccaf56ccdcbab5dcca' + +[webpki.clarify] +license = "ISC" # It actually says 'ISC-style' but I don't know the SPDX expression for that. +[[webpki.clarify.files]] +path = 'LICENSE' +checksum = '5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a' + +[fuchsia-cprng.clarify] +license = "BSD-3-Clause" +[[fuchsia-cprng.clarify.files]] +path = 'LICENSE' +checksum = '03b114f53e6587a398931762ee11e2395bfdba252a329940e2c8c9e81813845b' \ No newline at end of file diff --git a/styles/src/themes/one-light.ts b/styles/src/themes/one-light.ts index d8c8e5272c..a5ac1f7158 100644 --- a/styles/src/themes/one-light.ts +++ b/styles/src/themes/one-light.ts @@ -11,15 +11,15 @@ const license = { export const light = createColorScheme(`${name}`, true, { neutral: chroma.scale([ - "#090a0b", - "#202227", - "#383a42", - "#696c77", - "#a0a1a7", - "#e5e5e6", - "#f0f0f1", - "#fafafa", - ]) + "#090a0b", + "#202227", + "#383a42", + "#696c77", + "#a0a1a7", + "#e5e5e6", + "#f0f0f1", + "#fafafa", + ]) .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]), red: colorRamp(chroma("#ca1243")), From 0f0d5d5726cae330238a545e26c7910f470373a0 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 23 Jan 2023 12:43:19 -0800 Subject: [PATCH 105/108] Added cargo-about auto-install and CI steps --- .github/workflows/ci.yml | 10 ++++++++-- .gitignore | 1 + assets/licenses.md | 1 - crates/zed/src/zed.rs | 6 ++++-- script/generate-licenses | 9 +++++++-- script/licenses/zed-licenses.toml | 2 +- 6 files changed, 21 insertions(+), 8 deletions(-) delete mode 100644 assets/licenses.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7072a3fe94..fce5717ca9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,16 +41,19 @@ jobs: with: clean: false submodules: 'recursive' - + - name: Run tests run: cargo test --workspace --no-fail-fast - + - name: Build collab run: cargo build -p collab - name: Build other binaries run: cargo build --workspace --bins --all-features + - name: Generate license file + run: script/generate-licenses + bundle: name: Bundle app runs-on: @@ -109,6 +112,9 @@ jobs: exit 1 fi + - name: Generate license file + run: script/generate-licenses + - name: Create app bundle run: script/bundle diff --git a/.gitignore b/.gitignore index 356f4d97cd..8bca2eafac 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /assets/themes/*.json /assets/themes/Internal/*.json /assets/themes/Experiments/*.json +/assets/licenses.md **/venv .build Packages diff --git a/assets/licenses.md b/assets/licenses.md deleted file mode 100644 index 5d70eab88b..0000000000 --- a/assets/licenses.md +++ /dev/null @@ -1 +0,0 @@ -Place holder \ No newline at end of file diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 30f5bef53b..53bc9d5abc 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -35,7 +35,7 @@ use search::{BufferSearchBar, ProjectSearchBar}; use serde::Deserialize; use serde_json::to_string_pretty; use settings::{keymap_file_json_schema, settings_file_json_schema, Settings}; -use std::{env, path::Path, str, sync::Arc}; +use std::{borrow::Cow, env, path::Path, str, sync::Arc}; use system_specs::SystemSpecs; use util::{channel::ReleaseChannel, paths, ResultExt}; pub use workspace; @@ -684,7 +684,9 @@ fn open_bundled_file( .with_local_workspace(&app_state, cx, |workspace, cx| { let project = workspace.project().clone(); let buffer = project.update(cx, |project, cx| { - let text = Assets::get(asset_path).unwrap().data; + let text = Assets::get(asset_path) + .map(|f| f.data) + .unwrap_or_else(|| Cow::Borrowed(b"File not found")); let text = str::from_utf8(text.as_ref()).unwrap(); project .create_buffer(text, project.languages().get_language(language), cx) diff --git a/script/generate-licenses b/script/generate-licenses index 31febf263f..e1a917292c 100755 --- a/script/generate-licenses +++ b/script/generate-licenses @@ -1,5 +1,10 @@ #!/bin/bash -cargo about generate --workspace -o assets/licenses.md -c script/licenses/zed-licenses.toml script/licenses/template.hbs.md + +set -e + +[[ "$(cargo about --version)" == "cargo-about 0.5.2" ]] || cargo install cargo-about --locked --git https://github.com/zed-industries/cargo-about --branch error-code-on-warn + +cargo about generate --fail-on-missing-license -o assets/licenses.md -c script/licenses/zed-licenses.toml script/licenses/template.hbs.md # cargo about automatically html-escapes all output, so we need to undo it here: sed -i '' 's/"/"/g' assets/licenses.md @@ -7,4 +12,4 @@ sed -i '' 's/'/'\''/g' assets/licenses.md # `'\''` ends the string, appends sed -i '' 's/=/=/g' assets/licenses.md sed -i '' 's/`/`/g' assets/licenses.md sed -i '' 's/<//g' assets/licenses.md +sed -i '' 's/>/>/g' assets/licenses.md \ No newline at end of file diff --git a/script/licenses/zed-licenses.toml b/script/licenses/zed-licenses.toml index 2e294878d7..d338e7ab0b 100644 --- a/script/licenses/zed-licenses.toml +++ b/script/licenses/zed-licenses.toml @@ -34,4 +34,4 @@ checksum = '5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a' license = "BSD-3-Clause" [[fuchsia-cprng.clarify.files]] path = 'LICENSE' -checksum = '03b114f53e6587a398931762ee11e2395bfdba252a329940e2c8c9e81813845b' \ No newline at end of file +checksum = '03b114f53e6587a398931762ee11e2395bfdba252a329940e2c8c9e81813845b' From c9299a49e16d904cb268f7b154c82d7346b6f9e0 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 23 Jan 2023 18:19:10 -0500 Subject: [PATCH 106/108] Clean out unused code --- crates/feedback/src/feedback_editor.rs | 67 +------------------------- crates/theme/src/theme.rs | 15 ------ crates/workspace/src/pane.rs | 8 +-- styles/src/styleTree/app.ts | 2 - styles/src/styleTree/feedback.ts | 35 -------------- 5 files changed, 2 insertions(+), 125 deletions(-) delete mode 100644 styles/src/styleTree/feedback.ts diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index ca960a7c10..8185fbad9a 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -37,7 +37,7 @@ const FEEDBACK_CHAR_COUNT_RANGE: Range = Range { end: 1000, }; -const FEEDBACK_PLACEHOLDER_TEXT: &str = "Thanks for spending time with Zed. Enter your feedback here in the form of Markdown. Save the tab to submit your feedback."; +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_SUBMISSION_ERROR_TEXT: &str = "Feedback failed to submit, see error log for details."; @@ -77,71 +77,6 @@ impl View for FeedbackButton { ) .boxed() } - - fn focus_in(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) {} - - fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) {} - - fn key_down(&mut self, _: &gpui::KeyDownEvent, _: &mut ViewContext) -> bool { - false - } - - fn key_up(&mut self, _: &gpui::KeyUpEvent, _: &mut ViewContext) -> bool { - false - } - - fn modifiers_changed( - &mut self, - _: &gpui::ModifiersChangedEvent, - _: &mut ViewContext, - ) -> bool { - false - } - - fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap_matcher::KeymapContext { - Self::default_keymap_context() - } - - fn default_keymap_context() -> gpui::keymap_matcher::KeymapContext { - let mut cx = gpui::keymap_matcher::KeymapContext::default(); - cx.set.insert(Self::ui_name().into()); - cx - } - - fn debug_json(&self, _: &gpui::AppContext) -> gpui::serde_json::Value { - gpui::serde_json::Value::Null - } - - fn text_for_range(&self, _: Range, _: &gpui::AppContext) -> Option { - None - } - - fn selected_text_range(&self, _: &gpui::AppContext) -> Option> { - None - } - - fn marked_text_range(&self, _: &gpui::AppContext) -> Option> { - None - } - - fn unmark_text(&mut self, _: &mut ViewContext) {} - - fn replace_text_in_range( - &mut self, - _: Option>, - _: &str, - _: &mut ViewContext, - ) { - } - - fn replace_and_mark_text_in_range( - &mut self, - _: Option>, - _: &str, - _: Option>, - _: &mut ViewContext, - ) { - } } impl StatusItemView for FeedbackButton { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ed8fb1bb12..e463310b98 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -25,7 +25,6 @@ pub struct Theme { pub command_palette: CommandPalette, pub picker: Picker, pub editor: Editor, - pub feedback: Feedback, pub search: Search, pub project_diagnostics: ProjectDiagnostics, pub breadcrumbs: ContainedText, @@ -120,20 +119,6 @@ pub struct ContactList { pub calling_indicator: ContainedText, } -#[derive(Deserialize, Default)] -pub struct Feedback { - // pub feedback_popover: FeedbackPopover, - pub feedback_editor: FieldEditor, -} - -// #[derive(Deserialize, Default)] -// pub struct FeedbackPopover { -// #[serde(flatten)] -// pub container: ContainerStyle, -// pub height: f32, -// pub width: f32, -// } - #[derive(Deserialize, Default)] pub struct ProjectRow { #[serde(flatten)] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 863a036e8e..2d0222ef3a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -95,11 +95,6 @@ pub struct DeployNewMenu { position: Vector2F, } -#[derive(Clone, PartialEq)] -pub struct DeployFeedbackModal { - position: Vector2F, -} - impl_actions!(pane, [GoBack, GoForward, ActivateItem]); impl_internal_actions!( pane, @@ -108,8 +103,7 @@ impl_internal_actions!( DeploySplitMenu, DeployNewMenu, DeployDockMenu, - MoveItem, - DeployFeedbackModal + MoveItem ] ); diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index a51426b6f6..267d830506 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -19,7 +19,6 @@ import terminal from "./terminal"; import contactList from "./contactList"; import incomingCallNotification from "./incomingCallNotification"; import { ColorScheme } from "../themes/common/colorScheme"; -import feedback from "./feedback"; export default function app(colorScheme: ColorScheme): Object { return { @@ -38,7 +37,6 @@ export default function app(colorScheme: ColorScheme): Object { projectDiagnostics: projectDiagnostics(colorScheme), projectPanel: projectPanel(colorScheme), contactsPopover: contactsPopover(colorScheme), - feedback: feedback(colorScheme), contactFinder: contactFinder(colorScheme), contactList: contactList(colorScheme), search: search(colorScheme), diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts deleted file mode 100644 index 0521949871..0000000000 --- a/styles/src/styleTree/feedback.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, border, text } from "./components"; - -export default function feedback(colorScheme: ColorScheme) { - let layer = colorScheme.middle; - return { - feedbackEditor: { - background: background(layer, "on"), - cornerRadius: 6, - text: text(layer, "mono", "on"), - placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }), - selection: colorScheme.players[0], - border: border(layer, "on"), - padding: { - bottom: 4, - left: 8, - right: 8, - top: 4, - }, - margin: { - left: 6, - } - }, - feedbackPopover: { - background: background(layer), - cornerRadius: 6, - padding: { top: 6 }, - margin: { top: -6 }, - shadow: colorScheme.popoverShadow, - border: border(layer), - width: 500, - height: 400 - } - } -} From 2b3d09f70af6e7b1161276ea77d32f1e8fd3b9c0 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 23 Jan 2023 18:34:10 -0500 Subject: [PATCH 107/108] Fix CI missing license check --- crates/feedback/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index ee6ec2f6ed..b224f91a2f 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -2,6 +2,7 @@ name = "feedback" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/feedback.rs" From 96ffe84edbd5259a00acc7fb7316c70ea5028434 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 23 Jan 2023 21:51:10 -0700 Subject: [PATCH 108/108] Document Buffer::reparse --- crates/language/src/buffer.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 110e10564c..3c382b83b6 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -802,6 +802,29 @@ impl Buffer { self.sync_parse_timeout = timeout; } + /// Called after an edit to synchronize the buffer's main parse tree with + /// the buffer's new underlying state. + /// + /// Locks the syntax map and interpolates the edits since the last reparse + /// into the foreground syntax tree. + /// + /// Then takes a stable snapshot of the syntax map before unlocking it. + /// The snapshot with the interpolated edits is sent to a background thread, + /// where we ask Tree-sitter to perform an incremental parse. + /// + /// Meanwhile, in the foreground, we block the main thread for up to 1ms + /// waiting on the parse to complete. As soon as it completes, we proceed + /// synchronously, unless a 1ms timeout elapses. + /// + /// If we time out waiting on the parse, we spawn a second task waiting + /// until the parse does complete and return with the interpolated tree still + /// in the foreground. When the background parse completes, call back into + /// the main thread and assign the foreground parse state. + /// + /// If the buffer or grammar changed since the start of the background parse, + /// initiate an additional reparse recursively. To avoid concurrent parses + /// for the same buffer, we only initiate a new parse if we are not already + /// parsing in the background. fn reparse(&mut self, cx: &mut ModelContext) { if self.parsing_in_background { return;