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 d8ee49866b404d4d1d12efbd540b467096485d81..f63d44ee674c136f0461169aedf9b480e9bb8129 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 bf6cb57adb3ce6378fed2bc5db3f4ed2d8b22962..3ce8894238fd9d2ad86aa57cbd6ef6e95c493b70 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 618c650e02a538b53a70b0bd3083847f4d1e0425..5456ecaf60b90976395ae3fe413f2b45d6f6d714 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 55597312aea4a15876ed7c39ec2a8558522e6d4b..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..52397562f9e68b0943a5ad20177cd46638e296d5 --- /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 5ac7cb36b6db6ce66f7b6efbbc21e716cf4f9268..90527af555bda0a890991ccdaeac5e5f41befeda 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 b6c2c0fcba70347815c239b2adf052dea951e2b3..fc55f76f3574a88ead6282bc93ad7e129e094eee 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 e6d50f43af87966edbb2788008ca770c7c41aaa9..527da286d2938744c8fa094371d93dfab95c9a10 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 52397562f9e68b0943a5ad20177cd46638e296d5..e0721078a05d1872b2e38d16d01cb6a36e3425bf 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 e0721078a05d1872b2e38d16d01cb6a36e3425bf..556fb5922c9a510b41c6b8f71bcbff119ff7527e 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 556fb5922c9a510b41c6b8f71bcbff119ff7527e..46e7116e39d5d8399c020c086bcebeedc4f6d993 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 | 30 ++++++++--------- 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, 80 insertions(+), 51 deletions(-) create mode 100644 styles/src/styleTree/feedback.ts diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 6d9ec305b697981a6c1c4f8492655db4d9eb6757..49ac0062f8d8ae252133985b9c520ea134c011ae 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 ce8b713996be0c3ce993cb7b2473bd874a8583b0..4e32e020eb31bcedc915d460c5f10fbe60fa27f5 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 3ce8894238fd9d2ad86aa57cbd6ef6e95c493b70..d3f0b0324582ca50d2c3162a011b0c3a8e0af604 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 FeedbackBox { -// pub feedback_editor: FieldEditor, -// } +#[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 { diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs index 46e7116e39d5d8399c020c086bcebeedc4f6d993..b11ab1f4acf0610c5dfd04c997cb7128ffc88679 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 90527af555bda0a890991ccdaeac5e5f41befeda..ce68895768a6a4834d2550d67d72f149132c5843 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 267d83050667ccb130a8f0c4b20cf37574aaf2d7..a51426b6f642753db05959d49c3446f3d02dfc68 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 0000000000000000000000000000000000000000..05219498718289a84174ebf3fc9ad8f82ec6e697 --- /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 76092bbecfe793a055496f996c9873a8eaa7b426..67f17bc8f9dc5f302ee1432c00b99b2be8ea8093 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 e463310b9810eb2d6f30e924b1b66166c9b2e295..92dd68ac1b5c5a699b7d591fbfff4d098f434b1d 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 618c650e02a538b53a70b0bd3083847f4d1e0425..5456ecaf60b90976395ae3fe413f2b45d6f6d714 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 55597312aea4a15876ed7c39ec2a8558522e6d4b..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..52397562f9e68b0943a5ad20177cd46638e296d5 --- /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 f4fa919c48e5b2e2640adcde991af6a1447904c7..99b4c5a1a3e42d9790eb1c9f366fd73b1259fec0 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 b6c2c0fcba70347815c239b2adf052dea951e2b3..fc55f76f3574a88ead6282bc93ad7e129e094eee 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 c3af91306dce4b1ba79bf46f52fdce79daa34cff..9fda1491911cacc7d2eeaaa2ac0ade7db6e88149 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 52397562f9e68b0943a5ad20177cd46638e296d5..e0721078a05d1872b2e38d16d01cb6a36e3425bf 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 e0721078a05d1872b2e38d16d01cb6a36e3425bf..556fb5922c9a510b41c6b8f71bcbff119ff7527e 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 556fb5922c9a510b41c6b8f71bcbff119ff7527e..46e7116e39d5d8399c020c086bcebeedc4f6d993 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 | 30 ++++++++--------- 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, 80 insertions(+), 51 deletions(-) create mode 100644 styles/src/styleTree/feedback.ts diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index aa46d64fcb250d30ac4c41c97497bccd40b7ad74..69cdb16116ad1a15960080aebe9a2c5827f2ccb9 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 ce8b713996be0c3ce993cb7b2473bd874a8583b0..4e32e020eb31bcedc915d460c5f10fbe60fa27f5 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 92dd68ac1b5c5a699b7d591fbfff4d098f434b1d..3b118bbef94d23555c8339a3f8b69251488ef997 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 FeedbackBox { -// pub feedback_editor: FieldEditor, -// } +#[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 { diff --git a/crates/zed/src/feedback_popover.rs b/crates/zed/src/feedback_popover.rs index 46e7116e39d5d8399c020c086bcebeedc4f6d993..b11ab1f4acf0610c5dfd04c997cb7128ffc88679 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 99b4c5a1a3e42d9790eb1c9f366fd73b1259fec0..fe06ca2893f40f619a049dcca9f00efecee96c9f 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 267d83050667ccb130a8f0c4b20cf37574aaf2d7..a51426b6f642753db05959d49c3446f3d02dfc68 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 0000000000000000000000000000000000000000..05219498718289a84174ebf3fc9ad8f82ec6e697 --- /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 f090b384a44ae1c0a0e5743ed9060e14ccf6d052..26bd0931fe2d1d738406cf79406b8afea11df0c1 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 a4f90a8d72c095146b9a28527dd92f6f4069bae1..e02a9758dd24f4568cce6b33da0099f53c259fd0 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 14574cc19a7f1327d6dd3c47a3578f8a715f21a0..b6bd88e507ec728ca47e04337541864b85f57c6f 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 1ace51dbd5c417290438bc2314920666001d4efd..77469c0623d3f2b9c3254432faf93e9a32b9fe77 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 0000000000000000000000000000000000000000..afb43f501f0b1187475baab25fed3917315b2e1a --- /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 0000000000000000000000000000000000000000..58cb682f3ba11234b113df8ac3a40aeefb8cc757 --- /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 b11ab1f4acf0610c5dfd04c997cb7128ffc88679..b9a9312952aa51cc29af83a9d4dde7c79b2fc5e4 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 d295deea717c3d8ac9529c8fbc50431757b22946..b4b4b2475fdb660840f7a0608567a2a9c289ad70 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 fe06ca2893f40f619a049dcca9f00efecee96c9f..bd13088f7f81aabeeb29b2c922f44ab3cd7d5629 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 b46e8ad703721f81a9b5e3cf4c925be2e28545f5..834eb751e102a7fe1cd9222d0a42492a1a5a548a 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 9fda1491911cacc7d2eeaaa2ac0ade7db6e88149..21f5b5d3428877d8343b6aa7c32dc7f0dcb6fc32 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 | 101 ++++++++++++++++++++++++++----- 2 files changed, 94 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index 31ada105af9e8220f80e03433152959ed688f4df..375bd77ff0a832eb597738493fe5acc9590e46ab 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 3c6cb31196b881054f0a503d41242039c51bd037..55ba5f8294ef3765eb04416bdebc5f9f210a6555 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 | 116 +++++++++++++++++++++++++------ crates/sqlez/src/bindable.rs | 35 ++++++++++ 5 files changed, 182 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9feb3f11d08663d41cbfc4ecb241fbbd310d4ae9..f11061cb84893a05fff4629ed3c0881eb354c56c 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 ec678c6180121bac4768821242a56b91d4d1494b..f8fa3c9df0e68b0c813616e6480a9a98382866c9 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 375bd77ff0a832eb597738493fe5acc9590e46ab..2d8d1a74fd152b11092eaa74e017d10f1816bc59 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 eb89bfeec206d44af2a60602919309c353f3c7e1..0ca837a843ab7c0c56610641095068cf4a7843f6 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); - - ScrollAnchor { - top_anchor, - offset: vec2f( - scroll_position.x(), - scroll_position.y() - top_anchor.to_display_point(&map).row() as f32, - ), - } + .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, + ), + }, + 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 3649037e502ca34b4a99b7a6f53de3ab4ba03ef3..62212d8f18c66a9c59a4941b0fbb9268a4e5dc90 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 cd425c3a2861ecd025bbc4e3b9eab9cb9068159d..03a866f2f6cf362a2592e4bd8ced681aae5fd17c 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 8bf4cbce328f7800c2d297241faf8a0696280dca..ac6a9f38edd0d695679a8f49c6299797b1baf5f0 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 f4fa919c48e5b2e2640adcde991af6a1447904c7..3861c53e792eb0fd943ede744a69ed1ab392e471 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 5b73d7643c84d344bd8f133f2b52427c0b952adf..3924e84d61a051c26b0b5e34719f71a53b92d631 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 f0c64a1bb995f3a710301de16fd8c00e02dc0088..dd2be6e3185a53e1d75adaec7ef3c9d97c054d07 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 3861c53e792eb0fd943ede744a69ed1ab392e471..72115022d723d975629b26ac2159b0a55535d0de 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,15 +90,6 @@ fn main() { cx.set_global(*RELEASE_CHANNEL); cx.set_global(HomeDir(paths::HOME.to_path_buf())); - 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()); - let languages = Arc::new(languages); - let init_languages = cx - .background() - .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 @@ -105,7 +98,19 @@ fn main() { 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()); + let languages = Arc::new(languages); + let init_languages = cx + .background() + .spawn(languages::init(languages.clone(), cx.background().clone())); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.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 19fed4bf59004f917d86943f96cd45f8e0c6c4ab..ecc8d3f7b39b57080c2245fc460c613e0b55c03c 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 ac6a9f38edd0d695679a8f49c6299797b1baf5f0..027b9ba86a7fc8b4709944bb8eba5fae6e1f0e91 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 aa46d64fcb250d30ac4c41c97497bccd40b7ad74..213b2cf0424a42f8a6c8de30f251ce8395f9a2a8 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 ce8b713996be0c3ce993cb7b2473bd874a8583b0..e5d2dad41e9a0de40063b1ac891e6a32d4889158 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 4d29669c2f87015ddb15557c183b8e853e3d5534..1201665571e16b63b92d2397043e7d0266548e85 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 f11061cb84893a05fff4629ed3c0881eb354c56c..8847c01af7aeecd449137b7b0843e306bb4e8df4 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 dd2be6e3185a53e1d75adaec7ef3c9d97c054d07..e3e8516508d9d9048757fdc2780fe1703cdb7242 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 72115022d723d975629b26ac2159b0a55535d0de..bf86bb96528b16c1529389038b199f59352da510 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 608abc5815612b1c180b9dd0d674d4464e4c7e46..478271118c2c1be477fb6df8e4d44ff36ebe167f 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 a36831857f0ffed548fabe2d57ddb5f28aaac5fb..c9e159f391a1af7b8ea5a9d92ccb1932710fccc2 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 b067cac5ff596fb74837b565c6264ffc7ccbc31b..11c62005c2d2b4115899046bdf784468123c72c8 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 e3e8516508d9d9048757fdc2780fe1703cdb7242..e29a98e775f528ee6242c16da24364e3bf7b62e8 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 19fed4bf59004f917d86943f96cd45f8e0c6c4ab..d830f3474499ccb3e8b9ed9439eb4fe747de1153 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 0a8311fd5cb3998a392908c522fc7f5e1efbe792..19385038e4a81bd06c4fb2f63d78cca9840b0c4a 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 9153d12bbbb0776d30c4917820fb8d5288cf0206..24614e97c271176f6939b3199c9a3e7613fc504b 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 14574cc19a7f1327d6dd3c47a3578f8a715f21a0..d615546104ec99072cbd79a90f13f4e2a5e3d459 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 d295deea717c3d8ac9529c8fbc50431757b22946..38fa2ad4befffe33901f5499b1ab890a83e20bcc 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 d615546104ec99072cbd79a90f13f4e2a5e3d459..fb090ac835f58a7349f6ee86e51345979a209dc3 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 261289b9b325f1c8e291a35538ab7e2c5a3d0f64..1e26e6f5273004d40e7a11e713bb0fcfadd2c12e 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 bef6f48cb4a3776a85e7e90ffe313e1db6f0405d..12873a3e4e6a4515a02fe161dce4c57a49efabe4 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 9089eebcb5654c34cd5cbd5c91c479b2134ed51e..62b30730e8ea8d4a3a4d9f28120359872c5e8211 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 d88d496ee92a6da2323f4b51fed02b6a0dbeee60..d2ab40c7729a4941300be0d267cf57026d283372 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 4d582fea6b25fab4a8d9833ddfb8e5676d78b0a6..8c5dae1e01fa7db5c4d5e477d1c08874e1feaad3 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 ef5bb6ddd83f45e552e2c903363e9a64a3651e9c..3eba8699ef36d70894fc45c70389297b684c5382 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 f89e46f8c923923bc69dcc02cae1707f2bcd770d..32254d5757da77f7b90f6c675b0a432418d32624 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 e0f301b2e0c44b801819e94bc463d20077f6f476..1894d888b92a89508981abe5de7f5fc3e710184f 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 fb090ac835f58a7349f6ee86e51345979a209dc3..9a51f872a6038690458649ef96dedacf192b5bb5 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 1e26e6f5273004d40e7a11e713bb0fcfadd2c12e..dbb5a1cc995453939d1ef8bbceb8fb91d210cbe9 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 7fc59d86fbccfa8ab787d3eed12063bb94fa7ac4..39cdea71651ac8c127a73f10208a31c787126a2f 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 9a51f872a6038690458649ef96dedacf192b5bb5..3e79a68fc4041329aa868975fe0b8a767682eca9 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 dbb5a1cc995453939d1ef8bbceb8fb91d210cbe9..9c3f0f61c4e2d8e503bfab4d51007cd0a7e6ffcb 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 96b2065823915e039b93a6627dd04b1d83ccef9d..0ede6186da4a668dd80ac3368c7f78ad1ea35e22 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 d2ab40c7729a4941300be0d267cf57026d283372..d6391353cf7fc43980dd2866f69adf4d379721c8 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 3eba8699ef36d70894fc45c70389297b684c5382..ac8771f969ef40d7f37f6b66f429cde7b3085247 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 2f3316469068f0134fb0a959bacc3878e0a10621..6f358aad3939d3f5dbb4be3dba52e3d5020874ce 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 3db4f7b467ba7cbc0f91e33da1a46d4fed2e39ab..a5949127f5a9f3f7b96c2372c3bea40951de0a9a 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 0000000000000000000000000000000000000000..c233bf18725dac4b0c5d543526d813a66503452f --- /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 f54b09ceda55c5e94666fe16bbd7b29fc9fe0002..01b62577ad030deca39225c26c5b054d88a8303d 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 40edbb88d7462798e0a5a41739bb73c9c50c03b6..c5aff17e566b69b33e788ded686fd2eb8acef1d3 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 618c650e02a538b53a70b0bd3083847f4d1e0425..bd355b8b3e75465c17267468202eb8dbffa906cc 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 | 130 +++++++++++++----------------- 1 file changed, 55 insertions(+), 75 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 027b9ba86a7fc8b4709944bb8eba5fae6e1f0e91..eefce67371e33099c5b478f2f95b0999e5b7188b 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( + 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, - from: WeakViewHandle, - pane_to_split: WeakViewHandle, - item_id_to_move: usize, - split_direction: SplitDirection, + action: &SplitWithProjectEntry, 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(); + ) -> 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 bf86bb96528b16c1529389038b199f59352da510..6baf08b01e0191d312a26d8990fc838894db7998 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 23f6f04992ea65e0f63603558d23d8f2a6a242a8..095d5065d9a6a8fe7dcaa3ca4f08594305f3b703 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 563ba71918c780f0f945deff80e4f9d913156779..0000000000000000000000000000000000000000 --- 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 a3cb19799608e4934a1e0c6895786dc9b4b390f8..0000000000000000000000000000000000000000 --- 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 16f3d1af27b9c9c711e3301e7f048eb33abe3aa8..0000000000000000000000000000000000000000 --- 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 3bad018a56ca5528443aac5afa9f67e0637f2ab1..0000000000000000000000000000000000000000 --- 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 c6061f0995320f044faeac56bcac458a09747f1d..0000000000000000000000000000000000000000 --- 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 41962c960d333c12c2bbccedbf07fc13215a10b5..0000000000000000000000000000000000000000 --- 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 b53bb105a8772ab9d5228545dc008b4120411af5..0000000000000000000000000000000000000000 --- 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 9a649359170c80e85cd69c5b1e6373eeae9489f4..0000000000000000000000000000000000000000 --- 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 1da584a856dad08115cb6790c828956fc9e76169..0000000000000000000000000000000000000000 --- 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 298e34a6b56d76ef55b4944f85226cc66c441cf5..0000000000000000000000000000000000000000 --- 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 9ccb50fe79c841227fc44817611f9cfd7a4e9401..0000000000000000000000000000000000000000 --- 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 2e8868b1f1c37c37e5449efe6a81dd0be2df997b..0000000000000000000000000000000000000000 --- 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 57a7364a43df051cc26975720751d54d37c1ffcb..0000000000000000000000000000000000000000 --- 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 cbbc7b4ee3c5d0d594d304c8f1c6b44377b3793e..0000000000000000000000000000000000000000 --- 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 ad7ce929fef602468a5ffbb4a709927d767ec753..b1dbd0e92d9cef855d0476c2bca1b7a07f162d88 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 6b784833c70a5dcf7c66990463ff070fc99afffe..6a4086c5eb15cc1b126ce343c78b00d745302eb3 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 e007605cff5fe7005d25d16b015f163f6fecc139..c7de0352328d287b1248b80699c06df7fd07ae0e 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 b16b7f15523a3bd684aad1c3de5397be59f5df68..afd65d4f0424e0031090852d2441d1a6f8bd9420 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 b1dbd0e92d9cef855d0476c2bca1b7a07f162d88..28f5f80c8337696f06b47ccdb8ba595a3270d5df 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 5c13f7467aa1e5e0abd589babc49250a113d1329..bbaf1ed0bbce3ec2a096e37719354ce100d00db6 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 fbbb517c936b194318d409bd56c965e81d99d9a7..a0f437cf9182c2876197fb8b12a5be222712c481 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 3e79a68fc4041329aa868975fe0b8a767682eca9..d9976fbba7aa16fac83b7943cde97b04ca3b2895 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 683e3bdfcd05f7794f326cafd1b20baea8462e99..5153c1f7c166e68081d4edaabd9afe36fcbc7e9f 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 8847c01af7aeecd449137b7b0843e306bb4e8df4..fb8e37077ec8996b41bdbbebafd1e6a69aa6d3f1 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 9ceb6b32a994398307211b40e1b31a5880648f30..d1e7316f1b0c66b3cbdcb46c57bbd184ea6353b8 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 09ccc5d62138e1a351fcc60b9b5cb02088f17fb8..b7f35e9ad5cc8b20671faf49632339445af8c727 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 9ceb6b32a994398307211b40e1b31a5880648f30..b92f0a7c3e005f2be6de2ae1d133d0df405a6308 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 8626806abe852a3c7d8a1d5ff5f46b125ff55534..86494df05b30dd18fa75f44f58cce537d7a6ecf8 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 11c62005c2d2b4115899046bdf784468123c72c8..85173857983de9462dc25494f55b953c52d386a0 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 b92f0a7c3e005f2be6de2ae1d133d0df405a6308..0e546d4ba0b471014c1e468a9ad4f8f11f42ff16 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 478271118c2c1be477fb6df8e4d44ff36ebe167f..d3b3c11f2eb09a3584ba5063acd001de9674f18e 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 86494df05b30dd18fa75f44f58cce537d7a6ecf8..ba481ce45ba7ab148d2f9b347d5d483e448b44f6 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 39cdea71651ac8c127a73f10208a31c787126a2f..b05bc17906063fe51dc4ec349496f26a1338a9dc 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 8847c01af7aeecd449137b7b0843e306bb4e8df4..e2bf3c775ba2e0090552ddb5b7f6e825df5620f9 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 85173857983de9462dc25494f55b953c52d386a0..34346e2128a09fc7e338a7e82b9c4bf3f28bc66a 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 34346e2128a09fc7e338a7e82b9c4bf3f28bc66a..ed8f984d274228b7c777635c73e6ac2686407991 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 921b4189e824dab10e6692991654c2de223ac096..235ed66424487ce3b3af84e88378c0007c3d01d5 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 1bbfac85d3d7673fce279cd8ddc52879fe6fe934..63ea7fdd9e128ee76206f7bcf7829bc05bee8d52 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 9d42c11f8bba88bd164eaab443857d910b97230d..1e271675458114bda3039f2ef272eb74eaecc2b2 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 f8fa3c9df0e68b0c813616e6480a9a98382866c9..1fe0329405741d87cb7da4da686dfda37aca1116 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 2347d9a63d1fbaa8d88555c45eb8fe434530138a..c8a62c6a57326683b21515c06f3b3923135c0732 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 bd355b8b3e75465c17267468202eb8dbffa906cc..735e165c7d644d4842d15e6ae1dfa276585084c8 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 69c6db51ab3a4a3fbb28e91fc20b825d2ff6ec31..747541f87d6faaed9a3e3883a9a4d0e5f5072403 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 eefce67371e33099c5b478f2f95b0999e5b7188b..de5b50b468ece25dd293f64719af27a37275b54c 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 729da6d10980a6d8b8a7a335fbd16bd01d0b06e3..3f2a777f87927e0f9bb5b75c533a20a184e76284 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 e98aa645d562b8f2ec76c6e85e49ad565e733f56..d3078bce81493e749c9156635c12d895eea1bd30 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 1fe0329405741d87cb7da4da686dfda37aca1116..6568fe78dd01bcd1b7dcd1ef756c1f5bd79407a9 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 d3b3c11f2eb09a3584ba5063acd001de9674f18e..66b016e27c328d1e91e74dd305ff93d3a9994893 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 9d2f31e2690c38f1ece7bbf475fcaeb4c79311d8..d30f4d804c7d362c2054e365a7d4061f0607a9a5 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 e02a9758dd24f4568cce6b33da0099f53c259fd0..847dfc5ee5c6710a7faefda6fe80131812733954 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 cf9f03de45f351cae59a89eaca78654074daff85..3cda5d3e51078857d622478262b399400538f222 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 project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> { - SmallVec::new() - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - 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 19385038e4a81bd06c4fb2f63d78cca9840b0c4a..9d5b94bddb8a99e4f7be34d62ac88d913c131f4e 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 crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; + use std::{any::Any, borrow::Cow, cell::Cell}; - 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 735e165c7d644d4842d15e6ae1dfa276585084c8..4d5a73b2b33785dcbeaffc2f82efc83a328b42e2 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 d292ece3d5fb821f07e8ed80a0931ebb44bf0e96..b76535f6ed226fc0e63fa490779a731ef0dd089f 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 project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - Default::default() - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - 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 eefce67371e33099c5b478f2f95b0999e5b7188b..c7aa0798a3d81f304013f3fd4287a456f06de9b3 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 7b777a50edd9d69253688797f239c5c16e91f268..c526e3b1dc7eba5a2edbed82bcaf7408bf0574d8 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 8c5dae1e01fa7db5c4d5e477d1c08874e1feaad3..9f799ef37f9f3dbcecb168420d6892e3b8534587 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 e320962cfa30cf6ca91e39628c3b19f81237bc53..0214806e1195bd5d18f97f6eb15de8e023bb17aa 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 9d5b94bddb8a99e4f7be34d62ac88d913c131f4e..1bc713b5fb45c2b5beca9d5a4e8c539d1ba5d53d 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 4d5a73b2b33785dcbeaffc2f82efc83a328b42e2..de298e7be10cf01609c84de064967ad1aa764377 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 c7aa0798a3d81f304013f3fd4287a456f06de9b3..a521d02b5fbbdcecd1f438d06ae1b169e887e2e3 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 | 358 +++++++++++++++--------- 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, 245 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6bd88e507ec728ca47e04337541864b85f57c6f..66a3e3ee72255cb5005df33adae012adb6bc4e90 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 afb43f501f0b1187475baab25fed3917315b2e1a..c6f139e36e5dd15d4c81da1d9c83d85ee5f338d1 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 58cb682f3ba11234b113df8ac3a40aeefb8cc757..03379d655ca58ede54e061c47bd8465469afa0a3 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 b9a9312952aa51cc29af83a9d4dde7c79b2fc5e4..6beaea83f0c1c15a1d3a1f2acfb7be38fd953b29 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 + ChildView::new(&self.editor, cx).boxed() + } +} - let theme = cx.global::().theme.clone(); - let submit_feedback_text_button_height = 20.0; +impl Entity for FeedbackEditor { + type Event = (); +} - Flex::column() +impl Item for FeedbackEditor { + fn tab_content( + &self, + _: Option, + style: &theme::Tab, + _: &gpui::AppContext, + ) -> ElementBox { + Flex::row() .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, - ) + Label::new("Feedback".to_string(), style.label.clone()) + .aligned() + .contained() .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() } + + 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 fc55f76f3574a88ead6282bc93ad7e129e094eee..17e51a68153fd579cfc08630cf48b38f30a4dd6c 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 3b118bbef94d23555c8339a3f8b69251488ef997..ed8fb1bb120938f502647f32519084f50c6ae0f2 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 8bf4cbce328f7800c2d297241faf8a0696280dca..ff5748f2408249bbfd441886a4a38001fba94ebf 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 21f5b5d3428877d8343b6aa7c32dc7f0dcb6fc32..8f597ebe79f445060d7e64ead8971b0a46793ff8 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 d9976fbba7aa16fac83b7943cde97b04ca3b2895..66b0d88b229b03a34c27bda7376cb3f16504b6bb 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 4b79009a68ea31f4350e5bae5447613d7e8b748e..972a33c0b785c10ecfbe8052a1effb814b213a7b 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 9612deb5bd81a475f95911f35378d6e60238cd16..1b95e3ace9a21615bb803d3ad4f4015e5d17188b 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 d3b3c11f2eb09a3584ba5063acd001de9674f18e..ce7296994a8ff62c567ede76e44dd8a020292732 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 bd1dc690db8b34b78846f96cbb06d36ec70cc65b..e9ddcc8195a1c940369b9148b9f5b9fc93ff6e2f 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 53713e3f7a35f7e0d1f3c463575f60f0c5af0efe..74cc9a6789e779f1d5176e8bc66fd4640d1e0052 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 ed8f984d274228b7c777635c73e6ac2686407991..e0170f6648e3d03eb5fd1d81cad444ce498534dc 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 f8fa3c9df0e68b0c813616e6480a9a98382866c9..cce6d338f0278ff0d32e5db89a6ad73bd2dff6db 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 f22e91578565492f1b1a14ac8981f3f393f5eb8c..b65cf9e39bd96367a3e0b0d45989fcbdce76cf9b 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 0042695950f0642bb22aaee4240e576d3221fd14..73b558bd5bc0443bcaa7ddcbcb7fb6cbbdf8c628 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 66b0d88b229b03a34c27bda7376cb3f16504b6bb..bbe77d8c7568c8f1b2bd1c891c7335fc5332d3c3 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 1f6c9582beb1f70df03f4f77b396264f87dc38ea..30802801a676756017ee60ca6c8a29d5d058a92e 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 9d2f31e2690c38f1ece7bbf475fcaeb4c79311d8..a0f29738b01fc8d9af965772c7a95496dab81d02 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 73b558bd5bc0443bcaa7ddcbcb7fb6cbbdf8c628..e59353aae40e06773e4467731976e878baff1cbb 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 d9976fbba7aa16fac83b7943cde97b04ca3b2895..62674acf3d674e19e8befa82d90d77dd33aa9634 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 38fa2ad4befffe33901f5499b1ab890a83e20bcc..63719b70031a6543a7b72d8a67ef77795d33f5c9 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 62674acf3d674e19e8befa82d90d77dd33aa9634..c0b97a6b3ebdd6fccc11d301f8efccf865eda8bb 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 9c3f0f61c4e2d8e503bfab4d51007cd0a7e6ffcb..8c21af7273775ebee32b79327aa65e465fc15f9e 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 1bc713b5fb45c2b5beca9d5a4e8c539d1ba5d53d..b1888bb243c41001d330f108225893a1f9fb6208 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 de298e7be10cf01609c84de064967ad1aa764377..05d6de1ee22b6eae9f424c84de89dde9dd05fe0f 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 a521d02b5fbbdcecd1f438d06ae1b169e887e2e3..47e2be29dfc7aa6b53420b0e7cde247d62082c32 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 05d6de1ee22b6eae9f424c84de89dde9dd05fe0f..8d0d7315517b9930c74026c51097c07ec8269cd3 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 4b79009a68ea31f4350e5bae5447613d7e8b748e..a52ee34962e5f13e4686f119585cfe27cdcf9c56 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 b7f35e9ad5cc8b20671faf49632339445af8c727..5aa915220520d6ef8b491742c598b2e5e40b3405 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 849d2bdebf681ed932101a7c0c28b0c8451c29eb..2ff7ef25a0a9c9623f329369bfeccd2e8c1f2fd5 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 bbe77d8c7568c8f1b2bd1c891c7335fc5332d3c3..7be9d4529373cd767958139308fca6aaa05ad03a 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 2347d9a63d1fbaa8d88555c45eb8fe434530138a..4d576883d39c3dcb1fbaa43f45ed253a56a6c567 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 30802801a676756017ee60ca6c8a29d5d058a92e..3a5d9468fc028a44ba7349e0f7549ba14a9540b8 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 a0f29738b01fc8d9af965772c7a95496dab81d02..11318b1c4362b5a4ff22f9ff33776fb96b3f3239 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 4d576883d39c3dcb1fbaa43f45ed253a56a6c567..7812c347812af0a20885cdf0151ecf1d9280e7bb 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 11318b1c4362b5a4ff22f9ff33776fb96b3f3239..68c2f33a7518b7eb4d07021689dfc9aa4205bc02 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 a315cf0c5f1945b00019265578cd84006720087d..d3d5c437c5e6f6675d30ebf501322afd96d5d5d2 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 74cc9a6789e779f1d5176e8bc66fd4640d1e0052..d0796cfc6acb1d906698cb13af0906c3737765b6 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 6baf08b01e0191d312a26d8990fc838894db7998..16fd0c1eeab1dc3670ffb3a2e1ed66d46e85388e 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 7d69d3833c9ba4e26dfaed64f8901b043de6bb84..5c34b9838047187f8c0507e8669ab7a4e39ec813 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 132a915f0b6f99313720b0b6e21ac134d862639f..761e134262542268fbc866fe97dcc40aa4d50fe8 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 bbaf1ed0bbce3ec2a096e37719354ce100d00db6..1573b10a10035925414479b0ea6707d29d3ae1f9 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 00d1ddbf8bf61ee0457186d30f449d1c3ed95ef4..2effb8ab537f7bd3eda979f4e477b5804ac8ad5f 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 0fdc76ebbfefe8204983cd58e4a9f42a310d8024..7cbbc5b89746b5c8ddb81d0b89021e2876213e7c 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 4bc2d6e73d43e0e9e1a0afcba710bcddcb33e4d9..a31e59587f1b0e936da0d580dc80e17a472fa1d5 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 07c0bce6b6502b0550b84c27753e70f454601c24..a6ce5626318b519593fa3de624610d7a817d8625 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 a51fb4891d20ee303d35992ef1c2dbc298dd1562..daf93ce6a213b6083915d4d390b67194309dea80 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 a6ce5626318b519593fa3de624610d7a817d8625..81ab73e24ea34008b9163f7da0147055771a5dc8 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 6b5262c16ab97eff73491322a3f3607bcbb0b1d8..57fe5411cd732e39660b84ee65a6f0da79bb2532 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 ad3bdb6821d2c80b781a52b44c3ab7a8c567041e..5593241e70ba305d59855dc475631b36f872a020 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 c3f2c3716b43c208408f67218f191354fcbe0c7b..3c8b2b987b10f71f2f3a8894ae9f587bf402c958 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 65d01e949317bf64ab7ee65258df3c0c848e5602..458ffd8bc23a147a241ea719636e65389bdafafa 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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/crates/zed/src/languages/tsx/config.toml b/crates/zed/src/languages/tsx/config.toml index 7baa12385c89d684eb80a708497d02693017eca3..4f466cc961e3836ee8d458cdcc4d863f61ab92a5 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 0000000000000000000000000000000000000000..aa617b30862add7e3efbdeb9a2644e0b1b28ac05 --- /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 3351fb9eb9c14c7f82f0f4d27243776a5b419161..ffafa2d142c427614bc87daab65d85a947d92673 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 58517346097af8b6f79d1fe0bb60b837d037af60..6a8fdaa17b4e54d49d8868e7ebbbc3cdc8e32c21 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 81ab73e24ea34008b9163f7da0147055771a5dc8..0d62c1f6257935455341877de1de49fe26f01f22 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 57fe5411cd732e39660b84ee65a6f0da79bb2532..7079d197f9dfca676e84b595a77a8387c0f8e776 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 5593241e70ba305d59855dc475631b36f872a020..7ef78459527fecd2310d41abac1e5bc12ae8ff67 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 5aa915220520d6ef8b491742c598b2e5e40b3405..07198e8090c612df4adb142704dd797da8845e26 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 3c8b2b987b10f71f2f3a8894ae9f587bf402c958..928e20862746c4935db279c81d31eb389964be58 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 0250c53684675c5b5791048b658df91b32f817f0..d7fa67b1d451c13fc103cb5210650fd8efae9d4c 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 aa617b30862add7e3efbdeb9a2644e0b1b28ac05..183f7b4f98db4e016c0981b8c2f4509e213bfb97 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 ffafa2d142c427614bc87daab65d85a947d92673..3351fb9eb9c14c7f82f0f4d27243776a5b419161 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 daf93ce6a213b6083915d4d390b67194309dea80..f0daa3c7e8617040d10a616b2f6353b068310ece 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 0815d9c8d8c02c6756f65c61ca0b4c5187d0b016..858886a0e3e5ba7669e476ed6202ba20f72c011f 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 d027218040909e2412248221de1371fbd22d573a..ad4d7542d300ce9c64db6fb0d2de7f628f56337e 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 958ac2ebd67ea294c942dea7dfba7fc525e1f2d4..a929ba08a333458bf85f9b4b262b063506e6b704 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 c3af91306dce4b1ba79bf46f52fdce79daa34cff..248bab5fdacb58fdf5f5f715932cf4d0598b7691 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 958ac2ebd67ea294c942dea7dfba7fc525e1f2d4..ca233ccf655dc75eaf23399f9b7e437e5562e48c 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 07c0bce6b6502b0550b84c27753e70f454601c24..057a86e7f36501cb530df6cd04a5b057ca266361 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 5c34b9838047187f8c0507e8669ab7a4e39ec813..bce63ca0cf796ef0e9537ff7f40f241f196c5331 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 7369b0a6f40b5a20718d354e3a1ece9fe5c16aa4..6d003cae5dbaf42d54a85b56ea941d0851816538 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 0ca837a843ab7c0c56610641095068cf4a7843f6..1b3fd96dbd99fb0d3363d5a9fcd5827aea921f91 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 a929ba08a333458bf85f9b4b262b063506e6b704..f5a21c46440a4b4f1e45276a181f37e3b018dfe0 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 bbaf1ed0bbce3ec2a096e37719354ce100d00db6..4ccc319219c33c4f882d2a27ed2c0b0d43796e5f 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 6a8fdaa17b4e54d49d8868e7ebbbc3cdc8e32c21..9665db6be60955af11c10979cad87bbc23a29ed5 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 f0daa3c7e8617040d10a616b2f6353b068310ece..6ad533665e7477494e221da71f85a31646671447 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 858886a0e3e5ba7669e476ed6202ba20f72c011f..0815d9c8d8c02c6756f65c61ca0b4c5187d0b016 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 ad4d7542d300ce9c64db6fb0d2de7f628f56337e..99d607e4070c5eebb2039dba3754e6100a59b30a 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 f5a21c46440a4b4f1e45276a181f37e3b018dfe0..26305e86da7fe08e645b12536581934e18e15a38 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 9665db6be60955af11c10979cad87bbc23a29ed5..8735f33fbe6cfd3197bd825e8c5abc01652eea46 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 248bab5fdacb58fdf5f5f715932cf4d0598b7691..c3af91306dce4b1ba79bf46f52fdce79daa34cff 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 4104b0ebf893d7d57ef6a9f82327b140eba16b1c..0da2e4351e477476300a0ebfbfa3cc20e8a55324 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 8735f33fbe6cfd3197bd825e8c5abc01652eea46..9665db6be60955af11c10979cad87bbc23a29ed5 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 928e20862746c4935db279c81d31eb389964be58..766e181671e37722a1d6f851ab62b6fc38f45490 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 d7fa67b1d451c13fc103cb5210650fd8efae9d4c..240d1dc49e5ac6603b1c0760a2e11f748274b276 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 5b4841561043ad5c3aa6ed42d786ef50419e067b..1a5fe9339ad9fa3a0c9da1198ae5bebfd8600819 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 0000000000000000000000000000000000000000..0d14e6c205e3f4c624d8bfa3352d852cdcfb9193 --- /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 e9a793ec3c97b7ac7ee941e5e57cb3886ccec202..c83adfb067f2d2da8819b32b144c90f1c40992b4 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 0000000000000000000000000000000000000000..0d14e6c205e3f4c624d8bfa3352d852cdcfb9193 --- /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 28def3abd53d8bbd7fdcb7465cc402d4b38871bf..cf8d4f1e2f88682139fb6a4bf28623ca4826e338 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 0000000000000000000000000000000000000000..9617249d3c4b1f99750b752f51493dbacc740267 --- /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 4e1af93943d2e63763181b83590a4ff72f809c23..0185d8eec35a2173eb802f0e0618e43b23fd19dd 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 0000000000000000000000000000000000000000..ed1b20d44b262c5ce9dca529fc6f49425441d0e4 --- /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 280219a1191c1638190a6d52e1d7e717daa665fd..9cfcef0c8ba8ff4b3a5aedd574f01f6050c5798a 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 fc6167e311b10b000bcba0ff8645e40bdfa14c8d..3f9464594cd76528ff6bf0e80b75ad6d3a08d4cf 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 0000000000000000000000000000000000000000..fc32c47f681d48d2adfb4f94b918305136507d50 --- /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 3e618da25e06f5f37c3106db12bd1d6abf2b1a4c..f4f0ba26eac95488c79c6db91afb5700ebe6a4dd 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 0000000000000000000000000000000000000000..8f6a1f9eeae3dca51d2dc4473eae93215fd54472 --- /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 d7612d13f08ec2eac41d30bef1da7cc41eccbd20..a5a656393dcb6aa338a8431eb63d8dc5ca4898d5 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 0000000000000000000000000000000000000000..f19c1d9203a697d2a34101f031e74afb8101b3be --- /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 cb36279358a50dd138b0e9d0632b559895c19cd3..48a1fb0c993f0481d0862eb4a1820c922adcb12d 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 0000000000000000000000000000000000000000..83911ae4a514eed107152d3b8ad52c1a56f85d53 --- /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 c6b41ed700a5ef089499088e7b217924c1952e3c..a817de8e3ba87b2619d4935dbe536060d3522137 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 0000000000000000000000000000000000000000..3a7ad5f035efa113afe29444f8c75e40585aafcd --- /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 5600266de3a2a43fc0740cc6c1249bcf7d00149b..4fd21d8bd0a2598f30a006e5ccd232ec089475af 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 0000000000000000000000000000000000000000..3a7ad5f035efa113afe29444f8c75e40585aafcd --- /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 971ed27ebc3147ecee9ac6884712a3937ff1b7ba..f9f6b597173e49243c8552ab6a3cd64f7a7d1957 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 0000000000000000000000000000000000000000..29128d98e7f970f7f25622835ca14afd5da5c814 --- /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 7e63673834f201b63f77140d5449f08af2efa30c..46e11bdc1158cd89237c0c10ef35de3a6b8ceb14 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 0000000000000000000000000000000000000000..28a8a569dfd86c1f96f540777e0c3f074d2045c7 --- /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 cd624aecef07aadf8c9354749479676ae3768fe4..30797bf141bf0a77a0dfcab431445104ee067095 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 0000000000000000000000000000000000000000..3a7ad5f035efa113afe29444f8c75e40585aafcd --- /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 8e5886167c763c1bb86a5ee0e6e8d88d08ad0d7b..3c2146e0015254ac69cecae7acc07422bf1bce28 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 0000000000000000000000000000000000000000..3a7ad5f035efa113afe29444f8c75e40585aafcd --- /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 07198e8090c612df4adb142704dd797da8845e26..382311e2040d85e0fe930600bc36446215a32d16 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 766e181671e37722a1d6f851ab62b6fc38f45490..e926a776bd47435f6d365da6f7d81630dc8ec4fd 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 0d14e6c205e3f4c624d8bfa3352d852cdcfb9193..178355c67c9797b371be81de98c23cba9373c38d 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 0d14e6c205e3f4c624d8bfa3352d852cdcfb9193..178355c67c9797b371be81de98c23cba9373c38d 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 9617249d3c4b1f99750b752f51493dbacc740267..c0db9fe3274a7746ebb479618efbca117129bc60 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 ed1b20d44b262c5ce9dca529fc6f49425441d0e4..181254018126d5ee0faaad24911c3b71b1e30c5b 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 fc32c47f681d48d2adfb4f94b918305136507d50..9eb287df3f448b20848572f2bb81b9b4e9f80d3d 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 8f6a1f9eeae3dca51d2dc4473eae93215fd54472..97accffd6721b6feaf6fefa61a1f9d64019b89f1 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 f19c1d9203a697d2a34101f031e74afb8101b3be..5e43c4a94ad69e6b7d6c3afab091235b789c6e1d 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 83911ae4a514eed107152d3b8ad52c1a56f85d53..746dbc5cd9548285649091aaa0694ee65164b0fc 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 3a7ad5f035efa113afe29444f8c75e40585aafcd..8a58e304e5c5185166a09bc78eb835527a246301 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 3a7ad5f035efa113afe29444f8c75e40585aafcd..8a58e304e5c5185166a09bc78eb835527a246301 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 29128d98e7f970f7f25622835ca14afd5da5c814..216a3951476509b79e35bec2f79b3feb9a1afa44 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 28a8a569dfd86c1f96f540777e0c3f074d2045c7..8c0d41b046c6dbe72937d662a7c8ebdb023fe49e 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 3a7ad5f035efa113afe29444f8c75e40585aafcd..8a58e304e5c5185166a09bc78eb835527a246301 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 183f7b4f98db4e016c0981b8c2f4509e213bfb97..d86186a903373bb8f1351141b5611ba48aa090ca 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 3a7ad5f035efa113afe29444f8c75e40585aafcd..8a58e304e5c5185166a09bc78eb835527a246301 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 3924e84d61a051c26b0b5e34719f71a53b92d631..1ef2ac8a16c9420d5ed019d36a05e1201236849f 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 8dc29f3d606fdeaf0f74867863b197980dc5c43b..f257d9549358cdffcb932b535e11fb51e2ddf422 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 6a4086c5eb15cc1b126ce343c78b00d745302eb3..ad1fad85b1938220e4f2259ff623dc274748aac5 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 e29a98e775f528ee6242c16da24364e3bf7b62e8..8b2c12a59bf8b673965838ed62319318a0574c6f 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 c3af91306dce4b1ba79bf46f52fdce79daa34cff..e765a9926b769bcf820c3b5d61d17c33f6d4df58 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 cef32ff98cea92e0804caa983db638e3a0996e7b..4fbaaa80588edcd1cb329dc5481d9ee57ad4d344 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 66a3e3ee72255cb5005df33adae012adb6bc4e90..10c67ee33b83bba2ac8be915bd2edb053ec4eb58 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 c6f139e36e5dd15d4c81da1d9c83d85ee5f338d1..3eaa9448f949c9b4802e5da74c1dcc26254a9a47 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 03379d655ca58ede54e061c47bd8465469afa0a3..46c9c2efc272dc56ee26c62d1b3de9216d459d8a 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 6beaea83f0c1c15a1d3a1f2acfb7be38fd953b29..120431ab22f061421b9f16d2137406f75ca38ced 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 8f597ebe79f445060d7e64ead8971b0a46793ff8..167b57aaf28b443f57c0f20ff19ba8d521d3a448 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 10c67ee33b83bba2ac8be915bd2edb053ec4eb58..fad1906e059796502a948163e3f635bf388b1194 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 3eaa9448f949c9b4802e5da74c1dcc26254a9a47..b623c9b68e32f27256c4542a97849acc5c310a40 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 120431ab22f061421b9f16d2137406f75ca38ced..c5e90b72169199524149f1c336e30ffc2a717e91 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 7ef78459527fecd2310d41abac1e5bc12ae8ff67..5c966bbc723226e0a25156f13917a362452ed631 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 e926a776bd47435f6d365da6f7d81630dc8ec4fd..045e8dcd6f510772c2050a55cbbbb228823f40f3 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 98ea1ee40ea1f3bebf13ae392e9c3192bbe79c51..b52a7a8affdef6cf85f455759342617e70e5b862 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 504c235d102894d44059ab0fa6051dbf1ae6eaee..9ab6b029083fd5d8e3249916c00a5f90648eb3e2 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 0c08af449785a08c86ddd1e3ae6d0751d706bcaf..c39c76dc34dab4af3d8521d23b6008b1af540d27 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 c527fe8d25f3197bd5bcf29df8a90d97c83af484..2f29898c26d2e9179560e40787d304e6320f33f6 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 0da2e4351e477476300a0ebfbfa3cc20e8a55324..e93e63be921f3c50ecac1a2df49ea18cf55381c6 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 73e371c50bbc69ddb84790dc1a23ea37e7f9dfec..0909d95fd0a5ac4f53e5de600ca7bc5c540dfc00 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 9665db6be60955af11c10979cad87bbc23a29ed5..8735f33fbe6cfd3197bd825e8c5abc01652eea46 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 e765a9926b769bcf820c3b5d61d17c33f6d4df58..b6b00e7869992af0d38e9d8395741b58098952f2 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 | 195 ++++++++++++++++++++-------- crates/language/src/syntax_map.rs | 48 +------ crates/text/src/text.rs | 51 ++++++++ 4 files changed, 206 insertions(+), 98 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5c966bbc723226e0a25156f13917a362452ed631..110e10564c35ecf6b04892c4d06dd6a53ee5e679 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 382311e2040d85e0fe930600bc36446215a32d16..0b2ef1d7a782713159187b0a5262021afa34b8fe 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,90 @@ 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(); + | + " + .replace("|", "") // marker to preserve trailing whitespace + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); + + // Insert a closing brace. It is outdented. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + «}» + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a() { + b(); + } + " + .unindent() + ); + + // Manually edit the leading whitespace. The edit is preserved. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + « »} + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " fn a() { - { - b()? + b(); } - Ok(()) + " + .unindent() + ); + buffer + }); +} + +#[gpui::test] +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 mut buffer = Buffer::new( + 0, + " + fn a() { + i } - " - .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); - // Delete a closing curly brace changes the suggested indent for the line. - buffer.edit( - [(Point::new(3, 4)..Point::new(3, 5), "")], + // 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, ); @@ -880,19 +964,19 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta buffer.text(), " fn a() { - { - b()? - | - Ok(()) + if let Some(x) = y } " - .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), "")], + buffer.edit_via_marked_text( + &" + fn a() { + if let Some(x) = y« {» + } + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -900,14 +984,12 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta buffer.text(), " fn a() { - { - b()? - - Ok(()) + if let Some(x) = y { } " .unindent() ); + buffer }); } @@ -916,27 +998,42 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) { cx.set_global(Settings::test(cx)); cx.add_model(|cx| { - let text = " + let mut buffer = Buffer::new( + 0, + " fn a() {} - " - .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); - buffer.edit([(5..5, "\nb")], Some(AutoindentMode::EachLine), cx); + buffer.edit_via_marked_text( + &" + fn a(« + b») {} + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); assert_eq!( buffer.text(), " - fn a( - b) {} + 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 458ffd8bc23a147a241ea719636e65389bdafafa..8d6673085494263970baf3fdbb3a0f6c21a939d0 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 914023f305b4ba27aee8359205f3befc5245503a..c7d36e29def55aa2be84445ed14090b26a970b52 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 e93e63be921f3c50ecac1a2df49ea18cf55381c6..6126533644578ca0a023657eef851f26c16432a3 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 | 248 ++++++++++++------------- crates/zed/src/zed.rs | 2 +- 5 files changed, 120 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fad1906e059796502a948163e3f635bf388b1194..b503e1838a47bf93d0ea56ba4a681934b72f9623 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 b623c9b68e32f27256c4542a97849acc5c310a40..ec5aa52a1abc25e9bacf48bde310671fcd772979 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 46c9c2efc272dc56ee26c62d1b3de9216d459d8a..4b0dfc4df9f3f89fa0a3ea26efa83d47394cc61e 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 c5e90b72169199524149f1c336e30ffc2a717e91..564ac76203c8ada7d9be5f5d17c3ef1ec9f4cca9 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 this = cx.handle(); - - cx.spawn(|_, async_cx| { - async move { - 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 = 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?; - if !response_status.is_success() { - bail!("Feedback API failed with: {}", response_status) - } + let response_status = 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; - -// 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 +impl SearchableItem for FeedbackEditor { + type Match = Range; + + fn to_search_event(event: &Self::Event) -> Option { + Editor::to_search_event(event) + } + + 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) { + self.editor + .update(cx, |editor, cx| editor.update_matches(matches, cx)) + } + + 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, + ) { + 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> { + self.editor + .update(cx, |editor, cx| editor.find_matches(query, cx)) + } + + 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 167b57aaf28b443f57c0f20ff19ba8d521d3a448..4dca6e0de8e2d074dd613796ae6257e3c177a3a8 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 be257b2425e72b5773101684e758083ca4bd925c..f08a13902ebae7fcf33c163a330193f2013d4b53 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 ec5aa52a1abc25e9bacf48bde310671fcd772979..ee6ec2f6edc364b9058487d1eb3b1d7d4df79378 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 564ac76203c8ada7d9be5f5d17c3ef1ec9f4cca9..ca960a7c1084ce55b4962439d97c7ec4d8b7ba36 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,16 +345,12 @@ impl Item for FeedbackEditor { .boxed() } - fn to_item_events(_: &Self::Event) -> Vec { - Vec::new() + 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 project_path(&self, _: &gpui::AppContext) -> Option { - None - } - - fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> { - SmallVec::new() + fn to_item_events(_: &Self::Event) -> Vec { + Vec::new() } fn is_singleton(&self, _: &gpui::AppContext) -> bool { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c1c90f2560d1c70bfe6cff83c90a9edfcadb771e..a212949511f16819022a7122344f12a5e22b55d3 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 3b1d65c349900b8d562e760055d170d543f8b972..57219cf7aa2676e58566bb49118b4c0418974555 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 8d0d7315517b9930c74026c51097c07ec8269cd3..f9124bfd83dcff2824f0ffb313366ad2a13db193 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 0000000000000000000000000000000000000000..9f27c62f597a5daba9b0b818e1c6fac67540ea77 --- /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 0000000000000000000000000000000000000000..b18c75c886ce8312a9bb0ddd9d89dcbf19a159e7 --- /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 0000000000000000000000000000000000000000..a0193a4899fbb3fffb6490f20f7d8ed0d4d78d7b --- /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 0000000000000000000000000000000000000000..b4c705632bed76907d4a801e15eb66717b8f3f70 --- /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 8735f33fbe6cfd3197bd825e8c5abc01652eea46..e8af0d62fca5842aac3396f15d3f4d92042fba67 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 b6b00e7869992af0d38e9d8395741b58098952f2..30f5bef53ba484ee090625c7ade9cfc5afb8b48f 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 9f27c62f597a5daba9b0b818e1c6fac67540ea77..0000000000000000000000000000000000000000 --- 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 b18c75c886ce8312a9bb0ddd9d89dcbf19a159e7..0000000000000000000000000000000000000000 --- 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 a0193a4899fbb3fffb6490f20f7d8ed0d4d78d7b..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..2814feaa0897cb8f88eac5c13178c1dffc35dcd1 --- /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 b4c705632bed76907d4a801e15eb66717b8f3f70..5d70eab88b94891673130266c1a9700361a05578 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 63998fa47b3f9c0101b068f628e7d0e623b3a6de..78a4e752b25e3a5b3af36a9201855cc1f91a1a79 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 71db57f320ea586d8c784be601f255a7d464077f..ca96c92602fdeebb0b6c2ad5346ec11ed0245068 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 b1ca061614b5ddb400a69b32a90e3faecec706c0..5f672e759f61065cb46624e6c39369442b3765ee 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 e5cae74e8f61fc8177d1cc84b9a7463df5c61ddb..99476fdc0ac1e49210bee22204923c537599de48 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 e6aa357bef89ffdb0b4a1b1345fefe24fd39abab..156925fb72e3e12a67f7c4fac935d4fdf7cb916c 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 fafcc5ab682e298457be8bdfff2387feff88a735..f2bab22ea713594d041caa47b6c876798b4c8fc9 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 74533fbc3b047a3cd126f31e2a96c56489ce623a..347424d34e444fda74200ac6e6cc6676ea7fd807 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 8e17e15e5ead23d08517ff9eb93b6136d3a6eed4..1705fdc6d5ac2a08a6c4e166927f03f8e5c23d1d 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 8c21af7273775ebee32b79327aa65e465fc15f9e..456bcf6531a7d97e42fb395882c81937ea86fe9d 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 20db066ce72a1c0c514c64cf6983f1ccdde43f6a..ac13e361fd9144736c6ebbe2b96c97de1d2581b6 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 8e18cbd11db6d5aaa53d26a549f988f5e2152d8c..dcbc642c4c443228c3324dc89b95c6966091b455 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 85f5b36ed6c2103662a855e6b369e16e5c2d0a1f..555deff1ce6673fdf480eb7cc52c5033b1050a68 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 817893f43eba786bdb429e4e5ccdae66c54c472f..d764d4ddb8f186da35998bdeec46e6e7c3141559 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 8e12b06027a810fb85dd77c7d57f9365456018d4..496e61b81148335d54bb528610914b6173253a17 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 616f69117f199bfb70220b939ba88f11c759b6f5..ebb57e0636ae1a6cb97ab6c7753accc553e00404 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 4ab54ad8e659dddb1ba79f066db895c2e56c79fc..e378a5bc310842de33ac86f5ca21644ee8cd794a 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 f992ed5116d8e77ea593e0730e03d7c56e7b1a2a..26dd37104154c7428a46881e12c5eee092901b9a 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 6fd792940d923f8cd07a2655dca08620137e52c3..1d1a4dfb1bc507095a3c09b2d7a1beb3748cb744 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 5b9082d1147800d3db75b76ae29a41210a00236c..cd6c8f969c8192de244d543852df80f2bf559b5a 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 3bf7ae885b5dd9c5c239dad649138453b7ee922e..e71e247bf263f80130a9d18dc9696837021ced73 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 e36c22055a31c5ebd21b7342238719d85b221439..553c0497a5815e6e8d024a12dd084cf37c632fbd 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 66202a489aa0fcc9080f51a6ddd9f3fce8c1a857..3e88d723139447e393abb4b4fe61278309afed5f 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 93ae96f93e261813e21c8bf1065145b15e9f262c..def6361dc2a83dc57c5a199c3c70ff93d059686f 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 5153c1f7c166e68081d4edaabd9afe36fcbc7e9f..e1b6e11b46896787529a283518982c6811e2c6b5 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 e35e0b1d2b56819adb819ae37e30841a9b7a99dd..76daeae2a844fd5361d7014ce592d3df575e4beb 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 9622049a9cf2cafc80b9f703fa851f81afa8c632..b532397dd1ddcbc59bb24a21f3da0d6d61552c4c 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 6c074a2d75e0ef59523bd52dbfaf53bb563025d2..ab6c687b7a8e1e00e3b44433ffeab7138de80668 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 d0f54782b96d9a1ef4d823e32d4ccac4aad502fc..01457375084ae9992467e185ecfc38ed38c5e1e0 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 64267f62d17421838392bae09269a2baaff68b01..17ee3cd62e21a200999161d8f5ad2dc302386b59 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 94780a9472c83efb46d9eb3d2cf9fcb2095d6555..eb6e02aac91b7aaa29b4ab283dd03b45da6438a5 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 aad2b74c021273afa9abef6479ecb1fc9ed3829a..4c230819e2a6c0944d17fb649fa76760952182f5 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 cdcacd44163661557dc6401f4aa614caf5a01d74..c473df7ef01711e517c5af545b21e0c53c313653 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 5b4751e620485b81931099cd980d4e2a35524d92..661c84c8cdc8126aac2fdfc160a3f021367b9e86 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 64386979103dcb62ff503ad7cfbd44bc07374109..e7a8079caa944633560bc245a41082197c1e9cb4 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 6f37c458d9c76ff98305a8743471e3cebb886cfb..7bf551046597c416cef6d997536f446d8a17d228 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 b0a2c4e09bb8a5f4eed67a8fc12978cbb261f13f..32bfc6a01afe83c3dddb2a9243ed4fffc0cf42d2 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 a8c0a063a87c2a2972933409f5e965249f42d8f1..b5cfb9514f36ca936089fe2ccdb969399d0492bf 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 6d6560ea384ff651ae0dc5cf06a19bf76fbf2052..949dbdf9164adc0436b919889d46462f305dd32e 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 8704f57c8c038414a78e1753a413a9419fbd2c6d..cc27f40954f6e49ac22693388e53099634a95d79 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 a426e2e0d49ce55c64374dca325b23f7aa17eda5..e9283b14c9b7d2a9daa35c1ad01eaf3ac142383f 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 d633381365b078b5e48efcb542c3236b7d49801c..037c6fd4fb765f47af8309ea614019a7d7736cb0 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 e9ddcc8195a1c940369b9148b9f5b9fc93ff6e2f..8257ef4a6e7793bb9818dcda92418af4e6baeb02 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 cd959e75a174e38adf8f24766e4caa070fbdffcc..25c2ce1ca73c04d4f2b268909fa6972f18b87c0d 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 3a5d9468fc028a44ba7349e0f7549ba14a9540b8..f36865a89b4bd21cae7d10f838ef5f5e1bdfd5e5 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 a292358e7501bc2ca8e820d7c5696997bfe5993b..c1ec2a6210cdcee41dbd208ddbd60eec3a3282cd 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 03a0f8314a1e479ef3e1303cdcc7c1bd88437f6a..429f5d416e057920025edcbeafad26868acac19d 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 78bf83dc303cbda505347aefea9a7c7c7875e822..8409a1dff5ac2c46c9b3731be660388a9707e61c 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 423b4945005ec72a115b746be7d2f8f668a38be6..8d650074c3906026ec91caeb994e9a7e9ed6d847 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 02cad4fb9d90049bf264db8e0e322afaaa834cad..3aab16cc3e763f1f18c954aad4a1290ae021936e 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 0dea7bfbcfa80ef39150f68d26f5b74978c53b65..c6f33a0fc76ff714e170d142177ff28cea2033b2 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 05fda2c75f6893c5d502b6b223044f8bceaa5ad6..f12e4be03bd84d52bb3621023a42767101aa3f41 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 ad960ec93ed19a23fffb7059c516fb1a9a41af43..5fda4b613c03b88504e80a428aaaea05186efc0d 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 36de158afe725cbc8ce40e10d117090747a42408..1e9883860fa6bd04ec034dd247d02501d8ce4aaf 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 59cb5fbc2c9ec42fe33f170f69fc39b901efea78..8f6fc7460015a95532a02167a5016d08eab9641f 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 5fb263501f0adf99851010dbfc00b2e4561ca80d..763727fc68d45dddc843a1c0382b805878b3f079 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 0a0bacf53c0b5dc75ad59091c55319ca06f1c654..4cbaa382e84e3d099cc3a2aa2aa0276ce1f613f5 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 daefebdbdda7fabc308b4103efccd5aa7fafb11a..bd94e48b9e7075c5b8e6fc9b74a5fbba3408d5f5 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 5894a2a44e6fffc2cb6566eb4c82edd90213d6f7..60680f82a22a3541958e59e6ec219d7174b4b20c 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 63719b70031a6543a7b72d8a67ef77795d33f5c9..f2e82bd63fe169bccfd5f29bf6af0399a8a7d547 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 2814feaa0897cb8f88eac5c13178c1dffc35dcd1..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..31febf263f3263efb27843ed25189cf6562f821f --- /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 0000000000000000000000000000000000000000..a51b714dae7edfa339acf6c0675d6cd1946ca1b1 --- /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 0000000000000000000000000000000000000000..2e294878d797c200235a117dde8fb4d0baa073a3 --- /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 d8c8e5272c43b8623eda8fc2f5af6addaad021a1..a5ac1f71589525cbd58a6c81bdcf30e876edded3 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 7072a3fe94cc66746bbe04f6d045af58b89bd433..fce5717ca9aeeb68052b508d1806e1a6894b074c 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 356f4d97cde31206e575c7aa18772570e21bda7c..8bca2eafacb42c1496ad9b3d1ae145ce4b7d4570 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 5d70eab88b94891673130266c1a9700361a05578..0000000000000000000000000000000000000000 --- 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 30f5bef53ba484ee090625c7ade9cfc5afb8b48f..53bc9d5abce2a96f24d9d7239d6e56140475243e 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 31febf263f3263efb27843ed25189cf6562f821f..e1a917292c91f2f30f746db129982057aa1a9ca8 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 2e294878d797c200235a117dde8fb4d0baa073a3..d338e7ab0b918db66b050908533af7ebc4417c4a 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 ca960a7c1084ce55b4962439d97c7ec4d8b7ba36..8185fbad9ac61244b1acad73baa9d2323222e974 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 ed8fb1bb120938f502647f32519084f50c6ae0f2..e463310b9810eb2d6f30e924b1b66166c9b2e295 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 863a036e8e79062035a1ed52bb2959a8dd0682f2..2d0222ef3ad9118a5bd70bf5e84d624fd6a69449 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 a51426b6f642753db05959d49c3446f3d02dfc68..267d83050667ccb130a8f0c4b20cf37574aaf2d7 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 05219498718289a84174ebf3fc9ad8f82ec6e697..0000000000000000000000000000000000000000 --- 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 ee6ec2f6edc364b9058487d1eb3b1d7d4df79378..b224f91a2f2bca3a597fed6497fc0d9edb46ad5b 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 110e10564c35ecf6b04892c4d06dd6a53ee5e679..3c382b83b651741b385adc83a3b59449ff480bda 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;