From 828f406b4fc068febcd20521a03ad2f2a5910c72 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 9 Jan 2023 10:54:13 -0800 Subject: [PATCH 01/83] Fixed issue where serialized terminal working directories would be lost in complex interactions Co-authored-by: Kay Co-authored-by: Julia --- crates/terminal_view/src/persistence.rs | 6 +++--- crates/terminal_view/src/terminal_view.rs | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/terminal_view/src/persistence.rs b/crates/terminal_view/src/persistence.rs index f090b384a4..26bd0931fe 100644 --- a/crates/terminal_view/src/persistence.rs +++ b/crates/terminal_view/src/persistence.rs @@ -42,10 +42,10 @@ impl TerminalDb { } query! { - pub async fn take_working_directory(item_id: ItemId, workspace_id: WorkspaceId) -> Result> { - DELETE FROM terminals + pub fn get_working_directory(item_id: ItemId, workspace_id: WorkspaceId) -> Result> { + SELECT working_directory + FROM terminals WHERE item_id = ? AND workspace_id = ? - RETURNING working_directory } } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index a4f90a8d72..e02a9758dd 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -706,8 +706,7 @@ impl Item for TerminalView { let window_id = cx.window_id(); cx.spawn(|pane, mut cx| async move { let cwd = TERMINAL_DB - .take_working_directory(item_id, workspace_id) - .await + .get_working_directory(item_id, workspace_id) .log_err() .flatten(); From d237bdaa9b17d4a0f7d6f49ca421c20d892661f4 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 9 Jan 2023 12:41:37 -0800 Subject: [PATCH 02/83] Added support for ALTER TABLE syntax in the syntax error checker function Co-authored-by: Kay --- crates/editor/src/persistence.rs | 9 ++- crates/sqlez/src/connection.rs | 99 +++++++++++++++++++++++++++----- 2 files changed, 93 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index 31ada105af..375bd77ff0 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -15,8 +15,13 @@ define_connection!( FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE ON UPDATE CASCADE - ) STRICT; - )]; + ) STRICT; + ), + sql! ( + ALTER TABLE editors ADD COLUMN scroll_top_row INTEGER; + ALTER TABLE editors ADD COLUMN scroll_vertical_offset REAL; + ALTER TABLE editors ADD COLUMN scroll_horizontal_offset REAL; + )]; ); impl EditorDb { diff --git a/crates/sqlez/src/connection.rs b/crates/sqlez/src/connection.rs index 3c6cb31196..55ba5f8294 100644 --- a/crates/sqlez/src/connection.rs +++ b/crates/sqlez/src/connection.rs @@ -93,36 +93,77 @@ impl Connection { let sql_start = remaining_sql.as_ptr(); unsafe { + let mut alter_table = None; while { let remaining_sql_str = remaining_sql.to_str().unwrap().trim(); - remaining_sql_str != ";" && !remaining_sql_str.is_empty() + let any_remaining_sql = remaining_sql_str != ";" && !remaining_sql_str.is_empty(); + if any_remaining_sql { + alter_table = parse_alter_table(remaining_sql_str); + } + any_remaining_sql } { let mut raw_statement = ptr::null_mut::(); let mut remaining_sql_ptr = ptr::null(); - sqlite3_prepare_v2( - self.sqlite3, - remaining_sql.as_ptr(), - -1, - &mut raw_statement, - &mut remaining_sql_ptr, - ); - let res = sqlite3_errcode(self.sqlite3); - let offset = sqlite3_error_offset(self.sqlite3); - let message = sqlite3_errmsg(self.sqlite3); + let (res, offset, message, _conn) = if let Some(table_to_alter) = alter_table { + // ALTER TABLE is a weird statement. When preparing the statement the table's + // existence is checked *before* syntax checking any other part of the statement. + // Therefore, we need to make sure that the table has been created before calling + // prepare. As we don't want to trash whatever database this is connected to, we + // create a new in-memory DB to test. + + let temp_connection = Connection::open_memory(None); + //This should always succeed, if it doesn't then you really should know about it + temp_connection + .exec(&format!( + "CREATE TABLE {table_to_alter}(__place_holder_column_for_syntax_checking)" + )) + .unwrap()() + .unwrap(); + + sqlite3_prepare_v2( + temp_connection.sqlite3, + remaining_sql.as_ptr(), + -1, + &mut raw_statement, + &mut remaining_sql_ptr, + ); + + ( + sqlite3_errcode(temp_connection.sqlite3), + sqlite3_error_offset(temp_connection.sqlite3), + sqlite3_errmsg(temp_connection.sqlite3), + Some(temp_connection), + ) + } else { + sqlite3_prepare_v2( + self.sqlite3, + remaining_sql.as_ptr(), + -1, + &mut raw_statement, + &mut remaining_sql_ptr, + ); + ( + sqlite3_errcode(self.sqlite3), + sqlite3_error_offset(self.sqlite3), + sqlite3_errmsg(self.sqlite3), + None, + ) + }; sqlite3_finalize(raw_statement); if res == 1 && offset >= 0 { + let sub_statement_correction = + remaining_sql.as_ptr() as usize - sql_start as usize; let err_msg = String::from_utf8_lossy(CStr::from_ptr(message as *const _).to_bytes()) .into_owned(); - let sub_statement_correction = - remaining_sql.as_ptr() as usize - sql_start as usize; return Some((err_msg, offset as usize + sub_statement_correction)); } remaining_sql = CStr::from_ptr(remaining_sql_ptr); + alter_table = None; } } None @@ -162,6 +203,25 @@ impl Connection { } } +fn parse_alter_table(remaining_sql_str: &str) -> Option { + let remaining_sql_str = remaining_sql_str.to_lowercase(); + if remaining_sql_str.starts_with("alter") { + if let Some(table_offset) = remaining_sql_str.find("table") { + let after_table_offset = table_offset + "table".len(); + let table_to_alter = remaining_sql_str + .chars() + .skip(after_table_offset) + .skip_while(|c| c.is_whitespace()) + .take_while(|c| !c.is_whitespace()) + .collect::(); + if !table_to_alter.is_empty() { + return Some(table_to_alter); + } + } + } + None +} + impl Drop for Connection { fn drop(&mut self) { unsafe { sqlite3_close(self.sqlite3) }; @@ -331,4 +391,17 @@ mod test { assert_eq!(res, Some(first_stmt.len() + second_offset + 1)); } + + #[test] + fn test_alter_table_syntax() { + let connection = Connection::open_memory(Some("test_alter_table_syntax")); + + assert!(connection + .sql_has_syntax_error("ALTER TABLE test ADD x TEXT") + .is_none()); + + assert!(connection + .sql_has_syntax_error("ALTER TABLE test AAD x TEXT") + .is_some()); + } } From ebbe6e7aa95da46f6a3db0fb48d9990e868ee35f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 9 Jan 2023 14:06:40 -0800 Subject: [PATCH 03/83] Add serializing and restoring editor scroll position Co-authored-by: Kay --- crates/editor/src/editor.rs | 2 - crates/editor/src/items.rs | 8 ++- crates/editor/src/persistence.rs | 52 ++++++++++++-- crates/editor/src/scroll.rs | 114 +++++++++++++++++++++++++------ crates/sqlez/src/bindable.rs | 35 ++++++++++ 5 files changed, 181 insertions(+), 30 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9feb3f11d0..f11061cb84 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3629,9 +3629,7 @@ impl Editor { } pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext) { - dbg!("undo"); if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) { - dbg!(tx_id); if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() { self.change_selections(None, cx, |s| { s.select_anchors(selections.to_vec()); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index ec678c6180..f8fa3c9df0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -8,6 +8,7 @@ use anyhow::{anyhow, Context, Result}; use collections::HashSet; use futures::future::try_join_all; use futures::FutureExt; + use gpui::{ elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -765,6 +766,7 @@ impl Item for Editor { fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext) { let workspace_id = workspace.database_id(); let item_id = cx.view_id(); + self.workspace_id = Some(workspace_id); fn serialize( buffer: ModelHandle, @@ -836,7 +838,11 @@ impl Item for Editor { .context("Project item at stored path was not a buffer")?; Ok(cx.update(|cx| { - cx.add_view(pane, |cx| Editor::for_buffer(buffer, Some(project), cx)) + cx.add_view(pane, |cx| { + let mut editor = Editor::for_buffer(buffer, Some(project), cx); + editor.read_scroll_position_from_db(item_id, workspace_id, cx); + editor + }) })) }) }) diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index 375bd77ff0..2d8d1a74fd 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -2,9 +2,19 @@ use std::path::PathBuf; use db::sqlez_macros::sql; use db::{define_connection, query}; + use workspace::{ItemId, WorkspaceDb, WorkspaceId}; define_connection!( + // Current table shape using pseudo-rust syntax: + // editors( + // item_id: usize, + // workspace_id: usize, + // path: PathBuf, + // scroll_top_row: usize, + // scroll_vertical_offset: f32, + // scroll_horizontal_offset: f32, + // ) pub static ref DB: EditorDb = &[sql! ( CREATE TABLE editors( @@ -18,9 +28,9 @@ define_connection!( ) STRICT; ), sql! ( - ALTER TABLE editors ADD COLUMN scroll_top_row INTEGER; - ALTER TABLE editors ADD COLUMN scroll_vertical_offset REAL; - ALTER TABLE editors ADD COLUMN scroll_horizontal_offset REAL; + ALTER TABLE editors ADD COLUMN scroll_top_row INTEGER NOT NULL DEFAULT 0; + ALTER TABLE editors ADD COLUMN scroll_horizontal_offset REAL NOT NULL DEFAULT 0; + ALTER TABLE editors ADD COLUMN scroll_vertical_offset REAL NOT NULL DEFAULT 0; )]; ); @@ -34,8 +44,40 @@ impl EditorDb { query! { pub async fn save_path(item_id: ItemId, workspace_id: WorkspaceId, path: PathBuf) -> Result<()> { - INSERT OR REPLACE INTO editors(item_id, workspace_id, path) - VALUES (?, ?, ?) + INSERT INTO editors + (item_id, workspace_id, path) + VALUES + (?1, ?2, ?3) + ON CONFLICT DO UPDATE SET + item_id = ?1, + workspace_id = ?2, + path = ?3 + } + } + + // Returns the scroll top row, and offset + query! { + pub fn get_scroll_position(item_id: ItemId, workspace_id: WorkspaceId) -> Result> { + SELECT scroll_top_row, scroll_horizontal_offset, scroll_vertical_offset + FROM editors + WHERE item_id = ? AND workspace_id = ? + } + } + + query! { + pub async fn save_scroll_position( + item_id: ItemId, + workspace_id: WorkspaceId, + top_row: u32, + vertical_offset: f32, + horizontal_offset: f32 + ) -> Result<()> { + UPDATE OR IGNORE editors + SET + scroll_top_row = ?3, + scroll_horizontal_offset = ?4, + scroll_vertical_offset = ?5 + WHERE item_id = ?1 AND workspace_id = ?2 } } } diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index eb89bfeec2..0ca837a843 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -11,11 +11,14 @@ use gpui::{ geometry::vector::{vec2f, Vector2F}, Axis, MutableAppContext, Task, ViewContext, }; -use language::Bias; +use language::{Bias, Point}; +use util::ResultExt; +use workspace::WorkspaceId; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, + persistence::DB, Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint, }; @@ -170,37 +173,68 @@ impl ScrollManager { scroll_position: Vector2F, map: &DisplaySnapshot, local: bool, + workspace_id: Option, cx: &mut ViewContext, ) { - let new_anchor = if scroll_position.y() <= 0. { - ScrollAnchor { - top_anchor: Anchor::min(), - offset: scroll_position.max(vec2f(0., 0.)), - } + let (new_anchor, top_row) = if scroll_position.y() <= 0. { + ( + ScrollAnchor { + top_anchor: Anchor::min(), + offset: scroll_position.max(vec2f(0., 0.)), + }, + 0, + ) } else { - let scroll_top_buffer_offset = - DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right); + let scroll_top_buffer_point = + DisplayPoint::new(scroll_position.y() as u32, 0).to_point(&map); let top_anchor = map .buffer_snapshot - .anchor_at(scroll_top_buffer_offset, Bias::Right); + .anchor_at(scroll_top_buffer_point, Bias::Right); - ScrollAnchor { - top_anchor, - offset: vec2f( - scroll_position.x(), - scroll_position.y() - top_anchor.to_display_point(&map).row() as f32, - ), - } + ( + ScrollAnchor { + top_anchor, + offset: vec2f( + scroll_position.x(), + scroll_position.y() - top_anchor.to_display_point(&map).row() as f32, + ), + }, + scroll_top_buffer_point.row, + ) }; - self.set_anchor(new_anchor, local, cx); + self.set_anchor(new_anchor, top_row, local, workspace_id, cx); } - fn set_anchor(&mut self, anchor: ScrollAnchor, local: bool, cx: &mut ViewContext) { + fn set_anchor( + &mut self, + anchor: ScrollAnchor, + top_row: u32, + local: bool, + workspace_id: Option, + cx: &mut ViewContext, + ) { self.anchor = anchor; cx.emit(Event::ScrollPositionChanged { local }); self.show_scrollbar(cx); self.autoscroll_request.take(); + if let Some(workspace_id) = workspace_id { + let item_id = cx.view_id(); + + cx.background() + .spawn(async move { + DB.save_scroll_position( + item_id, + workspace_id, + top_row, + anchor.offset.x(), + anchor.offset.y(), + ) + .await + .log_err() + }) + .detach() + } cx.notify(); } @@ -274,8 +308,13 @@ impl Editor { let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); hide_hover(self, cx); - self.scroll_manager - .set_scroll_position(scroll_position, &map, local, cx); + self.scroll_manager.set_scroll_position( + scroll_position, + &map, + local, + self.workspace_id, + cx, + ); } pub fn scroll_position(&self, cx: &mut ViewContext) -> Vector2F { @@ -285,7 +324,12 @@ impl Editor { pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext) { hide_hover(self, cx); - self.scroll_manager.set_anchor(scroll_anchor, true, cx); + let top_row = scroll_anchor + .top_anchor + .to_point(&self.buffer().read(cx).snapshot(cx)) + .row; + self.scroll_manager + .set_anchor(scroll_anchor, top_row, true, self.workspace_id, cx); } pub(crate) fn set_scroll_anchor_remote( @@ -294,7 +338,12 @@ impl Editor { cx: &mut ViewContext, ) { hide_hover(self, cx); - self.scroll_manager.set_anchor(scroll_anchor, false, cx); + let top_row = scroll_anchor + .top_anchor + .to_point(&self.buffer().read(cx).snapshot(cx)) + .row; + self.scroll_manager + .set_anchor(scroll_anchor, top_row, false, self.workspace_id, cx); } pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext) { @@ -345,4 +394,25 @@ impl Editor { Ordering::Greater } + + pub fn read_scroll_position_from_db( + &mut self, + item_id: usize, + workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) { + let scroll_position = DB.get_scroll_position(item_id, workspace_id); + if let Ok(Some((top_row, x, y))) = scroll_position { + let top_anchor = self + .buffer() + .read(cx) + .snapshot(cx) + .anchor_at(Point::new(top_row as u32, 0), Bias::Left); + let scroll_anchor = ScrollAnchor { + offset: Vector2F::new(x, y), + top_anchor, + }; + self.set_scroll_anchor(scroll_anchor, cx); + } + } } diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 3649037e50..62212d8f18 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -89,6 +89,26 @@ impl Column for f64 { } } +impl Bind for f32 { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + statement + .bind_double(start_index, *self as f64) + .with_context(|| format!("Failed to bind f64 at index {start_index}"))?; + Ok(start_index + 1) + } +} + +impl Column for f32 { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let result = statement + .column_double(start_index) + .with_context(|| format!("Failed to parse f32 at index {start_index}"))? + as f32; + + Ok((result, start_index + 1)) + } +} + impl Bind for i32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -122,6 +142,21 @@ impl Column for i64 { } } +impl Bind for u32 { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + (*self as i64) + .bind(statement, start_index) + .with_context(|| format!("Failed to bind usize at index {start_index}")) + } +} + +impl Column for u32 { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let result = statement.column_int64(start_index)?; + Ok((result as u32, start_index + 1)) + } +} + impl Bind for usize { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) From 176738d674d177d44ff0d71dfaa4ae78e4217d89 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Mon, 9 Jan 2023 16:18:04 -0800 Subject: [PATCH 04/83] Address issue with workspaces where single file worktrees such as those from git commit messages would get restored Co-authored-by: Mikayla --- crates/workspace/src/persistence.rs | 19 ++++++++++--------- crates/workspace/src/workspace.rs | 4 ++-- crates/zed/src/main.rs | 22 ++++++++++++++-------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index cd425c3a28..03a866f2f6 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -216,7 +216,9 @@ impl WorkspaceDb { let mut result = Vec::new(); let mut delete_tasks = Vec::new(); for (id, location) in self.recent_workspaces()? { - if location.paths().iter().all(|path| path.exists()) { + if location.paths().iter().all(|path| path.exists()) + && location.paths().iter().any(|path| path.is_dir()) + { result.push((id, location)); } else { delete_tasks.push(self.delete_stale_workspace(id)); @@ -227,14 +229,13 @@ impl WorkspaceDb { Ok(result) } - query! { - pub fn last_workspace() -> Result> { - SELECT workspace_location - FROM workspaces - WHERE workspace_location IS NOT NULL - ORDER BY timestamp DESC - LIMIT 1 - } + pub async fn last_workspace(&self) -> Result> { + Ok(self + .recent_workspaces_on_disk() + .await? + .into_iter() + .next() + .map(|(_, location)| location)) } fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8bf4cbce32..ac6a9f38ed 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2680,8 +2680,8 @@ pub fn activate_workspace_for_project( None } -pub fn last_opened_workspace_paths() -> Option { - DB.last_workspace().log_err().flatten() +pub async fn last_opened_workspace_paths() -> Option { + DB.last_workspace().await.log_err().flatten() } #[allow(clippy::type_complexity)] diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f4fa919c48..3861c53e79 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -170,7 +170,8 @@ fn main() { cx.platform().activate(true); let paths = collect_path_args(); if paths.is_empty() { - restore_or_create_workspace(cx); + cx.spawn(|cx| async move { restore_or_create_workspace(cx).await }) + .detach() } else { cx.dispatch_global_action(OpenPaths { paths }); } @@ -179,7 +180,8 @@ fn main() { cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) .detach(); } else { - restore_or_create_workspace(cx); + cx.spawn(|cx| async move { restore_or_create_workspace(cx).await }) + .detach() } cx.spawn(|cx| async move { while let Some(connection) = cli_connections_rx.next().await { @@ -203,13 +205,17 @@ fn main() { }); } -fn restore_or_create_workspace(cx: &mut gpui::MutableAppContext) { - if let Some(location) = workspace::last_opened_workspace_paths() { - cx.dispatch_global_action(OpenPaths { - paths: location.paths().as_ref().clone(), - }) +async fn restore_or_create_workspace(mut cx: AsyncAppContext) { + if let Some(location) = workspace::last_opened_workspace_paths().await { + cx.update(|cx| { + cx.dispatch_global_action(OpenPaths { + paths: location.paths().as_ref().clone(), + }) + }); } else { - cx.dispatch_global_action(NewFile); + cx.update(|cx| { + cx.dispatch_global_action(NewFile); + }); } } From 866f0e1344f1818aec4186a54b316b4bdfd1dc0a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 15:06:48 -0800 Subject: [PATCH 05/83] Add the ability to opt-out of panic reporting Co-authored-by: Kay --- assets/settings/default.json | 7 ++ crates/settings/src/settings.rs | 32 ++++++- crates/zed/src/main.rs | 152 +++++++++++++++++--------------- 3 files changed, 119 insertions(+), 72 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 5b73d7643c..3924e84d61 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -79,6 +79,13 @@ "hard_tabs": false, // How many columns a tab should occupy. "tab_size": 4, + // Control what info Zed sends to our servers + "telemetry": { + // Send debug info like crash reports. + "diagnostics": true, + // Send anonymized usage data like what languages you're using Zed with. + "metrics": true + }, // Git gutter behavior configuration. "git": { // Control whether the git gutter is shown. May take 2 values: diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index f0c64a1bb9..dd2be6e318 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -51,9 +51,17 @@ pub struct Settings { pub language_overrides: HashMap, EditorSettings>, pub lsp: HashMap, LspSettings>, pub theme: Arc, + pub telemetry_defaults: TelemetrySettings, + pub telemetry_overrides: TelemetrySettings, pub staff_mode: bool, } +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct TelemetrySettings { + diagnostics: Option, + metrics: Option, +} + #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct FeatureFlags { pub experimental_themes: bool, @@ -302,6 +310,8 @@ pub struct SettingsFileContent { #[serde(default)] pub theme: Option, #[serde(default)] + pub telemetry: TelemetrySettings, + #[serde(default)] pub staff_mode: Option, } @@ -312,6 +322,7 @@ pub struct LspSettings { } impl Settings { + /// Fill out the settings corresponding to the default.json file, overrides will be set later pub fn defaults( assets: impl AssetSource, font_cache: &FontCache, @@ -363,11 +374,13 @@ impl Settings { language_overrides: Default::default(), lsp: defaults.lsp.clone(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), - + telemetry_defaults: defaults.telemetry, + telemetry_overrides: Default::default(), staff_mode: false, } } + // Fill out the overrride and etc. settings from the user's settings.json pub fn set_user_settings( &mut self, data: SettingsFileContent, @@ -419,6 +432,7 @@ impl Settings { self.terminal_overrides.copy_on_select = data.terminal.copy_on_select; self.terminal_overrides = data.terminal; self.language_overrides = data.languages; + self.telemetry_overrides = data.telemetry; self.lsp = data.lsp; } @@ -489,6 +503,20 @@ impl Settings { .unwrap_or_else(|| R::default()) } + pub fn telemetry_diagnostics(&self) -> bool { + self.telemetry_overrides + .diagnostics + .or(self.telemetry_defaults.diagnostics) + .expect("missing default") + } + + pub fn telemetry_metrics(&self) -> bool { + self.telemetry_overrides + .metrics + .or(self.telemetry_defaults.metrics) + .expect("missing default") + } + pub fn terminal_scroll(&self) -> AlternateScroll { self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.as_ref()) } @@ -540,6 +568,8 @@ impl Settings { lsp: Default::default(), projects_online_by_default: true, theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), + telemetry_defaults: Default::default(), + telemetry_overrides: Default::default(), staff_mode: false, } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 3861c53e79..72115022d7 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -18,7 +18,7 @@ use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, }; -use gpui::{executor::Background, App, AssetSource, AsyncAppContext, Task, ViewContext}; +use gpui::{App, AssetSource, AsyncAppContext, MutableAppContext, Task, ViewContext}; use isahc::{config::Configurable, Request}; use language::LanguageRegistry; use log::LevelFilter; @@ -50,10 +50,13 @@ fn main() { log::info!("========== starting zed =========="); let mut app = gpui::App::new(Assets).unwrap(); + let app_version = ZED_APP_VERSION .or_else(|| app.platform().app_version().ok()) .map_or("dev".to_string(), |v| v.to_string()); - init_panic_hook(app_version, http.clone(), app.background()); + init_panic_hook(app_version); + + app.background(); load_embedded_fonts(&app); @@ -61,7 +64,6 @@ fn main() { let themes = ThemeRegistry::new(Assets, app.font_cache()); let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes); - let config_files = load_config_files(&app, fs.clone()); let login_shell_env_loaded = if stdout_is_a_pty() { @@ -88,6 +90,18 @@ fn main() { cx.set_global(*RELEASE_CHANNEL); cx.set_global(HomeDir(paths::HOME.to_path_buf())); + let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap(); + + //Setup settings global before binding actions + cx.set_global(SettingsFile::new( + &paths::SETTINGS, + settings_file_content.clone(), + fs.clone(), + )); + + watch_settings_file(default_settings, settings_file_content, themes.clone(), cx); + upload_previous_panics(http.clone(), cx); + let client = client::Client::new(http.clone(), cx); let mut languages = LanguageRegistry::new(login_shell_env_loaded); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); @@ -97,15 +111,6 @@ fn main() { .spawn(languages::init(languages.clone(), cx.background().clone())); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); - let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap(); - - //Setup settings global before binding actions - cx.set_global(SettingsFile::new( - &paths::SETTINGS, - settings_file_content.clone(), - fs.clone(), - )); - watch_settings_file(default_settings, settings_file_content, themes.clone(), cx); watch_keymap_file(keymap_file, cx); context_menu::init(cx); @@ -251,65 +256,7 @@ fn init_logger() { } } -fn init_panic_hook(app_version: String, http: Arc, background: Arc) { - background - .spawn({ - async move { - let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); - let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; - while let Some(child) = children.next().await { - let child = child?; - let child_path = child.path(); - if child_path.extension() != Some(OsStr::new("panic")) { - continue; - } - let filename = if let Some(filename) = child_path.file_name() { - filename.to_string_lossy() - } else { - continue; - }; - - let mut components = filename.split('-'); - if components.next() != Some("zed") { - continue; - } - let version = if let Some(version) = components.next() { - version - } else { - continue; - }; - - let text = smol::fs::read_to_string(&child_path) - .await - .context("error reading panic file")?; - let body = serde_json::to_string(&json!({ - "text": text, - "version": version, - "token": ZED_SECRET_CLIENT_TOKEN, - })) - .unwrap(); - let request = Request::post(&panic_report_url) - .redirect_policy(isahc::config::RedirectPolicy::Follow) - .header("Content-Type", "application/json") - .body(body.into())?; - let response = http.send(request).await.context("error sending panic")?; - if response.status().is_success() { - std::fs::remove_file(child_path) - .context("error removing panic after sending it successfully") - .log_err(); - } else { - return Err(anyhow!( - "error uploading panic to server: {}", - response.status() - )); - } - } - Ok::<_, anyhow::Error>(()) - } - .log_err() - }) - .detach(); - +fn init_panic_hook(app_version: String) { let is_pty = stdout_is_a_pty(); panic::set_hook(Box::new(move |info| { let backtrace = Backtrace::new(); @@ -358,6 +305,69 @@ fn init_panic_hook(app_version: String, http: Arc, background: A })); } +fn upload_previous_panics(http: Arc, cx: &mut MutableAppContext) { + let diagnostics_telemetry = cx.global::().telemetry_diagnostics(); + + cx.background() + .spawn({ + async move { + let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); + let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; + while let Some(child) = children.next().await { + let child = child?; + let child_path = child.path(); + + if child_path.extension() != Some(OsStr::new("panic")) { + continue; + } + let filename = if let Some(filename) = child_path.file_name() { + filename.to_string_lossy() + } else { + continue; + }; + + let mut components = filename.split('-'); + if components.next() != Some("zed") { + continue; + } + let version = if let Some(version) = components.next() { + version + } else { + continue; + }; + + if diagnostics_telemetry { + let text = smol::fs::read_to_string(&child_path) + .await + .context("error reading panic file")?; + let body = serde_json::to_string(&json!({ + "text": text, + "version": version, + "token": ZED_SECRET_CLIENT_TOKEN, + })) + .unwrap(); + let request = Request::post(&panic_report_url) + .redirect_policy(isahc::config::RedirectPolicy::Follow) + .header("Content-Type", "application/json") + .body(body.into())?; + let response = http.send(request).await.context("error sending panic")?; + if !response.status().is_success() { + log::error!("Error uploading panic to server: {}", response.status()); + } + } + + // We've done what we can, delete the file + std::fs::remove_file(child_path) + .context("error removing panic") + .log_err(); + } + Ok::<_, anyhow::Error>(()) + } + .log_err() + }) + .detach(); +} + async fn load_login_shell_environment() -> Result<()> { let marker = "ZED_LOGIN_SHELL_START"; let shell = env::var("SHELL").context( From 551dc1f3187daea5e2b2ee4b581d4e9135730934 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Jan 2023 15:32:14 -0800 Subject: [PATCH 06/83] Fix crash when activating prev/next pane while dock is active Co-authored-by: Antonio Scandurra --- crates/workspace/src/dock.rs | 14 ++++++++++++++ crates/workspace/src/workspace.rs | 31 ++++++++++++------------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 19fed4bf59..ecc8d3f7b3 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -623,6 +623,20 @@ mod tests { cx.assert_dock_pane_active(); } + #[gpui::test] + async fn test_activate_next_and_prev_pane(cx: &mut TestAppContext) { + let mut cx = DockTestContext::new(cx).await; + + cx.move_dock(DockAnchor::Right); + cx.assert_dock_pane_active(); + + cx.update_workspace(|workspace, cx| workspace.activate_next_pane(cx)); + cx.assert_dock_pane_active(); + + cx.update_workspace(|workspace, cx| workspace.activate_previous_pane(cx)); + cx.assert_dock_pane_active(); + } + struct DockTestContext<'a> { pub cx: &'a mut TestAppContext, pub window_id: usize, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ac6a9f38ed..027b9ba86a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -44,6 +44,7 @@ use language::LanguageRegistry; use std::{ any::TypeId, borrow::Cow, + cmp, future::Future, path::{Path, PathBuf}, sync::Arc, @@ -1415,29 +1416,21 @@ impl Workspace { } pub fn activate_next_pane(&mut self, cx: &mut ViewContext) { - let next_pane = { - let panes = self.center.panes(); - let ix = panes - .iter() - .position(|pane| **pane == self.active_pane) - .unwrap(); + let panes = self.center.panes(); + if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { let next_ix = (ix + 1) % panes.len(); - panes[next_ix].clone() - }; - cx.focus(next_pane); + let next_pane = panes[next_ix].clone(); + cx.focus(next_pane); + } } pub fn activate_previous_pane(&mut self, cx: &mut ViewContext) { - let prev_pane = { - let panes = self.center.panes(); - let ix = panes - .iter() - .position(|pane| **pane == self.active_pane) - .unwrap(); - let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 }; - panes[prev_ix].clone() - }; - cx.focus(prev_pane); + let panes = self.center.panes(); + if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { + let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1); + let prev_pane = panes[prev_ix].clone(); + cx.focus(prev_pane); + } } fn handle_pane_focused(&mut self, pane: ViewHandle, cx: &mut ViewContext) { From 37a4de1a848ab9e2b0e3e0f8174b49f5454a5371 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 15:49:54 -0800 Subject: [PATCH 07/83] Add opt-out for metric reporting co-authored-by: kay --- crates/client/src/client.rs | 31 ++++++++++++++++++++++++++----- crates/client/src/telemetry.rs | 19 ++++++++++++++++++- crates/client/src/user.rs | 14 ++++++-------- crates/editor/src/editor.rs | 9 +++++---- crates/settings/src/settings.rs | 16 ++++++++++++++++ crates/zed/src/main.rs | 6 +++++- 6 files changed, 76 insertions(+), 19 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index aa46d64fcb..213b2cf042 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -25,6 +25,7 @@ use postage::watch; use rand::prelude::*; use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage}; use serde::Deserialize; +use settings::{Settings, TelemetrySettings}; use std::{ any::TypeId, collections::HashMap, @@ -423,7 +424,9 @@ impl Client { })); } Status::SignedOut | Status::UpgradeRequired => { - self.telemetry.set_authenticated_user_info(None, false); + let telemetry_settings = cx.read(|cx| cx.global::().telemetry()); + self.telemetry + .set_authenticated_user_info(None, false, telemetry_settings); state._reconnect_task.take(); } _ => {} @@ -706,7 +709,13 @@ impl Client { credentials = read_credentials_from_keychain(cx); read_from_keychain = credentials.is_some(); if read_from_keychain { - self.report_event("read credentials from keychain", Default::default()); + cx.read(|cx| { + self.report_event( + "read credentials from keychain", + Default::default(), + cx.global::().telemetry(), + ); + }); } } if credentials.is_none() { @@ -997,6 +1006,8 @@ impl Client { let executor = cx.background(); let telemetry = self.telemetry.clone(); let http = self.http.clone(); + let metrics_enabled = cx.read(|cx| cx.global::().telemetry()); + executor.clone().spawn(async move { // Generate a pair of asymmetric encryption keys. The public key will be used by the // zed server to encrypt the user's access token, so that it can'be intercepted by @@ -1079,7 +1090,11 @@ impl Client { .context("failed to decrypt access token")?; platform.activate(true); - telemetry.report_event("authenticate with browser", Default::default()); + telemetry.report_event( + "authenticate with browser", + Default::default(), + metrics_enabled, + ); Ok(Credentials { user_id: user_id.parse()?, @@ -1287,8 +1302,14 @@ impl Client { self.telemetry.start(); } - pub fn report_event(&self, kind: &str, properties: Value) { - self.telemetry.report_event(kind, properties.clone()); + pub fn report_event( + &self, + kind: &str, + properties: Value, + telemetry_settings: TelemetrySettings, + ) { + self.telemetry + .report_event(kind, properties.clone(), telemetry_settings); } pub fn telemetry_log_file_path(&self) -> Option { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index ce8b713996..e5d2dad41e 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -10,6 +10,7 @@ use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; use serde_json::json; +use settings::TelemetrySettings; use std::{ io::Write, mem, @@ -184,11 +185,18 @@ impl Telemetry { .detach(); } + /// This method takes the entire TelemetrySettings struct in order to force client code + /// to pull the struct out of the settings global. Do not remove! pub fn set_authenticated_user_info( self: &Arc, metrics_id: Option, is_staff: bool, + telemetry_settings: TelemetrySettings, ) { + if !telemetry_settings.metrics() { + return; + } + let this = self.clone(); let mut state = self.state.lock(); let device_id = state.device_id.clone(); @@ -221,7 +229,16 @@ impl Telemetry { } } - pub fn report_event(self: &Arc, kind: &str, properties: Value) { + pub fn report_event( + self: &Arc, + kind: &str, + properties: Value, + telemetry_settings: TelemetrySettings, + ) { + if !telemetry_settings.metrics() { + return; + } + let mut state = self.state.lock(); let event = MixpanelEvent { event: kind.to_string(), diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 4d29669c2f..1201665571 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -5,6 +5,7 @@ use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt}; use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task}; use postage::{sink::Sink, watch}; use rpc::proto::{RequestMessage, UsersResponse}; +use settings::Settings; use std::sync::{Arc, Weak}; use util::TryFutureExt as _; @@ -141,14 +142,11 @@ impl UserStore { let fetch_metrics_id = client.request(proto::GetPrivateUserInfo {}).log_err(); let (user, info) = futures::join!(fetch_user, fetch_metrics_id); - if let Some(info) = info { - client.telemetry.set_authenticated_user_info( - Some(info.metrics_id.clone()), - info.staff, - ); - } else { - client.telemetry.set_authenticated_user_info(None, false); - } + client.telemetry.set_authenticated_user_info( + info.as_ref().map(|info| info.metrics_id.clone()), + info.as_ref().map(|info| info.staff).unwrap_or(false), + cx.read(|cx| cx.global::().telemetry()), + ); current_user_tx.send(user).await.ok(); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f11061cb84..8847c01af7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6087,10 +6087,11 @@ impl Editor { let extension = Path::new(file.file_name(cx)) .extension() .and_then(|e| e.to_str()); - project - .read(cx) - .client() - .report_event(name, json!({ "File Extension": extension })); + project.read(cx).client().report_event( + name, + json!({ "File Extension": extension }), + cx.global::().telemetry(), + ); } } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index dd2be6e318..e3e8516508 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -62,6 +62,15 @@ pub struct TelemetrySettings { metrics: Option, } +impl TelemetrySettings { + pub fn metrics(&self) -> bool { + self.metrics.unwrap() + } + pub fn diagnostics(&self) -> bool { + self.diagnostics.unwrap() + } +} + #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct FeatureFlags { pub experimental_themes: bool, @@ -503,6 +512,13 @@ impl Settings { .unwrap_or_else(|| R::default()) } + pub fn telemetry(&self) -> TelemetrySettings { + TelemetrySettings { + diagnostics: Some(self.telemetry_diagnostics()), + metrics: Some(self.telemetry_metrics()), + } + } + pub fn telemetry_diagnostics(&self) -> bool { self.telemetry_overrides .diagnostics diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 72115022d7..bf86bb9652 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -148,7 +148,11 @@ fn main() { .detach(); client.start_telemetry(); - client.report_event("start app", Default::default()); + client.report_event( + "start app", + Default::default(), + cx.global::().telemetry(), + ); let app_state = Arc::new(AppState { languages, From 41ff42ddec3936306d7bdfc3e11310e7a55e0130 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Jan 2023 16:27:13 -0800 Subject: [PATCH 08/83] Fix crash when restarting a language server after it reports an unknown buffer version Co-authored-by: Antonio Scandurra --- crates/project/src/project.rs | 33 +++++++++---------- crates/project/src/project_tests.rs | 49 +++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 608abc5815..478271118c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2081,6 +2081,7 @@ impl Project { .buffer_snapshots .entry(buffer.remote_id()) .or_insert_with(|| vec![(0, buffer.text_snapshot())]); + let (version, initial_snapshot) = versions.last().unwrap(); let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); language_server @@ -2617,6 +2618,7 @@ impl Project { worktree_id: worktree.read(cx).id(), path: relative_path.into(), }; + if let Some(buffer) = self.get_open_buffer(&project_path, cx) { self.update_buffer_diagnostics(&buffer, diagnostics.clone(), version, cx)?; } @@ -6124,25 +6126,20 @@ impl Project { .buffer_snapshots .get_mut(&buffer_id) .ok_or_else(|| anyhow!("no snapshot found for buffer {}", buffer_id))?; - let mut found_snapshot = None; - snapshots.retain(|(snapshot_version, snapshot)| { - if snapshot_version + OLD_VERSIONS_TO_RETAIN < version { - false - } else { - if *snapshot_version == version { - found_snapshot = Some(snapshot.clone()); - } - true - } + let found_snapshot = snapshots + .binary_search_by_key(&version, |e| e.0) + .map(|ix| snapshots[ix].1.clone()) + .map_err(|_| { + anyhow!( + "snapshot not found for buffer {} at version {}", + buffer_id, + version + ) + })?; + snapshots.retain(|(snapshot_version, _)| { + snapshot_version + OLD_VERSIONS_TO_RETAIN >= version }); - - found_snapshot.ok_or_else(|| { - anyhow!( - "snapshot not found for buffer {} at version {}", - buffer_id, - version - ) - }) + Ok(found_snapshot) } else { Ok((buffer.read(cx)).text_snapshot()) } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index a36831857f..c9e159f391 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -806,6 +806,55 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC }); } +#[gpui::test] +async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let mut language = Language::new( + LanguageConfig { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-lsp", + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.background()); + fs.insert_tree("/dir", json!({ "a.rs": "" })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // Before restarting the server, report diagnostics with an unknown buffer version. + let fake_server = fake_servers.next().await.unwrap(); + fake_server.notify::(lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), + version: Some(10000), + diagnostics: Vec::new(), + }); + cx.foreground().run_until_parked(); + + project.update(cx, |project, cx| { + project.restart_language_servers_for_buffers([buffer.clone()], cx); + }); + let mut fake_server = fake_servers.next().await.unwrap(); + let notification = fake_server + .receive_notification::() + .await + .text_document; + assert_eq!(notification.version, 0); +} + #[gpui::test] async fn test_toggling_enable_language_server( deterministic: Arc, From a3da41bfad3a6df94d1a93f37cfb120193bce5f0 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 16:38:07 -0800 Subject: [PATCH 09/83] Fix test failures due to dependency on Settings global in client for telemetry co-authored-by: kay --- crates/collab/src/tests/randomized_integration_tests.rs | 5 +++++ crates/settings/src/settings.rs | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index b067cac5ff..11c62005c2 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -18,6 +18,7 @@ use rand::{ distributions::{Alphanumeric, DistString}, prelude::*, }; +use settings::Settings; use std::{env, ffi::OsStr, path::PathBuf, sync::Arc}; #[gpui::test(iterations = 100)] @@ -104,6 +105,8 @@ async fn test_random_collaboration( cx.function_name.clone(), ); + client_cx.update(|cx| cx.set_global(Settings::test(cx))); + let op_start_signal = futures::channel::mpsc::unbounded(); let client = server.create_client(&mut client_cx, &username).await; user_ids.push(client.current_user_id(&client_cx)); @@ -173,6 +176,7 @@ async fn test_random_collaboration( available_users.push((removed_user_id, client.username.clone())); client_cx.update(|cx| { cx.clear_globals(); + cx.set_global(Settings::test(cx)); drop(client); }); @@ -401,6 +405,7 @@ async fn test_random_collaboration( for (client, mut cx) in clients { cx.update(|cx| { cx.clear_globals(); + cx.set_global(Settings::test(cx)); drop(client); }); } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index e3e8516508..e29a98e775 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -584,7 +584,10 @@ impl Settings { lsp: Default::default(), projects_online_by_default: true, theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), - telemetry_defaults: Default::default(), + telemetry_defaults: TelemetrySettings { + diagnostics: Some(true), + metrics: Some(true), + }, telemetry_overrides: Default::default(), staff_mode: false, } From bfb43c67f84c99c2a40ab7f6e35b8a8bb449306c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 16:50:54 -0800 Subject: [PATCH 10/83] Silence spurious log error co-authored-by: Kay --- crates/workspace/src/dock.rs | 6 +++--- crates/workspace/src/item.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 19fed4bf59..d830f34744 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -470,7 +470,7 @@ mod tests { use super::*; use crate::{ dock, - item::test::TestItem, + item::{self, test::TestItem}, persistence::model::{ SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, }, @@ -492,7 +492,7 @@ mod tests { Settings::test_async(cx); cx.update(|cx| { - register_deserializable_item::(cx); + register_deserializable_item::(cx); }); let serialized_workspace = SerializedWorkspace { @@ -508,7 +508,7 @@ mod tests { children: vec![SerializedItem { active: true, item_id: 0, - kind: "test".into(), + kind: "TestItem".into(), }], }, left_sidebar_open: false, diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 0a8311fd5c..19385038e4 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -919,7 +919,7 @@ pub(crate) mod test { } fn serialized_item_kind() -> Option<&'static str> { - None + Some("TestItem") } fn deserialize( From db831c3fbb8a2fdc2d25ce23f5f7696bcdf6cf07 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Jan 2023 17:38:34 -0800 Subject: [PATCH 11/83] Remove roadmap from readme --- README.md | 53 ----------------------------------------------------- 1 file changed, 53 deletions(-) diff --git a/README.md b/README.md index 9153d12bbb..24614e97c2 100644 --- a/README.md +++ b/README.md @@ -83,56 +83,3 @@ rustup target add wasm32-wasi ``` Plugins can be found in the `plugins` folder in the root. For more information about how plugins work, check the [Plugin Guide](./crates/plugin_runtime/README.md) in `crates/plugin_runtime/README.md`. - -## Roadmap - -We will organize our efforts around the following major milestones. We'll create tracking issues for each of these milestones to detail the individual tasks that comprise them. - -### Minimal text editor - -[Tracking issue](https://github.com/zed-industries/zed/issues/2) - -Ship a minimal text editor to investors and other insiders. It should be extremely fast and stable, but all it can do is open, edit, and save text files, making it potentially useful for basic editing but not for real coding. - -Establish basic infrastructure for building the app bundle and uploading an artifact. Once this is released, we should regularly distribute updates as features land. - -### Collaborative code editor for internal use - -[Tracking issue](https://github.com/zed-industries/zed/issues/6) - -Turn the minimal text editor into a collaborative _code_ editor. This will include the minimal features that the Zed team needs to collaborate in Zed to build Zed without net loss in developer productivity. This includes productivity-critical features such as: - -- Syntax highlighting and syntax-aware editing and navigation -- The ability to see and edit non-local working copies of a repository -- Language server support for Rust code navigation, refactoring, diagnostics, etc. -- Project browsing and project-wide search and replace - -We want to tackle collaboration fairly early so that the rest of the design of the product can flow around that assumption. We could probably produce a single-player code editor more quickly, but at the risk of having collaboration feel more "bolted on" when we eventually add it. - -### Private alpha for Rust teams on macOS - -The "minimal" milestones were about getting Zed to a point where the Zed team could use Zed productively to build Zed. What features are required for someone outside the company to use Zed to productively work on another project that is also written in Rust? - -This includes infrastructure like auto-updates, error reporting, and metrics collection. It also includes some amount of polish to make the tool more discoverable for someone that didn't write it, such as a UI for updating settings and key bindings. We may also need to enhance the server to support user authentication and related concerns. - -The initial target audience is like us. A small team working in Rust that's potentially interested in collaborating. As the alpha proceeds, we can work with teams of different sizes. - -### Private beta for Rust teams on macOS - -Once we're getting sufficiently positive feedback from our initial alpha users, we widen the audience by letting people share invites. Now may be a good time to get Zed running on the web, so that it's extremely easy for a Zed user to share a link and be collaborating in seconds. Once someone is using Zed on the Web, we'll let them register for the private beta and download the native binary if they're on macOS. - -### Expand to other languages - -Depending on how the Rust beta is going, focus hard on dominating another niche language such as Elixr or getting a foothold within a niche of a larger language, such as React/Typescript. Alternatively, go wide at this point and add decent support several widely-used languages such as Python, Ruby, Typescript, etc. This would entail taking 1-2 weeks per language and making sure we ship a solid experience based on a publicly-available language server. Each language has slightly different development practices, so we need to make sure Zed's UX meshes well with those practices. - -### Future directions - -Each of these sections could probably broken into multiple milestones, but this part of the roadmap is too far in the future to go into that level of detail at this point. - -#### Expand to other platforms - -Support Linux and Windows. We'll probably want to hire at least one person that prefers to work on each respective platform and have them spearhead the effort to port Zed to that platform. Once they've done so, they can join the general development effort while ensuring the user experience stays good on that platform. - -#### Expand on collaboration - -To start with, we'll focus on synchronous collaboration because that's where we're most differentiated, but there's no reason we have to limit ourselves to that. How can our tool facilitate collaboration generally, whether it's sync or async? What would it take for a team to go 100% Zed and collaborate fully within the tool? If we haven't added it already, basic Git support would be nice. From 9c627e82a09d5a652347c4710d9f94baa72dafb5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 11 Jan 2023 10:34:11 -0800 Subject: [PATCH 12/83] v0.70.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14574cc19a..d615546104 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8187,7 +8187,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.69.0" +version = "0.70.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d295deea71..38fa2ad4be 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.69.0" +version = "0.70.0" [lib] name = "zed" From 048da9ddce8dc8fd60c8be3ac425dc1afc842c14 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 11 Jan 2023 10:50:16 -0800 Subject: [PATCH 13/83] collab 0.5.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d615546104..fb090ac835 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.4.2" +version = "0.5.0" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 261289b9b3..1e26e6f527 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.4.2" +version = "0.5.0" [[bin]] name = "collab" From 14eec66e38b634fad59ce878e35b4cf8f90865a1 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Mon, 9 Jan 2023 15:44:40 -0800 Subject: [PATCH 14/83] in progress --- assets/keymaps/vim.json | 10 +++++++++- crates/vim/src/motion.rs | 23 +---------------------- crates/vim/src/normal.rs | 19 +++++++++++++++++++ crates/vim/src/state.rs | 6 +++++- crates/vim/src/vim.rs | 35 ++++++++++++++++++++++++++++++++++- crates/vim/src/visual.rs | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 102 insertions(+), 25 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index bef6f48cb4..12873a3e4e 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -209,6 +209,10 @@ "ctrl-e": [ "vim::Scroll", "LineDown" + ], + "r": [ + "vim::PushOperator", + "Replace" ] } }, @@ -294,7 +298,11 @@ "d": "vim::VisualDelete", "x": "vim::VisualDelete", "y": "vim::VisualYank", - "p": "vim::VisualPaste" + "p": "vim::VisualPaste", + "r": [ + "vim::PushOperator", + "Replace" + ] } }, { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 9089eebcb5..62b30730e8 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -3,7 +3,7 @@ use editor::{ display_map::{DisplaySnapshot, ToDisplayPoint}, movement, Bias, CharKind, DisplayPoint, }; -use gpui::{actions, impl_actions, keymap_matcher::KeyPressed, MutableAppContext}; +use gpui::{actions, impl_actions, MutableAppContext}; use language::{Point, Selection, SelectionGoal}; use serde::Deserialize; use workspace::Workspace; @@ -109,27 +109,6 @@ pub fn init(cx: &mut MutableAppContext) { &PreviousWordStart { ignore_punctuation }: &PreviousWordStart, cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) }, ); - cx.add_action( - |_: &mut Workspace, KeyPressed { keystroke }: &KeyPressed, cx| match Vim::read(cx) - .active_operator() - { - Some(Operator::FindForward { before }) => motion( - Motion::FindForward { - before, - character: keystroke.key.chars().next().unwrap(), - }, - cx, - ), - Some(Operator::FindBackward { after }) => motion( - Motion::FindBackward { - after, - character: keystroke.key.chars().next().unwrap(), - }, - cx, - ), - _ => cx.propagate_action(), - }, - ) } pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) { diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index d88d496ee9..d2ab40c772 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -424,6 +424,25 @@ fn scroll(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext "c", Operator::Delete => "d", Operator::Yank => "y", + Operator::Replace => "r", Operator::FindForward { before: false } => "f", Operator::FindForward { before: true } => "t", Operator::FindBackward { after: false } => "F", @@ -127,7 +129,9 @@ impl Operator { pub fn context_flags(&self) -> &'static [&'static str] { match self { Operator::Object { .. } => &["VimObject"], - Operator::FindForward { .. } | Operator::FindBackward { .. } => &["VimWaiting"], + Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => { + &["VimWaiting"] + } _ => &[], } } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 4d582fea6b..8c5dae1e01 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -13,11 +13,18 @@ mod visual; use collections::HashMap; use command_palette::CommandPaletteFilter; use editor::{Bias, Cancel, Editor}; -use gpui::{impl_actions, MutableAppContext, Subscription, ViewContext, WeakViewHandle}; +use gpui::{ + impl_actions, + keymap_matcher::{KeyPressed, Keystroke}, + MutableAppContext, Subscription, ViewContext, WeakViewHandle, +}; use language::CursorShape; +use motion::Motion; +use normal::normal_replace; use serde::Deserialize; use settings::Settings; use state::{Mode, Operator, VimState}; +use visual::visual_replace; use workspace::{self, Workspace}; #[derive(Clone, Deserialize, PartialEq)] @@ -51,6 +58,11 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(|_: &mut Workspace, n: &Number, cx: _| { Vim::update(cx, |vim, cx| vim.push_number(n, cx)); }); + cx.add_action( + |_: &mut Workspace, KeyPressed { keystroke }: &KeyPressed, cx| { + Vim::key_pressed(keystroke, cx); + }, + ); // Editor Actions cx.add_action(|_: &mut Editor, _: &Cancel, cx| { @@ -208,6 +220,27 @@ impl Vim { self.state.operator_stack.last().copied() } + fn key_pressed(keystroke: &Keystroke, cx: &mut ViewContext) { + match Vim::read(cx).active_operator() { + Some(Operator::FindForward { before }) => { + if let Some(character) = keystroke.key.chars().next() { + motion::motion(Motion::FindForward { before, character }, cx) + } + } + Some(Operator::FindBackward { after }) => { + if let Some(character) = keystroke.key.chars().next() { + motion::motion(Motion::FindBackward { after, character }, cx) + } + } + Some(Operator::Replace) => match Vim::read(cx).state.mode { + Mode::Normal => normal_replace(&keystroke.key, cx), + Mode::Visual { line } => visual_replace(&keystroke.key, line, cx), + _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)), + }, + _ => cx.propagate_action(), + } + } + fn set_enabled(&mut self, enabled: bool, cx: &mut MutableAppContext) { if self.enabled != enabled { self.enabled = enabled; diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index ef5bb6ddd8..3eba8699ef 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -313,6 +313,40 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext }); } +pub(crate) fn visual_replace(text: &str, line: bool, cx: &mut MutableAppContext) { + Vim::update(cx, |vim, cx| { + vim.update_active_editor(cx, |editor, cx| { + editor.transact(cx, |editor, cx| { + let (display_map, selections) = editor.selections.all_adjusted_display(cx); + let mut new_selections = Vec::new(); + editor.buffer().update(cx, |buffer, cx| { + let mut edits = Vec::new(); + for selection in selections.iter() { + let mut selection = selection.clone(); + if !line && !selection.reversed { + // Head is at the end of the selection. Adjust the end position to + // to include the character under the cursor. + *selection.end.column_mut() = selection.end.column() + 1; + selection.end = display_map.clip_point(selection.end, Bias::Right); + } + + let range = selection + .map(|p| p.to_offset(&display_map, Bias::Right)) + .range(); + new_selections.push(range.start..range.start); + let text = text.repeat(range.len()); + edits.push((range, text)); + } + + buffer.edit(edits, None, cx); + }); + editor.change_selections(None, cx, |s| s.select_ranges(new_selections)); + }); + }); + vim.pop_operator(cx) + }); +} + #[cfg(test)] mod test { use indoc::indoc; From 49379924cb9a53a65d7a8140ed46f8232e09add5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 13:25:02 -0800 Subject: [PATCH 15/83] Avoid dropping is_complete column for backward compatibility Co-authored-by: Antonio Scandurra --- crates/collab/migrations.sqlite/20221109000000_test_schema.sql | 1 + ...230103200902_replace_is_completed_with_completed_scan_id.sql | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index f89e46f8c9..32254d5757 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -57,6 +57,7 @@ CREATE TABLE "worktrees" ( "abs_path" VARCHAR NOT NULL, "visible" BOOL NOT NULL, "scan_id" INTEGER NOT NULL, + "is_complete" BOOL NOT NULL DEFAULT FALSE, "completed_scan_id" INTEGER NOT NULL, PRIMARY KEY(project_id, id) ); diff --git a/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql b/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql index e0f301b2e0..1894d888b9 100644 --- a/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql +++ b/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql @@ -1,3 +1,3 @@ ALTER TABLE worktrees - DROP COLUMN is_complete, + ALTER COLUMN is_complete SET DEFAULT FALSE, ADD COLUMN completed_scan_id INT8; From 74f8b493b23fe7df75174f8816ec74b53d8a0505 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 13:25:28 -0800 Subject: [PATCH 16/83] collab 0.5.1 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb090ac835..9a51f872a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.0" +version = "0.5.1" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 1e26e6f527..dbb5a1cc99 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.0" +version = "0.5.1" [[bin]] name = "collab" From fe27f135c0a00cf2f545350f65866d94fd4fec73 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 14:00:16 -0800 Subject: [PATCH 17/83] Bump protocol version after reconnect support --- crates/rpc/src/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 7fc59d86fb..39cdea7165 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 44; +pub const PROTOCOL_VERSION: u32 = 45; From 02f6928328e69ffceef9cd77dd7013ba11f044b6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 14:00:44 -0800 Subject: [PATCH 18/83] collab 0.5.2 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a51f872a6..3e79a68fc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.1" +version = "0.5.2" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index dbb5a1cc99..9c3f0f61c4 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.1" +version = "0.5.2" [[bin]] name = "collab" From 216b1aec08f687e3e84cc4a973d88c040b0d2df4 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 11 Jan 2023 14:57:40 -0800 Subject: [PATCH 19/83] fix replace in normal and visual modes --- crates/editor/src/movement.rs | 23 ++++++++++++++++ crates/vim/src/normal.rs | 50 +++++++++++++++++++++++++++++----- crates/vim/src/visual.rs | 51 ++++++++++++++++++++++------------- 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 96b2065823..0ede6186da 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -352,6 +352,29 @@ pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range< start..end } +pub fn split_display_range_by_lines( + map: &DisplaySnapshot, + range: Range, +) -> Vec> { + let mut result = Vec::new(); + + let mut start = range.start; + // Loop over all the covered rows until the one containing the range end + for row in range.start.row()..range.end.row() { + let row_end_column = map.line_len(row); + let end = map.clip_point(DisplayPoint::new(row, row_end_column), Bias::Left); + if start != end { + result.push(start..end); + } + start = map.clip_point(DisplayPoint::new(row + 1, 0), Bias::Left); + } + + // Add the final range from the start of the last end to the original range end. + result.push(start..range.end); + + result +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index d2ab40c772..d6391353cf 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -429,14 +429,42 @@ pub(crate) fn normal_replace(text: &str, cx: &mut MutableAppContext) { vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(None, cx, |s| { - s.move_with(|map, selection| { - *selection.end.column_mut() += 1; - selection.end = map.clip_point(selection.end, Bias::Right); - }); + let (map, display_selections) = editor.selections.all_display(cx); + // Selections are biased right at the start. So we need to store + // anchors that are biased left so that we can restore the selections + // after the change + let stable_anchors = editor + .selections + .disjoint_anchors() + .into_iter() + .map(|selection| { + let start = selection.start.bias_left(&map.buffer_snapshot); + start..start + }) + .collect::>(); + + let edits = display_selections + .into_iter() + .map(|selection| { + let mut range = selection.range(); + *range.end.column_mut() += 1; + range.end = map.clip_point(range.end, Bias::Right); + + ( + range.start.to_offset(&map, Bias::Left) + ..range.end.to_offset(&map, Bias::Left), + text, + ) + }) + .collect::>(); + + editor.buffer().update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); }); - editor.insert(text, cx); editor.set_clip_at_line_ends(true, cx); + editor.change_selections(None, cx, |s| { + s.select_anchor_ranges(stable_anchors); + }); }); }); vim.pop_operator(cx) @@ -487,6 +515,16 @@ mod test { .await; } + // #[gpui::test] + // async fn test_enter(cx: &mut gpui::TestAppContext) { + // let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]); + // cx.assert_all(indoc! {" + // ˇThe qˇuick broˇwn + // ˇfox jumps" + // }) + // .await; + // } + #[gpui::test] async fn test_k(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await.binding(["k"]); diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 3eba8699ef..ac8771f969 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use collections::HashMap; use editor::{ - display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection, + display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection, }; use gpui::{actions, MutableAppContext, ViewContext}; use language::{AutoindentMode, SelectionGoal}; @@ -318,32 +318,47 @@ pub(crate) fn visual_replace(text: &str, line: bool, cx: &mut MutableAppContext) vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { let (display_map, selections) = editor.selections.all_adjusted_display(cx); - let mut new_selections = Vec::new(); - editor.buffer().update(cx, |buffer, cx| { - let mut edits = Vec::new(); - for selection in selections.iter() { - let mut selection = selection.clone(); - if !line && !selection.reversed { - // Head is at the end of the selection. Adjust the end position to - // to include the character under the cursor. - *selection.end.column_mut() = selection.end.column() + 1; - selection.end = display_map.clip_point(selection.end, Bias::Right); - } - let range = selection - .map(|p| p.to_offset(&display_map, Bias::Right)) - .range(); - new_selections.push(range.start..range.start); + // Selections are biased right at the start. So we need to store + // anchors that are biased left so that we can restore the selections + // after the change + let stable_anchors = editor + .selections + .disjoint_anchors() + .into_iter() + .map(|selection| { + let start = selection.start.bias_left(&display_map.buffer_snapshot); + start..start + }) + .collect::>(); + + let mut edits = Vec::new(); + for selection in selections.iter() { + let mut selection = selection.clone(); + if !line && !selection.reversed { + // Head is at the end of the selection. Adjust the end position to + // to include the character under the cursor. + *selection.end.column_mut() = selection.end.column() + 1; + selection.end = display_map.clip_point(selection.end, Bias::Right); + } + + for row_range in + movement::split_display_range_by_lines(&display_map, selection.range()) + { + let range = row_range.start.to_offset(&display_map, Bias::Right) + ..row_range.end.to_offset(&display_map, Bias::Right); let text = text.repeat(range.len()); edits.push((range, text)); } + } + editor.buffer().update(cx, |buffer, cx| { buffer.edit(edits, None, cx); }); - editor.change_selections(None, cx, |s| s.select_ranges(new_selections)); + editor.change_selections(None, cx, |s| s.select_ranges(stable_anchors)); }); }); - vim.pop_operator(cx) + vim.switch_mode(Mode::Normal, false, cx); }); } From febf992a430e496f195b6bb7f8a17f59b80af9d2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 11 Jan 2023 16:35:49 -0800 Subject: [PATCH 20/83] Fix bug where keybindings would not show in command palette --- crates/gpui/src/keymap_matcher/keymap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui/src/keymap_matcher/keymap.rs b/crates/gpui/src/keymap_matcher/keymap.rs index 2f33164690..6f358aad39 100644 --- a/crates/gpui/src/keymap_matcher/keymap.rs +++ b/crates/gpui/src/keymap_matcher/keymap.rs @@ -43,7 +43,7 @@ impl Keymap { pub(crate) fn add_bindings>(&mut self, bindings: T) { for binding in bindings { self.binding_indices_by_action_type - .entry(binding.action().type_id()) + .entry(binding.action().as_any().type_id()) .or_default() .push(self.bindings.len()); self.bindings.push(binding); From 8e02266d0769b60d6b2eec541514a09d9923fc22 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Sat, 14 Jan 2023 02:30:21 -0500 Subject: [PATCH 21/83] Add Discourse release action --- .github/workflows/release_actions.yml | 9 +++++++ script/discourse_release | 38 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100755 script/discourse_release diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 3db4f7b467..a5949127f5 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -21,6 +21,15 @@ jobs: ${{ github.event.release.body }} ``` + discourse_release: + runs-on: ubuntu-latest + steps: + - name: Install Node + uses: actions/setup-node@v2 + if: ${{ ! github.event.release.prerelease }} + with: + node-version: '16' + - run: script/discourse_release ${{ secrets.DISCOURSE_RELEASES_API_KEY }} ${{ github.event.release.tag_name }} ${{ github.event.release.body }} mixpanel_release: runs-on: ubuntu-latest steps: diff --git a/script/discourse_release b/script/discourse_release new file mode 100755 index 0000000000..c233bf1872 --- /dev/null +++ b/script/discourse_release @@ -0,0 +1,38 @@ +#!/usr/bin/env node --redirect-warnings=/dev/null + +main(); + +async function main() { + const apiKey = process.argv[2] + const zedVersion = process.argv[3] + const releaseNotes = process.argv[4] + const postBody = ` + 📣 Zed ${zedVersion} was just released! + + Restart your Zed or head to the [releases page](https://zed.dev/releases/latest) to grab it. + + --- + + ${releaseNotes} + ` + + const title = `${zedVersion} Release Notes` + + const options = { + method: "POST", + headers: { + "Api-Key": apiKey, + "Api-Username": "system" + }, + body: new URLSearchParams({ + title: title, + raw: postBody, + category: "8" + }) + }; + + fetch("https://forum.zed.dev/posts.json", options) + .then(response => response.json()) + .then(response => console.log(response)) + .catch(err => console.error(err)); +} \ No newline at end of file From 9779663c6b18fa35ecf9742c92c3f32a2a8d955d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Jan 2023 16:50:30 +0100 Subject: [PATCH 22/83] Use `cli.mjs` when available in TypeScript language server Otherwise, fall back to using `cli.js`. --- crates/zed/src/languages/typescript.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index f54b09ceda..01b62577ad 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -12,7 +12,8 @@ use util::ResultExt; pub struct TypeScriptLspAdapter; impl TypeScriptLspAdapter { - const BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js"; + const OLD_BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js"; + const NEW_BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs"; } struct Versions { @@ -57,7 +58,7 @@ impl LspAdapter for TypeScriptLspAdapter { fs::create_dir_all(&version_dir) .await .context("failed to create version directory")?; - let binary_path = version_dir.join(Self::BIN_PATH); + let binary_path = version_dir.join(Self::NEW_BIN_PATH); if fs::metadata(&binary_path).await.is_err() { npm_install_packages( @@ -98,9 +99,12 @@ impl LspAdapter for TypeScriptLspAdapter { } } let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let bin_path = last_version_dir.join(Self::BIN_PATH); - if bin_path.exists() { - Ok(bin_path) + let old_bin_path = last_version_dir.join(Self::OLD_BIN_PATH); + let new_bin_path = last_version_dir.join(Self::NEW_BIN_PATH); + if new_bin_path.exists() { + Ok(new_bin_path) + } else if old_bin_path.exists() { + Ok(old_bin_path) } else { Err(anyhow!( "missing executable in directory {:?}", From 2c1fd7b0bf635107e37d5d3ef41f64d7b83ed9a9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Jan 2023 16:51:45 +0100 Subject: [PATCH 23/83] Add a 5s timeout when running `npm info` and `npm install` This prevents those two commands from getting stuck when there is no internet connection. --- crates/zed/src/languages/installation.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/zed/src/languages/installation.rs b/crates/zed/src/languages/installation.rs index 40edbb88d7..c5aff17e56 100644 --- a/crates/zed/src/languages/installation.rs +++ b/crates/zed/src/languages/installation.rs @@ -37,6 +37,8 @@ pub(crate) struct GithubReleaseAsset { pub async fn npm_package_latest_version(name: &str) -> Result { let output = smol::process::Command::new("npm") + .args(["-fetch-retry-mintimeout", "2000"]) + .args(["-fetch-retry-maxtimeout", "5000"]) .args(["info", name, "--json"]) .output() .await @@ -60,6 +62,8 @@ pub async fn npm_install_packages( directory: &Path, ) -> Result<()> { let output = smol::process::Command::new("npm") + .args(["-fetch-retry-mintimeout", "2000"]) + .args(["-fetch-retry-maxtimeout", "5000"]) .arg("install") .arg("--prefix") .arg(directory) From b90e1012bf504f6d9320f6d365bfce3ab45d0cf3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 10:24:17 -0800 Subject: [PATCH 24/83] Don't render split drag targets in the dock --- crates/workspace/src/pane.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 618c650e02..bd355b8b3e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1452,7 +1452,11 @@ impl View for Pane { 0, self.active_item_index + 1, false, - Some(100.), + if self.docked.is_some() { + None + } else { + Some(100.) + }, cx, { let toolbar = self.toolbar.clone(); From aa9710f7c3184ca58676bb841aacdf8b485b92b7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 11:46:43 -0800 Subject: [PATCH 25/83] Avoid unwrapping pane split in SplitWithProjectEntry Also, implement pane-splitting operations more consistently. --- crates/workspace/src/workspace.rs | 134 +++++++++++++----------------- 1 file changed, 57 insertions(+), 77 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 027b9ba86a..eefce67371 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -231,54 +231,8 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { workspace.toggle_sidebar(SidebarSide::Right, cx); }); cx.add_action(Workspace::activate_pane_at_index); - cx.add_action( - |workspace: &mut Workspace, - SplitWithItem { - from, - pane_to_split, - item_id_to_move, - split_direction, - }: &_, - cx| { - workspace.split_pane_with_item( - from.clone(), - pane_to_split.clone(), - *item_id_to_move, - *split_direction, - cx, - ) - }, - ); - - cx.add_async_action( - |workspace: &mut Workspace, - SplitWithProjectEntry { - pane_to_split, - split_direction, - project_entry, - }: &_, - cx| { - pane_to_split.upgrade(cx).and_then(|pane_to_split| { - let new_pane = workspace.add_pane(cx); - workspace - .center - .split(&pane_to_split, &new_pane, *split_direction) - .unwrap(); - - workspace - .project - .read(cx) - .path_for_entry(*project_entry, cx) - .map(|path| { - let task = workspace.open_path(path, Some(new_pane.downgrade()), true, cx); - cx.foreground().spawn(async move { - task.await?; - Ok(()) - }) - }) - }) - }, - ); + cx.add_action(Workspace::split_pane_with_item); + cx.add_action(Workspace::split_pane_with_project_entry); cx.add_async_action( |workspace: &mut Workspace, @@ -1525,38 +1479,64 @@ impl Workspace { return None; } - pane.read(cx).active_item().map(|item| { - let new_pane = self.add_pane(cx); - if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) { - Pane::add_item(self, &new_pane, clone, true, true, None, cx); - } - self.center.split(&pane, &new_pane, direction).unwrap(); - cx.notify(); - new_pane - }) + let item = pane.read(cx).active_item()?; + let new_pane = self.add_pane(cx); + if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) { + Pane::add_item(self, &new_pane, clone, true, true, None, cx); + } + self.center.split(&pane, &new_pane, direction).unwrap(); + cx.notify(); + Some(new_pane) } - pub fn split_pane_with_item( - &mut self, - from: WeakViewHandle, - pane_to_split: WeakViewHandle, - item_id_to_move: usize, - split_direction: SplitDirection, - cx: &mut ViewContext, - ) { - if let Some((pane_to_split, from)) = pane_to_split.upgrade(cx).zip(from.upgrade(cx)) { - if &pane_to_split == self.dock_pane() { - warn!("Can't split dock pane."); - return; - } - - let new_pane = self.add_pane(cx); - Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx); - self.center - .split(&pane_to_split, &new_pane, split_direction) - .unwrap(); - cx.notify(); + pub fn split_pane_with_item(&mut self, action: &SplitWithItem, cx: &mut ViewContext) { + let Some(pane_to_split) = action.pane_to_split.upgrade(cx) else { return; }; + let Some(from) = action.from.upgrade(cx) else { return; }; + if &pane_to_split == self.dock_pane() { + warn!("Can't split dock pane."); + return; } + + let new_pane = self.add_pane(cx); + Pane::move_item( + self, + from.clone(), + new_pane.clone(), + action.item_id_to_move, + 0, + cx, + ); + self.center + .split(&pane_to_split, &new_pane, action.split_direction) + .unwrap(); + cx.notify(); + } + + pub fn split_pane_with_project_entry( + &mut self, + action: &SplitWithProjectEntry, + cx: &mut ViewContext, + ) -> Option>> { + let pane_to_split = action.pane_to_split.upgrade(cx)?; + if &pane_to_split == self.dock_pane() { + warn!("Can't split dock pane."); + return None; + } + + let new_pane = self.add_pane(cx); + self.center + .split(&pane_to_split, &new_pane, action.split_direction) + .unwrap(); + + let path = self + .project + .read(cx) + .path_for_entry(action.project_entry, cx)?; + let task = self.open_path(path, Some(new_pane.downgrade()), true, cx); + Some(cx.foreground().spawn(async move { + task.await?; + Ok(()) + })) } fn remove_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { From 765773cfe68e6022746b980263c567d2117b0ca8 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 16 Jan 2023 16:34:10 -0500 Subject: [PATCH 26/83] Make Finder "Open With" work correctly --- crates/zed/src/main.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index bf86bb9652..6baf08b01e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -75,6 +75,7 @@ fn main() { }; let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded(); + let (open_paths_tx, open_paths_rx) = mpsc::unbounded(); app.on_open_urls(move |urls, _| { if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { if let Some(cli_connection) = connect_to_cli(server_name).log_err() { @@ -83,6 +84,16 @@ fn main() { .map_err(|_| anyhow!("no listener for cli connections")) .log_err(); }; + } else { + let paths: Vec<_> = urls + .iter() + .flat_map(|url| url.strip_prefix("file://")) + .map(|path| PathBuf::from(path)) + .collect(); + open_paths_tx + .unbounded_send(paths) + .map_err(|_| anyhow!("no listener for open urls requests")) + .log_err(); } }); @@ -175,6 +186,9 @@ fn main() { cx.set_menus(menus::menus()); + cx.spawn(|cx| handle_open_paths(open_paths_rx, app_state.clone(), cx)) + .detach(); + if stdout_is_a_pty() { cx.platform().activate(true); let paths = collect_path_args(); @@ -500,6 +514,17 @@ fn load_config_files( rx } +async fn handle_open_paths( + mut rx: mpsc::UnboundedReceiver>, + app_state: Arc, + mut cx: AsyncAppContext, +) { + while let Some(paths) = rx.next().await { + cx.update(|cx| workspace::open_paths(&paths, &app_state, cx)) + .detach(); + } +} + fn connect_to_cli( server_name: &str, ) -> Result<(mpsc::Receiver, IpcSender)> { From f62d13de2119f600fbd2be63ce3d1dc097a3805a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 14:41:24 -0800 Subject: [PATCH 27/83] Use a hand-coded parser for keymap context predicates --- crates/gpui/build.rs | 12 - .../grammars/context-predicate/.gitignore | 2 - .../grammars/context-predicate/Cargo.toml | 20 - .../grammars/context-predicate/binding.gyp | 18 - .../bindings/node/binding.cc | 30 - .../context-predicate/bindings/node/index.js | 19 - .../context-predicate/bindings/rust/build.rs | 40 -- .../context-predicate/bindings/rust/lib.rs | 52 -- .../context-predicate/corpus/expressions.txt | 49 -- .../grammars/context-predicate/grammar.js | 31 - .../context-predicate/package-lock.json | 44 -- .../grammars/context-predicate/package.json | 10 - .../context-predicate/src/grammar.json | 208 ------- .../context-predicate/src/node-types.json | 353 ----------- .../grammars/context-predicate/src/parser.c | 584 ------------------ .../src/tree_sitter/parser.h | 223 ------- .../gpui/src/keymap_matcher/keymap_context.rs | 311 +++++++--- 17 files changed, 240 insertions(+), 1766 deletions(-) delete mode 100644 crates/gpui/grammars/context-predicate/.gitignore delete mode 100644 crates/gpui/grammars/context-predicate/Cargo.toml delete mode 100644 crates/gpui/grammars/context-predicate/binding.gyp delete mode 100644 crates/gpui/grammars/context-predicate/bindings/node/binding.cc delete mode 100644 crates/gpui/grammars/context-predicate/bindings/node/index.js delete mode 100644 crates/gpui/grammars/context-predicate/bindings/rust/build.rs delete mode 100644 crates/gpui/grammars/context-predicate/bindings/rust/lib.rs delete mode 100644 crates/gpui/grammars/context-predicate/corpus/expressions.txt delete mode 100644 crates/gpui/grammars/context-predicate/grammar.js delete mode 100644 crates/gpui/grammars/context-predicate/package-lock.json delete mode 100644 crates/gpui/grammars/context-predicate/package.json delete mode 100644 crates/gpui/grammars/context-predicate/src/grammar.json delete mode 100644 crates/gpui/grammars/context-predicate/src/node-types.json delete mode 100644 crates/gpui/grammars/context-predicate/src/parser.c delete mode 100644 crates/gpui/grammars/context-predicate/src/tree_sitter/parser.h diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index 23f6f04992..095d5065d9 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -6,7 +6,6 @@ use std::{ fn main() { generate_dispatch_bindings(); - compile_context_predicate_parser(); compile_metal_shaders(); generate_shader_bindings(); } @@ -30,17 +29,6 @@ fn generate_dispatch_bindings() { .expect("couldn't write dispatch bindings"); } -fn compile_context_predicate_parser() { - let dir = PathBuf::from("./grammars/context-predicate/src"); - let parser_c = dir.join("parser.c"); - - println!("cargo:rerun-if-changed={}", &parser_c.to_str().unwrap()); - cc::Build::new() - .include(&dir) - .file(parser_c) - .compile("tree_sitter_context_predicate"); -} - const SHADER_HEADER_PATH: &str = "./src/platform/mac/shaders/shaders.h"; fn compile_metal_shaders() { diff --git a/crates/gpui/grammars/context-predicate/.gitignore b/crates/gpui/grammars/context-predicate/.gitignore deleted file mode 100644 index 563ba71918..0000000000 --- a/crates/gpui/grammars/context-predicate/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/node_modules -/build \ No newline at end of file diff --git a/crates/gpui/grammars/context-predicate/Cargo.toml b/crates/gpui/grammars/context-predicate/Cargo.toml deleted file mode 100644 index a3cb197996..0000000000 --- a/crates/gpui/grammars/context-predicate/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "tree-sitter-context-predicate" -description = "context-predicate grammar for the tree-sitter parsing library" -version = "0.0.1" -keywords = ["incremental", "parsing", "context-predicate"] -categories = ["parsing", "text-editors"] -repository = "https://github.com/tree-sitter/tree-sitter-javascript" -edition = "2021" -license = "MIT" -build = "bindings/rust/build.rs" -include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*"] - -[lib] -path = "bindings/rust/lib.rs" - -[dependencies] -tree-sitter = "0.20" - -[build-dependencies] -cc = "1.0" diff --git a/crates/gpui/grammars/context-predicate/binding.gyp b/crates/gpui/grammars/context-predicate/binding.gyp deleted file mode 100644 index 16f3d1af27..0000000000 --- a/crates/gpui/grammars/context-predicate/binding.gyp +++ /dev/null @@ -1,18 +0,0 @@ -{ - "targets": [ - { - "target_name": "tree_sitter_context_predicate_binding", - "include_dirs": [ - " - -using namespace v8; - -extern "C" TSLanguage *tree_sitter_context_predicate(); - -namespace { - -NAN_METHOD(New) {} - -void Init(Local exports, Local module) { - Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New("Language").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - Local constructor = Nan::GetFunction(tpl).ToLocalChecked(); - Local instance = - constructor->NewInstance(Nan::GetCurrentContext()).ToLocalChecked(); - Nan::SetInternalFieldPointer(instance, 0, tree_sitter_context_predicate()); - - Nan::Set(instance, Nan::New("name").ToLocalChecked(), - Nan::New("context_predicate").ToLocalChecked()); - Nan::Set(module, Nan::New("exports").ToLocalChecked(), instance); -} - -NODE_MODULE(tree_sitter_context_predicate_binding, Init) - -} // namespace diff --git a/crates/gpui/grammars/context-predicate/bindings/node/index.js b/crates/gpui/grammars/context-predicate/bindings/node/index.js deleted file mode 100644 index 3bad018a56..0000000000 --- a/crates/gpui/grammars/context-predicate/bindings/node/index.js +++ /dev/null @@ -1,19 +0,0 @@ -try { - module.exports = require("../../build/Release/tree_sitter_context_predicate_binding"); -} catch (error1) { - if (error1.code !== 'MODULE_NOT_FOUND') { - throw error1; - } - try { - module.exports = require("../../build/Debug/tree_sitter_context_predicate_binding"); - } catch (error2) { - if (error2.code !== 'MODULE_NOT_FOUND') { - throw error2; - } - throw error1 - } -} - -try { - module.exports.nodeTypeInfo = require("../../src/node-types.json"); -} catch (_) {} diff --git a/crates/gpui/grammars/context-predicate/bindings/rust/build.rs b/crates/gpui/grammars/context-predicate/bindings/rust/build.rs deleted file mode 100644 index c6061f0995..0000000000 --- a/crates/gpui/grammars/context-predicate/bindings/rust/build.rs +++ /dev/null @@ -1,40 +0,0 @@ -fn main() { - let src_dir = std::path::Path::new("src"); - - let mut c_config = cc::Build::new(); - c_config.include(&src_dir); - c_config - .flag_if_supported("-Wno-unused-parameter") - .flag_if_supported("-Wno-unused-but-set-variable") - .flag_if_supported("-Wno-trigraphs"); - let parser_path = src_dir.join("parser.c"); - c_config.file(&parser_path); - - // If your language uses an external scanner written in C, - // then include this block of code: - - /* - let scanner_path = src_dir.join("scanner.c"); - c_config.file(&scanner_path); - println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); - */ - - c_config.compile("parser"); - println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); - - // If your language uses an external scanner written in C++, - // then include this block of code: - - /* - let mut cpp_config = cc::Build::new(); - cpp_config.cpp(true); - cpp_config.include(&src_dir); - cpp_config - .flag_if_supported("-Wno-unused-parameter") - .flag_if_supported("-Wno-unused-but-set-variable"); - let scanner_path = src_dir.join("scanner.cc"); - cpp_config.file(&scanner_path); - cpp_config.compile("scanner"); - println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); - */ -} diff --git a/crates/gpui/grammars/context-predicate/bindings/rust/lib.rs b/crates/gpui/grammars/context-predicate/bindings/rust/lib.rs deleted file mode 100644 index 41962c960d..0000000000 --- a/crates/gpui/grammars/context-predicate/bindings/rust/lib.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! This crate provides context_predicate language support for the [tree-sitter][] parsing library. -//! -//! Typically, you will use the [language][language func] function to add this language to a -//! tree-sitter [Parser][], and then use the parser to parse some code: -//! -//! ``` -//! let code = ""; -//! let mut parser = tree_sitter::Parser::new(); -//! parser.set_language(tree_sitter_context_predicate::language()).expect("Error loading context_predicate grammar"); -//! let tree = parser.parse(code, None).unwrap(); -//! ``` -//! -//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html -//! [language func]: fn.language.html -//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html -//! [tree-sitter]: https://tree-sitter.github.io/ - -use tree_sitter::Language; - -extern "C" { - fn tree_sitter_context_predicate() -> Language; -} - -/// Get the tree-sitter [Language][] for this grammar. -/// -/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html -pub fn language() -> Language { - unsafe { tree_sitter_context_predicate() } -} - -/// The content of the [`node-types.json`][] file for this grammar. -/// -/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types -pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json"); - -// Uncomment these to include any queries that this grammar contains - -// pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm"); -// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm"); -// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm"); -// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm"); - -#[cfg(test)] -mod tests { - #[test] - fn test_can_load_grammar() { - let mut parser = tree_sitter::Parser::new(); - parser - .set_language(super::language()) - .expect("Error loading context_predicate language"); - } -} diff --git a/crates/gpui/grammars/context-predicate/corpus/expressions.txt b/crates/gpui/grammars/context-predicate/corpus/expressions.txt deleted file mode 100644 index b53bb105a8..0000000000 --- a/crates/gpui/grammars/context-predicate/corpus/expressions.txt +++ /dev/null @@ -1,49 +0,0 @@ -================== -Identifiers -================== - -abc12 - ---- - -(source (identifier)) - -================== -Negation -================== - -!abc - ---- - -(source (not (identifier))) - -================== -And/Or -================== - -a || b && c && d - ---- - -(source - (or - (identifier) - (and - (and (identifier) (identifier)) - (identifier)))) - -================== -Expressions -================== - -a && (b == c || d != e) - ---- - -(source - (and - (identifier) - (parenthesized (or - (equal (identifier) (identifier)) - (not_equal (identifier) (identifier)))))) diff --git a/crates/gpui/grammars/context-predicate/grammar.js b/crates/gpui/grammars/context-predicate/grammar.js deleted file mode 100644 index 9a64935917..0000000000 --- a/crates/gpui/grammars/context-predicate/grammar.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = grammar({ - name: 'context_predicate', - - rules: { - source: $ => $._expression, - - _expression: $ => choice( - $.identifier, - $.not, - $.and, - $.or, - $.equal, - $.not_equal, - $.parenthesized, - ), - - identifier: $ => /[A-Za-z0-9_-]+/, - - not: $ => prec(3, seq("!", field("expression", $._expression))), - - and: $ => prec.left(2, seq(field("left", $._expression), "&&", field("right", $._expression))), - - or: $ => prec.left(1, seq(field("left", $._expression), "||", field("right", $._expression))), - - equal: $ => seq(field("left", $.identifier), "==", field("right", $.identifier)), - - not_equal: $ => seq(field("left", $.identifier), "!=", field("right", $.identifier)), - - parenthesized: $ => seq("(", field("expression", $._expression), ")"), - } -}); diff --git a/crates/gpui/grammars/context-predicate/package-lock.json b/crates/gpui/grammars/context-predicate/package-lock.json deleted file mode 100644 index 1da584a856..0000000000 --- a/crates/gpui/grammars/context-predicate/package-lock.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "tree-sitter-context-predicate", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "tree-sitter-context-predicate", - "dependencies": { - "nan": "^2.14.0" - }, - "devDependencies": { - "tree-sitter-cli": "^0.19.5" - } - }, - "node_modules/nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" - }, - "node_modules/tree-sitter-cli": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.19.5.tgz", - "integrity": "sha512-kRzKrUAwpDN9AjA3b0tPBwT1hd8N2oQvvvHup2OEsX6mdsSMLmAvR+NSqK9fe05JrRbVvG8mbteNUQsxlMQohQ==", - "dev": true, - "hasInstallScript": true, - "bin": { - "tree-sitter": "cli.js" - } - } - }, - "dependencies": { - "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" - }, - "tree-sitter-cli": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.19.5.tgz", - "integrity": "sha512-kRzKrUAwpDN9AjA3b0tPBwT1hd8N2oQvvvHup2OEsX6mdsSMLmAvR+NSqK9fe05JrRbVvG8mbteNUQsxlMQohQ==", - "dev": true - } - } -} diff --git a/crates/gpui/grammars/context-predicate/package.json b/crates/gpui/grammars/context-predicate/package.json deleted file mode 100644 index 298e34a6b5..0000000000 --- a/crates/gpui/grammars/context-predicate/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "tree-sitter-context-predicate", - "main": "bindings/node", - "devDependencies": { - "tree-sitter-cli": "^0.19.5" - }, - "dependencies": { - "nan": "^2.14.0" - } -} diff --git a/crates/gpui/grammars/context-predicate/src/grammar.json b/crates/gpui/grammars/context-predicate/src/grammar.json deleted file mode 100644 index 9ccb50fe79..0000000000 --- a/crates/gpui/grammars/context-predicate/src/grammar.json +++ /dev/null @@ -1,208 +0,0 @@ -{ - "name": "context_predicate", - "rules": { - "source": { - "type": "SYMBOL", - "name": "_expression" - }, - "_expression": { - "type": "CHOICE", - "members": [ - { - "type": "SYMBOL", - "name": "identifier" - }, - { - "type": "SYMBOL", - "name": "not" - }, - { - "type": "SYMBOL", - "name": "and" - }, - { - "type": "SYMBOL", - "name": "or" - }, - { - "type": "SYMBOL", - "name": "equal" - }, - { - "type": "SYMBOL", - "name": "not_equal" - }, - { - "type": "SYMBOL", - "name": "parenthesized" - } - ] - }, - "identifier": { - "type": "PATTERN", - "value": "[A-Za-z0-9_-]+" - }, - "not": { - "type": "PREC", - "value": 3, - "content": { - "type": "SEQ", - "members": [ - { - "type": "STRING", - "value": "!" - }, - { - "type": "FIELD", - "name": "expression", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - } - ] - } - }, - "and": { - "type": "PREC_LEFT", - "value": 2, - "content": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - }, - { - "type": "STRING", - "value": "&&" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - } - ] - } - }, - "or": { - "type": "PREC_LEFT", - "value": 1, - "content": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - }, - { - "type": "STRING", - "value": "||" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - } - ] - } - }, - "equal": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - }, - { - "type": "STRING", - "value": "==" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - } - ] - }, - "not_equal": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - }, - { - "type": "STRING", - "value": "!=" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - } - ] - }, - "parenthesized": { - "type": "SEQ", - "members": [ - { - "type": "STRING", - "value": "(" - }, - { - "type": "FIELD", - "name": "expression", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - }, - { - "type": "STRING", - "value": ")" - } - ] - } - }, - "extras": [ - { - "type": "PATTERN", - "value": "\\s" - } - ], - "conflicts": [], - "precedences": [], - "externals": [], - "inline": [], - "supertypes": [] -} - diff --git a/crates/gpui/grammars/context-predicate/src/node-types.json b/crates/gpui/grammars/context-predicate/src/node-types.json deleted file mode 100644 index 2e8868b1f1..0000000000 --- a/crates/gpui/grammars/context-predicate/src/node-types.json +++ /dev/null @@ -1,353 +0,0 @@ -[ - { - "type": "and", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "equal", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - } - } - }, - { - "type": "not", - "named": true, - "fields": { - "expression": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "not_equal", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - } - } - }, - { - "type": "or", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "parenthesized", - "named": true, - "fields": { - "expression": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "source", - "named": true, - "fields": {}, - "children": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - }, - { - "type": "!", - "named": false - }, - { - "type": "!=", - "named": false - }, - { - "type": "&&", - "named": false - }, - { - "type": "(", - "named": false - }, - { - "type": ")", - "named": false - }, - { - "type": "==", - "named": false - }, - { - "type": "identifier", - "named": true - }, - { - "type": "||", - "named": false - } -] \ No newline at end of file diff --git a/crates/gpui/grammars/context-predicate/src/parser.c b/crates/gpui/grammars/context-predicate/src/parser.c deleted file mode 100644 index 57a7364a43..0000000000 --- a/crates/gpui/grammars/context-predicate/src/parser.c +++ /dev/null @@ -1,584 +0,0 @@ -#include - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - -#define LANGUAGE_VERSION 13 -#define STATE_COUNT 18 -#define LARGE_STATE_COUNT 6 -#define SYMBOL_COUNT 17 -#define ALIAS_COUNT 0 -#define TOKEN_COUNT 9 -#define EXTERNAL_TOKEN_COUNT 0 -#define FIELD_COUNT 3 -#define MAX_ALIAS_SEQUENCE_LENGTH 3 -#define PRODUCTION_ID_COUNT 3 - -enum { - sym_identifier = 1, - anon_sym_BANG = 2, - anon_sym_AMP_AMP = 3, - anon_sym_PIPE_PIPE = 4, - anon_sym_EQ_EQ = 5, - anon_sym_BANG_EQ = 6, - anon_sym_LPAREN = 7, - anon_sym_RPAREN = 8, - sym_source = 9, - sym__expression = 10, - sym_not = 11, - sym_and = 12, - sym_or = 13, - sym_equal = 14, - sym_not_equal = 15, - sym_parenthesized = 16, -}; - -static const char *const ts_symbol_names[] = { - [ts_builtin_sym_end] = "end", - [sym_identifier] = "identifier", - [anon_sym_BANG] = "!", - [anon_sym_AMP_AMP] = "&&", - [anon_sym_PIPE_PIPE] = "||", - [anon_sym_EQ_EQ] = "==", - [anon_sym_BANG_EQ] = "!=", - [anon_sym_LPAREN] = "(", - [anon_sym_RPAREN] = ")", - [sym_source] = "source", - [sym__expression] = "_expression", - [sym_not] = "not", - [sym_and] = "and", - [sym_or] = "or", - [sym_equal] = "equal", - [sym_not_equal] = "not_equal", - [sym_parenthesized] = "parenthesized", -}; - -static const TSSymbol ts_symbol_map[] = { - [ts_builtin_sym_end] = ts_builtin_sym_end, - [sym_identifier] = sym_identifier, - [anon_sym_BANG] = anon_sym_BANG, - [anon_sym_AMP_AMP] = anon_sym_AMP_AMP, - [anon_sym_PIPE_PIPE] = anon_sym_PIPE_PIPE, - [anon_sym_EQ_EQ] = anon_sym_EQ_EQ, - [anon_sym_BANG_EQ] = anon_sym_BANG_EQ, - [anon_sym_LPAREN] = anon_sym_LPAREN, - [anon_sym_RPAREN] = anon_sym_RPAREN, - [sym_source] = sym_source, - [sym__expression] = sym__expression, - [sym_not] = sym_not, - [sym_and] = sym_and, - [sym_or] = sym_or, - [sym_equal] = sym_equal, - [sym_not_equal] = sym_not_equal, - [sym_parenthesized] = sym_parenthesized, -}; - -static const TSSymbolMetadata ts_symbol_metadata[] = { - [ts_builtin_sym_end] = - { - .visible = false, - .named = true, - }, - [sym_identifier] = - { - .visible = true, - .named = true, - }, - [anon_sym_BANG] = - { - .visible = true, - .named = false, - }, - [anon_sym_AMP_AMP] = - { - .visible = true, - .named = false, - }, - [anon_sym_PIPE_PIPE] = - { - .visible = true, - .named = false, - }, - [anon_sym_EQ_EQ] = - { - .visible = true, - .named = false, - }, - [anon_sym_BANG_EQ] = - { - .visible = true, - .named = false, - }, - [anon_sym_LPAREN] = - { - .visible = true, - .named = false, - }, - [anon_sym_RPAREN] = - { - .visible = true, - .named = false, - }, - [sym_source] = - { - .visible = true, - .named = true, - }, - [sym__expression] = - { - .visible = false, - .named = true, - }, - [sym_not] = - { - .visible = true, - .named = true, - }, - [sym_and] = - { - .visible = true, - .named = true, - }, - [sym_or] = - { - .visible = true, - .named = true, - }, - [sym_equal] = - { - .visible = true, - .named = true, - }, - [sym_not_equal] = - { - .visible = true, - .named = true, - }, - [sym_parenthesized] = - { - .visible = true, - .named = true, - }, -}; - -enum { - field_expression = 1, - field_left = 2, - field_right = 3, -}; - -static const char *const ts_field_names[] = { - [0] = NULL, - [field_expression] = "expression", - [field_left] = "left", - [field_right] = "right", -}; - -static const TSFieldMapSlice ts_field_map_slices[PRODUCTION_ID_COUNT] = { - [1] = {.index = 0, .length = 1}, - [2] = {.index = 1, .length = 2}, -}; - -static const TSFieldMapEntry ts_field_map_entries[] = { - [0] = {field_expression, 1}, - [1] = {field_left, 0}, - {field_right, 2}, -}; - -static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT] - [MAX_ALIAS_SEQUENCE_LENGTH] = { - [0] = {0}, -}; - -static const uint16_t ts_non_terminal_alias_map[] = { - 0, -}; - -static bool ts_lex(TSLexer *lexer, TSStateId state) { - START_LEXER(); - eof = lexer->eof(lexer); - switch (state) { - case 0: - if (eof) - ADVANCE(7); - if (lookahead == '!') - ADVANCE(10); - if (lookahead == '&') - ADVANCE(2); - if (lookahead == '(') - ADVANCE(15); - if (lookahead == ')') - ADVANCE(16); - if (lookahead == '=') - ADVANCE(4); - if (lookahead == '|') - ADVANCE(5); - if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' || - lookahead == ' ') - SKIP(0) - if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || - ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) - ADVANCE(8); - END_STATE(); - case 1: - if (lookahead == '!') - ADVANCE(9); - if (lookahead == '(') - ADVANCE(15); - if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' || - lookahead == ' ') - SKIP(1) - if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || - ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) - ADVANCE(8); - END_STATE(); - case 2: - if (lookahead == '&') - ADVANCE(11); - END_STATE(); - case 3: - if (lookahead == '=') - ADVANCE(14); - END_STATE(); - case 4: - if (lookahead == '=') - ADVANCE(13); - END_STATE(); - case 5: - if (lookahead == '|') - ADVANCE(12); - END_STATE(); - case 6: - if (eof) - ADVANCE(7); - if (lookahead == '!') - ADVANCE(3); - if (lookahead == '&') - ADVANCE(2); - if (lookahead == ')') - ADVANCE(16); - if (lookahead == '=') - ADVANCE(4); - if (lookahead == '|') - ADVANCE(5); - if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' || - lookahead == ' ') - SKIP(6) - END_STATE(); - case 7: - ACCEPT_TOKEN(ts_builtin_sym_end); - END_STATE(); - case 8: - ACCEPT_TOKEN(sym_identifier); - if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || - ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) - ADVANCE(8); - END_STATE(); - case 9: - ACCEPT_TOKEN(anon_sym_BANG); - END_STATE(); - case 10: - ACCEPT_TOKEN(anon_sym_BANG); - if (lookahead == '=') - ADVANCE(14); - END_STATE(); - case 11: - ACCEPT_TOKEN(anon_sym_AMP_AMP); - END_STATE(); - case 12: - ACCEPT_TOKEN(anon_sym_PIPE_PIPE); - END_STATE(); - case 13: - ACCEPT_TOKEN(anon_sym_EQ_EQ); - END_STATE(); - case 14: - ACCEPT_TOKEN(anon_sym_BANG_EQ); - END_STATE(); - case 15: - ACCEPT_TOKEN(anon_sym_LPAREN); - END_STATE(); - case 16: - ACCEPT_TOKEN(anon_sym_RPAREN); - END_STATE(); - default: - return false; - } -} - -static const TSLexMode ts_lex_modes[STATE_COUNT] = { - [0] = {.lex_state = 0}, [1] = {.lex_state = 1}, [2] = {.lex_state = 1}, - [3] = {.lex_state = 1}, [4] = {.lex_state = 1}, [5] = {.lex_state = 1}, - [6] = {.lex_state = 6}, [7] = {.lex_state = 0}, [8] = {.lex_state = 0}, - [9] = {.lex_state = 0}, [10] = {.lex_state = 0}, [11] = {.lex_state = 0}, - [12] = {.lex_state = 0}, [13] = {.lex_state = 0}, [14] = {.lex_state = 0}, - [15] = {.lex_state = 0}, [16] = {.lex_state = 0}, [17] = {.lex_state = 0}, -}; - -static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { - [0] = - { - [ts_builtin_sym_end] = ACTIONS(1), - [sym_identifier] = ACTIONS(1), - [anon_sym_BANG] = ACTIONS(1), - [anon_sym_AMP_AMP] = ACTIONS(1), - [anon_sym_PIPE_PIPE] = ACTIONS(1), - [anon_sym_EQ_EQ] = ACTIONS(1), - [anon_sym_BANG_EQ] = ACTIONS(1), - [anon_sym_LPAREN] = ACTIONS(1), - [anon_sym_RPAREN] = ACTIONS(1), - }, - [1] = - { - [sym_source] = STATE(15), - [sym__expression] = STATE(13), - [sym_not] = STATE(13), - [sym_and] = STATE(13), - [sym_or] = STATE(13), - [sym_equal] = STATE(13), - [sym_not_equal] = STATE(13), - [sym_parenthesized] = STATE(13), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [2] = - { - [sym__expression] = STATE(7), - [sym_not] = STATE(7), - [sym_and] = STATE(7), - [sym_or] = STATE(7), - [sym_equal] = STATE(7), - [sym_not_equal] = STATE(7), - [sym_parenthesized] = STATE(7), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [3] = - { - [sym__expression] = STATE(14), - [sym_not] = STATE(14), - [sym_and] = STATE(14), - [sym_or] = STATE(14), - [sym_equal] = STATE(14), - [sym_not_equal] = STATE(14), - [sym_parenthesized] = STATE(14), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [4] = - { - [sym__expression] = STATE(11), - [sym_not] = STATE(11), - [sym_and] = STATE(11), - [sym_or] = STATE(11), - [sym_equal] = STATE(11), - [sym_not_equal] = STATE(11), - [sym_parenthesized] = STATE(11), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [5] = - { - [sym__expression] = STATE(12), - [sym_not] = STATE(12), - [sym_and] = STATE(12), - [sym_or] = STATE(12), - [sym_equal] = STATE(12), - [sym_not_equal] = STATE(12), - [sym_parenthesized] = STATE(12), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, -}; - -static const uint16_t ts_small_parse_table[] = { - [0] = 3, - ACTIONS(11), - 1, - anon_sym_EQ_EQ, - ACTIONS(13), - 1, - anon_sym_BANG_EQ, - ACTIONS(9), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [13] = 1, - ACTIONS(15), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [20] = 1, - ACTIONS(17), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [27] = 1, - ACTIONS(19), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [34] = 1, - ACTIONS(21), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [41] = 1, - ACTIONS(23), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [48] = 2, - ACTIONS(27), - 1, - anon_sym_AMP_AMP, - ACTIONS(25), - 3, - ts_builtin_sym_end, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [57] = 3, - ACTIONS(27), - 1, - anon_sym_AMP_AMP, - ACTIONS(29), - 1, - ts_builtin_sym_end, - ACTIONS(31), - 1, - anon_sym_PIPE_PIPE, - [67] = 3, - ACTIONS(27), - 1, - anon_sym_AMP_AMP, - ACTIONS(31), - 1, - anon_sym_PIPE_PIPE, - ACTIONS(33), - 1, - anon_sym_RPAREN, - [77] = 1, - ACTIONS(35), - 1, - ts_builtin_sym_end, - [81] = 1, - ACTIONS(37), - 1, - sym_identifier, - [85] = 1, - ACTIONS(39), - 1, - sym_identifier, -}; - -static const uint32_t ts_small_parse_table_map[] = { - [SMALL_STATE(6)] = 0, [SMALL_STATE(7)] = 13, [SMALL_STATE(8)] = 20, - [SMALL_STATE(9)] = 27, [SMALL_STATE(10)] = 34, [SMALL_STATE(11)] = 41, - [SMALL_STATE(12)] = 48, [SMALL_STATE(13)] = 57, [SMALL_STATE(14)] = 67, - [SMALL_STATE(15)] = 77, [SMALL_STATE(16)] = 81, [SMALL_STATE(17)] = 85, -}; - -static const TSParseActionEntry ts_parse_actions[] = { - [0] = {.entry = {.count = 0, .reusable = false}}, - [1] = {.entry = {.count = 1, .reusable = false}}, - RECOVER(), - [3] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(6), - [5] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(2), - [7] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(3), - [9] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym__expression, 1), - [11] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(16), - [13] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(17), - [15] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_not, 2, .production_id = 1), - [17] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_equal, 3, .production_id = 2), - [19] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_not_equal, 3, .production_id = 2), - [21] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_parenthesized, 3, .production_id = 1), - [23] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_and, 3, .production_id = 2), - [25] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_or, 3, .production_id = 2), - [27] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(4), - [29] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_source, 1), - [31] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(5), - [33] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(10), - [35] = {.entry = {.count = 1, .reusable = true}}, - ACCEPT_INPUT(), - [37] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(8), - [39] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(9), -}; - -#ifdef __cplusplus -extern "C" { -#endif -#ifdef _WIN32 -#define extern __declspec(dllexport) -#endif - -extern const TSLanguage *tree_sitter_context_predicate(void) { - static const TSLanguage language = { - .version = LANGUAGE_VERSION, - .symbol_count = SYMBOL_COUNT, - .alias_count = ALIAS_COUNT, - .token_count = TOKEN_COUNT, - .external_token_count = EXTERNAL_TOKEN_COUNT, - .state_count = STATE_COUNT, - .large_state_count = LARGE_STATE_COUNT, - .production_id_count = PRODUCTION_ID_COUNT, - .field_count = FIELD_COUNT, - .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH, - .parse_table = &ts_parse_table[0][0], - .small_parse_table = ts_small_parse_table, - .small_parse_table_map = ts_small_parse_table_map, - .parse_actions = ts_parse_actions, - .symbol_names = ts_symbol_names, - .field_names = ts_field_names, - .field_map_slices = ts_field_map_slices, - .field_map_entries = ts_field_map_entries, - .symbol_metadata = ts_symbol_metadata, - .public_symbol_map = ts_symbol_map, - .alias_map = ts_non_terminal_alias_map, - .alias_sequences = &ts_alias_sequences[0][0], - .lex_modes = ts_lex_modes, - .lex_fn = ts_lex, - }; - return &language; -} -#ifdef __cplusplus -} -#endif diff --git a/crates/gpui/grammars/context-predicate/src/tree_sitter/parser.h b/crates/gpui/grammars/context-predicate/src/tree_sitter/parser.h deleted file mode 100644 index cbbc7b4ee3..0000000000 --- a/crates/gpui/grammars/context-predicate/src/tree_sitter/parser.h +++ /dev/null @@ -1,223 +0,0 @@ -#ifndef TREE_SITTER_PARSER_H_ -#define TREE_SITTER_PARSER_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#define ts_builtin_sym_error ((TSSymbol)-1) -#define ts_builtin_sym_end 0 -#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 - -typedef uint16_t TSStateId; - -#ifndef TREE_SITTER_API_H_ -typedef uint16_t TSSymbol; -typedef uint16_t TSFieldId; -typedef struct TSLanguage TSLanguage; -#endif - -typedef struct { - TSFieldId field_id; - uint8_t child_index; - bool inherited; -} TSFieldMapEntry; - -typedef struct { - uint16_t index; - uint16_t length; -} TSFieldMapSlice; - -typedef struct { - bool visible; - bool named; - bool supertype; -} TSSymbolMetadata; - -typedef struct TSLexer TSLexer; - -struct TSLexer { - int32_t lookahead; - TSSymbol result_symbol; - void (*advance)(TSLexer *, bool); - void (*mark_end)(TSLexer *); - uint32_t (*get_column)(TSLexer *); - bool (*is_at_included_range_start)(const TSLexer *); - bool (*eof)(const TSLexer *); -}; - -typedef enum { - TSParseActionTypeShift, - TSParseActionTypeReduce, - TSParseActionTypeAccept, - TSParseActionTypeRecover, -} TSParseActionType; - -typedef union { - struct { - uint8_t type; - TSStateId state; - bool extra; - bool repetition; - } shift; - struct { - uint8_t type; - uint8_t child_count; - TSSymbol symbol; - int16_t dynamic_precedence; - uint16_t production_id; - } reduce; - uint8_t type; -} TSParseAction; - -typedef struct { - uint16_t lex_state; - uint16_t external_lex_state; -} TSLexMode; - -typedef union { - TSParseAction action; - struct { - uint8_t count; - bool reusable; - } entry; -} TSParseActionEntry; - -struct TSLanguage { - uint32_t version; - uint32_t symbol_count; - uint32_t alias_count; - uint32_t token_count; - uint32_t external_token_count; - uint32_t state_count; - uint32_t large_state_count; - uint32_t production_id_count; - uint32_t field_count; - uint16_t max_alias_sequence_length; - const uint16_t *parse_table; - const uint16_t *small_parse_table; - const uint32_t *small_parse_table_map; - const TSParseActionEntry *parse_actions; - const char * const *symbol_names; - const char * const *field_names; - const TSFieldMapSlice *field_map_slices; - const TSFieldMapEntry *field_map_entries; - const TSSymbolMetadata *symbol_metadata; - const TSSymbol *public_symbol_map; - const uint16_t *alias_map; - const TSSymbol *alias_sequences; - const TSLexMode *lex_modes; - bool (*lex_fn)(TSLexer *, TSStateId); - bool (*keyword_lex_fn)(TSLexer *, TSStateId); - TSSymbol keyword_capture_token; - struct { - const bool *states; - const TSSymbol *symbol_map; - void *(*create)(void); - void (*destroy)(void *); - bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); - unsigned (*serialize)(void *, char *); - void (*deserialize)(void *, const char *, unsigned); - } external_scanner; -}; - -/* - * Lexer Macros - */ - -#define START_LEXER() \ - bool result = false; \ - bool skip = false; \ - bool eof = false; \ - int32_t lookahead; \ - goto start; \ - next_state: \ - lexer->advance(lexer, skip); \ - start: \ - skip = false; \ - lookahead = lexer->lookahead; - -#define ADVANCE(state_value) \ - { \ - state = state_value; \ - goto next_state; \ - } - -#define SKIP(state_value) \ - { \ - skip = true; \ - state = state_value; \ - goto next_state; \ - } - -#define ACCEPT_TOKEN(symbol_value) \ - result = true; \ - lexer->result_symbol = symbol_value; \ - lexer->mark_end(lexer); - -#define END_STATE() return result; - -/* - * Parse Table Macros - */ - -#define SMALL_STATE(id) id - LARGE_STATE_COUNT - -#define STATE(id) id - -#define ACTIONS(id) id - -#define SHIFT(state_value) \ - {{ \ - .shift = { \ - .type = TSParseActionTypeShift, \ - .state = state_value \ - } \ - }} - -#define SHIFT_REPEAT(state_value) \ - {{ \ - .shift = { \ - .type = TSParseActionTypeShift, \ - .state = state_value, \ - .repetition = true \ - } \ - }} - -#define SHIFT_EXTRA() \ - {{ \ - .shift = { \ - .type = TSParseActionTypeShift, \ - .extra = true \ - } \ - }} - -#define REDUCE(symbol_val, child_count_val, ...) \ - {{ \ - .reduce = { \ - .type = TSParseActionTypeReduce, \ - .symbol = symbol_val, \ - .child_count = child_count_val, \ - __VA_ARGS__ \ - }, \ - }} - -#define RECOVER() \ - {{ \ - .type = TSParseActionTypeRecover \ - }} - -#define ACCEPT_INPUT() \ - {{ \ - .type = TSParseActionTypeAccept \ - }} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_PARSER_H_ diff --git a/crates/gpui/src/keymap_matcher/keymap_context.rs b/crates/gpui/src/keymap_matcher/keymap_context.rs index ad7ce929fe..b1dbd0e92d 100644 --- a/crates/gpui/src/keymap_matcher/keymap_context.rs +++ b/crates/gpui/src/keymap_matcher/keymap_context.rs @@ -1,11 +1,5 @@ -use anyhow::anyhow; - +use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet}; -use tree_sitter::{Language, Node, Parser}; - -extern "C" { - fn tree_sitter_context_predicate() -> Language; -} #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct KeymapContext { @@ -35,70 +29,13 @@ pub enum KeymapContextPredicate { } impl KeymapContextPredicate { - pub fn parse(source: &str) -> anyhow::Result { - let mut parser = Parser::new(); - let language = unsafe { tree_sitter_context_predicate() }; - parser.set_language(language).unwrap(); - let source = source.as_bytes(); - let tree = parser.parse(source, None).unwrap(); - Self::from_node(tree.root_node(), source) - } - - fn from_node(node: Node, source: &[u8]) -> anyhow::Result { - let parse_error = "error parsing context predicate"; - let kind = node.kind(); - - match kind { - "source" => Self::from_node(node.child(0).ok_or_else(|| anyhow!(parse_error))?, source), - "identifier" => Ok(Self::Identifier(node.utf8_text(source)?.into())), - "not" => { - let child = Self::from_node( - node.child_by_field_name("expression") - .ok_or_else(|| anyhow!(parse_error))?, - source, - )?; - Ok(Self::Not(Box::new(child))) - } - "and" | "or" => { - let left = Box::new(Self::from_node( - node.child_by_field_name("left") - .ok_or_else(|| anyhow!(parse_error))?, - source, - )?); - let right = Box::new(Self::from_node( - node.child_by_field_name("right") - .ok_or_else(|| anyhow!(parse_error))?, - source, - )?); - if kind == "and" { - Ok(Self::And(left, right)) - } else { - Ok(Self::Or(left, right)) - } - } - "equal" | "not_equal" => { - let left = node - .child_by_field_name("left") - .ok_or_else(|| anyhow!(parse_error))? - .utf8_text(source)? - .into(); - let right = node - .child_by_field_name("right") - .ok_or_else(|| anyhow!(parse_error))? - .utf8_text(source)? - .into(); - if kind == "equal" { - Ok(Self::Equal(left, right)) - } else { - Ok(Self::NotEqual(left, right)) - } - } - "parenthesized" => Self::from_node( - node.child_by_field_name("expression") - .ok_or_else(|| anyhow!(parse_error))?, - source, - ), - _ => Err(anyhow!(parse_error)), + pub fn parse(source: &str) -> Result { + let source = Self::skip_whitespace(source); + let (predicate, rest) = Self::parse_expr(source, 0)?; + if let Some(next) = rest.chars().next() { + Err(anyhow!("unexpected character {next:?}")) + } else { + Ok(predicate) } } @@ -120,4 +57,236 @@ impl KeymapContextPredicate { Self::Or(left, right) => left.eval(context) || right.eval(context), } } + + fn parse_expr( + mut source: &str, + min_precedence: u32, + ) -> anyhow::Result<(KeymapContextPredicate, &str)> { + type Op = + fn(KeymapContextPredicate, KeymapContextPredicate) -> Result; + + let (mut predicate, rest) = Self::parse_primary(source)?; + source = rest; + + 'parse: loop { + for (operator, precedence, constructor) in [ + ("&&", PRECEDENCE_AND, KeymapContextPredicate::new_and as Op), + ("||", PRECEDENCE_OR, KeymapContextPredicate::new_or as Op), + ("==", PRECEDENCE_EQ, KeymapContextPredicate::new_eq as Op), + ("!=", PRECEDENCE_EQ, KeymapContextPredicate::new_neq as Op), + ] { + if source.starts_with(operator) && precedence >= min_precedence { + source = Self::skip_whitespace(&source[operator.len()..]); + let (right, rest) = Self::parse_expr(source, precedence + 1)?; + predicate = constructor(predicate, right)?; + source = rest; + continue 'parse; + } + } + break; + } + + Ok((predicate, source)) + } + + fn parse_primary(mut source: &str) -> anyhow::Result<(KeymapContextPredicate, &str)> { + let next = source + .chars() + .next() + .ok_or_else(|| anyhow!("unexpected eof"))?; + match next { + '(' => { + source = Self::skip_whitespace(&source[1..]); + let (predicate, rest) = Self::parse_expr(source, 0)?; + if rest.starts_with(')') { + source = Self::skip_whitespace(&rest[1..]); + Ok((predicate, source)) + } else { + Err(anyhow!("expected a ')'")) + } + } + '!' => { + let source = Self::skip_whitespace(&source[1..]); + let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?; + Ok((KeymapContextPredicate::Not(Box::new(predicate)), source)) + } + _ if next.is_alphanumeric() || next == '_' => { + let len = source + .find(|c: char| !(c.is_alphanumeric() || c == '_')) + .unwrap_or(source.len()); + let (identifier, rest) = source.split_at(len); + source = Self::skip_whitespace(rest); + Ok(( + KeymapContextPredicate::Identifier(identifier.into()), + source, + )) + } + _ => Err(anyhow!("unexpected character {next:?}")), + } + } + + fn skip_whitespace(source: &str) -> &str { + let len = source + .find(|c: char| !c.is_whitespace()) + .unwrap_or(source.len()); + &source[len..] + } + + fn new_or(self, other: Self) -> Result { + Ok(Self::Or(Box::new(self), Box::new(other))) + } + + fn new_and(self, other: Self) -> Result { + Ok(Self::And(Box::new(self), Box::new(other))) + } + + fn new_eq(self, other: Self) -> Result { + if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) { + Ok(Self::Equal(left, right)) + } else { + Err(anyhow!("operands must be identifiers")) + } + } + + fn new_neq(self, other: Self) -> Result { + if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) { + Ok(Self::NotEqual(left, right)) + } else { + Err(anyhow!("operands must be identifiers")) + } + } +} + +const PRECEDENCE_OR: u32 = 1; +const PRECEDENCE_AND: u32 = 2; +const PRECEDENCE_EQ: u32 = 3; +const PRECEDENCE_NOT: u32 = 4; + +#[cfg(test)] +mod tests { + use super::KeymapContextPredicate::{self, *}; + + #[test] + fn test_parse_identifiers() { + // Identifiers + assert_eq!( + KeymapContextPredicate::parse("abc12").unwrap(), + Identifier("abc12".into()) + ); + assert_eq!( + KeymapContextPredicate::parse("_1a").unwrap(), + Identifier("_1a".into()) + ); + } + + #[test] + fn test_parse_negations() { + assert_eq!( + KeymapContextPredicate::parse("!abc").unwrap(), + Not(Box::new(Identifier("abc".into()))) + ); + assert_eq!( + KeymapContextPredicate::parse(" ! ! abc").unwrap(), + Not(Box::new(Not(Box::new(Identifier("abc".into()))))) + ); + } + + #[test] + fn test_parse_equality_operators() { + assert_eq!( + KeymapContextPredicate::parse("a == b").unwrap(), + Equal("a".into(), "b".into()) + ); + assert_eq!( + KeymapContextPredicate::parse("c!=d").unwrap(), + NotEqual("c".into(), "d".into()) + ); + assert_eq!( + KeymapContextPredicate::parse("c == !d") + .unwrap_err() + .to_string(), + "operands must be identifiers" + ); + } + + #[test] + fn test_parse_boolean_operators() { + assert_eq!( + KeymapContextPredicate::parse("a || b").unwrap(), + Or( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a || !b && c").unwrap(), + Or( + Box::new(Identifier("a".into())), + Box::new(And( + Box::new(Not(Box::new(Identifier("b".into())))), + Box::new(Identifier("c".into())) + )) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a && b || c&&d").unwrap(), + Or( + Box::new(And( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())) + )), + Box::new(And( + Box::new(Identifier("c".into())), + Box::new(Identifier("d".into())) + )) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a == b && c || d == e && f").unwrap(), + Or( + Box::new(And( + Box::new(Equal("a".into(), "b".into())), + Box::new(Identifier("c".into())) + )), + Box::new(And( + Box::new(Equal("d".into(), "e".into())), + Box::new(Identifier("f".into())) + )) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a && b && c && d").unwrap(), + And( + Box::new(And( + Box::new(And( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())) + )), + Box::new(Identifier("c".into())), + )), + Box::new(Identifier("d".into())) + ), + ); + } + + #[test] + fn test_parse_parenthesized_expressions() { + assert_eq!( + KeymapContextPredicate::parse("a && (b == c || d != e)").unwrap(), + And( + Box::new(Identifier("a".into())), + Box::new(Or( + Box::new(Equal("b".into(), "c".into())), + Box::new(NotEqual("d".into(), "e".into())), + )), + ), + ); + assert_eq!( + KeymapContextPredicate::parse(" ( a || b ) ").unwrap(), + Or( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())), + ) + ); + } } From 373902d9333ccecba5eafcd9e45b695f488ec099 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 16:00:46 -0800 Subject: [PATCH 28/83] Add '>' child operator in keymap context predicates --- crates/gpui/src/app.rs | 54 ++++++++---- crates/gpui/src/keymap_matcher.rs | 84 +++++++++++++++---- crates/gpui/src/keymap_matcher/binding.rs | 12 +-- .../gpui/src/keymap_matcher/keymap_context.rs | 40 +++++---- crates/gpui/src/presenter.rs | 2 +- 5 files changed, 136 insertions(+), 56 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 6b784833c7..6a4086c5eb 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1349,21 +1349,24 @@ impl MutableAppContext { /// Return keystrokes that would dispatch the given action closest to the focused view, if there are any. pub(crate) fn keystrokes_for_action( - &self, + &mut self, window_id: usize, - dispatch_path: &[usize], + view_stack: &[usize], action: &dyn Action, ) -> Option> { - for view_id in dispatch_path.iter().rev() { + self.keystroke_matcher.contexts.clear(); + for view_id in view_stack.iter().rev() { let view = self .cx .views .get(&(window_id, *view_id)) .expect("view in responder chain does not exist"); - let keymap_context = view.keymap_context(self.as_ref()); + self.keystroke_matcher + .contexts + .push(view.keymap_context(self.as_ref())); let keystrokes = self .keystroke_matcher - .keystrokes_for_action(action, &keymap_context); + .keystrokes_for_action(action, &self.keystroke_matcher.contexts); if keystrokes.is_some() { return keystrokes; } @@ -6681,7 +6684,7 @@ mod tests { view_3 }); - // This keymap's only binding dispatches an action on view 2 because that view will have + // This binding only dispatches an action on view 2 because that view will have // "a" and "b" in its context, but not "c". cx.add_bindings(vec![Binding::new( "a", @@ -6691,16 +6694,31 @@ mod tests { cx.add_bindings(vec![Binding::new("b", Action("b".to_string()), None)]); + // This binding only dispatches an action on views 2 and 3, because they have + // a parent view with a in its context + cx.add_bindings(vec![Binding::new( + "c", + Action("c".to_string()), + Some("b > c"), + )]); + + // This binding only dispatches an action on view 2, because they have + // a parent view with a in its context + cx.add_bindings(vec![Binding::new( + "d", + Action("d".to_string()), + Some("a && !b > b"), + )]); + let actions = Rc::new(RefCell::new(Vec::new())); cx.add_action({ let actions = actions.clone(); move |view: &mut View, action: &Action, cx| { - if action.0 == "a" { - actions.borrow_mut().push(format!("{} a", view.id)); - } else { - actions - .borrow_mut() - .push(format!("{} {}", view.id, action.0)); + actions + .borrow_mut() + .push(format!("{} {}", view.id, action.0)); + + if action.0 == "b" { cx.propagate_action(); } } @@ -6714,14 +6732,20 @@ mod tests { }); cx.dispatch_keystroke(window_id, &Keystroke::parse("a").unwrap()); - assert_eq!(&*actions.borrow(), &["2 a"]); - actions.borrow_mut().clear(); cx.dispatch_keystroke(window_id, &Keystroke::parse("b").unwrap()); - assert_eq!(&*actions.borrow(), &["3 b", "2 b", "1 b", "global b"]); + actions.borrow_mut().clear(); + + cx.dispatch_keystroke(window_id, &Keystroke::parse("c").unwrap()); + assert_eq!(&*actions.borrow(), &["3 c"]); + actions.borrow_mut().clear(); + + cx.dispatch_keystroke(window_id, &Keystroke::parse("d").unwrap()); + assert_eq!(&*actions.borrow(), &["2 d"]); + actions.borrow_mut().clear(); } #[crate::test(self)] diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index e007605cff..c7de035232 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -25,6 +25,7 @@ pub struct KeyPressed { impl_actions!(gpui, [KeyPressed]); pub struct KeymapMatcher { + pub contexts: Vec, pending_views: HashMap, pending_keystrokes: Vec, keymap: Keymap, @@ -33,6 +34,7 @@ pub struct KeymapMatcher { impl KeymapMatcher { pub fn new(keymap: Keymap) -> Self { Self { + contexts: Vec::new(), pending_views: Default::default(), pending_keystrokes: Vec::new(), keymap, @@ -70,7 +72,7 @@ impl KeymapMatcher { pub fn push_keystroke( &mut self, keystroke: Keystroke, - dispatch_path: Vec<(usize, KeymapContext)>, + mut dispatch_path: Vec<(usize, KeymapContext)>, ) -> MatchResult { let mut any_pending = false; let mut matched_bindings: Vec<(usize, Box)> = Vec::new(); @@ -78,7 +80,11 @@ impl KeymapMatcher { let first_keystroke = self.pending_keystrokes.is_empty(); self.pending_keystrokes.push(keystroke.clone()); - for (view_id, context) in dispatch_path { + self.contexts.clear(); + self.contexts + .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1))); + + for (i, (view_id, _)) in dispatch_path.into_iter().enumerate() { // Don't require pending view entry if there are no pending keystrokes if !first_keystroke && !self.pending_views.contains_key(&view_id) { continue; @@ -87,14 +93,15 @@ impl KeymapMatcher { // If there is a previous view context, invalidate that view if it // has changed if let Some(previous_view_context) = self.pending_views.remove(&view_id) { - if previous_view_context != context { + if previous_view_context != self.contexts[i] { continue; } } // Find the bindings which map the pending keystrokes and current context for binding in self.keymap.bindings().iter().rev() { - match binding.match_keys_and_context(&self.pending_keystrokes, &context) { + match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..]) + { BindingMatchResult::Complete(mut action) => { // Swap in keystroke for special KeyPressed action if action.name() == "KeyPressed" && action.namespace() == "gpui" { @@ -105,7 +112,7 @@ impl KeymapMatcher { matched_bindings.push((view_id, action)) } BindingMatchResult::Partial => { - self.pending_views.insert(view_id, context.clone()); + self.pending_views.insert(view_id, self.contexts[i].clone()); any_pending = true; } _ => {} @@ -129,13 +136,13 @@ impl KeymapMatcher { pub fn keystrokes_for_action( &self, action: &dyn Action, - context: &KeymapContext, + contexts: &[KeymapContext], ) -> Option> { self.keymap .bindings() .iter() .rev() - .find_map(|binding| binding.keystrokes_for_action(action, context)) + .find_map(|binding| binding.keystrokes_for_action(action, contexts)) } } @@ -349,27 +356,70 @@ mod tests { } #[test] - fn test_context_predicate_eval() -> Result<()> { - let predicate = KeymapContextPredicate::parse("a && b || c == d")?; + fn test_context_predicate_eval() { + let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap(); let mut context = KeymapContext::default(); context.set.insert("a".into()); - assert!(!predicate.eval(&context)); + assert!(!predicate.eval(&[context])); + let mut context = KeymapContext::default(); + context.set.insert("a".into()); context.set.insert("b".into()); - assert!(predicate.eval(&context)); + assert!(predicate.eval(&[context])); - context.set.remove("b"); + let mut context = KeymapContext::default(); + context.set.insert("a".into()); context.map.insert("c".into(), "x".into()); - assert!(!predicate.eval(&context)); + assert!(!predicate.eval(&[context])); + let mut context = KeymapContext::default(); + context.set.insert("a".into()); context.map.insert("c".into(), "d".into()); - assert!(predicate.eval(&context)); + assert!(predicate.eval(&[context])); - let predicate = KeymapContextPredicate::parse("!a")?; - assert!(predicate.eval(&KeymapContext::default())); + let predicate = KeymapContextPredicate::parse("!a").unwrap(); + assert!(predicate.eval(&[KeymapContext::default()])); + } - Ok(()) + #[test] + fn test_context_child_predicate_eval() { + let predicate = KeymapContextPredicate::parse("a && b > c").unwrap(); + let contexts = [ + context_set(&["e", "f"]), + context_set(&["c", "d"]), // match this context + context_set(&["a", "b"]), + ]; + + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); + + let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap(); + let contexts = [ + context_set(&["f"]), + context_set(&["e"]), // only match this context + context_set(&["c"]), + context_set(&["a", "b"]), + context_set(&["e"]), + context_set(&["c", "d"]), + context_set(&["a", "b"]), + ]; + + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); + assert!(!predicate.eval(&contexts[3..])); + assert!(!predicate.eval(&contexts[4..])); + assert!(!predicate.eval(&contexts[5..])); + assert!(!predicate.eval(&contexts[6..])); + + fn context_set(names: &[&str]) -> KeymapContext { + KeymapContext { + set: names.iter().copied().map(str::to_string).collect(), + ..Default::default() + } + } } #[test] diff --git a/crates/gpui/src/keymap_matcher/binding.rs b/crates/gpui/src/keymap_matcher/binding.rs index b16b7f1552..afd65d4f04 100644 --- a/crates/gpui/src/keymap_matcher/binding.rs +++ b/crates/gpui/src/keymap_matcher/binding.rs @@ -41,24 +41,24 @@ impl Binding { }) } - fn match_context(&self, context: &KeymapContext) -> bool { + fn match_context(&self, contexts: &[KeymapContext]) -> bool { self.context_predicate .as_ref() - .map(|predicate| predicate.eval(context)) + .map(|predicate| predicate.eval(contexts)) .unwrap_or(true) } pub fn match_keys_and_context( &self, pending_keystrokes: &Vec, - context: &KeymapContext, + contexts: &[KeymapContext], ) -> BindingMatchResult { if self .keystrokes .as_ref() .map(|keystrokes| keystrokes.starts_with(&pending_keystrokes)) .unwrap_or(true) - && self.match_context(context) + && self.match_context(contexts) { // If the binding is completed, push it onto the matches list if self @@ -79,9 +79,9 @@ impl Binding { pub fn keystrokes_for_action( &self, action: &dyn Action, - context: &KeymapContext, + contexts: &[KeymapContext], ) -> Option> { - if self.action.eq(action) && self.match_context(context) { + if self.action.eq(action) && self.match_context(contexts) { self.keystrokes.clone() } else { None diff --git a/crates/gpui/src/keymap_matcher/keymap_context.rs b/crates/gpui/src/keymap_matcher/keymap_context.rs index b1dbd0e92d..28f5f80c83 100644 --- a/crates/gpui/src/keymap_matcher/keymap_context.rs +++ b/crates/gpui/src/keymap_matcher/keymap_context.rs @@ -23,6 +23,7 @@ pub enum KeymapContextPredicate { Identifier(String), Equal(String, String), NotEqual(String, String), + Child(Box, Box), Not(Box), And(Box, Box), Or(Box, Box), @@ -39,7 +40,8 @@ impl KeymapContextPredicate { } } - pub fn eval(&self, context: &KeymapContext) -> bool { + pub fn eval(&self, contexts: &[KeymapContext]) -> bool { + let Some(context) = contexts.first() else { return false }; match self { Self::Identifier(name) => context.set.contains(name.as_str()), Self::Equal(left, right) => context @@ -52,16 +54,14 @@ impl KeymapContextPredicate { .get(left) .map(|value| value != right) .unwrap_or(true), - Self::Not(pred) => !pred.eval(context), - Self::And(left, right) => left.eval(context) && right.eval(context), - Self::Or(left, right) => left.eval(context) || right.eval(context), + Self::Not(pred) => !pred.eval(contexts), + Self::Child(parent, child) => parent.eval(&contexts[1..]) && child.eval(contexts), + Self::And(left, right) => left.eval(contexts) && right.eval(contexts), + Self::Or(left, right) => left.eval(contexts) || right.eval(contexts), } } - fn parse_expr( - mut source: &str, - min_precedence: u32, - ) -> anyhow::Result<(KeymapContextPredicate, &str)> { + fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> { type Op = fn(KeymapContextPredicate, KeymapContextPredicate) -> Result; @@ -70,10 +70,11 @@ impl KeymapContextPredicate { 'parse: loop { for (operator, precedence, constructor) in [ - ("&&", PRECEDENCE_AND, KeymapContextPredicate::new_and as Op), - ("||", PRECEDENCE_OR, KeymapContextPredicate::new_or as Op), - ("==", PRECEDENCE_EQ, KeymapContextPredicate::new_eq as Op), - ("!=", PRECEDENCE_EQ, KeymapContextPredicate::new_neq as Op), + (">", PRECEDENCE_CHILD, Self::new_child as Op), + ("&&", PRECEDENCE_AND, Self::new_and as Op), + ("||", PRECEDENCE_OR, Self::new_or as Op), + ("==", PRECEDENCE_EQ, Self::new_eq as Op), + ("!=", PRECEDENCE_EQ, Self::new_neq as Op), ] { if source.starts_with(operator) && precedence >= min_precedence { source = Self::skip_whitespace(&source[operator.len()..]); @@ -89,7 +90,7 @@ impl KeymapContextPredicate { Ok((predicate, source)) } - fn parse_primary(mut source: &str) -> anyhow::Result<(KeymapContextPredicate, &str)> { + fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> { let next = source .chars() .next() @@ -140,6 +141,10 @@ impl KeymapContextPredicate { Ok(Self::And(Box::new(self), Box::new(other))) } + fn new_child(self, other: Self) -> Result { + Ok(Self::Child(Box::new(self), Box::new(other))) + } + fn new_eq(self, other: Self) -> Result { if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) { Ok(Self::Equal(left, right)) @@ -157,10 +162,11 @@ impl KeymapContextPredicate { } } -const PRECEDENCE_OR: u32 = 1; -const PRECEDENCE_AND: u32 = 2; -const PRECEDENCE_EQ: u32 = 3; -const PRECEDENCE_NOT: u32 = 4; +const PRECEDENCE_CHILD: u32 = 1; +const PRECEDENCE_OR: u32 = 2; +const PRECEDENCE_AND: u32 = 3; +const PRECEDENCE_EQ: u32 = 4; +const PRECEDENCE_NOT: u32 = 5; #[cfg(test)] mod tests { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 5c13f7467a..bbaf1ed0bb 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -573,7 +573,7 @@ pub struct LayoutContext<'a> { impl<'a> LayoutContext<'a> { pub(crate) fn keystrokes_for_action( - &self, + &mut self, action: &dyn Action, ) -> Option> { self.app From 3312a063688f047566cf95f7f5c9972e10f5da5b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 16:01:15 -0800 Subject: [PATCH 29/83] Move focus back from buffer search using tab, not cmd-f --- assets/keymaps/default.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index fbbb517c93..a0f437cf91 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -186,10 +186,10 @@ } }, { - "context": "BufferSearchBar", + "context": "BufferSearchBar > Editor", "bindings": { "escape": "buffer_search::Dismiss", - "cmd-f": "buffer_search::FocusEditor", + "tab": "buffer_search::FocusEditor", "enter": "search::SelectNextMatch", "shift-enter": "search::SelectPrevMatch" } From 6810490bf4f856f187e53ca2af7cf1a0c04299d4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 16:11:13 -0800 Subject: [PATCH 30/83] Remove tree-sitter dependency from gpui --- Cargo.lock | 1 - crates/gpui/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e79a68fc4..d9976fbba7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2563,7 +2563,6 @@ dependencies = [ "sum_tree", "time 0.3.17", "tiny-skia", - "tree-sitter", "usvg", "util", "waker-fn", diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 683e3bdfcd..5153c1f7c1 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -45,7 +45,6 @@ smallvec = { version = "1.6", features = ["union"] } smol = "1.2" time = { version = "0.3", features = ["serde", "serde-well-known"] } tiny-skia = "0.5" -tree-sitter = "0.20" usvg = "0.14" waker-fn = "1.1.0" From 244f2593314e6c83c2c890af6c5351fea0ec037b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 17:42:00 -0800 Subject: [PATCH 31/83] Always auto-indent in block-wise mode when pasting If the text was copied outside of Zed, so the original indent column is unknown, then act as if the first line was copied in its entirety. --- crates/editor/src/editor.rs | 4 +++- crates/language/src/buffer.rs | 30 ++++++++++++++++-------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8847c01af7..fb8e37077e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2020,7 +2020,9 @@ impl Editor { old_selections .iter() .map(|s| (s.start..s.end, text.clone())), - Some(AutoindentMode::EachLine), + Some(AutoindentMode::Block { + original_indent_columns: Vec::new(), + }), cx, ); anchors diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 9ceb6b32a9..d1e7316f1b 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1332,13 +1332,6 @@ impl Buffer { let edit_id = edit_operation.local_timestamp(); if let Some((before_edit, mode)) = autoindent_request { - let (start_columns, is_block_mode) = match mode { - AutoindentMode::Block { - original_indent_columns: start_columns, - } => (start_columns, true), - AutoindentMode::EachLine => (Default::default(), false), - }; - let mut delta = 0isize; let entries = edits .into_iter() @@ -1352,7 +1345,7 @@ impl Buffer { let mut range_of_insertion_to_indent = 0..new_text_len; let mut first_line_is_new = false; - let mut start_column = None; + let mut original_indent_column = None; // When inserting an entire line at the beginning of an existing line, // treat the insertion as new. @@ -1364,14 +1357,23 @@ impl Buffer { // When inserting text starting with a newline, avoid auto-indenting the // previous line. - if new_text[range_of_insertion_to_indent.clone()].starts_with('\n') { + if new_text.starts_with('\n') { range_of_insertion_to_indent.start += 1; first_line_is_new = true; } // Avoid auto-indenting after the insertion. - if is_block_mode { - start_column = start_columns.get(ix).copied(); + if let AutoindentMode::Block { + original_indent_columns, + } = &mode + { + original_indent_column = + Some(original_indent_columns.get(ix).copied().unwrap_or_else(|| { + indent_size_for_text( + new_text[range_of_insertion_to_indent.clone()].chars(), + ) + .len + })); if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') { range_of_insertion_to_indent.end -= 1; } @@ -1379,7 +1381,7 @@ impl Buffer { AutoindentRequestEntry { first_line_is_new, - original_indent_column: start_column, + original_indent_column, indent_size: before_edit.language_indent_size_at(range.start, cx), range: self.anchor_before(new_start + range_of_insertion_to_indent.start) ..self.anchor_after(new_start + range_of_insertion_to_indent.end), @@ -1390,7 +1392,7 @@ impl Buffer { self.autoindent_requests.push(Arc::new(AutoindentRequest { before_edit, entries, - is_block_mode, + is_block_mode: matches!(mode, AutoindentMode::Block { .. }), })); } @@ -2397,7 +2399,7 @@ impl BufferSnapshot { } } -pub fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize { +fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize { indent_size_for_text(text.chars_at(Point::new(row, 0))) } From 0bd6f9b6ceb09d639e8bb9aec886f5acb2008305 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 18:06:58 -0800 Subject: [PATCH 32/83] Add a test for block-wise auto-indent without original indent info --- crates/language/src/buffer_tests.rs | 95 +++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 6 deletions(-) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 09ccc5d621..b7f35e9ad5 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -995,12 +995,17 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { .unindent(); let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + // When this text was copied, both of the quotation marks were at the same + // indent level, but the indentation of the first line was not included in + // the copied text. This information is retained in the + // 'original_indent_columns' vector. + let original_indent_columns = vec![4]; let inserted_text = r#" " - c - d - e - " + c + d + e + " "# .unindent(); @@ -1009,7 +1014,7 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { buffer.edit( [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())], Some(AutoindentMode::Block { - original_indent_columns: vec![0], + original_indent_columns: original_indent_columns.clone(), }), cx, ); @@ -1037,7 +1042,7 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { buffer.edit( [(Point::new(2, 8)..Point::new(2, 8), inserted_text)], Some(AutoindentMode::Block { - original_indent_columns: vec![0], + original_indent_columns: original_indent_columns.clone(), }), cx, ); @@ -1060,6 +1065,84 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { }); } +#[gpui::test] +fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut MutableAppContext) { + cx.set_global(Settings::test(cx)); + cx.add_model(|cx| { + let text = r#" + fn a() { + if b() { + + } + } + "# + .unindent(); + let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + + // The original indent columns are not known, so this text is + // auto-indented in a block as if the first line was copied in + // its entirety. + let original_indent_columns = Vec::new(); + let inserted_text = " c\n .d()\n .e();"; + + // Insert the block at column zero. The entire block is indented + // so that the first line matches the previous line's indentation. + buffer.edit( + [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())], + Some(AutoindentMode::Block { + original_indent_columns: original_indent_columns.clone(), + }), + cx, + ); + assert_eq!( + buffer.text(), + r#" + fn a() { + if b() { + c + .d() + .e(); + } + } + "# + .unindent() + ); + + // Grouping is disabled in tests, so we need 2 undos + buffer.undo(cx); // Undo the auto-indent + buffer.undo(cx); // Undo the original edit + + // Insert the block at a deeper indent level. The entire block is outdented. + buffer.edit( + [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))], + None, + cx, + ); + buffer.edit( + [(Point::new(2, 12)..Point::new(2, 12), inserted_text)], + Some(AutoindentMode::Block { + original_indent_columns: Vec::new(), + }), + cx, + ); + assert_eq!( + buffer.text(), + r#" + fn a() { + if b() { + c + .d() + .e(); + } + } + "# + .unindent() + ); + + buffer + }); +} + #[gpui::test] fn test_autoindent_language_without_indents_query(cx: &mut MutableAppContext) { cx.set_global(Settings::test(cx)); From 467e5691b968134e8d29ee6c4a2ad2c73fcf134c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Jan 2023 16:22:47 +0100 Subject: [PATCH 33/83] Include saved mtime and fingerprint when serializing buffers This still doesn't include: - An assertion in the randomized test to ensure buffers are not spuriously marked as modified - Sending an update when synchronizing buffers after a reconnection --- crates/language/src/buffer.rs | 7 +++++++ crates/rpc/proto/zed.proto | 2 ++ 2 files changed, 9 insertions(+) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 9ceb6b32a9..b92f0a7c3e 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -385,6 +385,11 @@ impl Buffer { rpc::proto::LineEnding::from_i32(message.line_ending) .ok_or_else(|| anyhow!("missing line_ending"))?, )); + this.saved_version_fingerprint = message.saved_version_fingerprint; + this.saved_mtime = message + .saved_mtime + .ok_or_else(|| anyhow!("invalid saved_mtime"))? + .into(); Ok(this) } @@ -395,6 +400,8 @@ impl Buffer { base_text: self.base_text().to_string(), diff_base: self.diff_base.as_ref().map(|h| h.to_string()), line_ending: proto::serialize_line_ending(self.line_ending()) as i32, + saved_version_fingerprint: self.saved_version_fingerprint.clone(), + saved_mtime: Some(self.saved_mtime.into()), } } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 8626806abe..86494df05b 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -975,6 +975,8 @@ message BufferState { string base_text = 3; optional string diff_base = 4; LineEnding line_ending = 5; + string saved_version_fingerprint = 6; + Timestamp saved_mtime = 7; } message BufferChunk { From 2cd9db1cfeb4dd787d88a806aca1301d69060136 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:20:09 +0100 Subject: [PATCH 34/83] Ensure `Buffer::{is_dirty,has_conflict}` converge in randomized test --- .../src/tests/randomized_integration_tests.rs | 99 ++++++++++++++----- 1 file changed, 77 insertions(+), 22 deletions(-) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index 11c62005c2..8517385798 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -10,7 +10,7 @@ use collections::BTreeMap; use fs::{FakeFs, Fs as _}; use futures::StreamExt as _; use gpui::{executor::Deterministic, ModelHandle, TestAppContext}; -use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16}; +use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16, Rope}; use lsp::FakeLanguageServer; use parking_lot::Mutex; use project::{search::SearchQuery, Project}; @@ -19,7 +19,12 @@ use rand::{ prelude::*, }; use settings::Settings; -use std::{env, ffi::OsStr, path::PathBuf, sync::Arc}; +use std::{ + env, + ffi::OsStr, + path::{Path, PathBuf}, + sync::Arc, +}; #[gpui::test(iterations = 100)] async fn test_random_collaboration( @@ -398,6 +403,14 @@ async fn test_random_collaboration( let guest_diff_base = guest_buffer .read_with(client_cx, |b, _| b.diff_base().map(ToString::to_string)); assert_eq!(guest_diff_base, host_diff_base); + + let host_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); + let guest_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); + assert_eq!(guest_is_dirty, host_is_dirty); + + let host_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); + let guest_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); + assert_eq!(guest_has_conflict, host_has_conflict); } } } @@ -638,14 +651,7 @@ async fn randomly_mutate_git(client: &mut TestClient, rng: &Mutex) { client.fs.create_dir(&git_dir_path).await.unwrap(); } - let mut child_paths = client.fs.read_dir(&dir_path).await.unwrap(); - let mut child_file_paths = Vec::new(); - while let Some(child_path) = child_paths.next().await { - let child_path = child_path.unwrap(); - if client.fs.is_file(&child_path).await { - child_file_paths.push(child_path); - } - } + let mut child_file_paths = child_file_paths(client, &dir_path).await; let count = rng.lock().gen_range(0..=child_file_paths.len()); child_file_paths.shuffle(&mut *rng.lock()); child_file_paths.truncate(count); @@ -669,26 +675,63 @@ async fn randomly_mutate_git(client: &mut TestClient, rng: &Mutex) { } async fn randomly_mutate_fs(client: &mut TestClient, rng: &Mutex) { - let is_dir = rng.lock().gen::(); - let mut new_path = client + let parent_dir_path = client .fs .directories() .await .choose(&mut *rng.lock()) .unwrap() .clone(); - new_path.push(gen_file_name(rng)); + + let is_dir = rng.lock().gen::(); if is_dir { - log::info!("{}: creating local dir at {:?}", client.username, new_path); - client.fs.create_dir(&new_path).await.unwrap(); + let mut dir_path = parent_dir_path.clone(); + dir_path.push(gen_file_name(rng)); + log::info!("{}: creating local dir at {:?}", client.username, dir_path); + client.fs.create_dir(&dir_path).await.unwrap(); } else { - new_path.set_extension("rs"); - log::info!("{}: creating local file at {:?}", client.username, new_path); - client - .fs - .create_file(&new_path, Default::default()) - .await - .unwrap(); + let child_file_paths = child_file_paths(client, &parent_dir_path).await; + let create_new_file = child_file_paths.is_empty() || rng.lock().gen(); + let text = Alphanumeric.sample_string(&mut *rng.lock(), 16); + if create_new_file { + let mut file_path = parent_dir_path.clone(); + file_path.push(gen_file_name(rng)); + file_path.set_extension("rs"); + log::info!( + "{}: creating local file at {:?}", + client.username, + file_path + ); + client + .fs + .create_file(&file_path, Default::default()) + .await + .unwrap(); + log::info!( + "{}: setting local file {:?} text to {:?}", + client.username, + file_path, + text + ); + client + .fs + .save(&file_path, &Rope::from(text.as_str()), fs::LineEnding::Unix) + .await + .unwrap(); + } else { + let file_path = child_file_paths.choose(&mut *rng.lock()).unwrap(); + log::info!( + "{}: setting local file {:?} text to {:?}", + client.username, + file_path, + text + ); + client + .fs + .save(file_path, &Rope::from(text.as_str()), fs::LineEnding::Unix) + .await + .unwrap(); + } } } @@ -1154,3 +1197,15 @@ fn gen_file_name(rng: &Mutex) -> String { } name } + +async fn child_file_paths(client: &TestClient, dir_path: &Path) -> Vec { + let mut child_paths = client.fs.read_dir(dir_path).await.unwrap(); + let mut child_file_paths = Vec::new(); + while let Some(child_path) = child_paths.next().await { + let child_path = child_path.unwrap(); + if client.fs.is_file(&child_path).await { + child_file_paths.push(child_path); + } + } + child_file_paths +} From bb200aa082f7bb760463fd55eebb562dc86d1601 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:20:50 +0100 Subject: [PATCH 35/83] Relay saved version metadata to ensure buffers modified state converges --- crates/language/src/buffer.rs | 10 ++++++++++ crates/project/src/project.rs | 13 +++++++++++++ crates/rpc/proto/zed.proto | 5 +++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index b92f0a7c3e..0e546d4ba0 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -385,6 +385,7 @@ impl Buffer { rpc::proto::LineEnding::from_i32(message.line_ending) .ok_or_else(|| anyhow!("missing line_ending"))?, )); + this.saved_version = proto::deserialize_version(message.saved_version); this.saved_version_fingerprint = message.saved_version_fingerprint; this.saved_mtime = message .saved_mtime @@ -400,6 +401,7 @@ impl Buffer { base_text: self.base_text().to_string(), diff_base: self.diff_base.as_ref().map(|h| h.to_string()), line_ending: proto::serialize_line_ending(self.line_ending()) as i32, + saved_version: proto::serialize_version(&self.saved_version), saved_version_fingerprint: self.saved_version_fingerprint.clone(), saved_mtime: Some(self.saved_mtime.into()), } @@ -556,6 +558,14 @@ impl Buffer { &self.saved_version } + pub fn saved_version_fingerprint(&self) -> &str { + &self.saved_version_fingerprint + } + + pub fn saved_mtime(&self) -> SystemTime { + self.saved_mtime + } + pub fn set_language(&mut self, language: Option>, cx: &mut ModelContext) { self.syntax_map.lock().clear(); self.language = language; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 478271118c..d3b3c11f2e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -5209,6 +5209,19 @@ impl Project { }) .log_err(); + client + .send(proto::BufferReloaded { + project_id, + buffer_id, + version: language::proto::serialize_version(buffer.saved_version()), + mtime: Some(buffer.saved_mtime().into()), + fingerprint: buffer.saved_version_fingerprint().into(), + line_ending: language::proto::serialize_line_ending( + buffer.line_ending(), + ) as i32, + }) + .log_err(); + cx.background() .spawn( async move { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 86494df05b..ba481ce45b 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -975,8 +975,9 @@ message BufferState { string base_text = 3; optional string diff_base = 4; LineEnding line_ending = 5; - string saved_version_fingerprint = 6; - Timestamp saved_mtime = 7; + repeated VectorClockEntry saved_version = 6; + string saved_version_fingerprint = 7; + Timestamp saved_mtime = 8; } message BufferChunk { From fcf97ab41eabfa6719138a368e0d0301e3daa030 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:21:20 +0100 Subject: [PATCH 36/83] Bump protocol version --- crates/rpc/src/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 39cdea7165..b05bc17906 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 45; +pub const PROTOCOL_VERSION: u32 = 46; From 7726a9ec3d8e2390f55fb0c10585c8861626d5f7 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 17 Jan 2023 10:42:53 -0500 Subject: [PATCH 37/83] Utilize fit autoscroll for various go-to actions --- crates/editor/src/editor.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8847c01af7..e2bf3c775b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4799,7 +4799,7 @@ impl Editor { if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() { let (group_id, jump_to) = popover.activation_info(); if self.activate_diagnostics(group_id, cx) { - self.change_selections(Some(Autoscroll::center()), cx, |s| { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { let mut new_selection = s.newest_anchor().clone(); new_selection.collapse_to(jump_to, SelectionGoal::None); s.select_anchors(vec![new_selection.clone()]); @@ -4845,7 +4845,7 @@ impl Editor { if let Some((primary_range, group_id)) = group { if self.activate_diagnostics(group_id, cx) { - self.change_selections(Some(Autoscroll::center()), cx, |s| { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select(vec![Selection { id: selection.id, start: primary_range.start, @@ -4920,7 +4920,7 @@ impl Editor { .dedup(); if let Some(hunk) = hunks.next() { - this.change_selections(Some(Autoscroll::center()), cx, |s| { + this.change_selections(Some(Autoscroll::fit()), cx, |s| { let row = hunk.start_display_row(); let point = DisplayPoint::new(row, 0); s.select_display_ranges([point..point]); @@ -5018,7 +5018,7 @@ impl Editor { if editor_handle != target_editor_handle { pane.update(cx, |pane, _| pane.disable_history()); } - target_editor.change_selections(Some(Autoscroll::center()), cx, |s| { + target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges([range]); }); From cc788dc5f74164a9bab5eeefed015e7be165157f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:46:06 +0100 Subject: [PATCH 38/83] Verify `saved_version`, `saved_version_fingerprint` and `saved_mtime` --- .../src/tests/randomized_integration_tests.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index 8517385798..34346e2128 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -404,6 +404,25 @@ async fn test_random_collaboration( .read_with(client_cx, |b, _| b.diff_base().map(ToString::to_string)); assert_eq!(guest_diff_base, host_diff_base); + let host_saved_version = + host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); + let guest_saved_version = + host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); + assert_eq!(guest_saved_version, host_saved_version); + + let host_saved_version_fingerprint = host_buffer + .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); + let guest_saved_version_fingerprint = host_buffer + .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); + assert_eq!( + guest_saved_version_fingerprint, + host_saved_version_fingerprint + ); + + let host_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); + let guest_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); + assert_eq!(guest_saved_mtime, host_saved_mtime); + let host_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); let guest_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); assert_eq!(guest_is_dirty, host_is_dirty); From dc88a67f502dc77273b0170d7358ce7c45f79453 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 18:09:45 +0100 Subject: [PATCH 39/83] Fix assertions --- .../collab/src/tests/randomized_integration_tests.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index 34346e2128..ed8f984d27 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -407,28 +407,28 @@ async fn test_random_collaboration( let host_saved_version = host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); let guest_saved_version = - host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); + guest_buffer.read_with(client_cx, |b, _| b.saved_version().clone()); assert_eq!(guest_saved_version, host_saved_version); let host_saved_version_fingerprint = host_buffer .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); - let guest_saved_version_fingerprint = host_buffer - .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); + let guest_saved_version_fingerprint = guest_buffer + .read_with(client_cx, |b, _| b.saved_version_fingerprint().to_string()); assert_eq!( guest_saved_version_fingerprint, host_saved_version_fingerprint ); let host_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); - let guest_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); + let guest_saved_mtime = guest_buffer.read_with(client_cx, |b, _| b.saved_mtime()); assert_eq!(guest_saved_mtime, host_saved_mtime); let host_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); - let guest_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); + let guest_is_dirty = guest_buffer.read_with(client_cx, |b, _| b.is_dirty()); assert_eq!(guest_is_dirty, host_is_dirty); let host_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); - let guest_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); + let guest_has_conflict = guest_buffer.read_with(client_cx, |b, _| b.has_conflict()); assert_eq!(guest_has_conflict, host_has_conflict); } } From c3b102f5a82efa010b6d0fd9824b76f562a198ca Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 17 Jan 2023 16:46:01 -0500 Subject: [PATCH 40/83] Add users to mailing list when using an invite link --- crates/collab/src/api.rs | 3 +++ crates/collab/src/db.rs | 2 ++ crates/collab/src/db/tests.rs | 40 ++++++++++++++++++++++++++--------- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 921b4189e8..235ed66424 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -353,6 +353,8 @@ pub struct CreateInviteFromCodeParams { invite_code: String, email_address: String, device_id: Option, + #[serde(default)] + added_to_mailing_list: bool, } async fn create_invite_from_code( @@ -365,6 +367,7 @@ async fn create_invite_from_code( ¶ms.invite_code, ¶ms.email_address, params.device_id.as_deref(), + params.added_to_mailing_list, ) .await?, )) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 1bbfac85d3..63ea7fdd9e 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -882,6 +882,7 @@ impl Database { code: &str, email_address: &str, device_id: Option<&str>, + added_to_mailing_list: bool, ) -> Result { self.transaction(|tx| async move { let existing_user = user::Entity::find() @@ -933,6 +934,7 @@ impl Database { platform_windows: ActiveValue::set(false), platform_unknown: ActiveValue::set(true), device_id: ActiveValue::set(device_id.map(|device_id| device_id.into())), + added_to_mailing_list: ActiveValue::set(added_to_mailing_list), ..Default::default() }) .on_conflict( diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index 9d42c11f8b..1e27167545 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -567,7 +567,12 @@ async fn test_invite_codes() { // User 2 redeems the invite code and becomes a contact of user 1. let user2_invite = db - .create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id")) + .create_invite_from_code( + &invite_code, + "user2@example.com", + Some("user-2-device-id"), + true, + ) .await .unwrap(); let NewUserResult { @@ -617,7 +622,7 @@ async fn test_invite_codes() { // User 3 redeems the invite code and becomes a contact of user 1. let user3_invite = db - .create_invite_from_code(&invite_code, "user3@example.com", None) + .create_invite_from_code(&invite_code, "user3@example.com", None, true) .await .unwrap(); let NewUserResult { @@ -672,9 +677,14 @@ async fn test_invite_codes() { ); // Trying to reedem the code for the third time results in an error. - db.create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id")) - .await - .unwrap_err(); + db.create_invite_from_code( + &invite_code, + "user4@example.com", + Some("user-4-device-id"), + true, + ) + .await + .unwrap_err(); // Invite count can be updated after the code has been created. db.set_invite_count_for_user(user1, 2).await.unwrap(); @@ -684,7 +694,12 @@ async fn test_invite_codes() { // User 4 can now redeem the invite code and becomes a contact of user 1. let user4_invite = db - .create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id")) + .create_invite_from_code( + &invite_code, + "user4@example.com", + Some("user-4-device-id"), + true, + ) .await .unwrap(); let user4 = db @@ -739,9 +754,14 @@ async fn test_invite_codes() { ); // An existing user cannot redeem invite codes. - db.create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id")) - .await - .unwrap_err(); + db.create_invite_from_code( + &invite_code, + "user2@example.com", + Some("user-2-device-id"), + true, + ) + .await + .unwrap_err(); let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap(); assert_eq!(invite_count, 1); @@ -763,7 +783,7 @@ async fn test_invite_codes() { db.set_invite_count_for_user(user5, 5).await.unwrap(); let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap(); let user5_invite_to_user1 = db - .create_invite_from_code(&user5_invite_code, "user1@different.com", None) + .create_invite_from_code(&user5_invite_code, "user1@different.com", None, true) .await .unwrap(); let user1_2 = db From 292708573fc2e85602e452bd4551acaeb7f64c7d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 17 Jan 2023 16:13:26 -0800 Subject: [PATCH 41/83] Replace MultiBuffer::files with ::for_each_buffer --- crates/editor/src/items.rs | 14 ++++++++------ crates/editor/src/multi_buffer.rs | 12 +++++------- crates/workspace/src/pane.rs | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index f8fa3c9df0..1fe0329405 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -565,12 +565,14 @@ impl Item for Editor { } fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - self.buffer - .read(cx) - .files(cx) - .into_iter() - .filter_map(|file| File::from_dyn(Some(file))?.project_entry_id(cx)) - .collect() + let mut result = SmallVec::new(); + self.buffer.read(cx).for_each_buffer(|buffer| { + let buffer = buffer.read(cx); + if let Some(file) = File::from_dyn(buffer.file()) { + result.extend(file.project_entry_id(cx)); + } + }); + result } fn is_singleton(&self, cx: &AppContext) -> bool { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 2347d9a63d..c8a62c6a57 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -9,11 +9,10 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, - DiagnosticEntry, File, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, + DiagnosticEntry, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; -use smallvec::SmallVec; use std::{ borrow::Cow, cell::{Ref, RefCell}, @@ -1311,12 +1310,11 @@ impl MultiBuffer { .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset)) } - pub fn files<'a>(&'a self, cx: &'a AppContext) -> SmallVec<[&'a Arc; 2]> { - let buffers = self.buffers.borrow(); - buffers + pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle)) { + self.buffers + .borrow() .values() - .filter_map(|buffer| buffer.buffer.read(cx).file()) - .collect() + .for_each(|state| f(&state.buffer)) } pub fn title<'a>(&'a self, cx: &'a AppContext) -> Cow<'a, str> { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index bd355b8b3e..735e165c7d 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -482,7 +482,7 @@ impl Pane { ) -> Box { let existing_item = pane.update(cx, |pane, cx| { for (index, item) in pane.items.iter().enumerate() { - if item.project_path(cx).is_some() + if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [project_entry_id] { let item = item.boxed_clone(); From c9a306b4ac0995aa27d3f2ff8f984bf462446b1f Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 17 Jan 2023 16:58:55 -0800 Subject: [PATCH 42/83] Change sidebars to use the window width as a max width rather than participating in the flex co-authored-by: Mikayla --- crates/workspace/src/dock.rs | 26 ++++++++++++++++++++++++-- crates/workspace/src/workspace.rs | 27 +++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 69c6db51ab..747541f87d 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -2,8 +2,10 @@ use collections::HashMap; use gpui::{ actions, elements::{ChildView, Container, Empty, MouseEventHandler, ParentElement, Side, Stack, Svg}, + geometry::vector::Vector2F, impl_internal_actions, Border, CursorStyle, Element, ElementBox, Entity, MouseButton, - MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle, + MutableAppContext, RenderContext, SizeConstraint, View, ViewContext, ViewHandle, + WeakViewHandle, }; use serde::Deserialize; use settings::{DockAnchor, Settings}; @@ -312,7 +314,27 @@ impl Dock { } }); - resizable.flex(5., false).boxed() + if anchor == DockAnchor::Right { + resizable + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new(cx.window_size.x() * 0.8, constraint.max.y()), + ) + }) + .boxed() + } else { + resizable + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(constraint.min.x(), 50.), + Vector2F::new(constraint.max.x(), cx.window_size.y() * 0.8), + ) + }) + .boxed() + } } DockAnchor::Expanded => { enum ExpandedDockWash {} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index eefce67371..de5b50b468 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -32,12 +32,13 @@ use futures::{ use gpui::{ actions, elements::*, + geometry::vector::Vector2F, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, - MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -2471,7 +2472,16 @@ impl View for Workspace { if self.left_sidebar.read(cx).active_item().is_some() { Some( ChildView::new(&self.left_sidebar, cx) - .flex(0.8, false) + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new( + cx.window_size.x() * 0.8, + constraint.max.y(), + ), + ) + }) .boxed(), ) } else { @@ -2508,7 +2518,16 @@ impl View for Workspace { if self.right_sidebar.read(cx).active_item().is_some() { Some( ChildView::new(&self.right_sidebar, cx) - .flex(0.8, false) + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new( + cx.window_size.x() * 0.8, + constraint.max.y(), + ), + ) + }) .boxed(), ) } else { From 8651320c9f129ea2bf12f26ea425d36f439e9313 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 17 Jan 2023 17:21:06 -0800 Subject: [PATCH 43/83] Make workspace items expose their underlying models, remove file-related methods --- crates/collab/src/tests/integration_tests.rs | 6 +- crates/diagnostics/src/diagnostics.rs | 9 +- crates/editor/src/items.rs | 34 ++---- crates/project/src/project.rs | 10 +- crates/search/src/project_search.rs | 9 +- crates/terminal_view/src/terminal_view.rs | 11 +- crates/theme_testbench/src/theme_testbench.rs | 21 ++-- crates/workspace/src/item.rs | 115 +++++++++++++----- crates/workspace/src/pane.rs | 45 +++---- crates/workspace/src/shared_screen.rs | 19 +-- crates/workspace/src/workspace.rs | 115 ++++++++---------- 11 files changed, 198 insertions(+), 196 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 729da6d109..3f2a777f87 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -32,7 +32,9 @@ use std::{ sync::Arc, }; use unindent::Unindent as _; -use workspace::{item::Item, shared_screen::SharedScreen, SplitDirection, ToggleFollow, Workspace}; +use workspace::{ + item::ItemHandle as _, shared_screen::SharedScreen, SplitDirection, ToggleFollow, Workspace, +}; #[ctor::ctor] fn init_logger() { @@ -5602,7 +5604,7 @@ async fn test_following( }); assert!(cx_b.read(|cx| editor_b2.is_focused(cx))); assert_eq!( - editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)), + cx_b.read(|cx| editor_b2.project_path(cx)), Some((worktree_id, "2.txt").into()) ); assert_eq!( diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index e98aa645d5..d3078bce81 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -21,7 +21,6 @@ use language::{ use project::{DiagnosticSummary, Project, ProjectPath}; use serde_json::json; use settings::Settings; -use smallvec::SmallVec; use std::{ any::{Any, TypeId}, cmp::Ordering, @@ -521,12 +520,8 @@ impl Item for ProjectDiagnosticsEditor { ) } - fn project_path(&self, _: &AppContext) -> Option { - None - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - self.editor.project_entry_ids(cx) + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.editor.for_each_project_item(cx, f) } fn is_singleton(&self, _: &AppContext) -> bool { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 1fe0329405..6568fe78dd 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -14,11 +14,10 @@ use gpui::{ RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::proto::serialize_anchor as serialize_text_anchor; -use language::{Bias, Buffer, File as _, OffsetRangeExt, Point, SelectionGoal}; -use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath}; +use language::{Bias, Buffer, OffsetRangeExt, Point, SelectionGoal}; +use project::{FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; -use smallvec::SmallVec; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -555,24 +554,10 @@ impl Item for Editor { .boxed() } - fn project_path(&self, cx: &AppContext) -> Option { - let buffer = self.buffer.read(cx).as_singleton()?; - let file = buffer.read(cx).file(); - File::from_dyn(file).map(|file| ProjectPath { - worktree_id: file.worktree_id(cx), - path: file.path().clone(), - }) - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - let mut result = SmallVec::new(); - self.buffer.read(cx).for_each_buffer(|buffer| { - let buffer = buffer.read(cx); - if let Some(file) = File::from_dyn(buffer.file()) { - result.extend(file.project_entry_id(cx)); - } - }); - result + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.buffer + .read(cx) + .for_each_buffer(|buffer| f(buffer.id(), buffer.read(cx))); } fn is_singleton(&self, cx: &AppContext) -> bool { @@ -609,7 +594,12 @@ impl Item for Editor { } fn can_save(&self, cx: &AppContext) -> bool { - !self.buffer().read(cx).is_singleton() || self.project_path(cx).is_some() + let buffer = &self.buffer().read(cx); + if let Some(buffer) = buffer.as_singleton() { + buffer.read(cx).project_path(cx).is_some() + } else { + true + } } fn save( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d3b3c11f2e..66b016e27c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -67,8 +67,9 @@ use util::{debug_panic, defer, post_inc, ResultExt, TryFutureExt as _}; pub use fs::*; pub use worktree::*; -pub trait Item: Entity { +pub trait Item { fn entry_id(&self, cx: &AppContext) -> Option; + fn project_path(&self, cx: &AppContext) -> Option; } // Language server state is stored across 3 collections: @@ -6401,4 +6402,11 @@ impl Item for Buffer { fn entry_id(&self, cx: &AppContext) -> Option { File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx)) } + + fn project_path(&self, cx: &AppContext) -> Option { + File::from_dyn(self.file()).map(|file| ProjectPath { + worktree_id: file.worktree_id(cx), + path: file.path().clone(), + }) + } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 9d2f31e269..d30f4d804c 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -15,7 +15,6 @@ use gpui::{ use menu::Confirm; use project::{search::SearchQuery, Project}; use settings::Settings; -use smallvec::SmallVec; use std::{ any::{Any, TypeId}, ops::Range, @@ -264,12 +263,8 @@ impl Item for ProjectSearchView { .boxed() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - None - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - self.results_editor.project_entry_ids(cx) + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.results_editor.for_each_project_item(cx, f) } fn is_singleton(&self, _: &AppContext) -> bool { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index e02a9758dd..847dfc5ee5 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -18,10 +18,9 @@ use gpui::{ AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use project::{LocalWorktree, Project, ProjectPath}; +use project::{LocalWorktree, Project}; use serde::Deserialize; use settings::{Settings, TerminalBlink, WorkingDirectory}; -use smallvec::SmallVec; use smol::Timer; use terminal::{ alacritty_terminal::{ @@ -616,13 +615,7 @@ impl Item for TerminalView { None } - fn project_path(&self, _cx: &gpui::AppContext) -> Option { - None - } - - fn project_entry_ids(&self, _cx: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - SmallVec::new() - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} fn is_singleton(&self, _cx: &gpui::AppContext) -> bool { false diff --git a/crates/theme_testbench/src/theme_testbench.rs b/crates/theme_testbench/src/theme_testbench.rs index cf9f03de45..3cda5d3e51 100644 --- a/crates/theme_testbench/src/theme_testbench.rs +++ b/crates/theme_testbench/src/theme_testbench.rs @@ -6,12 +6,11 @@ use gpui::{ Padding, ParentElement, }, fonts::TextStyle, - Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + AppContext, Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task, + View, ViewContext, ViewHandle, WeakViewHandle, }; -use project::{Project, ProjectEntryId, ProjectPath}; +use project::Project; use settings::Settings; -use smallvec::SmallVec; use theme::{ColorScheme, Layer, Style, StyleSet}; use workspace::{ item::{Item, ItemEvent}, @@ -306,7 +305,7 @@ impl Item for ThemeTestbench { &self, _: Option, style: &theme::Tab, - _: &gpui::AppContext, + _: &AppContext, ) -> gpui::ElementBox { Label::new("Theme Testbench".into(), style.label.clone()) .aligned() @@ -314,21 +313,15 @@ impl Item for ThemeTestbench { .boxed() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - None - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> { - SmallVec::new() - } - - fn is_singleton(&self, _: &gpui::AppContext) -> bool { + fn is_singleton(&self, _: &AppContext) -> bool { false } fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} - fn can_save(&self, _: &gpui::AppContext) -> bool { + fn can_save(&self, _: &AppContext) -> bool { false } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 19385038e4..9d5b94bddb 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -49,8 +49,7 @@ pub trait Item: View { } fn tab_content(&self, detail: Option, style: &theme::Tab, cx: &AppContext) -> ElementBox; - fn project_path(&self, cx: &AppContext) -> Option; - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)); fn is_singleton(&self, cx: &AppContext) -> bool; fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext); fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option @@ -147,6 +146,8 @@ pub trait ItemHandle: 'static + fmt::Debug { -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; + fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>; + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)); fn is_singleton(&self, cx: &AppContext) -> bool; fn boxed_clone(&self) -> Box; fn clone_on_split( @@ -240,11 +241,36 @@ impl ItemHandle for ViewHandle { } fn project_path(&self, cx: &AppContext) -> Option { - self.read(cx).project_path(cx) + let this = self.read(cx); + let mut result = None; + if this.is_singleton(cx) { + this.for_each_project_item(cx, &mut |_, item| { + result = item.project_path(cx); + }); + } + result } fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - self.read(cx).project_entry_ids(cx) + let mut result = SmallVec::new(); + self.read(cx).for_each_project_item(cx, &mut |_, item| { + if let Some(id) = item.entry_id(cx) { + result.push(id); + } + }); + result + } + + fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]> { + let mut result = SmallVec::new(); + self.read(cx).for_each_project_item(cx, &mut |id, _| { + result.push(id); + }); + result + } + + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.read(cx).for_each_project_item(cx, f) } fn is_singleton(&self, cx: &AppContext) -> bool { @@ -582,7 +608,7 @@ impl WeakItemHandle for WeakViewHandle { } pub trait ProjectItem: Item { - type Item: project::Item; + type Item: project::Item + gpui::Entity; fn for_project_item( project: ModelHandle, @@ -690,18 +716,18 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { - use std::{any::Any, borrow::Cow, cell::Cell}; - + use super::{Item, ItemEvent}; + use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ - elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext, Task, - View, ViewContext, ViewHandle, WeakViewHandle, + elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, + RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use project::{Project, ProjectEntryId, ProjectPath}; - use smallvec::SmallVec; + use std::{any::Any, borrow::Cow, cell::Cell}; - use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; - - use super::{Item, ItemEvent}; + pub struct TestProjectItem { + pub entry_id: Option, + } pub struct TestItem { pub workspace_id: WorkspaceId, @@ -713,13 +739,27 @@ pub(crate) mod test { pub is_dirty: bool, pub is_singleton: bool, pub has_conflict: bool, - pub project_entry_ids: Vec, + pub project_items: Vec>, pub project_path: Option, pub nav_history: Option, pub tab_descriptions: Option>, pub tab_detail: Cell>, } + impl Entity for TestProjectItem { + type Event = (); + } + + impl project::Item for TestProjectItem { + fn entry_id(&self, _: &AppContext) -> Option { + self.entry_id + } + + fn project_path(&self, _: &AppContext) -> Option { + None + } + } + pub enum TestItemEvent { Edit, } @@ -735,7 +775,7 @@ pub(crate) mod test { is_dirty: self.is_dirty, is_singleton: self.is_singleton, has_conflict: self.has_conflict, - project_entry_ids: self.project_entry_ids.clone(), + project_items: self.project_items.clone(), project_path: self.project_path.clone(), nav_history: None, tab_descriptions: None, @@ -755,7 +795,7 @@ pub(crate) mod test { reload_count: 0, is_dirty: false, has_conflict: false, - project_entry_ids: Vec::new(), + project_items: Vec::new(), project_path: None, is_singleton: true, nav_history: None, @@ -781,13 +821,26 @@ pub(crate) mod test { self } - pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self { - self.project_entry_ids.extend( - project_entry_ids - .iter() - .copied() - .map(ProjectEntryId::from_proto), - ); + pub fn with_dirty(mut self, dirty: bool) -> Self { + self.is_dirty = dirty; + self + } + + pub fn with_conflict(mut self, has_conflict: bool) -> Self { + self.has_conflict = has_conflict; + self + } + + pub fn with_project_entry_ids( + mut self, + project_entry_ids: &[u64], + cx: &mut MutableAppContext, + ) -> Self { + self.project_items + .extend(project_entry_ids.iter().copied().map(|id| { + let id = ProjectEntryId::from_proto(id); + cx.add_model(|_| TestProjectItem { entry_id: Some(id) }) + })); self } @@ -830,12 +883,14 @@ pub(crate) mod test { Empty::new().boxed() } - fn project_path(&self, _: &AppContext) -> Option { - self.project_path.clone() - } - - fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - self.project_entry_ids.iter().copied().collect() + fn for_each_project_item( + &self, + cx: &AppContext, + f: &mut dyn FnMut(usize, &dyn project::Item), + ) { + self.project_items + .iter() + .for_each(|item| f(item.id(), item.read(cx))) } fn is_singleton(&self, _: &AppContext) -> bool { @@ -880,7 +935,7 @@ pub(crate) mod test { } fn can_save(&self, _: &AppContext) -> bool { - !self.project_entry_ids.is_empty() + !self.project_items.is_empty() } fn save( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 735e165c7d..4d5a73b2b3 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -809,8 +809,8 @@ impl Pane { // Find the item's current index and its set of project entries. Avoid // storing these in advance, in case they have changed since this task // was started. - let (item_ix, mut project_entry_ids) = pane.read_with(&cx, |pane, cx| { - (pane.index_for_item(&*item), item.project_entry_ids(cx)) + let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { + (pane.index_for_item(&*item), item.project_item_model_ids(cx)) }); let item_ix = if let Some(ix) = item_ix { ix @@ -823,25 +823,20 @@ impl Pane { // any project entries that are not open anywhere else in the workspace, // AND that the user has not already been prompted to save. If there are // any such project entries, prompt the user to save this item. - let should_save = if project_entry_ids.is_empty() { - true - } else { - workspace.read_with(&cx, |workspace, cx| { - for item in workspace.items(cx) { - if !items_to_close - .iter() - .any(|item_to_close| item_to_close.id() == item.id()) - { - let other_project_entry_ids = item.project_entry_ids(cx); - project_entry_ids - .retain(|id| !other_project_entry_ids.contains(id)); - } + workspace.read_with(&cx, |workspace, cx| { + for item in workspace.items(cx) { + if !items_to_close + .iter() + .any(|item_to_close| item_to_close.id() == item.id()) + { + let other_project_item_ids = item.project_item_model_ids(cx); + project_item_ids.retain(|id| !other_project_item_ids.contains(id)); } - }); - project_entry_ids - .iter() - .any(|id| saved_project_entry_ids.insert(*id)) - }; + } + }); + let should_save = project_item_ids + .iter() + .any(|id| saved_project_entry_ids.insert(*id)); if should_save && !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx) @@ -1866,7 +1861,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, @@ -1885,7 +1880,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, @@ -1904,7 +1899,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 2") - .with_project_entry_ids(&[2]); + .with_project_entry_ids(&[2], cx); Pane::add_item( workspace, @@ -1923,7 +1918,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, @@ -1942,7 +1937,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1b") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index d292ece3d5..b76535f6ed 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -8,12 +8,11 @@ use futures::StreamExt; use gpui::{ elements::*, geometry::{rect::RectF, vector::vec2f}, - Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext, ViewHandle, - WeakViewHandle, + AppContext, Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext, + ViewHandle, WeakViewHandle, }; use project::Project; use settings::Settings; -use smallvec::SmallVec; use std::{ path::PathBuf, sync::{Arc, Weak}, @@ -106,7 +105,7 @@ impl Item for SharedScreen { &self, _: Option, style: &theme::Tab, - _: &gpui::AppContext, + _: &AppContext, ) -> gpui::ElementBox { Flex::row() .with_child( @@ -130,15 +129,9 @@ impl Item for SharedScreen { .boxed() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - Default::default() - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - Default::default() - } - - fn is_singleton(&self, _: &gpui::AppContext) -> bool { + fn is_singleton(&self, _: &AppContext) -> bool { false } @@ -155,7 +148,7 @@ impl Item for SharedScreen { Some(Self::new(&track, self.peer_id, self.user.clone(), cx)) } - fn can_save(&self, _: &gpui::AppContext) -> bool { + fn can_save(&self, _: &AppContext) -> bool { false } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index eefce67371..c7aa0798a3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2943,16 +2943,11 @@ mod tests { // When there are dirty untitled items, prompt to save each one. If the user // cancels any prompt, then abort. - let item2 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item - }); - let item3 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item2 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); + let item3 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_project_entry_ids(&[1], cx) }); workspace.update(cx, |w, cx| { w.add_item(Box::new(item2.clone()), cx); @@ -2977,31 +2972,24 @@ mod tests { Workspace::new(Default::default(), 0, project, default_item_factory, cx) }); - let item1 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item1 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_project_entry_ids(&[1], cx) }); - let item2 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.has_conflict = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(2)]; - item + let item2 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_conflict(true) + .with_project_entry_ids(&[2], cx) }); - let item3 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.has_conflict = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(3)]; - item - }); - let item4 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item + let item3 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_conflict(true) + .with_project_entry_ids(&[3], cx) }); + let item4 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); let pane = workspace.update(cx, |workspace, cx| { workspace.add_item(Box::new(item1.clone()), cx); workspace.add_item(Box::new(item2.clone()), cx); @@ -3078,29 +3066,26 @@ mod tests { // workspace items with multiple project entries. let single_entry_items = (0..=4) .map(|project_entry_id| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)]; - item.is_singleton = true; - item + cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_singleton(true) + .with_project_entry_ids(&[project_entry_id], cx) + }) }) .collect::>(); - let item_2_3 = { - let mut item = TestItem::new(); - item.is_dirty = true; - item.is_singleton = false; - item.project_entry_ids = - vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)]; - item - }; - let item_3_4 = { - let mut item = TestItem::new(); - item.is_dirty = true; - item.is_singleton = false; - item.project_entry_ids = - vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)]; - item - }; + let item_2_3 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_singleton(false) + .with_project_entry_ids(&[2, 3], cx) + }); + let item_3_4 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_singleton(false) + .with_project_entry_ids(&[3, 4], cx) + }); // Create two panes that contain the following project entries: // left pane: @@ -3111,9 +3096,9 @@ mod tests { // multi-entry items: (3, 4) let left_pane = workspace.update(cx, |workspace, cx| { let left_pane = workspace.active_pane().clone(); - workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx); - for item in &single_entry_items { - workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx); + workspace.add_item(Box::new(item_2_3.clone()), cx); + for item in single_entry_items { + workspace.add_item(Box::new(item), cx); } left_pane.update(cx, |pane, cx| { pane.activate_item(2, true, true, cx); @@ -3128,7 +3113,7 @@ mod tests { //Need to cause an effect flush in order to respect new focus workspace.update(cx, |workspace, cx| { - workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx); + workspace.add_item(Box::new(item_3_4.clone()), cx); cx.focus(left_pane.clone()); }); @@ -3177,10 +3162,8 @@ mod tests { Workspace::new(Default::default(), 0, project, default_item_factory, cx) }); - let item = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_entry_ids(&[1], cx) }); let item_id = item.id(); workspace.update(cx, |workspace, cx| { @@ -3265,7 +3248,9 @@ mod tests { workspace.add_item(Box::new(item.clone()), cx); }); item.update(cx, |item, cx| { - item.project_entry_ids = Default::default(); + item.project_items[0].update(cx, |item, _| { + item.entry_id = None; + }); item.is_dirty = true; cx.blur(); }); @@ -3296,10 +3281,8 @@ mod tests { Workspace::new(Default::default(), 0, project, default_item_factory, cx) }); - let item = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_entry_ids(&[1], cx) }); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); From c3518cefe832535a8ba2a3cd7c8a7a8ebb9a0b27 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 17 Jan 2023 17:32:10 -0800 Subject: [PATCH 44/83] disable vim mode in non full editors --- crates/vim/src/editor_events.rs | 4 +--- crates/vim/src/vim.rs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index 7b777a50ed..c526e3b1dc 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -37,9 +37,7 @@ fn editor_focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppCont let editor_mode = editor.mode(); let newest_selection_empty = editor.selections.newest::(cx).is_empty(); - if editor_mode != EditorMode::Full { - vim.switch_mode(Mode::Insert, true, cx); - } else if !newest_selection_empty { + if editor_mode == EditorMode::Full && !newest_selection_empty { vim.switch_mode(Mode::Visual { line: false }, true, cx); } }); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 8c5dae1e01..9f799ef37f 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -12,7 +12,7 @@ mod visual; use collections::HashMap; use command_palette::CommandPaletteFilter; -use editor::{Bias, Cancel, Editor}; +use editor::{Bias, Cancel, Editor, EditorMode}; use gpui::{ impl_actions, keymap_matcher::{KeyPressed, Keystroke}, @@ -267,7 +267,7 @@ impl Vim { for editor in self.editors.values() { if let Some(editor) = editor.upgrade(cx) { editor.update(cx, |editor, cx| { - if self.enabled { + if self.enabled && editor.mode() == EditorMode::Full { editor.set_cursor_shape(cursor_shape, cx); editor.set_clip_at_line_ends(state.clip_at_line_end(), cx); editor.set_input_enabled(!state.vim_controlled()); From cf193154e10266c35dfc5539702d04a0cc1c7979 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 17 Jan 2023 17:35:39 -0800 Subject: [PATCH 45/83] fix broken test --- crates/vim/src/test.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index e320962cfa..0214806e11 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -84,9 +84,6 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) { ); cx.simulate_keystroke("/"); - // We now use a weird insert mode with selection when jumping to a single line editor - assert_eq!(cx.mode(), Mode::Insert); - let search_bar = cx.workspace(|workspace, cx| { workspace .active_pane() From a0a50cb412ba23e97f010013153719132582676a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 17 Jan 2023 17:37:52 -0800 Subject: [PATCH 46/83] Set up fake project paths correctly in tests --- crates/workspace/src/item.rs | 24 ++++++++++++--------- crates/workspace/src/pane.rs | 10 ++++----- crates/workspace/src/workspace.rs | 36 ++++++++++++++----------------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 9d5b94bddb..1bc713b5fb 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -722,11 +722,12 @@ pub(crate) mod test { elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; - use project::{Project, ProjectEntryId, ProjectPath}; - use std::{any::Any, borrow::Cow, cell::Cell}; + use project::{Project, ProjectEntryId, ProjectPath, WorktreeId}; + use std::{any::Any, borrow::Cow, cell::Cell, path::Path}; pub struct TestProjectItem { pub entry_id: Option, + pub project_path: Option, } pub struct TestItem { @@ -740,7 +741,6 @@ pub(crate) mod test { pub is_singleton: bool, pub has_conflict: bool, pub project_items: Vec>, - pub project_path: Option, pub nav_history: Option, pub tab_descriptions: Option>, pub tab_detail: Cell>, @@ -756,7 +756,7 @@ pub(crate) mod test { } fn project_path(&self, _: &AppContext) -> Option { - None + self.project_path.clone() } } @@ -776,7 +776,6 @@ pub(crate) mod test { is_singleton: self.is_singleton, has_conflict: self.has_conflict, project_items: self.project_items.clone(), - project_path: self.project_path.clone(), nav_history: None, tab_descriptions: None, tab_detail: Default::default(), @@ -796,7 +795,6 @@ pub(crate) mod test { is_dirty: false, has_conflict: false, project_items: Vec::new(), - project_path: None, is_singleton: true, nav_history: None, tab_descriptions: None, @@ -831,15 +829,21 @@ pub(crate) mod test { self } - pub fn with_project_entry_ids( + pub fn with_project_items( mut self, - project_entry_ids: &[u64], + items: &[(u64, &str)], cx: &mut MutableAppContext, ) -> Self { self.project_items - .extend(project_entry_ids.iter().copied().map(|id| { + .extend(items.iter().copied().map(|(id, path)| { let id = ProjectEntryId::from_proto(id); - cx.add_model(|_| TestProjectItem { entry_id: Some(id) }) + cx.add_model(|_| TestProjectItem { + entry_id: Some(id), + project_path: Some(ProjectPath { + worktree_id: WorktreeId::from_usize(0), + path: Path::new(path).into(), + }), + }) })); self } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 4d5a73b2b3..de298e7be1 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1861,7 +1861,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "one.txt")], cx); Pane::add_item( workspace, @@ -1880,7 +1880,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "1.txt")], cx); Pane::add_item( workspace, @@ -1899,7 +1899,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 2") - .with_project_entry_ids(&[2], cx); + .with_project_items(&[(2, "2.txt")], cx); Pane::add_item( workspace, @@ -1918,7 +1918,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "1.txt")], cx); Pane::add_item( workspace, @@ -1937,7 +1937,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1b") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "1.txt")], cx); Pane::add_item( workspace, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c7aa0798a3..a521d02b5f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2834,15 +2834,11 @@ mod tests { project.worktrees(cx).next().unwrap().read(cx).id() }); - let item1 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_path = Some((worktree_id, "one.txt").into()); - item + let item1 = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_items(&[(1, "one.txt")], cx) }); - let item2 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_path = Some((worktree_id, "two.txt").into()); - item + let item2 = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_items(&[(2, "two.txt")], cx) }); // Add an item to an empty pane @@ -2947,7 +2943,7 @@ mod tests { let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_entry_ids(&[1], cx) + .with_project_items(&[(1, "1.txt")], cx) }); workspace.update(cx, |w, cx| { w.add_item(Box::new(item2.clone()), cx); @@ -2975,19 +2971,19 @@ mod tests { let item1 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_entry_ids(&[1], cx) + .with_project_items(&[(1, "1.txt")], cx) }); let item2 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_entry_ids(&[2], cx) + .with_project_items(&[(2, "2.txt")], cx) }); let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_entry_ids(&[3], cx) + .with_project_items(&[(3, "3.txt")], cx) }); let item4 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); let pane = workspace.update(cx, |workspace, cx| { @@ -3067,10 +3063,10 @@ mod tests { let single_entry_items = (0..=4) .map(|project_entry_id| { cx.add_view(&workspace, |cx| { - TestItem::new() - .with_dirty(true) - .with_singleton(true) - .with_project_entry_ids(&[project_entry_id], cx) + TestItem::new().with_dirty(true).with_project_items( + &[(project_entry_id, &format!("{project_entry_id}.txt"))], + cx, + ) }) }) .collect::>(); @@ -3078,13 +3074,13 @@ mod tests { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_entry_ids(&[2, 3], cx) + .with_project_items(&[(2, "2.txt"), (3, "3.txt")], cx) }); let item_3_4 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_entry_ids(&[3, 4], cx) + .with_project_items(&[(3, "3.txt"), (4, "4.txt")], cx) }); // Create two panes that contain the following project entries: @@ -3163,7 +3159,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_entry_ids(&[1], cx) + TestItem::new().with_project_items(&[(1, "1.txt")], cx) }); let item_id = item.id(); workspace.update(cx, |workspace, cx| { @@ -3282,7 +3278,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_entry_ids(&[1], cx) + TestItem::new().with_project_items(&[(1, "1.txt")], cx) }); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); From a653e87658618b9315bd96d975c341cd5a6f56a5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Jan 2023 12:22:08 +0100 Subject: [PATCH 47/83] WIP: Avoid converting RopeFingerprint into a string Co-Authored-By: Petros Amoiridis --- Cargo.lock | 3 +-- crates/language/src/buffer.rs | 25 +++++++++++++------------ crates/language/src/proto.rs | 9 +++++++++ crates/project/src/project.rs | 16 ++++++++++------ crates/rope/Cargo.toml | 2 +- crates/rope/src/rope.rs | 8 +++++--- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9976fbba7..66b0d88b22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -739,8 +739,7 @@ dependencies = [ [[package]] name = "bromberg_sl2" version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ed88064f69518b7e3ea50ecfc1b61d43f19248618a377b95ae5c8b611134d4d" +source = "git+https://github.com/zed-industries/bromberg_sl2?rev=dac565a90e8f9245f48ff46225c915dc50f76920#dac565a90e8f9245f48ff46225c915dc50f76920" dependencies = [ "digest 0.9.0", "lazy_static", diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 4b79009a68..972a33c0b7 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -60,7 +60,7 @@ pub struct Buffer { git_diff_status: GitDiffStatus, file: Option>, saved_version: clock::Global, - saved_version_fingerprint: String, + saved_version_fingerprint: RopeFingerprint, saved_mtime: SystemTime, transaction_depth: usize, was_dirty_before_starting_transaction: Option, @@ -221,7 +221,7 @@ pub trait File: Send + Sync { version: clock::Global, line_ending: LineEnding, cx: &mut MutableAppContext, - ) -> Task>; + ) -> Task>; fn as_any(&self) -> &dyn Any; @@ -238,7 +238,7 @@ pub trait LocalFile: File { &self, buffer_id: u64, version: &clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, line_ending: LineEnding, mtime: SystemTime, cx: &mut MutableAppContext, @@ -386,7 +386,8 @@ impl Buffer { .ok_or_else(|| anyhow!("missing line_ending"))?, )); this.saved_version = proto::deserialize_version(message.saved_version); - this.saved_version_fingerprint = message.saved_version_fingerprint; + this.saved_version_fingerprint = + proto::deserialize_fingerprint(&message.saved_version_fingerprint)?; this.saved_mtime = message .saved_mtime .ok_or_else(|| anyhow!("invalid saved_mtime"))? @@ -402,7 +403,7 @@ impl Buffer { diff_base: self.diff_base.as_ref().map(|h| h.to_string()), line_ending: proto::serialize_line_ending(self.line_ending()) as i32, saved_version: proto::serialize_version(&self.saved_version), - saved_version_fingerprint: self.saved_version_fingerprint.clone(), + saved_version_fingerprint: proto::serialize_fingerprint(self.saved_version_fingerprint), saved_mtime: Some(self.saved_mtime.into()), } } @@ -530,7 +531,7 @@ impl Buffer { pub fn save( &mut self, cx: &mut ModelContext, - ) -> Task> { + ) -> Task> { let file = if let Some(file) = self.file.as_ref() { file } else { @@ -548,7 +549,7 @@ impl Buffer { cx.spawn(|this, mut cx| async move { let (version, fingerprint, mtime) = save.await?; this.update(&mut cx, |this, cx| { - this.did_save(version.clone(), fingerprint.clone(), mtime, None, cx); + this.did_save(version.clone(), fingerprint, mtime, None, cx); }); Ok((version, fingerprint, mtime)) }) @@ -558,8 +559,8 @@ impl Buffer { &self.saved_version } - pub fn saved_version_fingerprint(&self) -> &str { - &self.saved_version_fingerprint + pub fn saved_version_fingerprint(&self) -> RopeFingerprint { + self.saved_version_fingerprint } pub fn saved_mtime(&self) -> SystemTime { @@ -581,7 +582,7 @@ impl Buffer { pub fn did_save( &mut self, version: clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, mtime: SystemTime, new_file: Option>, cx: &mut ModelContext, @@ -630,7 +631,7 @@ impl Buffer { pub fn did_reload( &mut self, version: clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, line_ending: LineEnding, mtime: SystemTime, cx: &mut ModelContext, @@ -643,7 +644,7 @@ impl Buffer { file.buffer_reloaded( self.remote_id(), &self.saved_version, - self.saved_version_fingerprint.clone(), + self.saved_version_fingerprint, self.line_ending(), self.saved_mtime, cx, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 9612deb5bd..1b95e3ace9 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -11,6 +11,15 @@ use text::*; pub use proto::{BufferState, Operation}; +pub fn serialize_fingerprint(fingerprint: RopeFingerprint) -> String { + fingerprint.to_hex() +} + +pub fn deserialize_fingerprint(fingerprint: &str) -> Result { + RopeFingerprint::from_hex(fingerprint) + .map_err(|error| anyhow!("invalid fingerprint: {}", error)) +} + pub fn deserialize_line_ending(message: proto::LineEnding) -> fs::LineEnding { match message { proto::LineEnding::Unix => fs::LineEnding::Unix, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d3b3c11f2e..ce7296994a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -22,8 +22,8 @@ use gpui::{ use language::{ point_to_lsp, proto::{ - deserialize_anchor, deserialize_line_ending, deserialize_version, serialize_anchor, - serialize_version, + deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, + serialize_anchor, serialize_version, }, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, @@ -5123,7 +5123,7 @@ impl Project { buffer_id, version: serialize_version(&saved_version), mtime: Some(mtime.into()), - fingerprint, + fingerprint: language::proto::serialize_fingerprint(fingerprint), }) } @@ -5215,7 +5215,9 @@ impl Project { buffer_id, version: language::proto::serialize_version(buffer.saved_version()), mtime: Some(buffer.saved_mtime().into()), - fingerprint: buffer.saved_version_fingerprint().into(), + fingerprint: language::proto::serialize_fingerprint( + buffer.saved_version_fingerprint(), + ), line_ending: language::proto::serialize_line_ending( buffer.line_ending(), ) as i32, @@ -5970,6 +5972,7 @@ impl Project { _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { + let fingerprint = deserialize_fingerprint(&envelope.payload.fingerprint)?; let version = deserialize_version(envelope.payload.version); let mtime = envelope .payload @@ -5984,7 +5987,7 @@ impl Project { .and_then(|buffer| buffer.upgrade(cx)); if let Some(buffer) = buffer { buffer.update(cx, |buffer, cx| { - buffer.did_save(version, envelope.payload.fingerprint, mtime, None, cx); + buffer.did_save(version, fingerprint, mtime, None, cx); }); } Ok(()) @@ -5999,6 +6002,7 @@ impl Project { ) -> Result<()> { let payload = envelope.payload; let version = deserialize_version(payload.version); + let fingerprint = deserialize_fingerprint(&payload.fingerprint)?; let line_ending = deserialize_line_ending( proto::LineEnding::from_i32(payload.line_ending) .ok_or_else(|| anyhow!("missing line ending"))?, @@ -6014,7 +6018,7 @@ impl Project { .and_then(|buffer| buffer.upgrade(cx)); if let Some(buffer) = buffer { buffer.update(cx, |buffer, cx| { - buffer.did_reload(version, payload.fingerprint, line_ending, mtime, cx); + buffer.did_reload(version, fingerprint, line_ending, mtime, cx); }); } Ok(()) diff --git a/crates/rope/Cargo.toml b/crates/rope/Cargo.toml index bd1dc690db..e9ddcc8195 100644 --- a/crates/rope/Cargo.toml +++ b/crates/rope/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" path = "src/rope.rs" [dependencies] -bromberg_sl2 = "0.6" +bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "dac565a90e8f9245f48ff46225c915dc50f76920" } smallvec = { version = "1.6", features = ["union"] } sum_tree = { path = "../sum_tree" } arrayvec = "0.7.1" diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 53713e3f7a..74cc9a6789 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -4,7 +4,7 @@ mod point_utf16; mod unclipped; use arrayvec::ArrayString; -use bromberg_sl2::{DigestString, HashMatrix}; +use bromberg_sl2::HashMatrix; use smallvec::SmallVec; use std::{ cmp, fmt, io, mem, @@ -25,6 +25,8 @@ const CHUNK_BASE: usize = 6; #[cfg(not(test))] const CHUNK_BASE: usize = 16; +pub type RopeFingerprint = HashMatrix; + #[derive(Clone, Default, Debug)] pub struct Rope { chunks: SumTree, @@ -361,8 +363,8 @@ impl Rope { .column } - pub fn fingerprint(&self) -> String { - self.chunks.summary().fingerprint.to_hex() + pub fn fingerprint(&self) -> RopeFingerprint { + self.chunks.summary().fingerprint } } From 8ca0f9ac99457095e3a0a6a181a65c1a34fea579 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Jan 2023 13:53:48 +0100 Subject: [PATCH 48/83] Fix compile errors --- .../src/tests/randomized_integration_tests.rs | 8 ++++---- crates/editor/src/items.rs | 12 ++++++++---- crates/project/src/worktree.rs | 19 +++++++++++-------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index ed8f984d27..e0170f6648 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -410,10 +410,10 @@ async fn test_random_collaboration( guest_buffer.read_with(client_cx, |b, _| b.saved_version().clone()); assert_eq!(guest_saved_version, host_saved_version); - let host_saved_version_fingerprint = host_buffer - .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); - let guest_saved_version_fingerprint = guest_buffer - .read_with(client_cx, |b, _| b.saved_version_fingerprint().to_string()); + let host_saved_version_fingerprint = + host_buffer.read_with(host_cx, |b, _| b.saved_version_fingerprint()); + let guest_saved_version_fingerprint = + guest_buffer.read_with(client_cx, |b, _| b.saved_version_fingerprint()); assert_eq!( guest_saved_version_fingerprint, host_saved_version_fingerprint diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index f8fa3c9df0..cce6d338f0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -13,8 +13,10 @@ use gpui::{ elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use language::proto::serialize_anchor as serialize_text_anchor; -use language::{Bias, Buffer, File as _, OffsetRangeExt, Point, SelectionGoal}; +use language::{ + proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, File as _, OffsetRangeExt, + Point, SelectionGoal, +}; use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; @@ -1165,9 +1167,11 @@ fn path_for_file<'a>( mod tests { use super::*; use gpui::MutableAppContext; + use language::RopeFingerprint; use std::{ path::{Path, PathBuf}, sync::Arc, + time::SystemTime, }; #[gpui::test] @@ -1197,7 +1201,7 @@ mod tests { todo!() } - fn mtime(&self) -> std::time::SystemTime { + fn mtime(&self) -> SystemTime { todo!() } @@ -1216,7 +1220,7 @@ mod tests { _: clock::Global, _: project::LineEnding, _: &mut MutableAppContext, - ) -> gpui::Task> { + ) -> gpui::Task> { todo!() } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index f22e915785..b65cf9e39b 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -20,10 +20,12 @@ use gpui::{ executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, }; -use language::Unclipped; use language::{ - proto::{deserialize_version, serialize_line_ending, serialize_version}, - Buffer, DiagnosticEntry, PointUtf16, Rope, + proto::{ + deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending, + serialize_version, + }, + Buffer, DiagnosticEntry, PointUtf16, Rope, RopeFingerprint, Unclipped, }; use parking_lot::Mutex; use postage::{ @@ -1863,7 +1865,7 @@ impl language::File for File { version: clock::Global, line_ending: LineEnding, cx: &mut MutableAppContext, - ) -> Task> { + ) -> Task> { self.worktree.update(cx, |worktree, cx| match worktree { Worktree::Local(worktree) => { let rpc = worktree.client.clone(); @@ -1878,7 +1880,7 @@ impl language::File for File { buffer_id, version: serialize_version(&version), mtime: Some(entry.mtime.into()), - fingerprint: fingerprint.clone(), + fingerprint: serialize_fingerprint(fingerprint), })?; } Ok((version, fingerprint, entry.mtime)) @@ -1896,11 +1898,12 @@ impl language::File for File { }) .await?; let version = deserialize_version(response.version); + let fingerprint = deserialize_fingerprint(&response.fingerprint)?; let mtime = response .mtime .ok_or_else(|| anyhow!("missing mtime"))? .into(); - Ok((version, response.fingerprint, mtime)) + Ok((version, fingerprint, mtime)) }) } }) @@ -1943,7 +1946,7 @@ impl language::LocalFile for File { &self, buffer_id: u64, version: &clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, line_ending: LineEnding, mtime: SystemTime, cx: &mut MutableAppContext, @@ -1957,7 +1960,7 @@ impl language::LocalFile for File { buffer_id, version: serialize_version(version), mtime: Some(mtime.into()), - fingerprint, + fingerprint: serialize_fingerprint(fingerprint), line_ending: serialize_line_ending(line_ending) as i32, }) .log_err(); From f8d092fdc6dd0059fe61d5397b18fce6a0da8071 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 18 Jan 2023 15:22:20 +0200 Subject: [PATCH 49/83] Fix mouse interrupting file/dir editing in project panel Co-Authored-By: Antonio Scandurra --- crates/project_panel/src/project_panel.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 0042695950..73b558bd5b 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1176,13 +1176,15 @@ impl ProjectPanel { ) }) .on_click(MouseButton::Left, move |e, cx| { - if kind == EntryKind::Dir { - cx.dispatch_action(ToggleExpanded(entry_id)) - } else { - cx.dispatch_action(Open { - entry_id, - change_focus: e.click_count > 1, - }) + if !show_editor { + if kind == EntryKind::Dir { + cx.dispatch_action(ToggleExpanded(entry_id)) + } else { + cx.dispatch_action(Open { + entry_id, + change_focus: e.click_count > 1, + }) + } } }) .on_down(MouseButton::Right, move |e, cx| { From a8f466b42269bf507ec8600d69608bdc7c0a1d99 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Jan 2023 14:22:23 +0100 Subject: [PATCH 50/83] Don't starve the main thread adding too many search excerpts at once --- Cargo.lock | 1 + crates/search/Cargo.toml | 1 + crates/search/src/project_search.rs | 45 +++++++++++++++++++++-------- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66b0d88b22..bbe77d8c75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5520,6 +5520,7 @@ dependencies = [ "serde_json", "settings", "smallvec", + "smol", "theme", "unindent", "util", diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 1f6c9582be..30802801a6 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -23,6 +23,7 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] } postage = { version = "0.4.1", features = ["futures-traits"] } serde = { version = "1.0", features = ["derive", "rc"] } smallvec = { version = "1.6", features = ["union"] } +smol = "1.2" [dev-dependencies] editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 9d2f31e269..a0f29738b0 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -18,6 +18,7 @@ use settings::Settings; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, + mem, ops::Range, path::PathBuf, sync::Arc, @@ -67,6 +68,7 @@ struct ProjectSearch { pending_search: Option>>, match_ranges: Vec>, active_query: Option, + search_id: usize, } pub struct ProjectSearchView { @@ -78,6 +80,7 @@ pub struct ProjectSearchView { regex: bool, query_contains_error: bool, active_match_index: Option, + search_id: usize, } pub struct ProjectSearchBar { @@ -98,6 +101,7 @@ impl ProjectSearch { pending_search: Default::default(), match_ranges: Default::default(), active_query: None, + search_id: 0, } } @@ -110,6 +114,7 @@ impl ProjectSearch { pending_search: Default::default(), match_ranges: self.match_ranges.clone(), active_query: self.active_query.clone(), + search_id: self.search_id, }) } @@ -117,21 +122,25 @@ impl ProjectSearch { let search = self .project .update(cx, |project, cx| project.search(query.clone(), cx)); + self.search_id += 1; self.active_query = Some(query); self.match_ranges.clear(); self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move { let matches = search.await.log_err()?; - if let Some(this) = this.upgrade(&cx) { + let this = this.upgrade(&cx)?; + let mut matches = matches.into_iter().collect::>(); + this.update(&mut cx, |this, cx| { + this.match_ranges.clear(); + matches.sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); + this.excerpts.update(cx, |excerpts, cx| excerpts.clear(cx)); + }); + + for matches_chunk in matches.chunks(100) { this.update(&mut cx, |this, cx| { - this.match_ranges.clear(); - let mut matches = matches.into_iter().collect::>(); - matches - .sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); this.excerpts.update(cx, |excerpts, cx| { - excerpts.clear(cx); - for (buffer, buffer_matches) in matches { + for (buffer, buffer_matches) in matches_chunk { let ranges_to_highlight = excerpts.push_excerpts_with_context_lines( - buffer, + buffer.clone(), buffer_matches.clone(), 1, cx, @@ -139,10 +148,19 @@ impl ProjectSearch { this.match_ranges.extend(ranges_to_highlight); } }); - this.pending_search.take(); + cx.notify(); }); + + // Don't starve the main thread when adding lots of excerpts. + smol::future::yield_now().await; } + + this.update(&mut cx, |this, cx| { + this.pending_search.take(); + cx.notify(); + }); + None })); cx.notify(); @@ -398,7 +416,7 @@ impl ProjectSearchView { whole_word = active_query.whole_word(); } } - cx.observe(&model, |this, _, cx| this.model_changed(true, cx)) + cx.observe(&model, |this, _, cx| this.model_changed(cx)) .detach(); let query_editor = cx.add_view(|cx| { @@ -433,6 +451,7 @@ impl ProjectSearchView { .detach(); let mut this = ProjectSearchView { + search_id: model.read(cx).search_id, model, query_editor, results_editor, @@ -442,7 +461,7 @@ impl ProjectSearchView { query_contains_error: false, active_match_index: None, }; - this.model_changed(false, cx); + this.model_changed(cx); this } @@ -562,11 +581,13 @@ impl ProjectSearchView { cx.focus(&self.results_editor); } - fn model_changed(&mut self, reset_selections: bool, cx: &mut ViewContext) { + fn model_changed(&mut self, cx: &mut ViewContext) { let match_ranges = self.model.read(cx).match_ranges.clone(); if match_ranges.is_empty() { self.active_match_index = None; } else { + let prev_search_id = mem::replace(&mut self.search_id, self.model.read(cx).search_id); + let reset_selections = self.search_id != prev_search_id; self.results_editor.update(cx, |editor, cx| { if reset_selections { editor.change_selections(Some(Autoscroll::fit()), cx, |s| { From 27a6951403cd11234edaa62c339e52678b9a2b86 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 18 Jan 2023 17:35:21 +0200 Subject: [PATCH 51/83] Allow pasting the same entry more than once in project panel Co-Authored-By: Antonio Scandurra --- crates/project_panel/src/project_panel.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 73b558bd5b..e59353aae4 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -763,7 +763,6 @@ impl ProjectPanel { ix += 1; } - self.clipboard_entry.take(); if clipboard_entry.is_cut() { if let Some(task) = self.project.update(cx, |project, cx| { project.rename_entry(clipboard_entry.entry_id(), new_path, cx) From b0fb5913b6aa9fd734f1ee40a1cfa5d322b19d1e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 18 Jan 2023 12:39:38 -0800 Subject: [PATCH 52/83] v0.71.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9976fbba7..62674acf3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8186,7 +8186,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.70.0" +version = "0.71.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 38fa2ad4be..63719b7003 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.70.0" +version = "0.71.0" [lib] name = "zed" From 203f569f2eda03cb1179f600ea2e6eb321bdbe52 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 18 Jan 2023 12:52:58 -0800 Subject: [PATCH 53/83] collab 0.5.3 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62674acf3d..c0b97a6b3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.2" +version = "0.5.3" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 9c3f0f61c4..8c21af7273 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.2" +version = "0.5.3" [[bin]] name = "collab" From 06c31a0daad2a3a8b6017a5843e1e4174808b5bb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Jan 2023 15:00:40 -0800 Subject: [PATCH 54/83] Fix workspace tests after changing Item trait --- crates/workspace/src/item.rs | 46 ++++++++++++++--------- crates/workspace/src/pane.rs | 16 ++++---- crates/workspace/src/workspace.rs | 62 ++++++++++++++++++++++--------- 3 files changed, 82 insertions(+), 42 deletions(-) diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 1bc713b5fb..b1888bb243 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -784,6 +784,27 @@ pub(crate) mod test { } } + impl TestProjectItem { + pub fn new(id: u64, path: &str, cx: &mut MutableAppContext) -> ModelHandle { + let entry_id = Some(ProjectEntryId::from_proto(id)); + let project_path = Some(ProjectPath { + worktree_id: WorktreeId::from_usize(0), + path: Path::new(path).into(), + }); + cx.add_model(|_| Self { + entry_id, + project_path, + }) + } + + pub fn new_untitled(cx: &mut MutableAppContext) -> ModelHandle { + cx.add_model(|_| Self { + project_path: None, + entry_id: None, + }) + } + } + impl TestItem { pub fn new() -> Self { Self { @@ -829,22 +850,9 @@ pub(crate) mod test { self } - pub fn with_project_items( - mut self, - items: &[(u64, &str)], - cx: &mut MutableAppContext, - ) -> Self { - self.project_items - .extend(items.iter().copied().map(|(id, path)| { - let id = ProjectEntryId::from_proto(id); - cx.add_model(|_| TestProjectItem { - entry_id: Some(id), - project_path: Some(ProjectPath { - worktree_id: WorktreeId::from_usize(0), - path: Path::new(path).into(), - }), - }) - })); + pub fn with_project_items(mut self, items: &[ModelHandle]) -> Self { + self.project_items.clear(); + self.project_items.extend(items.iter().cloned()); self } @@ -938,8 +946,12 @@ pub(crate) mod test { self.has_conflict } - fn can_save(&self, _: &AppContext) -> bool { + fn can_save(&self, cx: &AppContext) -> bool { !self.project_items.is_empty() + && self + .project_items + .iter() + .all(|item| item.read(cx).entry_id.is_some()) } fn save( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index de298e7be1..05d6de1ee2 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -804,7 +804,7 @@ impl Pane { items_to_close.sort_by_key(|item| !item.is_singleton(cx)); cx.spawn(|workspace, mut cx| async move { - let mut saved_project_entry_ids = HashSet::default(); + let mut saved_project_items_ids = HashSet::default(); for item in items_to_close.clone() { // Find the item's current index and its set of project entries. Avoid // storing these in advance, in case they have changed since this task @@ -836,7 +836,7 @@ impl Pane { }); let should_save = project_item_ids .iter() - .any(|id| saved_project_entry_ids.insert(*id)); + .any(|id| saved_project_items_ids.insert(*id)); if should_save && !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx) @@ -1672,7 +1672,7 @@ mod tests { use std::sync::Arc; use super::*; - use crate::item::test::TestItem; + use crate::item::test::{TestItem, TestProjectItem}; use gpui::{executor::Deterministic, TestAppContext}; use project::FakeFs; @@ -1861,7 +1861,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_items(&[(1, "one.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]); Pane::add_item( workspace, @@ -1880,7 +1880,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, @@ -1899,7 +1899,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 2") - .with_project_items(&[(2, "2.txt")], cx); + .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]); Pane::add_item( workspace, @@ -1918,7 +1918,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, @@ -1937,7 +1937,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1b") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a521d02b5f..47e2be29df 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2727,7 +2727,7 @@ pub fn open_new(app_state: &Arc, cx: &mut MutableAppContext) -> Task<( mod tests { use std::{cell::RefCell, rc::Rc}; - use crate::item::test::{TestItem, TestItemEvent}; + use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; use super::*; use fs::FakeFs; @@ -2835,10 +2835,10 @@ mod tests { }); let item1 = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "one.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) }); let item2 = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(2, "two.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)]) }); // Add an item to an empty pane @@ -2943,7 +2943,7 @@ mod tests { let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_items(&[(1, "1.txt")], cx) + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); workspace.update(cx, |w, cx| { w.add_item(Box::new(item2.clone()), cx); @@ -2971,21 +2971,25 @@ mod tests { let item1 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_items(&[(1, "1.txt")], cx) + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let item2 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_items(&[(2, "2.txt")], cx) + .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]) }); let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_items(&[(3, "3.txt")], cx) + .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)]) + }); + let item4 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_project_items(&[TestProjectItem::new_untitled(cx)]) }); - let item4 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); let pane = workspace.update(cx, |workspace, cx| { workspace.add_item(Box::new(item1.clone()), cx); workspace.add_item(Box::new(item2.clone()), cx); @@ -3007,15 +3011,20 @@ mod tests { [item1_id, item3_id, item4_id].contains(&id) }) }); - cx.foreground().run_until_parked(); + + // There's a prompt to save item 1. pane.read_with(cx, |pane, _| { assert_eq!(pane.items_len(), 4); assert_eq!(pane.active_item().unwrap().id(), item1.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Confirm saving item 1. cx.simulate_prompt_answer(window_id, 0); cx.foreground().run_until_parked(); + + // Item 1 is saved. There's a prompt to save item 3. pane.read_with(cx, |pane, cx| { assert_eq!(item1.read(cx).save_count, 1); assert_eq!(item1.read(cx).save_as_count, 0); @@ -3023,9 +3032,13 @@ mod tests { assert_eq!(pane.items_len(), 3); assert_eq!(pane.active_item().unwrap().id(), item3.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Cancel saving item 3. cx.simulate_prompt_answer(window_id, 1); cx.foreground().run_until_parked(); + + // Item 3 is reloaded. There's a prompt to save item 4. pane.read_with(cx, |pane, cx| { assert_eq!(item3.read(cx).save_count, 0); assert_eq!(item3.read(cx).save_as_count, 0); @@ -3033,11 +3046,17 @@ mod tests { assert_eq!(pane.items_len(), 2); assert_eq!(pane.active_item().unwrap().id(), item4.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Confirm saving item 4. cx.simulate_prompt_answer(window_id, 0); cx.foreground().run_until_parked(); + + // There's a prompt for a path for item 4. cx.simulate_new_path_selection(|_| Some(Default::default())); close_items.await.unwrap(); + + // The requested items are closed. pane.read_with(cx, |pane, cx| { assert_eq!(item4.read(cx).save_count, 0); assert_eq!(item4.read(cx).save_as_count, 1); @@ -3063,10 +3082,13 @@ mod tests { let single_entry_items = (0..=4) .map(|project_entry_id| { cx.add_view(&workspace, |cx| { - TestItem::new().with_dirty(true).with_project_items( - &[(project_entry_id, &format!("{project_entry_id}.txt"))], - cx, - ) + TestItem::new() + .with_dirty(true) + .with_project_items(&[TestProjectItem::new( + project_entry_id, + &format!("{project_entry_id}.txt"), + cx, + )]) }) }) .collect::>(); @@ -3074,13 +3096,19 @@ mod tests { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_items(&[(2, "2.txt"), (3, "3.txt")], cx) + .with_project_items(&[ + single_entry_items[2].read(cx).project_items[0].clone(), + single_entry_items[3].read(cx).project_items[0].clone(), + ]) }); let item_3_4 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_items(&[(3, "3.txt"), (4, "4.txt")], cx) + .with_project_items(&[ + single_entry_items[3].read(cx).project_items[0].clone(), + single_entry_items[4].read(cx).project_items[0].clone(), + ]) }); // Create two panes that contain the following project entries: @@ -3159,7 +3187,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "1.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let item_id = item.id(); workspace.update(cx, |workspace, cx| { @@ -3278,7 +3306,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "1.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); From 062e7a03a9604c39ec8ead4542d0493b469f9a19 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Jan 2023 15:17:44 -0800 Subject: [PATCH 55/83] Update comments in Pane::close_items --- crates/workspace/src/pane.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 05d6de1ee2..8d0d731551 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -806,7 +806,7 @@ impl Pane { cx.spawn(|workspace, mut cx| async move { let mut saved_project_items_ids = HashSet::default(); for item in items_to_close.clone() { - // Find the item's current index and its set of project entries. Avoid + // Find the item's current index and its set of project item models. Avoid // storing these in advance, in case they have changed since this task // was started. let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { @@ -818,11 +818,9 @@ impl Pane { continue; }; - // If an item hasn't yet been associated with a project entry, then always - // prompt to save it before closing it. Otherwise, check if the item has - // any project entries that are not open anywhere else in the workspace, - // AND that the user has not already been prompted to save. If there are - // any such project entries, prompt the user to save this item. + // Check if this view has any project items that are not open anywhere else + // in the workspace, AND that the user has not already been prompted to save. + // If there are any such project entries, prompt the user to save this item. workspace.read_with(&cx, |workspace, cx| { for item in workspace.items(cx) { if !items_to_close From d9948bf77273df94f92d0cca7cb8f38ea0ef46b5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Jan 2023 16:43:18 -0800 Subject: [PATCH 56/83] Prevent outline items from accidentally spanning multiple lines --- crates/language/src/buffer.rs | 10 +++++++--- crates/language/src/buffer_tests.rs | 26 +++++++++++++++++++++++++ crates/zed/src/languages/go/outline.scm | 3 +-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 4b79009a68..a52ee34962 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2168,8 +2168,6 @@ impl BufferSnapshot { continue; } - // TODO - move later, after processing captures - let mut text = String::new(); let mut name_ranges = Vec::new(); let mut highlight_ranges = Vec::new(); @@ -2183,7 +2181,13 @@ impl BufferSnapshot { continue; } - let range = capture.node.start_byte()..capture.node.end_byte(); + let mut range = capture.node.start_byte()..capture.node.end_byte(); + let start = capture.node.start_position(); + if capture.node.end_position().row > start.row { + range.end = + range.start + self.line_len(start.row as u32) as usize - start.column; + } + if !text.is_empty() { text.push(' '); } diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index b7f35e9ad5..5aa9152205 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -455,6 +455,32 @@ async fn test_outline(cx: &mut gpui::TestAppContext) { } } +#[gpui::test] +async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) { + let text = r#" + impl A for B< + C + > { + }; + "# + .unindent(); + + let buffer = + cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx)); + let outline = buffer + .read_with(cx, |buffer, _| buffer.snapshot().outline(None)) + .unwrap(); + + assert_eq!( + outline + .items + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect::>(), + &[("impl A for B<", 0)] + ); +} + #[gpui::test] async fn test_symbols_containing(cx: &mut gpui::TestAppContext) { let text = r#" diff --git a/crates/zed/src/languages/go/outline.scm b/crates/zed/src/languages/go/outline.scm index 849d2bdebf..2ff7ef25a0 100644 --- a/crates/zed/src/languages/go/outline.scm +++ b/crates/zed/src/languages/go/outline.scm @@ -40,5 +40,4 @@ ")" @context)) @item (field_declaration - name: (_) @name - type: (_) @context) @item \ No newline at end of file + name: (_) @name) @item \ No newline at end of file From 5ce065ac92381ad629dcd8e6bd4ee5ab2a1155bd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 15:42:14 +0100 Subject: [PATCH 57/83] Introduce `MultiBuffer::stream_excerpts_with_context_lines` This allows us to push excerpts in a streaming fashion without blocking the main thread. --- Cargo.lock | 1 + crates/editor/src/multi_buffer.rs | 134 +++++++++++++++++++++------- crates/search/Cargo.toml | 1 + crates/search/src/project_search.rs | 29 +++--- 4 files changed, 114 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbe77d8c75..7be9d45293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5510,6 +5510,7 @@ dependencies = [ "anyhow", "collections", "editor", + "futures 0.3.25", "gpui", "language", "log", diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 2347d9a63d..4d576883d3 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -4,6 +4,7 @@ pub use anchor::{Anchor, AnchorRangeExt}; use anyhow::Result; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet}; +use futures::{channel::mpsc, SinkExt}; use git::diff::DiffHunk; use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; @@ -764,6 +765,63 @@ impl MultiBuffer { None } + pub fn stream_excerpts_with_context_lines( + &mut self, + excerpts: Vec<(ModelHandle, Vec>)>, + context_line_count: u32, + cx: &mut ModelContext, + ) -> (Task<()>, mpsc::Receiver>) { + let (mut tx, rx) = mpsc::channel(256); + let task = cx.spawn(|this, mut cx| async move { + for (buffer, ranges) in excerpts { + let buffer_id = buffer.id(); + let buffer_snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot()); + + let mut excerpt_ranges = Vec::new(); + let mut range_counts = Vec::new(); + cx.background() + .scoped(|scope| { + scope.spawn(async { + let (ranges, counts) = + build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); + excerpt_ranges = ranges; + range_counts = counts; + }); + }) + .await; + + let mut ranges = ranges.into_iter(); + let mut range_counts = range_counts.into_iter(); + for excerpt_ranges in excerpt_ranges.chunks(100) { + let excerpt_ids = this.update(&mut cx, |this, cx| { + this.push_excerpts(buffer.clone(), excerpt_ranges.iter().cloned(), cx) + }); + + for (excerpt_id, range_count) in + excerpt_ids.into_iter().zip(range_counts.by_ref()) + { + for range in ranges.by_ref().take(range_count) { + let start = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: range.start, + }; + let end = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: range.end, + }; + if tx.send(start..end).await.is_err() { + break; + } + } + } + } + } + }); + (task, rx) + } + pub fn push_excerpts( &mut self, buffer: ModelHandle, @@ -788,39 +846,8 @@ impl MultiBuffer { { let buffer_id = buffer.id(); let buffer_snapshot = buffer.read(cx).snapshot(); - let max_point = buffer_snapshot.max_point(); - - let mut range_counts = Vec::new(); - let mut excerpt_ranges = Vec::new(); - let mut range_iter = ranges - .iter() - .map(|range| { - range.start.to_point(&buffer_snapshot)..range.end.to_point(&buffer_snapshot) - }) - .peekable(); - while let Some(range) = range_iter.next() { - let excerpt_start = Point::new(range.start.row.saturating_sub(context_line_count), 0); - let mut excerpt_end = - Point::new(range.end.row + 1 + context_line_count, 0).min(max_point); - let mut ranges_in_excerpt = 1; - - while let Some(next_range) = range_iter.peek() { - if next_range.start.row <= excerpt_end.row + context_line_count { - excerpt_end = - Point::new(next_range.end.row + 1 + context_line_count, 0).min(max_point); - ranges_in_excerpt += 1; - range_iter.next(); - } else { - break; - } - } - - excerpt_ranges.push(ExcerptRange { - context: excerpt_start..excerpt_end, - primary: Some(range), - }); - range_counts.push(ranges_in_excerpt); - } + let (excerpt_ranges, range_counts) = + build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); let excerpt_ids = self.push_excerpts(buffer, excerpt_ranges, cx); @@ -3605,6 +3632,47 @@ impl ToPointUtf16 for PointUtf16 { } } +fn build_excerpt_ranges( + buffer: &BufferSnapshot, + ranges: &[Range], + context_line_count: u32, +) -> (Vec>, Vec) +where + T: text::ToPoint, +{ + let max_point = buffer.max_point(); + let mut range_counts = Vec::new(); + let mut excerpt_ranges = Vec::new(); + let mut range_iter = ranges + .iter() + .map(|range| range.start.to_point(buffer)..range.end.to_point(buffer)) + .peekable(); + while let Some(range) = range_iter.next() { + let excerpt_start = Point::new(range.start.row.saturating_sub(context_line_count), 0); + let mut excerpt_end = Point::new(range.end.row + 1 + context_line_count, 0).min(max_point); + let mut ranges_in_excerpt = 1; + + while let Some(next_range) = range_iter.peek() { + if next_range.start.row <= excerpt_end.row + context_line_count { + excerpt_end = + Point::new(next_range.end.row + 1 + context_line_count, 0).min(max_point); + ranges_in_excerpt += 1; + range_iter.next(); + } else { + break; + } + } + + excerpt_ranges.push(ExcerptRange { + context: excerpt_start..excerpt_end, + primary: Some(range), + }); + range_counts.push(ranges_in_excerpt); + } + + (excerpt_ranges, range_counts) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 30802801a6..3a5d9468fc 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -19,6 +19,7 @@ theme = { path = "../theme" } util = { path = "../util" } workspace = { path = "../workspace" } anyhow = "1.0" +futures = "0.3" log = { version = "0.4.16", features = ["kv_unstable_serde"] } postage = { version = "0.4.1", features = ["futures-traits"] } serde = { version = "1.0", features = ["derive", "rc"] } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a0f29738b0..11318b1c43 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -7,6 +7,7 @@ use editor::{ items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN, }; +use futures::StreamExt; use gpui::{ actions, elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, @@ -129,31 +130,23 @@ impl ProjectSearch { let matches = search.await.log_err()?; let this = this.upgrade(&cx)?; let mut matches = matches.into_iter().collect::>(); - this.update(&mut cx, |this, cx| { + let (_rebuild, mut match_ranges) = this.update(&mut cx, |this, cx| { this.match_ranges.clear(); matches.sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); - this.excerpts.update(cx, |excerpts, cx| excerpts.clear(cx)); + this.excerpts.update(cx, |excerpts, cx| { + excerpts.clear(cx); + excerpts.stream_excerpts_with_context_lines(matches, 1, cx) + }) }); - for matches_chunk in matches.chunks(100) { + while let Some(match_range) = match_ranges.next().await { this.update(&mut cx, |this, cx| { - this.excerpts.update(cx, |excerpts, cx| { - for (buffer, buffer_matches) in matches_chunk { - let ranges_to_highlight = excerpts.push_excerpts_with_context_lines( - buffer.clone(), - buffer_matches.clone(), - 1, - cx, - ); - this.match_ranges.extend(ranges_to_highlight); - } - }); - + this.match_ranges.push(match_range); + while let Ok(Some(match_range)) = match_ranges.try_next() { + this.match_ranges.push(match_range); + } cx.notify(); }); - - // Don't starve the main thread when adding lots of excerpts. - smol::future::yield_now().await; } this.update(&mut cx, |this, cx| { From c124caeb0dccad8073f6e25413f6cb54143f5ca7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 15:52:59 +0100 Subject: [PATCH 58/83] Add test for `stream_excerpts_with_context_lines` --- crates/editor/src/multi_buffer.rs | 39 +++++++++++++++++++++++++++++ crates/search/src/project_search.rs | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 4d576883d3..7812c34781 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -3676,6 +3676,7 @@ where #[cfg(test)] mod tests { use super::*; + use futures::StreamExt; use gpui::{MutableAppContext, TestAppContext}; use language::{Buffer, Rope}; use rand::prelude::*; @@ -4080,6 +4081,44 @@ mod tests { ); } + #[gpui::test] + async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) { + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(20, 3, 'a'), cx)); + let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); + let (task, anchor_ranges) = multibuffer.update(cx, |multibuffer, cx| { + let snapshot = buffer.read(cx); + let ranges = vec![ + snapshot.anchor_before(Point::new(3, 2))..snapshot.anchor_before(Point::new(4, 2)), + snapshot.anchor_before(Point::new(7, 1))..snapshot.anchor_before(Point::new(7, 3)), + snapshot.anchor_before(Point::new(15, 0)) + ..snapshot.anchor_before(Point::new(15, 0)), + ]; + multibuffer.stream_excerpts_with_context_lines(vec![(buffer.clone(), ranges)], 2, cx) + }); + + let anchor_ranges = anchor_ranges.collect::>().await; + // Ensure task is finished when stream completes. + task.await; + + let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx)); + assert_eq!( + snapshot.text(), + "bbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\n\nnnn\nooo\nppp\nqqq\nrrr\n" + ); + + assert_eq!( + anchor_ranges + .iter() + .map(|range| range.to_point(&snapshot)) + .collect::>(), + vec![ + Point::new(2, 2)..Point::new(3, 2), + Point::new(6, 1)..Point::new(6, 3), + Point::new(12, 0)..Point::new(12, 0) + ] + ); + } + #[gpui::test] fn test_empty_multibuffer(cx: &mut MutableAppContext) { let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 11318b1c43..68c2f33a75 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -130,7 +130,7 @@ impl ProjectSearch { let matches = search.await.log_err()?; let this = this.upgrade(&cx)?; let mut matches = matches.into_iter().collect::>(); - let (_rebuild, mut match_ranges) = this.update(&mut cx, |this, cx| { + let (_task, mut match_ranges) = this.update(&mut cx, |this, cx| { this.match_ranges.clear(); matches.sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); this.excerpts.update(cx, |excerpts, cx| { From 0a7111d21607cbf780cef0159729602265ac429f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 16:26:27 +0100 Subject: [PATCH 59/83] Fix tests --- crates/search/src/project_search.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a315cf0c5f..d3d5c437c5 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -949,13 +949,13 @@ impl ToolbarItemView for ProjectSearchBar { mod tests { use super::*; use editor::DisplayPoint; - use gpui::{color::Color, TestAppContext}; + use gpui::{color::Color, executor::Deterministic, TestAppContext}; use project::FakeFs; use serde_json::json; use std::sync::Arc; #[gpui::test] - async fn test_project_search(cx: &mut TestAppContext) { + async fn test_project_search(deterministic: Arc, cx: &mut TestAppContext) { let fonts = cx.font_cache(); let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default); theme.search.match_background = Color::red(); @@ -987,7 +987,7 @@ mod tests { .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx)); search_view.search(cx); }); - search_view.next_notification(cx).await; + deterministic.run_until_parked(); search_view.update(cx, |search_view, cx| { assert_eq!( search_view From 1f649e52dedd1d46fb034749f111d31ab60f71c4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 17:25:59 +0100 Subject: [PATCH 60/83] Document RopeFingerprint --- crates/rope/src/rope.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 74cc9a6789..d0796cfc6a 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -25,6 +25,10 @@ const CHUNK_BASE: usize = 6; #[cfg(not(test))] const CHUNK_BASE: usize = 16; +/// Type alias to [HashMatrix], an implementation of a homomorphic hash function. Two [Rope] instances +/// containing the same text will produce the same fingerprint. This hash function is special in that +/// it allows us to hash individual chunks and aggregate them up the [Rope]'s tree, with the resulting +/// hash being equivalent to hashing all the text contained in the [Rope] at once. pub type RopeFingerprint = HashMatrix; #[derive(Clone, Default, Debug)] @@ -858,7 +862,7 @@ impl sum_tree::Item for Chunk { #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct ChunkSummary { text: TextSummary, - fingerprint: HashMatrix, + fingerprint: RopeFingerprint, } impl<'a> From<&'a str> for ChunkSummary { From f088de5947f6b467171a1f1950239761d525b544 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Thu, 19 Jan 2023 19:05:17 +0200 Subject: [PATCH 61/83] Add date to the log format Co-Authored-By: Mikayla --- crates/zed/src/main.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 6baf08b01e..16fd0c1eea 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -29,6 +29,7 @@ use settings::{ self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent, WorkingDirectory, }; +use simplelog::ConfigBuilder; use smol::process::Command; use std::fs::OpenOptions; use std::{env, ffi::OsStr, panic, path::PathBuf, sync::Arc, thread, time::Duration}; @@ -269,8 +270,12 @@ fn init_logger() { .append(true) .open(&*paths::LOG) .expect("could not open logfile"); - simplelog::WriteLogger::init(level, simplelog::Config::default(), log_file) - .expect("could not initialize logger"); + + let config = ConfigBuilder::new() + .set_time_format_str("%Y-%m-%dT%T") //All timestamps are UTC + .build(); + + simplelog::WriteLogger::init(level, config, log_file).expect("could not initialize logger"); } } From ad1947fa50d1561fd9741dafb1e9744480941c8f Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 19 Jan 2023 12:34:13 -0500 Subject: [PATCH 62/83] Add in-window on-move-out mouse handler concept --- crates/editor/src/element.rs | 3 +++ .../gpui/src/elements/mouse_event_handler.rs | 10 ++++++- crates/gpui/src/presenter.rs | 17 ++++++++++-- crates/gpui/src/scene/mouse_event.rs | 13 +++++++++ crates/gpui/src/scene/mouse_region.rs | 27 ++++++++++++++++++- 5 files changed, 66 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7d69d3833c..5c34b98380 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -190,6 +190,9 @@ impl EditorElement { } } }) + .on_move_out(|e, cx| { + println!("on move out"); + }) .on_scroll({ let position_map = position_map.clone(); move |e, cx| { diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 132a915f0b..761e134262 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -7,7 +7,7 @@ use crate::{ platform::CursorStyle, scene::{ CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover, - MouseMove, MouseScrollWheel, MouseUp, MouseUpOut, + MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, }, DebugContext, Element, ElementBox, EventContext, LayoutContext, MeasurementContext, MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View, @@ -82,6 +82,14 @@ impl MouseEventHandler { self } + pub fn on_move_out( + mut self, + handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_move_out(handler); + self + } + pub fn on_down( mut self, button: MouseButton, diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index bbaf1ed0bb..1573b10a10 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -8,7 +8,7 @@ use crate::{ platform::{CursorStyle, Event}, scene::{ CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, - MouseMove, MouseScrollWheel, MouseUp, MouseUpOut, Scene, + MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene, }, text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, Appearance, @@ -245,8 +245,11 @@ impl Presenter { // -> Also updates mouse-related state match &event { Event::KeyDown(e) => return cx.dispatch_key_down(self.window_id, e), + Event::KeyUp(e) => return cx.dispatch_key_up(self.window_id, e), + Event::ModifiersChanged(e) => return cx.dispatch_modifiers_changed(self.window_id, e), + Event::MouseDown(e) => { // Click events are weird because they can be fired after a drag event. // MDN says that browsers handle this by starting from 'the most @@ -279,6 +282,7 @@ impl Presenter { platform_event: e.clone(), })); } + Event::MouseUp(e) => { // NOTE: The order of event pushes is important! MouseUp events MUST be fired // before click events, and so the MouseUp events need to be pushed before @@ -296,6 +300,7 @@ impl Presenter { platform_event: e.clone(), })); } + Event::MouseMoved( e @ MouseMovedEvent { position, @@ -347,9 +352,13 @@ impl Presenter { platform_event: e.clone(), started: false, })); + mouse_events.push(MouseEvent::MoveOut(MouseMoveOut { + region: Default::default(), + })); self.last_mouse_moved_event = Some(event.clone()); } + Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel { region: Default::default(), platform_event: e.clone(), @@ -407,6 +416,7 @@ impl Presenter { } } } + MouseEvent::Down(_) | MouseEvent::Up(_) => { for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(self.mouse_position) { @@ -417,6 +427,7 @@ impl Presenter { } } } + MouseEvent::Click(e) => { // Only raise click events if the released button is the same as the one stored if self @@ -439,6 +450,7 @@ impl Presenter { } } } + MouseEvent::Drag(_) => { for (mouse_region, _) in self.mouse_regions.iter().rev() { if self.clicked_region_ids.contains(&mouse_region.id()) { @@ -447,7 +459,7 @@ impl Presenter { } } - MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => { + MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => { for (mouse_region, _) in self.mouse_regions.iter().rev() { // NOT contains if !mouse_region.bounds.contains_point(self.mouse_position) { @@ -455,6 +467,7 @@ impl Presenter { } } } + _ => { for (mouse_region, _) in self.mouse_regions.iter().rev() { // Contains diff --git a/crates/gpui/src/scene/mouse_event.rs b/crates/gpui/src/scene/mouse_event.rs index 00d1ddbf8b..2effb8ab53 100644 --- a/crates/gpui/src/scene/mouse_event.rs +++ b/crates/gpui/src/scene/mouse_event.rs @@ -21,6 +21,11 @@ impl Deref for MouseMove { } } +#[derive(Debug, Default, Clone)] +pub struct MouseMoveOut { + pub region: RectF, +} + #[derive(Debug, Default, Clone)] pub struct MouseDrag { pub region: RectF, @@ -138,6 +143,7 @@ impl Deref for MouseScrollWheel { #[derive(Debug, Clone)] pub enum MouseEvent { Move(MouseMove), + MoveOut(MouseMoveOut), Drag(MouseDrag), Hover(MouseHover), Down(MouseDown), @@ -152,6 +158,7 @@ impl MouseEvent { pub fn set_region(&mut self, region: RectF) { match self { MouseEvent::Move(r) => r.region = region, + MouseEvent::MoveOut(r) => r.region = region, MouseEvent::Drag(r) => r.region = region, MouseEvent::Hover(r) => r.region = region, MouseEvent::Down(r) => r.region = region, @@ -168,6 +175,7 @@ impl MouseEvent { pub fn is_capturable(&self) -> bool { match self { MouseEvent::Move(_) => true, + MouseEvent::MoveOut(_) => false, MouseEvent::Drag(_) => true, MouseEvent::Hover(_) => false, MouseEvent::Down(_) => true, @@ -185,6 +193,10 @@ impl MouseEvent { discriminant(&MouseEvent::Move(Default::default())) } + pub fn move_out_disc() -> Discriminant { + discriminant(&MouseEvent::MoveOut(Default::default())) + } + pub fn drag_disc() -> Discriminant { discriminant(&MouseEvent::Drag(Default::default())) } @@ -220,6 +232,7 @@ impl MouseEvent { pub fn handler_key(&self) -> HandlerKey { match self { MouseEvent::Move(_) => HandlerKey::new(Self::move_disc(), None), + MouseEvent::MoveOut(_) => HandlerKey::new(Self::move_out_disc(), None), MouseEvent::Drag(e) => HandlerKey::new(Self::drag_disc(), e.pressed_button), MouseEvent::Hover(_) => HandlerKey::new(Self::hover_disc(), None), MouseEvent::Down(e) => HandlerKey::new(Self::down_disc(), Some(e.button)), diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 0fdc76ebbf..7cbbc5b897 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -12,7 +12,7 @@ use super::{ MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, MouseMove, MouseUp, MouseUpOut, }, - MouseScrollWheel, + MouseMoveOut, MouseScrollWheel, }; #[derive(Clone)] @@ -124,6 +124,14 @@ impl MouseRegion { self } + pub fn on_move_out( + mut self, + handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_move_out(handler); + self + } + pub fn on_scroll( mut self, handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static, @@ -289,6 +297,23 @@ impl HandlerSet { self } + pub fn on_move_out( + mut self, + handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, + ) -> Self { + self.insert(MouseEvent::move_out_disc(), None, + Rc::new(move |region_event, cx| { + if let MouseEvent::MoveOut(e) = region_event { + handler(e, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::MoveOut, found {:?}", + region_event); + } + })); + self + } + pub fn on_down( mut self, button: MouseButton, From 38476f5429562246a7e293e1a9ee22d45e6a0b4a Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Thu, 19 Jan 2023 13:45:05 -0500 Subject: [PATCH 63/83] Disable soft wrap in single line editors Co-Authored-By: Mikayla Maki --- crates/editor/src/editor.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 07c0bce6b6..a6ce562631 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1086,6 +1086,8 @@ impl Editor { let blink_manager = cx.add_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx)); + let soft_wrap_mode_override = + (mode == EditorMode::SingleLine).then(|| settings::SoftWrap::None); let mut this = Self { handle: cx.weak_handle(), buffer: buffer.clone(), @@ -1101,7 +1103,7 @@ impl Editor { select_larger_syntax_node_stack: Vec::new(), ime_transaction: Default::default(), active_diagnostics: None, - soft_wrap_mode_override: None, + soft_wrap_mode_override, get_field_editor_theme, project, focused: false, From 4a46227909820fe6f5014f72b7758a3242d67e6c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 11:43:46 -0800 Subject: [PATCH 64/83] Change incoming call notification to only require one click --- crates/collab_ui/src/incoming_call_notification.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index a51fb4891d..daf93ce6a2 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -48,6 +48,7 @@ pub fn init(cx: &mut MutableAppContext) { }, |_| IncomingCallNotification::new(incoming_call.clone()), ); + cx.activate_window(window_id); notification_windows.push(window_id); } } From 1851e2e77cd0a1e59ec9c1487000d01311714f05 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 19 Jan 2023 12:31:34 -0800 Subject: [PATCH 65/83] Start work on language config overrides Co-authored-by: Julia Risley --- crates/editor/src/editor.rs | 9 +- crates/editor/src/multi_buffer.rs | 11 +- crates/language/src/buffer.rs | 23 +++- crates/language/src/language.rs | 123 +++++++++++++++--- crates/language/src/syntax_map.rs | 35 +++++ .../zed/src/languages/javascript/contexts.scm | 0 crates/zed/src/languages/tsx/config.toml | 9 ++ crates/zed/src/languages/tsx/overrides.scm | 2 + 8 files changed, 189 insertions(+), 23 deletions(-) create mode 100644 crates/zed/src/languages/javascript/contexts.scm create mode 100644 crates/zed/src/languages/tsx/overrides.scm diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a6ce562631..81ab73e24e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1737,7 +1737,7 @@ impl Editor { for (selection, autoclose_region) in self.selections_with_autoclose_regions(selections, &snapshot) { - if let Some(language) = snapshot.language_at(selection.head()) { + if let Some(language) = snapshot.language_config_at(selection.head()) { // Determine if the inserted text matches the opening or closing // bracket of any of this language's bracket pairs. let mut bracket_pair = None; @@ -1898,7 +1898,7 @@ impl Editor { let end = selection.end; let mut insert_extra_newline = false; - if let Some(language) = buffer.language_at(start) { + if let Some(language) = buffer.language_config_at(start) { let leading_whitespace_len = buffer .reversed_chars_at(start) .take_while(|c| c.is_whitespace() && *c != '\n') @@ -4533,7 +4533,10 @@ impl Editor { // TODO: Handle selections that cross excerpts for selection in &mut selections { - let language = if let Some(language) = snapshot.language_at(selection.start) { + let start_column = snapshot.indent_size_for_line(selection.start.row).len; + let language = if let Some(language) = + snapshot.language_config_at(Point::new(selection.start.row, start_column)) + { language } else { continue; diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 6b5262c16a..57fe5411cd 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -10,9 +10,9 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, - DiagnosticEntry, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, - Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, - ToPointUtf16 as _, TransactionId, Unclipped, + DiagnosticEntry, IndentSize, Language, LanguageConfigYeet, OffsetRangeExt, OffsetUtf16, + Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, + ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; use std::{ borrow::Cow, @@ -2691,6 +2691,11 @@ impl MultiBufferSnapshot { .and_then(|(buffer, offset)| buffer.language_at(offset)) } + pub fn language_config_at<'a, T: ToOffset>(&'a self, point: T) -> Option { + self.point_to_buffer_offset(point) + .and_then(|(buffer, offset)| buffer.language_config_at(offset)) + } + pub fn is_dirty(&self) -> bool { self.is_dirty } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index ad3bdb6821..5593241e70 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -9,7 +9,7 @@ use crate::{ syntax_map::{ SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint, }, - CodeLabel, Outline, + CodeLabel, LanguageConfigYeet, Outline, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -2015,6 +2015,27 @@ impl BufferSnapshot { .or(self.language.as_ref()) } + pub fn language_config_at(&self, position: D) -> Option { + let offset = position.to_offset(self); + + if let Some(layer_info) = self + .syntax + .layers_for_range(offset..offset, &self.text) + .filter(|l| l.node.end_byte() > offset) + .last() + { + Some(LanguageConfigYeet { + language: layer_info.language.clone(), + override_id: layer_info.override_id(offset, &self.text), + }) + } else { + self.language.clone().map(|language| LanguageConfigYeet { + language, + override_id: None, + }) + } + } + pub fn surrounding_word(&self, start: T) -> (Range, Option) { let mut start = start.to_offset(self); let mut end = start; diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c3f2c3716b..3c8b2b987b 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -22,7 +22,10 @@ use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; -use serde::{de, Deserialize, Deserializer}; +use serde::{ + de::{self}, + Deserialize, Deserializer, +}; use serde_json::Value; use std::{ any::Any, @@ -243,6 +246,45 @@ pub struct LanguageConfig { pub line_comment: Option>, #[serde(default)] pub block_comment: Option<(Arc, Arc)>, + #[serde(default)] + pub overrides: HashMap, +} + +#[derive(Clone)] +pub struct LanguageConfigYeet { + language: Arc, + override_id: Option, +} + +#[derive(Deserialize)] +pub struct LanguageConfigOverride { + #[serde(default)] + pub line_comment: Override>, + #[serde(default)] + pub block_comment: Override<(Arc, Arc)>, +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum Override { + Remove { remove: bool }, + Set(T), +} + +impl Default for Override { + fn default() -> Self { + Override::Remove { remove: false } + } +} + +impl Override { + fn as_option<'a>(this: Option<&'a Self>, original: &'a Option) -> Option<&'a T> { + match this { + Some(Self::Set(value)) => Some(value), + Some(Self::Remove { remove: true }) => None, + Some(Self::Remove { remove: false }) | None => original.as_ref(), + } + } } impl Default for LanguageConfig { @@ -257,6 +299,7 @@ impl Default for LanguageConfig { autoclose_before: Default::default(), line_comment: Default::default(), block_comment: Default::default(), + overrides: Default::default(), } } } @@ -311,6 +354,7 @@ pub struct Grammar { pub(crate) indents_config: Option, pub(crate) outline_config: Option, pub(crate) injection_config: Option, + pub(crate) override_config: Option, pub(crate) highlight_map: Mutex, } @@ -336,6 +380,11 @@ struct InjectionConfig { patterns: Vec, } +struct OverrideConfig { + query: Query, + values: HashMap, +} + #[derive(Default, Clone)] struct InjectionPatternConfig { language: Option>, @@ -635,6 +684,7 @@ impl Language { outline_config: None, indents_config: None, injection_config: None, + override_config: None, ts_language, highlight_map: Default::default(), }) @@ -775,6 +825,25 @@ impl Language { Ok(self) } + pub fn with_override_query(mut self, source: &str) -> Result { + let query = Query::new(self.grammar_mut().ts_language, source)?; + + let mut values = HashMap::default(); + for (ix, name) in query.capture_names().iter().enumerate() { + if let Some(override_name) = name.strip_prefix("override.") { + let value = self + .config + .overrides + .remove(override_name) + .ok_or_else(|| anyhow!("no such override {override_name}"))?; + values.insert(ix as u32, value); + } + } + + self.grammar_mut().override_config = Some(OverrideConfig { query, values }); + Ok(self) + } + fn grammar_mut(&mut self) -> &mut Grammar { Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap() } @@ -800,17 +869,6 @@ impl Language { self.config.name.clone() } - pub fn line_comment_prefix(&self) -> Option<&Arc> { - self.config.line_comment.as_ref() - } - - pub fn block_comment_delimiters(&self) -> Option<(&Arc, &Arc)> { - self.config - .block_comment - .as_ref() - .map(|(start, end)| (start, end)) - } - pub async fn disk_based_diagnostic_sources(&self) -> &[String] { match self.adapter.as_ref() { Some(adapter) => &adapter.disk_based_diagnostic_sources, @@ -886,10 +944,6 @@ impl Language { result } - pub fn brackets(&self) -> &[BracketPair] { - &self.config.brackets - } - pub fn path_suffixes(&self) -> &[String] { &self.config.path_suffixes } @@ -912,6 +966,43 @@ impl Language { } } +impl LanguageConfigYeet { + pub fn line_comment_prefix(&self) -> Option<&Arc> { + Override::as_option( + self.over_ride().map(|o| &o.line_comment), + &self.language.config.line_comment, + ) + } + + pub fn block_comment_delimiters(&self) -> Option<(&Arc, &Arc)> { + Override::as_option( + self.over_ride().map(|o| &o.block_comment), + &self.language.config.block_comment, + ) + .map(|e| (&e.0, &e.1)) + } + + pub fn brackets(&self) -> &[BracketPair] { + &self.language.config.brackets + } + + pub fn should_autoclose_before(&self, c: char) -> bool { + c.is_whitespace() || self.language.config.autoclose_before.contains(c) + } + + fn over_ride(&self) -> Option<&LanguageConfigOverride> { + self.override_id.and_then(|id| { + self.language + .grammar + .as_ref()? + .override_config + .as_ref()? + .values + .get(&id) + }) + } +} + impl Hash for Language { fn hash(&self, state: &mut H) { self.id().hash(state) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 65d01e9493..458ffd8bc2 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1127,6 +1127,41 @@ fn splice_included_ranges( ranges } +impl<'a> SyntaxLayerInfo<'a> { + pub(crate) fn override_id(&self, offset: usize, text: &text::BufferSnapshot) -> Option { + let text = TextProvider(text.as_rope()); + let config = self.language.grammar.as_ref()?.override_config.as_ref()?; + + let mut query_cursor = QueryCursorHandle::new(); + query_cursor.set_byte_range(offset..offset); + + let mut smallest_match: Option<(u32, Range)> = None; + for mat in query_cursor.matches(&config.query, self.node, text) { + for capture in mat.captures { + if !config.values.contains_key(&capture.index) { + continue; + } + + let range = capture.node.byte_range(); + if offset <= range.start || offset >= range.end { + continue; + } + + if let Some((_, smallest_range)) = &smallest_match { + if range.len() < smallest_range.len() { + smallest_match = Some((capture.index, range)) + } + continue; + } + + smallest_match = Some((capture.index, range)); + } + } + + smallest_match.map(|(index, _)| index) + } +} + impl std::ops::Deref for SyntaxMap { type Target = SyntaxSnapshot; diff --git a/crates/zed/src/languages/javascript/contexts.scm b/crates/zed/src/languages/javascript/contexts.scm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/zed/src/languages/tsx/config.toml b/crates/zed/src/languages/tsx/config.toml index 7baa12385c..4f466cc961 100644 --- a/crates/zed/src/languages/tsx/config.toml +++ b/crates/zed/src/languages/tsx/config.toml @@ -12,3 +12,12 @@ brackets = [ { start = "`", end = "`", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.element] +line_comment = { remove = true } +block_comment = ["{/* ", " */}"] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/tsx/overrides.scm b/crates/zed/src/languages/tsx/overrides.scm new file mode 100644 index 0000000000..aa617b3086 --- /dev/null +++ b/crates/zed/src/languages/tsx/overrides.scm @@ -0,0 +1,2 @@ +(jsx_element) @override.element +(string) @override.string From 4eeb1aec50f45a07ce3101bfa12eacc950235ee2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 14:22:12 -0800 Subject: [PATCH 66/83] Adds UI for showing the followed-by status to collaboration --- crates/collab_ui/src/collab_titlebar_item.rs | 12 ++++++++++-- crates/workspace/src/workspace.rs | 11 ++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 3351fb9eb9..ffafa2d142 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -417,9 +417,13 @@ impl CollabTitlebarItem { theme: &Theme, cx: &mut RenderContext, ) -> ElementBox { - let is_followed = peer.map_or(false, |(peer_id, _, _)| { - workspace.read(cx).is_following(peer_id) + let (is_followed, is_following) = peer.map_or((false, false), |(peer_id, _, _)| { + ( + workspace.read(cx).is_following(peer_id), + workspace.read(cx).is_followed(peer_id), + ) }); + // my color, around their avatar. let mut avatar_style; if let Some((_, _, location)) = peer.as_ref() { @@ -442,6 +446,10 @@ impl CollabTitlebarItem { replica_color = Some(color); if is_followed { avatar_style.border = Border::all(1.0, color); + } else if is_following { + let our_id = workspace.read(cx).project().read(cx).replica_id(); + let our_color = theme.editor.replica_selection_style(our_id).cursor; + avatar_style.border = Border::all(1.0, our_color); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5851734609..6a8fdaa17b 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1623,6 +1623,7 @@ impl Workspace { project_id, leader_id: Some(leader_id), }); + Some(cx.spawn_weak(|this, mut cx| async move { let response = request.await?; if let Some(this) = this.upgrade(&cx) { @@ -1719,6 +1720,10 @@ impl Workspace { self.follower_states_by_leader.contains_key(&peer_id) } + pub fn is_followed(&self, peer_id: PeerId) -> bool { + self.leader_state.followers.contains(&peer_id) + } + fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { let project = &self.project.read(cx); let mut worktree_root_names = String::new(); @@ -1896,6 +1901,9 @@ impl Workspace { .to_proto(), ) }); + + cx.notify(); + Ok(proto::FollowResponse { active_view_id, views: this @@ -1928,10 +1936,11 @@ impl Workspace { _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { - this.update(&mut cx, |this, _| { + this.update(&mut cx, |this, cx| { this.leader_state .followers .remove(&envelope.original_sender_id()?); + cx.notify(); Ok(()) }) } From 2967b46a17b9e5bb64dda54fe6fb5a68dee0b539 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 19 Jan 2023 15:04:27 -0800 Subject: [PATCH 67/83] Implement scope-specific bracket matching and comment toggling Co-authored-by: Julia Risley --- crates/editor/src/editor.rs | 6 +- crates/editor/src/multi_buffer.rs | 10 +-- crates/language/src/buffer.rs | 8 +-- crates/language/src/buffer_tests.rs | 83 ++++++++++++++++++++++ crates/language/src/language.rs | 48 ++++++------- crates/zed/src/languages.rs | 5 ++ crates/zed/src/languages/tsx/overrides.scm | 7 +- 7 files changed, 129 insertions(+), 38 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 81ab73e24e..0d62c1f625 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1737,7 +1737,7 @@ impl Editor { for (selection, autoclose_region) in self.selections_with_autoclose_regions(selections, &snapshot) { - if let Some(language) = snapshot.language_config_at(selection.head()) { + if let Some(language) = snapshot.language_scope_at(selection.head()) { // Determine if the inserted text matches the opening or closing // bracket of any of this language's bracket pairs. let mut bracket_pair = None; @@ -1898,7 +1898,7 @@ impl Editor { let end = selection.end; let mut insert_extra_newline = false; - if let Some(language) = buffer.language_config_at(start) { + if let Some(language) = buffer.language_scope_at(start) { let leading_whitespace_len = buffer .reversed_chars_at(start) .take_while(|c| c.is_whitespace() && *c != '\n') @@ -4535,7 +4535,7 @@ impl Editor { for selection in &mut selections { let start_column = snapshot.indent_size_for_line(selection.start.row).len; let language = if let Some(language) = - snapshot.language_config_at(Point::new(selection.start.row, start_column)) + snapshot.language_scope_at(Point::new(selection.start.row, start_column)) { language } else { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 57fe5411cd..7079d197f9 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -10,9 +10,9 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, - DiagnosticEntry, IndentSize, Language, LanguageConfigYeet, OffsetRangeExt, OffsetUtf16, - Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, - ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, + DiagnosticEntry, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, + OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, + ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; use std::{ borrow::Cow, @@ -2691,9 +2691,9 @@ impl MultiBufferSnapshot { .and_then(|(buffer, offset)| buffer.language_at(offset)) } - pub fn language_config_at<'a, T: ToOffset>(&'a self, point: T) -> Option { + pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option { self.point_to_buffer_offset(point) - .and_then(|(buffer, offset)| buffer.language_config_at(offset)) + .and_then(|(buffer, offset)| buffer.language_scope_at(offset)) } pub fn is_dirty(&self) -> bool { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5593241e70..7ef7845952 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -9,7 +9,7 @@ use crate::{ syntax_map::{ SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint, }, - CodeLabel, LanguageConfigYeet, Outline, + CodeLabel, LanguageScope, Outline, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -2015,7 +2015,7 @@ impl BufferSnapshot { .or(self.language.as_ref()) } - pub fn language_config_at(&self, position: D) -> Option { + pub fn language_scope_at(&self, position: D) -> Option { let offset = position.to_offset(self); if let Some(layer_info) = self @@ -2024,12 +2024,12 @@ impl BufferSnapshot { .filter(|l| l.node.end_byte() > offset) .last() { - Some(LanguageConfigYeet { + Some(LanguageScope { language: layer_info.language.clone(), override_id: layer_info.override_id(offset, &self.text), }) } else { - self.language.clone().map(|language| LanguageConfigYeet { + self.language.clone().map(|language| LanguageScope { language, override_id: None, }) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 5aa9152205..07198e8090 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1369,6 +1369,89 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut MutableAppContext) { }); } +#[gpui::test] +fn test_language_config_at(cx: &mut MutableAppContext) { + cx.set_global(Settings::test(cx)); + cx.add_model(|cx| { + let language = Language::new( + LanguageConfig { + name: "JavaScript".into(), + line_comment: Some("// ".into()), + brackets: vec![ + BracketPair { + start: "{".into(), + end: "}".into(), + close: true, + newline: false, + }, + BracketPair { + start: "'".into(), + end: "'".into(), + close: true, + newline: false, + }, + ], + overrides: [ + ( + "element".into(), + LanguageConfigOverride { + line_comment: Override::Remove { remove: true }, + block_comment: Override::Set(("{/*".into(), "*/}".into())), + ..Default::default() + }, + ), + ( + "string".into(), + LanguageConfigOverride { + brackets: Override::Set(vec![BracketPair { + start: "{".into(), + end: "}".into(), + close: true, + newline: false, + }]), + ..Default::default() + }, + ), + ] + .into_iter() + .collect(), + ..Default::default() + }, + Some(tree_sitter_javascript::language()), + ) + .with_override_query( + r#" + (jsx_element) @override.element + (string) @override.string + "#, + ) + .unwrap(); + + let text = r#"a["b"] = ;"#; + + let buffer = Buffer::new(0, text, cx).with_language(Arc::new(language), cx); + let snapshot = buffer.snapshot(); + + let config = snapshot.language_scope_at(0).unwrap(); + assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// "); + assert_eq!(config.brackets().len(), 2); + + let string_config = snapshot.language_scope_at(3).unwrap(); + assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// "); + assert_eq!(string_config.brackets().len(), 1); + + let element_config = snapshot.language_scope_at(10).unwrap(); + assert_eq!(element_config.line_comment_prefix(), None); + assert_eq!( + element_config.block_comment_delimiters(), + Some((&"{/*".into(), &"*/}".into())) + ); + assert_eq!(element_config.brackets().len(), 2); + + buffer + }); +} + #[gpui::test] fn test_serialization(cx: &mut gpui::MutableAppContext) { let mut now = Instant::now(); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 3c8b2b987b..928e208627 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -22,10 +22,7 @@ use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; -use serde::{ - de::{self}, - Deserialize, Deserializer, -}; +use serde::{de, Deserialize, Deserializer}; use serde_json::Value; use std::{ any::Any, @@ -251,20 +248,22 @@ pub struct LanguageConfig { } #[derive(Clone)] -pub struct LanguageConfigYeet { +pub struct LanguageScope { language: Arc, override_id: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, Default, Debug)] pub struct LanguageConfigOverride { #[serde(default)] pub line_comment: Override>, #[serde(default)] pub block_comment: Override<(Arc, Arc)>, + #[serde(default)] + pub brackets: Override>, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(untagged)] pub enum Override { Remove { remove: bool }, @@ -278,11 +277,11 @@ impl Default for Override { } impl Override { - fn as_option<'a>(this: Option<&'a Self>, original: &'a Option) -> Option<&'a T> { + fn as_option<'a>(this: Option<&'a Self>, original: Option<&'a T>) -> Option<&'a T> { match this { Some(Self::Set(value)) => Some(value), Some(Self::Remove { remove: true }) => None, - Some(Self::Remove { remove: false }) | None => original.as_ref(), + Some(Self::Remove { remove: false }) | None => original, } } } @@ -966,40 +965,39 @@ impl Language { } } -impl LanguageConfigYeet { +impl LanguageScope { pub fn line_comment_prefix(&self) -> Option<&Arc> { Override::as_option( - self.over_ride().map(|o| &o.line_comment), - &self.language.config.line_comment, + self.config_override().map(|o| &o.line_comment), + self.language.config.line_comment.as_ref(), ) } pub fn block_comment_delimiters(&self) -> Option<(&Arc, &Arc)> { Override::as_option( - self.over_ride().map(|o| &o.block_comment), - &self.language.config.block_comment, + self.config_override().map(|o| &o.block_comment), + self.language.config.block_comment.as_ref(), ) .map(|e| (&e.0, &e.1)) } pub fn brackets(&self) -> &[BracketPair] { - &self.language.config.brackets + Override::as_option( + self.config_override().map(|o| &o.brackets), + Some(&self.language.config.brackets), + ) + .map_or(&[], Vec::as_slice) } pub fn should_autoclose_before(&self, c: char) -> bool { c.is_whitespace() || self.language.config.autoclose_before.contains(c) } - fn over_ride(&self) -> Option<&LanguageConfigOverride> { - self.override_id.and_then(|id| { - self.language - .grammar - .as_ref()? - .override_config - .as_ref()? - .values - .get(&id) - }) + fn config_override(&self) -> Option<&LanguageConfigOverride> { + let id = self.override_id?; + let grammar = self.language.grammar.as_ref()?; + let override_config = grammar.override_config.as_ref()?; + override_config.values.get(&id) } } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 0250c53684..d7fa67b1d4 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -173,6 +173,11 @@ pub(crate) fn language( .with_injection_query(query.as_ref()) .expect("failed to load injection query"); } + if let Some(query) = load_query(name, "/overrides") { + language = language + .with_override_query(query.as_ref()) + .expect("failed to load override query"); + } if let Some(lsp_adapter) = lsp_adapter { language = language.with_lsp_adapter(lsp_adapter) } diff --git a/crates/zed/src/languages/tsx/overrides.scm b/crates/zed/src/languages/tsx/overrides.scm index aa617b3086..183f7b4f98 100644 --- a/crates/zed/src/languages/tsx/overrides.scm +++ b/crates/zed/src/languages/tsx/overrides.scm @@ -1,2 +1,7 @@ -(jsx_element) @override.element +[ + (jsx_element) + (jsx_fragment) + (jsx_self_closing_element) + (jsx_expression) +] @override.element (string) @override.string From 88170df7f0dfcb445f3c3dc3d34c3d34054ce89e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 15:21:26 -0800 Subject: [PATCH 68/83] Switched from active hover to NSViews acceptsFirstMouse API --- crates/collab_ui/src/collab_titlebar_item.rs | 12 ++---------- .../collab_ui/src/incoming_call_notification.rs | 4 +++- .../collab_ui/src/project_shared_notification.rs | 1 + crates/gpui/src/platform.rs | 2 ++ crates/gpui/src/platform/mac/window.rs | 15 +++++++++++++++ crates/zed/src/zed.rs | 1 + 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index ffafa2d142..3351fb9eb9 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -417,13 +417,9 @@ impl CollabTitlebarItem { theme: &Theme, cx: &mut RenderContext, ) -> ElementBox { - let (is_followed, is_following) = peer.map_or((false, false), |(peer_id, _, _)| { - ( - workspace.read(cx).is_following(peer_id), - workspace.read(cx).is_followed(peer_id), - ) + let is_followed = peer.map_or(false, |(peer_id, _, _)| { + workspace.read(cx).is_following(peer_id) }); - // my color, around their avatar. let mut avatar_style; if let Some((_, _, location)) = peer.as_ref() { @@ -446,10 +442,6 @@ impl CollabTitlebarItem { replica_color = Some(color); if is_followed { avatar_style.border = Border::all(1.0, color); - } else if is_following { - let our_id = workspace.read(cx).project().read(cx).replica_id(); - let our_color = theme.editor.replica_selection_style(our_id).cursor; - avatar_style.border = Border::all(1.0, our_color); } } diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index daf93ce6a2..f0daa3c7e8 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -45,10 +45,11 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), + accepts_first_mouse: true, }, |_| IncomingCallNotification::new(incoming_call.clone()), ); - cx.activate_window(window_id); + notification_windows.push(window_id); } } @@ -226,6 +227,7 @@ impl View for IncomingCallNotification { .theme .incoming_call_notification .background; + Flex::row() .with_child(self.render_caller(cx)) .with_child(self.render_buttons(cx)) diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 0815d9c8d8..858886a0e3 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -44,6 +44,7 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), + accepts_first_mouse: true, }, |_| { ProjectSharedNotification::new( diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index d027218040..ad4d7542d3 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -156,6 +156,7 @@ pub struct WindowOptions<'a> { pub kind: WindowKind, pub is_movable: bool, pub screen: Option>, + pub accepts_first_mouse: bool, } #[derive(Debug)] @@ -301,6 +302,7 @@ impl<'a> Default for WindowOptions<'a> { kind: WindowKind::Normal, is_movable: true, screen: None, + accepts_first_mouse: false, } } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 958ac2ebd6..a929ba08a3 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -252,6 +252,11 @@ unsafe fn build_classes() { do_command_by_selector as extern "C" fn(&Object, Sel, Sel), ); + decl.add_method( + sel!(acceptsFirstMouse:), + accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL, + ); + decl.register() }; } @@ -338,6 +343,7 @@ struct WindowState { ime_state: ImeState, //Retains the last IME Text ime_text: Option, + accepts_first_mouse: bool, } struct InsertText { @@ -444,6 +450,7 @@ impl Window { previous_modifiers_changed_event: None, ime_state: ImeState::None, ime_text: None, + accepts_first_mouse: options.accepts_first_mouse, }))); (*native_window).set_ivar( @@ -1404,6 +1411,14 @@ extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) { } } +extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL { + unsafe { + let state = get_window_state(this); + let state_borrow = state.as_ref().borrow(); + return state_borrow.accepts_first_mouse as BOOL; + } +} + async fn synthetic_drag( window_state: Weak>, drag_id: usize, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c3af91306d..248bab5fda 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -408,6 +408,7 @@ pub fn build_window_options() -> WindowOptions<'static> { kind: WindowKind::Normal, is_movable: true, screen: None, + accepts_first_mouse: false, } } From 131f3471fc5b16f3972ad0bac072b32c6fe17be2 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 20 Jan 2023 09:50:02 -0500 Subject: [PATCH 69/83] Don't dispatch mousemove without focus & avoid swallowing external moves Co-Authored-By: Antonio Scandurra --- crates/gpui/src/platform/mac/window.rs | 33 ++++++++++---------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 958ac2ebd6..ca233ccf65 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -66,12 +66,6 @@ const NSNormalWindowLevel: NSInteger = 0; #[allow(non_upper_case_globals)] const NSPopUpWindowLevel: NSInteger = 101; #[allow(non_upper_case_globals)] -const NSTrackingMouseMoved: NSUInteger = 0x02; -#[allow(non_upper_case_globals)] -const NSTrackingActiveAlways: NSUInteger = 0x80; -#[allow(non_upper_case_globals)] -const NSTrackingInVisibleRect: NSUInteger = 0x200; -#[allow(non_upper_case_globals)] const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4; #[repr(C)] @@ -469,15 +463,7 @@ impl Window { native_window.setTitlebarAppearsTransparent_(YES); } - let tracking_area: id = msg_send![class!(NSTrackingArea), alloc]; - let _: () = msg_send![ - tracking_area, - initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)) - options: NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect - owner: native_view - userInfo: nil - ]; - let _: () = msg_send![native_view, addTrackingArea: tracking_area.autorelease()]; + native_window.setAcceptsMouseMovedEvents_(YES); native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable); native_view.setWantsBestResolutionOpenGLSurface_(YES); @@ -873,11 +859,10 @@ extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) { extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL { let window_state = unsafe { get_window_state(this) }; - let mut window_state_borrow = window_state.as_ref().borrow_mut(); - let event = - unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) }; + let window_height = window_state_borrow.content_size().y(); + let event = unsafe { Event::from_native(native_event, Some(window_height)) }; if let Some(event) = event { if key_equivalent { @@ -902,6 +887,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: function_is_held = event.keystroke.function; Some((event, None)) } + _ => return NO, }; @@ -968,9 +954,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let window_state = unsafe { get_window_state(this) }; let weak_window_state = Rc::downgrade(&window_state); let mut window_state_borrow = window_state.as_ref().borrow_mut(); + let is_active = unsafe { window_state_borrow.native_window.isKeyWindow() == YES }; + + let window_height = window_state_borrow.content_size().y(); + let event = unsafe { Event::from_native(native_event, Some(window_height)) }; - let event = - unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) }; if let Some(event) = event { match &event { Event::MouseMoved( @@ -989,12 +977,16 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { )) .detach(); } + + Event::MouseMoved(_) if !is_active => return, + Event::MouseUp(MouseButtonEvent { button: MouseButton::Left, .. }) => { window_state_borrow.synthetic_drag_counter += 1; } + Event::ModifiersChanged(ModifiersChangedEvent { modifiers }) => { // Only raise modifiers changed event when they have actually changed if let Some(Event::ModifiersChanged(ModifiersChangedEvent { @@ -1008,6 +1000,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { window_state_borrow.previous_modifiers_changed_event = Some(event.clone()); } + _ => {} } From 467e3dc50ac846260bbf44a51d458e3028311a5b Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 20 Jan 2023 10:16:24 -0500 Subject: [PATCH 70/83] Hide editor hover on mouse move out & always notify when hiding hover Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 10 +++++----- crates/editor/src/element.rs | 10 +++++++--- crates/editor/src/hover_popover.rs | 16 ++++++++++++---- crates/editor/src/scroll.rs | 8 ++++---- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 07c0bce6b6..057a86e7f3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -44,7 +44,7 @@ use gpui::{ ViewContext, ViewHandle, WeakViewHandle, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; -use hover_popover::{hide_hover, HoverState}; +use hover_popover::{hide_hover, HideHover, HoverState}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -1319,7 +1319,7 @@ impl Editor { } } - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); if old_cursor_position.to_display_point(&display_map).row() != new_cursor_position.to_display_point(&display_map).row() @@ -1694,7 +1694,7 @@ impl Editor { return; } - if hide_hover(self, cx) { + if hide_hover(self, &HideHover, cx) { return; } @@ -6174,7 +6174,7 @@ impl View for Editor { cx.defer(move |cx| { if let Some(editor) = handle.upgrade(cx) { editor.update(cx, |editor, cx| { - hide_hover(editor, cx); + hide_hover(editor, &HideHover, cx); hide_link_definition(editor, cx); }) } @@ -6223,7 +6223,7 @@ impl View for Editor { self.buffer .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); self.hide_context_menu(cx); - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); cx.emit(Event::Blurred); cx.notify(); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 5c34b98380..bce63ca0cf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -7,7 +7,7 @@ use crate::{ display_map::{BlockStyle, DisplaySnapshot, TransformBlock}, git::{diff_hunk_to_display, DisplayDiffHunk}, hover_popover::{ - HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, + HideHover, HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, }, link_go_to_definition::{ GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink, @@ -114,6 +114,7 @@ impl EditorElement { fn attach_mouse_handlers( view: &WeakViewHandle, position_map: &Arc, + has_popovers: bool, visible_bounds: RectF, text_bounds: RectF, gutter_bounds: RectF, @@ -190,8 +191,10 @@ impl EditorElement { } } }) - .on_move_out(|e, cx| { - println!("on move out"); + .on_move_out(move |_, cx| { + if has_popovers { + cx.dispatch_action(HideHover); + } }) .on_scroll({ let position_map = position_map.clone(); @@ -1873,6 +1876,7 @@ impl Element for EditorElement { Self::attach_mouse_handlers( &self.view, &layout.position_map, + layout.hover_popovers.is_some(), visible_bounds, text_bounds, gutter_bounds, diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 7369b0a6f4..6d003cae5d 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -29,12 +29,16 @@ pub struct HoverAt { pub point: Option, } +#[derive(Copy, Clone, PartialEq)] +pub struct HideHover; + actions!(editor, [Hover]); -impl_internal_actions!(editor, [HoverAt]); +impl_internal_actions!(editor, [HoverAt, HideHover]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(hover); cx.add_action(hover_at); + cx.add_action(hide_hover); } /// Bindable action which uses the most recent selection head to trigger a hover @@ -50,7 +54,7 @@ pub fn hover_at(editor: &mut Editor, action: &HoverAt, cx: &mut ViewContext) -> bool { +pub fn hide_hover(editor: &mut Editor, _: &HideHover, cx: &mut ViewContext) -> bool { let did_hide = editor.hover_state.info_popover.take().is_some() | editor.hover_state.diagnostic_popover.take().is_some(); @@ -67,6 +71,10 @@ pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext) -> bool { editor.clear_background_highlights::(cx); + if did_hide { + cx.notify(); + } + did_hide } @@ -121,7 +129,7 @@ fn show_hover( // Hover triggered from same location as last time. Don't show again. return; } else { - hide_hover(editor, cx); + hide_hover(editor, &HideHover, cx); } } } diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 0ca837a843..1b3fd96dbd 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -17,7 +17,7 @@ use workspace::WorkspaceId; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, - hover_popover::hide_hover, + hover_popover::{hide_hover, HideHover}, persistence::DB, Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint, }; @@ -307,7 +307,7 @@ impl Editor { ) { let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); self.scroll_manager.set_scroll_position( scroll_position, &map, @@ -323,7 +323,7 @@ impl Editor { } pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext) { - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); let top_row = scroll_anchor .top_anchor .to_point(&self.buffer().read(cx).snapshot(cx)) @@ -337,7 +337,7 @@ impl Editor { scroll_anchor: ScrollAnchor, cx: &mut ViewContext, ) { - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); let top_row = scroll_anchor .top_anchor .to_point(&self.buffer().read(cx).snapshot(cx)) From 95e661a78c38947c83f59f69e82ba9497924db5a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 15:21:26 -0800 Subject: [PATCH 71/83] Switched from active hover to NSViews acceptsFirstMouse API Co-authored-by: Nathan --- crates/gpui/src/platform/mac/window.rs | 1 + crates/gpui/src/presenter.rs | 1 + crates/workspace/src/workspace.rs | 11 ++++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index a929ba08a3..f5a21c4644 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -443,6 +443,7 @@ impl Window { scene_to_render: Default::default(), renderer: Renderer::new(true, fonts), last_fresh_keydown: None, + accepts_first_mouse: matches!(options.kind, WindowKind::PopUp), traffic_light_position: options .titlebar .as_ref() diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index bbaf1ed0bb..4ccc319219 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -156,6 +156,7 @@ impl Presenter { self.cursor_regions = scene.cursor_regions(); self.mouse_regions = scene.mouse_regions(); + // window.is_topmost for the mouse moved event's postion? if cx.window_is_active(self.window_id) { if let Some(event) = self.last_mouse_moved_event.clone() { self.dispatch_event(event, true, cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6a8fdaa17b..9665db6be6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,14 +31,18 @@ use futures::{ }; use gpui::{ actions, + color::Color, elements::*, - geometry::vector::Vector2F, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, + Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -98,7 +102,8 @@ actions!( ToggleLeftSidebar, ToggleRightSidebar, NewTerminal, - NewSearch + NewSearch, + ShowNotif, ] ); From 97203e1e02fdf3c25d5ffd037d495947a74ac6d5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 09:19:58 -0800 Subject: [PATCH 72/83] Fix broken merge --- crates/collab_ui/src/incoming_call_notification.rs | 1 - crates/collab_ui/src/project_shared_notification.rs | 1 - crates/gpui/src/platform.rs | 4 +--- crates/gpui/src/platform/mac/window.rs | 3 +-- crates/workspace/src/workspace.rs | 8 ++------ crates/zed/src/zed.rs | 1 - 6 files changed, 4 insertions(+), 14 deletions(-) diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index f0daa3c7e8..6ad533665e 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -45,7 +45,6 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), - accepts_first_mouse: true, }, |_| IncomingCallNotification::new(incoming_call.clone()), ); diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 858886a0e3..0815d9c8d8 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -44,7 +44,6 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), - accepts_first_mouse: true, }, |_| { ProjectSharedNotification::new( diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index ad4d7542d3..99d607e407 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -156,7 +156,6 @@ pub struct WindowOptions<'a> { pub kind: WindowKind, pub is_movable: bool, pub screen: Option>, - pub accepts_first_mouse: bool, } #[derive(Debug)] @@ -180,7 +179,7 @@ impl Default for Appearance { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum WindowKind { Normal, PopUp, @@ -302,7 +301,6 @@ impl<'a> Default for WindowOptions<'a> { kind: WindowKind::Normal, is_movable: true, screen: None, - accepts_first_mouse: false, } } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index f5a21c4644..26305e86da 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -443,7 +443,7 @@ impl Window { scene_to_render: Default::default(), renderer: Renderer::new(true, fonts), last_fresh_keydown: None, - accepts_first_mouse: matches!(options.kind, WindowKind::PopUp), + accepts_first_mouse: options.kind == WindowKind::PopUp, traffic_light_position: options .titlebar .as_ref() @@ -451,7 +451,6 @@ impl Window { previous_modifiers_changed_event: None, ime_state: ImeState::None, ime_text: None, - accepts_first_mouse: options.accepts_first_mouse, }))); (*native_window).set_ivar( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9665db6be6..8735f33fbe 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,18 +31,14 @@ use futures::{ }; use gpui::{ actions, - color::Color, elements::*, - geometry::{ - rect::RectF, - vector::{vec2f, Vector2F}, - }, + geometry::vector::Vector2F, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 248bab5fda..c3af91306d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -408,7 +408,6 @@ pub fn build_window_options() -> WindowOptions<'static> { kind: WindowKind::Normal, is_movable: true, screen: None, - accepts_first_mouse: false, } } From 8af1294ba51d6c8b114d06d3f8a1aa7cfbcc20ae Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 09:37:09 -0800 Subject: [PATCH 73/83] Changed platform mouse moved handling to only fire on active or popup windows co-authored-by: Antonio --- crates/gpui/src/platform/mac/window.rs | 12 ++++++++---- crates/workspace/src/workspace.rs | 8 ++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 4104b0ebf8..0da2e4351e 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -316,6 +316,7 @@ enum ImeState { struct WindowState { id: usize, native_window: id, + kind: WindowKind, event_callback: Option bool>>, activate_callback: Option>, resize_callback: Option>, @@ -337,7 +338,6 @@ struct WindowState { ime_state: ImeState, //Retains the last IME Text ime_text: Option, - accepts_first_mouse: bool, } struct InsertText { @@ -422,6 +422,7 @@ impl Window { let window = Self(Rc::new(RefCell::new(WindowState { id, native_window, + kind: options.kind, event_callback: None, resize_callback: None, should_close_callback: None, @@ -437,7 +438,6 @@ impl Window { scene_to_render: Default::default(), renderer: Renderer::new(true, fonts), last_fresh_keydown: None, - accepts_first_mouse: options.kind == WindowKind::PopUp, traffic_light_position: options .titlebar .as_ref() @@ -985,7 +985,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { .detach(); } - Event::MouseMoved(_) if !is_active => return, + Event::MouseMoved(_) + if !(is_active || window_state_borrow.kind == WindowKind::PopUp) => + { + return + } Event::MouseUp(MouseButtonEvent { button: MouseButton::Left, @@ -1408,7 +1412,7 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL { unsafe { let state = get_window_state(this); let state_borrow = state.as_ref().borrow(); - return state_borrow.accepts_first_mouse as BOOL; + return state_borrow.kind == WindowKind::PopUp; } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8735f33fbe..9665db6be6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,14 +31,18 @@ use futures::{ }; use gpui::{ actions, + color::Color, elements::*, - geometry::vector::Vector2F, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, + Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; From 2aa7a9e95bcbab7533e8f9930d88980ac2f35027 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 10:39:31 -0800 Subject: [PATCH 74/83] Add overrides for all languages Co-authored-by: Julia Risley --- crates/language/src/language.rs | 19 ++++++++++++++----- crates/zed/src/languages.rs | 3 +++ crates/zed/src/languages/c/config.toml | 15 +++++++++++++++ crates/zed/src/languages/c/overrides.scm | 2 ++ crates/zed/src/languages/cpp/config.toml | 15 +++++++++++++++ crates/zed/src/languages/cpp/overrides.scm | 2 ++ crates/zed/src/languages/css/config.toml | 17 ++++++++++++++++- crates/zed/src/languages/css/overrides.scm | 2 ++ crates/zed/src/languages/elixir/config.toml | 17 ++++++++++++++++- crates/zed/src/languages/elixir/overrides.scm | 2 ++ crates/zed/src/languages/erb/config.toml | 3 +-- crates/zed/src/languages/go/config.toml | 15 +++++++++++++++ crates/zed/src/languages/go/overrides.scm | 6 ++++++ crates/zed/src/languages/html/config.toml | 16 +++++++++++++++- crates/zed/src/languages/html/overrides.scm | 2 ++ .../zed/src/languages/javascript/config.toml | 18 ++++++++++++++++++ .../src/languages/javascript/overrides.scm | 9 +++++++++ crates/zed/src/languages/json/config.toml | 6 ++++++ crates/zed/src/languages/json/overrides.scm | 2 ++ crates/zed/src/languages/python/config.toml | 16 +++++++++++++++- crates/zed/src/languages/python/overrides.scm | 2 ++ crates/zed/src/languages/ruby/config.toml | 18 ++++++++++++++++-- crates/zed/src/languages/ruby/overrides.scm | 2 ++ crates/zed/src/languages/rust/config.toml | 16 ++++++++++++++++ crates/zed/src/languages/rust/overrides.scm | 8 ++++++++ crates/zed/src/languages/scheme/config.toml | 12 ++++++++++++ crates/zed/src/languages/scheme/overrides.scm | 6 ++++++ crates/zed/src/languages/toml/config.toml | 13 +++++++++++++ crates/zed/src/languages/toml/overrides.scm | 2 ++ .../zed/src/languages/typescript/config.toml | 14 ++++++++++++++ .../src/languages/typescript/overrides.scm | 2 ++ 31 files changed, 269 insertions(+), 13 deletions(-) create mode 100644 crates/zed/src/languages/c/overrides.scm create mode 100644 crates/zed/src/languages/cpp/overrides.scm create mode 100644 crates/zed/src/languages/css/overrides.scm create mode 100644 crates/zed/src/languages/elixir/overrides.scm create mode 100644 crates/zed/src/languages/go/overrides.scm create mode 100644 crates/zed/src/languages/html/overrides.scm create mode 100644 crates/zed/src/languages/javascript/overrides.scm create mode 100644 crates/zed/src/languages/json/overrides.scm create mode 100644 crates/zed/src/languages/python/overrides.scm create mode 100644 crates/zed/src/languages/ruby/overrides.scm create mode 100644 crates/zed/src/languages/rust/overrides.scm create mode 100644 crates/zed/src/languages/scheme/overrides.scm create mode 100644 crates/zed/src/languages/toml/overrides.scm create mode 100644 crates/zed/src/languages/typescript/overrides.scm diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 928e208627..766e181671 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -830,15 +830,24 @@ impl Language { let mut values = HashMap::default(); for (ix, name) in query.capture_names().iter().enumerate() { if let Some(override_name) = name.strip_prefix("override.") { - let value = self - .config - .overrides - .remove(override_name) - .ok_or_else(|| anyhow!("no such override {override_name}"))?; + let value = self.config.overrides.remove(override_name).ok_or_else(|| { + anyhow!( + "language {:?} has override in query but not in config: {override_name:?}", + self.config.name + ) + })?; values.insert(ix as u32, value); } } + if !self.config.overrides.is_empty() { + let keys = self.config.overrides.keys().collect::>(); + Err(anyhow!( + "language {:?} has overrides in config not in query: {keys:?}", + self.config.name + ))?; + } + self.grammar_mut().override_config = Some(OverrideConfig { query, values }); Ok(self) } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index d7fa67b1d4..240d1dc49e 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use gpui::executor::Background; pub use language::*; use lazy_static::lazy_static; @@ -145,7 +146,9 @@ pub(crate) fn language( .unwrap() .data, ) + .with_context(|| format!("failed to load config.toml for language {name:?}")) .unwrap(); + let mut language = Language::new(config, Some(grammar)); if let Some(query) = load_query(name, "/highlights") { diff --git a/crates/zed/src/languages/c/config.toml b/crates/zed/src/languages/c/config.toml index 5b48415610..1a5fe9339a 100644 --- a/crates/zed/src/languages/c/config.toml +++ b/crates/zed/src/languages/c/config.toml @@ -7,5 +7,20 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/c/overrides.scm b/crates/zed/src/languages/c/overrides.scm new file mode 100644 index 0000000000..0d14e6c205 --- /dev/null +++ b/crates/zed/src/languages/c/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string_literal) @override.string diff --git a/crates/zed/src/languages/cpp/config.toml b/crates/zed/src/languages/cpp/config.toml index e9a793ec3c..c83adfb067 100644 --- a/crates/zed/src/languages/cpp/config.toml +++ b/crates/zed/src/languages/cpp/config.toml @@ -7,5 +7,20 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/cpp/overrides.scm b/crates/zed/src/languages/cpp/overrides.scm new file mode 100644 index 0000000000..0d14e6c205 --- /dev/null +++ b/crates/zed/src/languages/cpp/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string_literal) @override.string diff --git a/crates/zed/src/languages/css/config.toml b/crates/zed/src/languages/css/config.toml index 28def3abd5..cf8d4f1e2f 100644 --- a/crates/zed/src/languages/css/config.toml +++ b/crates/zed/src/languages/css/config.toml @@ -5,5 +5,20 @@ brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, - { start = "\"", end = "\"", close = true, newline = false } + { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, ] diff --git a/crates/zed/src/languages/css/overrides.scm b/crates/zed/src/languages/css/overrides.scm new file mode 100644 index 0000000000..9617249d3c --- /dev/null +++ b/crates/zed/src/languages/css/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string_value) @override.string diff --git a/crates/zed/src/languages/elixir/config.toml b/crates/zed/src/languages/elixir/config.toml index 4e1af93943..0185d8eec3 100644 --- a/crates/zed/src/languages/elixir/config.toml +++ b/crates/zed/src/languages/elixir/config.toml @@ -6,5 +6,20 @@ brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, - { start = "\"", end = "\"", close = true, newline = false } + { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, ] diff --git a/crates/zed/src/languages/elixir/overrides.scm b/crates/zed/src/languages/elixir/overrides.scm new file mode 100644 index 0000000000..ed1b20d44b --- /dev/null +++ b/crates/zed/src/languages/elixir/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +[(string) (charlist)] @override.string diff --git a/crates/zed/src/languages/erb/config.toml b/crates/zed/src/languages/erb/config.toml index 280219a119..9cfcef0c8b 100644 --- a/crates/zed/src/languages/erb/config.toml +++ b/crates/zed/src/languages/erb/config.toml @@ -4,5 +4,4 @@ autoclose_before = ">})" brackets = [ { start = "<", end = ">", close = true, newline = true }, ] - -block_comment = ["<%#", "%>"] \ No newline at end of file +block_comment = ["<%#", "%>"] diff --git a/crates/zed/src/languages/go/config.toml b/crates/zed/src/languages/go/config.toml index fc6167e311..3f9464594c 100644 --- a/crates/zed/src/languages/go/config.toml +++ b/crates/zed/src/languages/go/config.toml @@ -7,5 +7,20 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/go/overrides.scm b/crates/zed/src/languages/go/overrides.scm new file mode 100644 index 0000000000..fc32c47f68 --- /dev/null +++ b/crates/zed/src/languages/go/overrides.scm @@ -0,0 +1,6 @@ +(comment) @override.comment +[ + (interpreted_string_literal) + (raw_string_literal) + (rune_literal) +] @override.string diff --git a/crates/zed/src/languages/html/config.toml b/crates/zed/src/languages/html/config.toml index 3e618da25e..f4f0ba26ea 100644 --- a/crates/zed/src/languages/html/config.toml +++ b/crates/zed/src/languages/html/config.toml @@ -9,4 +9,18 @@ brackets = [ { start = "!--", end = " --", close = true, newline = false }, ] -block_comment = [""] \ No newline at end of file +block_comment = [""] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/html/overrides.scm b/crates/zed/src/languages/html/overrides.scm new file mode 100644 index 0000000000..8f6a1f9eea --- /dev/null +++ b/crates/zed/src/languages/html/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(quoted_attribute_value) @override.string \ No newline at end of file diff --git a/crates/zed/src/languages/javascript/config.toml b/crates/zed/src/languages/javascript/config.toml index d7612d13f0..a5a656393d 100644 --- a/crates/zed/src/languages/javascript/config.toml +++ b/crates/zed/src/languages/javascript/config.toml @@ -12,3 +12,21 @@ brackets = [ { start = "`", end = "`", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.element] +line_comment = { remove = true } +block_comment = ["{/* ", " */}"] diff --git a/crates/zed/src/languages/javascript/overrides.scm b/crates/zed/src/languages/javascript/overrides.scm new file mode 100644 index 0000000000..f19c1d9203 --- /dev/null +++ b/crates/zed/src/languages/javascript/overrides.scm @@ -0,0 +1,9 @@ +(comment) @override.comment +(string) @override.string + +[ + (jsx_element) + (jsx_fragment) + (jsx_self_closing_element) + (jsx_expression) +] @override.element diff --git a/crates/zed/src/languages/json/config.toml b/crates/zed/src/languages/json/config.toml index cb36279358..48a1fb0c99 100644 --- a/crates/zed/src/languages/json/config.toml +++ b/crates/zed/src/languages/json/config.toml @@ -7,3 +7,9 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, ] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/json/overrides.scm b/crates/zed/src/languages/json/overrides.scm new file mode 100644 index 0000000000..83911ae4a5 --- /dev/null +++ b/crates/zed/src/languages/json/overrides.scm @@ -0,0 +1,2 @@ +(comment) @comment +(string) @override.string \ No newline at end of file diff --git a/crates/zed/src/languages/python/config.toml b/crates/zed/src/languages/python/config.toml index c6b41ed700..a817de8e3b 100644 --- a/crates/zed/src/languages/python/config.toml +++ b/crates/zed/src/languages/python/config.toml @@ -12,4 +12,18 @@ brackets = [ auto_indent_using_last_non_empty_line = false increase_indent_pattern = ":$" -decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:" \ No newline at end of file +decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:" + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/python/overrides.scm b/crates/zed/src/languages/python/overrides.scm new file mode 100644 index 0000000000..3a7ad5f035 --- /dev/null +++ b/crates/zed/src/languages/python/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/ruby/config.toml b/crates/zed/src/languages/ruby/config.toml index 5600266de3..4fd21d8bd0 100644 --- a/crates/zed/src/languages/ruby/config.toml +++ b/crates/zed/src/languages/ruby/config.toml @@ -7,5 +7,19 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, - { start = "'", end = "'", close = false, newline = false }, -] \ No newline at end of file + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/ruby/overrides.scm b/crates/zed/src/languages/ruby/overrides.scm new file mode 100644 index 0000000000..3a7ad5f035 --- /dev/null +++ b/crates/zed/src/languages/ruby/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/rust/config.toml b/crates/zed/src/languages/rust/config.toml index 971ed27ebc..f9f6b59717 100644 --- a/crates/zed/src/languages/rust/config.toml +++ b/crates/zed/src/languages/rust/config.toml @@ -11,3 +11,19 @@ brackets = [ { start = "'", end = "'", close = false, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = false, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = false, newline = true }, +] diff --git a/crates/zed/src/languages/rust/overrides.scm b/crates/zed/src/languages/rust/overrides.scm new file mode 100644 index 0000000000..29128d98e7 --- /dev/null +++ b/crates/zed/src/languages/rust/overrides.scm @@ -0,0 +1,8 @@ +[ + (string_literal) + (raw_string_literal) +] @override.string +[ + (line_comment) + (block_comment) +] @override.comment diff --git a/crates/zed/src/languages/scheme/config.toml b/crates/zed/src/languages/scheme/config.toml index 7e63673834..46e11bdc11 100644 --- a/crates/zed/src/languages/scheme/config.toml +++ b/crates/zed/src/languages/scheme/config.toml @@ -7,3 +7,15 @@ brackets = [ { start = "(", end = ")", close = true, newline = false }, { start = "\"", end = "\"", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/scheme/overrides.scm b/crates/zed/src/languages/scheme/overrides.scm new file mode 100644 index 0000000000..28a8a569df --- /dev/null +++ b/crates/zed/src/languages/scheme/overrides.scm @@ -0,0 +1,6 @@ +[ + (comment) + (block_comment) + (directive) +] @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/toml/config.toml b/crates/zed/src/languages/toml/config.toml index cd624aecef..30797bf141 100644 --- a/crates/zed/src/languages/toml/config.toml +++ b/crates/zed/src/languages/toml/config.toml @@ -6,4 +6,17 @@ brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, ] diff --git a/crates/zed/src/languages/toml/overrides.scm b/crates/zed/src/languages/toml/overrides.scm new file mode 100644 index 0000000000..3a7ad5f035 --- /dev/null +++ b/crates/zed/src/languages/toml/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/typescript/config.toml b/crates/zed/src/languages/typescript/config.toml index 8e5886167c..3c2146e001 100644 --- a/crates/zed/src/languages/typescript/config.toml +++ b/crates/zed/src/languages/typescript/config.toml @@ -12,3 +12,17 @@ brackets = [ { start = "`", end = "`", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/typescript/overrides.scm b/crates/zed/src/languages/typescript/overrides.scm new file mode 100644 index 0000000000..3a7ad5f035 --- /dev/null +++ b/crates/zed/src/languages/typescript/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string From 8dcef46842ea329a1d47e5b6e2ba9be44fd2b1cf Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 10:44:33 -0800 Subject: [PATCH 75/83] Drop 'override.' prefix from capture names in override query Co-authored-by: Julia Risley --- crates/language/src/buffer_tests.rs | 4 ++-- crates/language/src/language.rs | 6 +++--- crates/zed/src/languages/c/overrides.scm | 4 ++-- crates/zed/src/languages/cpp/overrides.scm | 4 ++-- crates/zed/src/languages/css/overrides.scm | 4 ++-- crates/zed/src/languages/elixir/overrides.scm | 4 ++-- crates/zed/src/languages/go/overrides.scm | 4 ++-- crates/zed/src/languages/html/overrides.scm | 4 ++-- crates/zed/src/languages/javascript/overrides.scm | 6 +++--- crates/zed/src/languages/json/overrides.scm | 3 +-- crates/zed/src/languages/python/overrides.scm | 4 ++-- crates/zed/src/languages/ruby/overrides.scm | 4 ++-- crates/zed/src/languages/rust/overrides.scm | 4 ++-- crates/zed/src/languages/scheme/overrides.scm | 4 ++-- crates/zed/src/languages/toml/overrides.scm | 4 ++-- crates/zed/src/languages/tsx/overrides.scm | 4 ++-- crates/zed/src/languages/typescript/overrides.scm | 4 ++-- 17 files changed, 35 insertions(+), 36 deletions(-) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 07198e8090..382311e204 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1421,8 +1421,8 @@ fn test_language_config_at(cx: &mut MutableAppContext) { ) .with_override_query( r#" - (jsx_element) @override.element - (string) @override.string + (jsx_element) @element + (string) @string "#, ) .unwrap(); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 766e181671..e926a776bd 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -829,10 +829,10 @@ impl Language { let mut values = HashMap::default(); for (ix, name) in query.capture_names().iter().enumerate() { - if let Some(override_name) = name.strip_prefix("override.") { - let value = self.config.overrides.remove(override_name).ok_or_else(|| { + if !name.starts_with('_') { + let value = self.config.overrides.remove(name).ok_or_else(|| { anyhow!( - "language {:?} has override in query but not in config: {override_name:?}", + "language {:?} has override in query but not in config: {name:?}", self.config.name ) })?; diff --git a/crates/zed/src/languages/c/overrides.scm b/crates/zed/src/languages/c/overrides.scm index 0d14e6c205..178355c67c 100644 --- a/crates/zed/src/languages/c/overrides.scm +++ b/crates/zed/src/languages/c/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string_literal) @override.string +(comment) @comment +(string_literal) @string diff --git a/crates/zed/src/languages/cpp/overrides.scm b/crates/zed/src/languages/cpp/overrides.scm index 0d14e6c205..178355c67c 100644 --- a/crates/zed/src/languages/cpp/overrides.scm +++ b/crates/zed/src/languages/cpp/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string_literal) @override.string +(comment) @comment +(string_literal) @string diff --git a/crates/zed/src/languages/css/overrides.scm b/crates/zed/src/languages/css/overrides.scm index 9617249d3c..c0db9fe327 100644 --- a/crates/zed/src/languages/css/overrides.scm +++ b/crates/zed/src/languages/css/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string_value) @override.string +(comment) @comment +(string_value) @string diff --git a/crates/zed/src/languages/elixir/overrides.scm b/crates/zed/src/languages/elixir/overrides.scm index ed1b20d44b..1812540181 100644 --- a/crates/zed/src/languages/elixir/overrides.scm +++ b/crates/zed/src/languages/elixir/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -[(string) (charlist)] @override.string +(comment) @comment +[(string) (charlist)] @string diff --git a/crates/zed/src/languages/go/overrides.scm b/crates/zed/src/languages/go/overrides.scm index fc32c47f68..9eb287df3f 100644 --- a/crates/zed/src/languages/go/overrides.scm +++ b/crates/zed/src/languages/go/overrides.scm @@ -1,6 +1,6 @@ -(comment) @override.comment +(comment) @comment [ (interpreted_string_literal) (raw_string_literal) (rune_literal) -] @override.string +] @string diff --git a/crates/zed/src/languages/html/overrides.scm b/crates/zed/src/languages/html/overrides.scm index 8f6a1f9eea..97accffd67 100644 --- a/crates/zed/src/languages/html/overrides.scm +++ b/crates/zed/src/languages/html/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(quoted_attribute_value) @override.string \ No newline at end of file +(comment) @comment +(quoted_attribute_value) @string \ No newline at end of file diff --git a/crates/zed/src/languages/javascript/overrides.scm b/crates/zed/src/languages/javascript/overrides.scm index f19c1d9203..5e43c4a94a 100644 --- a/crates/zed/src/languages/javascript/overrides.scm +++ b/crates/zed/src/languages/javascript/overrides.scm @@ -1,9 +1,9 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string [ (jsx_element) (jsx_fragment) (jsx_self_closing_element) (jsx_expression) -] @override.element +] @element diff --git a/crates/zed/src/languages/json/overrides.scm b/crates/zed/src/languages/json/overrides.scm index 83911ae4a5..746dbc5cd9 100644 --- a/crates/zed/src/languages/json/overrides.scm +++ b/crates/zed/src/languages/json/overrides.scm @@ -1,2 +1 @@ -(comment) @comment -(string) @override.string \ No newline at end of file +(string) @string \ No newline at end of file diff --git a/crates/zed/src/languages/python/overrides.scm b/crates/zed/src/languages/python/overrides.scm index 3a7ad5f035..8a58e304e5 100644 --- a/crates/zed/src/languages/python/overrides.scm +++ b/crates/zed/src/languages/python/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string diff --git a/crates/zed/src/languages/ruby/overrides.scm b/crates/zed/src/languages/ruby/overrides.scm index 3a7ad5f035..8a58e304e5 100644 --- a/crates/zed/src/languages/ruby/overrides.scm +++ b/crates/zed/src/languages/ruby/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string diff --git a/crates/zed/src/languages/rust/overrides.scm b/crates/zed/src/languages/rust/overrides.scm index 29128d98e7..216a395147 100644 --- a/crates/zed/src/languages/rust/overrides.scm +++ b/crates/zed/src/languages/rust/overrides.scm @@ -1,8 +1,8 @@ [ (string_literal) (raw_string_literal) -] @override.string +] @string [ (line_comment) (block_comment) -] @override.comment +] @comment diff --git a/crates/zed/src/languages/scheme/overrides.scm b/crates/zed/src/languages/scheme/overrides.scm index 28a8a569df..8c0d41b046 100644 --- a/crates/zed/src/languages/scheme/overrides.scm +++ b/crates/zed/src/languages/scheme/overrides.scm @@ -2,5 +2,5 @@ (comment) (block_comment) (directive) -] @override.comment -(string) @override.string +] @comment +(string) @string diff --git a/crates/zed/src/languages/toml/overrides.scm b/crates/zed/src/languages/toml/overrides.scm index 3a7ad5f035..8a58e304e5 100644 --- a/crates/zed/src/languages/toml/overrides.scm +++ b/crates/zed/src/languages/toml/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string diff --git a/crates/zed/src/languages/tsx/overrides.scm b/crates/zed/src/languages/tsx/overrides.scm index 183f7b4f98..d86186a903 100644 --- a/crates/zed/src/languages/tsx/overrides.scm +++ b/crates/zed/src/languages/tsx/overrides.scm @@ -3,5 +3,5 @@ (jsx_fragment) (jsx_self_closing_element) (jsx_expression) -] @override.element -(string) @override.string +] @element +(string) @string diff --git a/crates/zed/src/languages/typescript/overrides.scm b/crates/zed/src/languages/typescript/overrides.scm index 3a7ad5f035..8a58e304e5 100644 --- a/crates/zed/src/languages/typescript/overrides.scm +++ b/crates/zed/src/languages/typescript/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string From 2f42af2ac3ef922cfa87a0fce930b6360e452edf Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 13:02:38 -0800 Subject: [PATCH 76/83] Add confirm_quit setting --- assets/settings/default.json | 2 ++ crates/collab/src/tests.rs | 5 +---- crates/gpui/src/app.rs | 2 +- crates/settings/src/settings.rs | 13 ++++++------- crates/zed/src/zed.rs | 22 +++++++++++++++++++++- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 3924e84d61..1ef2ac8a16 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -13,6 +13,8 @@ // Whether to show the informational hover box when moving the mouse // over symbols in the editor. "hover_popover_enabled": true, + // Whether to confirm before quitting Zed. + "confirm_quit": false, // Whether the cursor blinks in the editor. "cursor_blink": true, // Whether to pop the completions menu while typing in an editor without diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 8dc29f3d60..f257d95493 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -101,10 +101,7 @@ impl TestServer { async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient { cx.update(|cx| { cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf())); - - let mut settings = Settings::test(cx); - settings.projects_online_by_default = false; - cx.set_global(settings); + cx.set_global(Settings::test(cx)); }); let http = FakeHttpClient::with_404_response(); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 6a4086c5eb..ad1fad85b1 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -989,7 +989,7 @@ impl MutableAppContext { window.toggle_full_screen(); } - fn prompt( + pub fn prompt( &self, window_id: usize, level: PromptLevel, diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index e29a98e775..8b2c12a59b 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -28,12 +28,12 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; #[derive(Clone)] pub struct Settings { pub experiments: FeatureFlags, - pub projects_online_by_default: bool, pub buffer_font_family: FamilyId, pub default_buffer_font_size: f32, pub buffer_font_size: f32, pub active_pane_magnification: f32, pub cursor_blink: bool, + pub confirm_quit: bool, pub hover_popover_enabled: bool, pub show_completions_on_input: bool, pub vim_mode: bool, @@ -294,6 +294,8 @@ pub struct SettingsFileContent { #[serde(default)] pub cursor_blink: Option, #[serde(default)] + pub confirm_quit: Option, + #[serde(default)] pub hover_popover_enabled: Option, #[serde(default)] pub show_completions_on_input: Option, @@ -356,10 +358,10 @@ impl Settings { buffer_font_size: defaults.buffer_font_size.unwrap(), active_pane_magnification: defaults.active_pane_magnification.unwrap(), default_buffer_font_size: defaults.buffer_font_size.unwrap(), + confirm_quit: defaults.confirm_quit.unwrap(), cursor_blink: defaults.cursor_blink.unwrap(), hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), show_completions_on_input: defaults.show_completions_on_input.unwrap(), - projects_online_by_default: defaults.projects_online_by_default.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), default_dock_anchor: defaults.default_dock_anchor.unwrap(), @@ -407,10 +409,6 @@ impl Settings { } } - merge( - &mut self.projects_online_by_default, - data.projects_online_by_default, - ); merge(&mut self.buffer_font_size, data.buffer_font_size); merge( &mut self.active_pane_magnification, @@ -418,6 +416,7 @@ impl Settings { ); merge(&mut self.default_buffer_font_size, data.buffer_font_size); merge(&mut self.cursor_blink, data.cursor_blink); + merge(&mut self.confirm_quit, data.confirm_quit); merge(&mut self.hover_popover_enabled, data.hover_popover_enabled); merge( &mut self.show_completions_on_input, @@ -557,6 +556,7 @@ impl Settings { buffer_font_size: 14., active_pane_magnification: 1., default_buffer_font_size: 14., + confirm_quit: false, cursor_blink: true, hover_popover_enabled: true, show_completions_on_input: true, @@ -582,7 +582,6 @@ impl Settings { language_defaults: Default::default(), language_overrides: Default::default(), lsp: Default::default(), - projects_online_by_default: true, theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), telemetry_defaults: TelemetrySettings { diagnostics: Some(true), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c3af91306d..e765a9926b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -14,6 +14,7 @@ use collections::VecDeque; pub use editor; use editor::{Editor, MultiBuffer}; +use futures::StreamExt; use gpui::{ actions, geometry::{ @@ -22,7 +23,8 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, ClipboardItem, TitlebarOptions, ViewContext, WindowKind, + AssetSource, AsyncAppContext, ClipboardItem, PromptLevel, TitlebarOptions, ViewContext, + WindowKind, }; use language::Rope; use lazy_static::lazy_static; @@ -421,7 +423,25 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { // prompt in the active window before switching to a different window. workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id())); + let should_confirm = cx.global::().confirm_quit; cx.spawn(|mut cx| async move { + dbg!(should_confirm, workspaces.first()); + + if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { + let answer = cx + .prompt( + workspace.window_id(), + PromptLevel::Info, + "Are you sure you want to quit?", + &["Quit", "Cancel"], + ) + .next() + .await; + if answer != Some(0) { + return Ok(()); + } + } + // If the user cancels any save prompt, then keep the app open. for workspace in workspaces { if !workspace From 2e37c0ea4a8bcbd0629a42250a43d916e342b7e7 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 20 Jan 2023 13:28:13 -0800 Subject: [PATCH 77/83] Open multiple definitions in a multibuffer instead of opening the files directly --- crates/editor/src/editor.rs | 147 ++++++++++++++++++++++++------------ 1 file changed, 97 insertions(+), 50 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cef32ff98c..4fbaaa8058 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -62,7 +62,7 @@ pub use multi_buffer::{ }; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; -use project::{FormatTrigger, LocationLink, Project, ProjectPath, ProjectTransaction}; +use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -5012,13 +5012,15 @@ impl Editor { cx: &mut ViewContext, ) { let pane = workspace.active_pane().clone(); - for definition in definitions { + // If there is one definition, just open it directly + if let [definition] = definitions.as_slice() { let range = definition .target .range .to_offset(definition.target.buffer.read(cx)); - let target_editor_handle = workspace.open_project_item(definition.target.buffer, cx); + let target_editor_handle = + workspace.open_project_item(definition.target.buffer.clone(), cx); target_editor_handle.update(cx, |target_editor, cx| { // When selecting a definition in a different buffer, disable the nav history // to avoid creating a history entry at the previous cursor location. @@ -5031,6 +5033,28 @@ impl Editor { pane.update(cx, |pane, _| pane.enable_history()); }); + } else { + let replica_id = editor_handle.read(cx).replica_id(cx); + let title = definitions + .iter() + .find(|definition| definition.origin.is_some()) + .and_then(|definition| { + definition.origin.as_ref().map(|origin| { + let buffer = origin.buffer.read(cx); + format!( + "Definitions for {}", + buffer + .text_for_range(origin.range.clone()) + .collect::() + ) + }) + }) + .unwrap_or("Definitions".to_owned()); + let locations = definitions + .into_iter() + .map(|definition| definition.target) + .collect(); + Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx) } } @@ -5051,64 +5075,87 @@ impl Editor { let project = workspace.project().clone(); let references = project.update(cx, |project, cx| project.references(&buffer, head, cx)); Some(cx.spawn(|workspace, mut cx| async move { - let mut locations = references.await?; + let locations = references.await?; if locations.is_empty() { return Ok(()); } - locations.sort_by_key(|location| location.buffer.id()); - let mut locations = locations.into_iter().peekable(); - let mut ranges_to_highlight = Vec::new(); - - let excerpt_buffer = cx.add_model(|cx| { - let mut symbol_name = None; - let mut multibuffer = MultiBuffer::new(replica_id); - while let Some(location) = locations.next() { - let buffer = location.buffer.read(cx); - let mut ranges_for_buffer = Vec::new(); - let range = location.range.to_offset(buffer); - ranges_for_buffer.push(range.clone()); - if symbol_name.is_none() { - symbol_name = Some(buffer.text_for_range(range).collect::()); - } - - while let Some(next_location) = locations.peek() { - if next_location.buffer == location.buffer { - ranges_for_buffer.push(next_location.range.to_offset(buffer)); - locations.next(); - } else { - break; - } - } - - ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end))); - ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines( - location.buffer.clone(), - ranges_for_buffer, - 1, - cx, - )); - } - multibuffer.with_title(format!("References to `{}`", symbol_name.unwrap())) - }); - workspace.update(&mut cx, |workspace, cx| { - let editor = - cx.add_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx)); - editor.update(cx, |editor, cx| { - editor.highlight_background::( - ranges_to_highlight, - |theme| theme.editor.highlighted_line_background, - cx, - ); - }); - workspace.add_item(Box::new(editor), cx); + let title = locations + .first() + .as_ref() + .map(|location| { + let buffer = location.buffer.read(cx); + format!( + "References to `{}`", + buffer + .text_for_range(location.range.clone()) + .collect::() + ) + }) + .unwrap(); + Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx); }); Ok(()) })) } + /// Opens a multibuffer with the given project locations in it + pub fn open_locations_in_multibuffer( + workspace: &mut Workspace, + mut locations: Vec, + replica_id: ReplicaId, + title: String, + cx: &mut ViewContext, + ) { + // If there are multiple definitions, open them in a multibuffer + locations.sort_by_key(|location| location.buffer.id()); + let mut locations = locations.into_iter().peekable(); + let mut ranges_to_highlight = Vec::new(); + + let excerpt_buffer = cx.add_model(|cx| { + let mut multibuffer = MultiBuffer::new(replica_id); + while let Some(location) = locations.next() { + let buffer = location.buffer.read(cx); + let mut ranges_for_buffer = Vec::new(); + let range = location.range.to_offset(buffer); + ranges_for_buffer.push(range.clone()); + + while let Some(next_location) = locations.peek() { + if next_location.buffer == location.buffer { + ranges_for_buffer.push(next_location.range.to_offset(buffer)); + locations.next(); + } else { + break; + } + } + + ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end))); + ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines( + location.buffer.clone(), + ranges_for_buffer, + 1, + cx, + )) + } + + multibuffer.with_title(title) + }); + + let editor = cx.add_view(|cx| { + Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), cx) + }); + editor.update(cx, |editor, cx| { + editor.highlight_background::( + ranges_to_highlight, + |theme| theme.editor.highlighted_line_background, + cx, + ); + }); + workspace.add_item(Box::new(editor), cx); + } + pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext) -> Option>> { use language::ToOffset as _; From f7ceebfce30b242017705080bb94b61b70f3f341 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 15:56:45 -0800 Subject: [PATCH 78/83] Avoid adjusting indentation of lines inside newly-created errors --- crates/language/src/buffer.rs | 56 +++++++++++++++++++++++++++------ crates/language/src/language.rs | 2 ++ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 7ef7845952..5c966bbc72 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -282,6 +282,7 @@ struct AutoindentRequestEntry { struct IndentSuggestion { basis_row: u32, delta: Ordering, + within_error: bool, } struct BufferChunkHighlights<'a> { @@ -937,7 +938,7 @@ impl Buffer { // Build a map containing the suggested indentation for each of the edited lines // with respect to the state of the buffer before these edits. This map is keyed // by the rows for these lines in the current state of the buffer. - let mut old_suggestions = BTreeMap::::default(); + let mut old_suggestions = BTreeMap::::default(); let old_edited_ranges = contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields); let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable(); @@ -963,14 +964,17 @@ impl Buffer { let suggested_indent = old_to_new_rows .get(&suggestion.basis_row) - .and_then(|from_row| old_suggestions.get(from_row).copied()) + .and_then(|from_row| { + Some(old_suggestions.get(from_row).copied()?.0) + }) .unwrap_or_else(|| { request .before_edit .indent_size_for_line(suggestion.basis_row) }) .with_delta(suggestion.delta, language_indent_size); - old_suggestions.insert(new_row, suggested_indent); + old_suggestions + .insert(new_row, (suggested_indent, suggestion.within_error)); } } yield_now().await; @@ -1016,12 +1020,13 @@ impl Buffer { snapshot.indent_size_for_line(suggestion.basis_row) }) .with_delta(suggestion.delta, language_indent_size); - if old_suggestions - .get(&new_row) - .map_or(true, |old_indentation| { + if old_suggestions.get(&new_row).map_or( + true, + |(old_indentation, was_within_error)| { suggested_indent != *old_indentation - }) - { + && (!suggestion.within_error || *was_within_error) + }, + ) { indent_sizes.insert(new_row, suggested_indent); } } @@ -1779,7 +1784,7 @@ impl BufferSnapshot { let start = Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0); let end = Point::new(row_range.end, 0); let range = (start..end).to_offset(&self.text); - let mut matches = self.syntax.matches(range, &self.text, |grammar| { + let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| { Some(&grammar.indents_config.as_ref()?.query) }); let indent_configs = matches @@ -1825,6 +1830,30 @@ impl BufferSnapshot { } } + let mut error_ranges = Vec::>::new(); + let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| { + Some(&grammar.error_query) + }); + while let Some(mat) = matches.peek() { + let node = mat.captures[0].node; + let start = Point::from_ts_point(node.start_position()); + let end = Point::from_ts_point(node.end_position()); + let range = start..end; + let ix = match error_ranges.binary_search_by_key(&range.start, |r| r.start) { + Ok(ix) | Err(ix) => ix, + }; + let mut end_ix = ix; + while let Some(existing_range) = error_ranges.get(end_ix) { + if existing_range.end < end { + end_ix += 1; + } else { + break; + } + } + error_ranges.splice(ix..end_ix, [range]); + matches.advance(); + } + outdent_positions.sort(); for outdent_position in outdent_positions { // find the innermost indent range containing this outdent_position @@ -1902,33 +1931,42 @@ impl BufferSnapshot { } } + let within_error = error_ranges + .iter() + .any(|e| e.start.row < row && e.end > row_start); + let suggestion = if outdent_to_row == prev_row || (outdent_from_prev_row && indent_from_prev_row) { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Equal, + within_error, }) } else if indent_from_prev_row { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Greater, + within_error, }) } else if outdent_to_row < prev_row { Some(IndentSuggestion { basis_row: outdent_to_row, delta: Ordering::Equal, + within_error, }) } else if outdent_from_prev_row { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Less, + within_error, }) } else if config.auto_indent_using_last_non_empty_line || !self.is_line_blank(prev_row) { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Equal, + within_error, }) } else { None diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e926a776bd..045e8dcd6f 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -348,6 +348,7 @@ pub struct Language { pub struct Grammar { id: usize, pub(crate) ts_language: tree_sitter::Language, + pub(crate) error_query: Query, pub(crate) highlights_query: Option, pub(crate) brackets_config: Option, pub(crate) indents_config: Option, @@ -684,6 +685,7 @@ impl Language { indents_config: None, injection_config: None, override_config: None, + error_query: Query::new(ts_language, "(ERROR) @error").unwrap(), ts_language, highlight_map: Default::default(), }) From 9f74d6e4ac9f75ecf2c13ec3c3ac4897ae938f72 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 15:56:56 -0800 Subject: [PATCH 79/83] Highlight and auto-indent await expressions in rust --- crates/zed/src/languages/rust/highlights.scm | 1 + crates/zed/src/languages/rust/indents.scm | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/zed/src/languages/rust/highlights.scm b/crates/zed/src/languages/rust/highlights.scm index 98ea1ee40e..b52a7a8aff 100644 --- a/crates/zed/src/languages/rust/highlights.scm +++ b/crates/zed/src/languages/rust/highlights.scm @@ -52,6 +52,7 @@ [ "as" "async" + "await" "break" "const" "continue" diff --git a/crates/zed/src/languages/rust/indents.scm b/crates/zed/src/languages/rust/indents.scm index 504c235d10..9ab6b02908 100644 --- a/crates/zed/src/languages/rust/indents.scm +++ b/crates/zed/src/languages/rust/indents.scm @@ -5,6 +5,7 @@ (assignment_expression) (let_declaration) (let_chain) + (await_expression) ] @indent (_ "[" "]" @end) @indent From 310d867aab01e6b0cc8b7f0954d13c68d88123c4 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 10:32:44 -0800 Subject: [PATCH 80/83] Switch PopUp windows to use the NSTrackingArea API and add support for the mouseExited event Co-authored-by: Antonio --- crates/gpui/src/platform/event.rs | 17 +++++++++++++ crates/gpui/src/platform/mac/event.rs | 12 ++++++++- crates/gpui/src/platform/mac/window.rs | 34 +++++++++++++++++++++++--- crates/gpui/src/presenter.rs | 15 ++++++++++++ crates/workspace/src/workspace.rs | 8 ++---- 5 files changed, 75 insertions(+), 11 deletions(-) diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 0c08af4497..c39c76dc34 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -178,6 +178,21 @@ impl MouseMovedEvent { } } +#[derive(Clone, Copy, Debug, Default)] +pub struct MouseExitedEvent { + pub position: Vector2F, + pub pressed_button: Option, + pub modifiers: Modifiers, +} + +impl Deref for MouseExitedEvent { + type Target = Modifiers; + + fn deref(&self) -> &Self::Target { + &self.modifiers + } +} + #[derive(Clone, Debug)] pub enum Event { KeyDown(KeyDownEvent), @@ -186,6 +201,7 @@ pub enum Event { MouseDown(MouseButtonEvent), MouseUp(MouseButtonEvent), MouseMoved(MouseMovedEvent), + MouseExited(MouseExitedEvent), ScrollWheel(ScrollWheelEvent), } @@ -197,6 +213,7 @@ impl Event { Event::ModifiersChanged { .. } => None, Event::MouseDown(event) | Event::MouseUp(event) => Some(event.position), Event::MouseMoved(event) => Some(event.position), + Event::MouseExited(event) => Some(event.position), Event::ScrollWheel(event) => Some(event.position), } } diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index c527fe8d25..2f29898c26 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -3,7 +3,7 @@ use crate::{ keymap_matcher::Keystroke, platform::{Event, NavigationDirection}, KeyDownEvent, KeyUpEvent, Modifiers, ModifiersChangedEvent, MouseButton, MouseButtonEvent, - MouseMovedEvent, ScrollDelta, ScrollWheelEvent, TouchPhase, + MouseExitedEvent, MouseMovedEvent, ScrollDelta, ScrollWheelEvent, TouchPhase, }; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType}, @@ -221,6 +221,16 @@ impl Event { modifiers: read_modifiers(native_event), }) }), + NSEventType::NSMouseExited => window_height.map(|window_height| { + Self::MouseExited(MouseExitedEvent { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + pressed_button: None, + modifiers: read_modifiers(native_event), + }) + }), _ => None, } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 0da2e4351e..e93e63be92 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -66,6 +66,14 @@ const NSNormalWindowLevel: NSInteger = 0; #[allow(non_upper_case_globals)] const NSPopUpWindowLevel: NSInteger = 101; #[allow(non_upper_case_globals)] +const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01; +#[allow(non_upper_case_globals)] +const NSTrackingMouseMoved: NSUInteger = 0x02; +#[allow(non_upper_case_globals)] +const NSTrackingActiveAlways: NSUInteger = 0x80; +#[allow(non_upper_case_globals)] +const NSTrackingInVisibleRect: NSUInteger = 0x200; +#[allow(non_upper_case_globals)] const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4; #[repr(C)] @@ -164,6 +172,10 @@ unsafe fn build_classes() { sel!(mouseMoved:), handle_view_event as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(mouseExited:), + handle_view_event as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(mouseDragged:), handle_view_event as extern "C" fn(&Object, Sel, id), @@ -470,8 +482,6 @@ impl Window { native_window.setTitlebarAppearsTransparent_(YES); } - native_window.setAcceptsMouseMovedEvents_(YES); - native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable); native_view.setWantsBestResolutionOpenGLSurface_(YES); @@ -494,8 +504,25 @@ impl Window { } match options.kind { - WindowKind::Normal => native_window.setLevel_(NSNormalWindowLevel), + WindowKind::Normal => { + native_window.setLevel_(NSNormalWindowLevel); + native_window.setAcceptsMouseMovedEvents_(YES); + } WindowKind::PopUp => { + // Use a tracking area to allow receiving MouseMoved events even when + // the window or application aren't active, which is often the case + // e.g. for notification windows. + let tracking_area: id = msg_send![class!(NSTrackingArea), alloc]; + let _: () = msg_send![ + tracking_area, + initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)) + options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect + owner: native_view + userInfo: nil + ]; + let _: () = + msg_send![native_view, addTrackingArea: tracking_area.autorelease()]; + native_window.setLevel_(NSPopUpWindowLevel); let _: () = msg_send![ native_window, @@ -965,7 +992,6 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let window_height = window_state_borrow.content_size().y(); let event = unsafe { Event::from_native(native_event, Some(window_height)) }; - if let Some(event) = event { match &event { Event::MouseMoved( diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 73e371c50b..0909d95fd0 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -360,6 +360,21 @@ impl Presenter { self.last_mouse_moved_event = Some(event.clone()); } + Event::MouseExited(event) => { + // When the platform sends a MouseExited event, synthesize + // a MouseMoved event whose position is outside the window's + // bounds so that hover and cursor state can be updated. + return self.dispatch_event( + Event::MouseMoved(MouseMovedEvent { + position: event.position, + pressed_button: event.pressed_button, + modifiers: event.modifiers, + }), + event_reused, + cx, + ); + } + Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel { region: Default::default(), platform_event: e.clone(), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9665db6be6..8735f33fbe 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,18 +31,14 @@ use futures::{ }; use gpui::{ actions, - color::Color, elements::*, - geometry::{ - rect::RectF, - vector::{vec2f, Vector2F}, - }, + geometry::vector::Vector2F, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; From bb24f1142fd11754eeba40c800f2c9b280c23232 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 16:47:23 -0800 Subject: [PATCH 81/83] Removed dbg --- crates/zed/src/zed.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e765a9926b..b6b00e7869 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -425,8 +425,6 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { let should_confirm = cx.global::().confirm_quit; cx.spawn(|mut cx| async move { - dbg!(should_confirm, workspaces.first()); - if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { let answer = cx .prompt( From 56080771e61f95b387b5f737bab158ca28fa05f8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 17:02:38 -0800 Subject: [PATCH 82/83] Add test for avoiding indent adjustment inside newly-created errors --- crates/language/src/buffer.rs | 10 ++ crates/language/src/buffer_tests.rs | 207 ++++++++++++++++++++-------- crates/language/src/syntax_map.rs | 48 +------ crates/text/src/text.rs | 51 +++++++ 4 files changed, 212 insertions(+), 104 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5c966bbc72..110e10564c 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1666,6 +1666,16 @@ impl Buffer { #[cfg(any(test, feature = "test-support"))] impl Buffer { + pub fn edit_via_marked_text( + &mut self, + marked_string: &str, + autoindent_mode: Option, + cx: &mut ModelContext, + ) { + let edits = self.edits_for_marked_text(marked_string); + self.edit(edits, autoindent_mode, cx); + } + pub fn set_group_interval(&mut self, group_interval: Duration) { self.text.set_group_interval(group_interval); } diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 382311e204..0b2ef1d7a7 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -800,23 +800,29 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta cx.set_global(settings); cx.add_model(|cx| { - let text = " + let mut buffer = Buffer::new( + 0, + " fn a() { c; d; } - " - .unindent(); - - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); // Lines 2 and 3 don't match the indentation suggestion. When editing these lines, // their indentation is not adjusted. - buffer.edit( - [ - (empty(Point::new(1, 1)), "()"), - (empty(Point::new(2, 1)), "()"), - ], + buffer.edit_via_marked_text( + &" + fn a() { + c«()»; + d«()»; + } + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -833,14 +839,22 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta // When appending new content after these lines, the indentation is based on the // preceding lines' actual indentation. - buffer.edit( - [ - (empty(Point::new(1, 1)), "\n.f\n.g"), - (empty(Point::new(2, 1)), "\n.f\n.g"), - ], + buffer.edit_via_marked_text( + &" + fn a() { + c« + .f + .g()»; + d« + .f + .g()»; + } + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); + assert_eq!( buffer.text(), " @@ -859,20 +873,27 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta }); cx.add_model(|cx| { - let text = " + let mut buffer = Buffer::new( + 0, + " fn a() { - { - b()? - } - Ok(()) - } - " - .unindent(); - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + b(); + | + " + .replace("|", "") // marker to preserve trailing whitespace + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); - // Delete a closing curly brace changes the suggested indent for the line. - buffer.edit( - [(Point::new(3, 4)..Point::new(3, 5), "")], + // Insert a closing brace. It is outdented. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + «}» + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -880,19 +901,20 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta buffer.text(), " fn a() { - { - b()? - | - Ok(()) + b(); } " - .replace('|', "") // included in the string to preserve trailing whites .unindent() ); - // Manually editing the leading whitespace - buffer.edit( - [(Point::new(3, 0)..Point::new(3, 12), "")], + // Manually edit the leading whitespace. The edit is preserved. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + « »} + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -900,11 +922,8 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta buffer.text(), " fn a() { - { - b()? - - Ok(()) - } + b(); + } " .unindent() ); @@ -913,30 +932,108 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta } #[gpui::test] -fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) { - cx.set_global(Settings::test(cx)); +fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut MutableAppContext) { + let settings = Settings::test(cx); + cx.set_global(settings); + cx.add_model(|cx| { - let text = " - fn a() {} - " - .unindent(); + let mut buffer = Buffer::new( + 0, + " + fn a() { + i + } + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); - - buffer.edit([(5..5, "\nb")], Some(AutoindentMode::EachLine), cx); + // Regression test: line does not get outdented due to syntax error + buffer.edit_via_marked_text( + &" + fn a() { + i«f let Some(x) = y» + } + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); assert_eq!( buffer.text(), " - fn a( - b) {} + fn a() { + if let Some(x) = y + } + " + .unindent() + ); + + buffer.edit_via_marked_text( + &" + fn a() { + if let Some(x) = y« {» + } + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a() { + if let Some(x) = y { + } + " + .unindent() + ); + + buffer + }); +} + +#[gpui::test] +fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) { + cx.set_global(Settings::test(cx)); + cx.add_model(|cx| { + let mut buffer = Buffer::new( + 0, + " + fn a() {} + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); + + buffer.edit_via_marked_text( + &" + fn a(« + b») {} + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a( + b) {} " .unindent() ); // The indentation suggestion changed because `@end` node (a close paren) // is now at the beginning of the line. - buffer.edit( - [(Point::new(1, 4)..Point::new(1, 5), "")], + buffer.edit_via_marked_text( + &" + fn a( + ˇ) {} + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -1894,7 +1991,3 @@ fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> Str layers[0].node.to_sexp() }) } - -fn empty(point: Point) -> Range { - point..point -} diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 458ffd8bc2..8d66730854 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -2262,7 +2262,7 @@ mod tests { mutated_syntax_map.reparse(language.clone(), &buffer); for (i, marked_string) in steps.into_iter().enumerate() { - edit_buffer(&mut buffer, &marked_string.unindent()); + buffer.edit_via_marked_text(&marked_string.unindent()); // Reparse the syntax map mutated_syntax_map.interpolate(&buffer); @@ -2452,52 +2452,6 @@ mod tests { assert_eq!(actual_ranges, expected_ranges); } - fn edit_buffer(buffer: &mut Buffer, marked_string: &str) { - let old_text = buffer.text(); - let (new_text, mut ranges) = marked_text_ranges(marked_string, false); - if ranges.is_empty() { - ranges.push(0..new_text.len()); - } - - assert_eq!( - old_text[..ranges[0].start], - new_text[..ranges[0].start], - "invalid edit" - ); - - let mut delta = 0; - let mut edits = Vec::new(); - let mut ranges = ranges.into_iter().peekable(); - - while let Some(inserted_range) = ranges.next() { - let new_start = inserted_range.start; - let old_start = (new_start as isize - delta) as usize; - - let following_text = if let Some(next_range) = ranges.peek() { - &new_text[inserted_range.end..next_range.start] - } else { - &new_text[inserted_range.end..] - }; - - let inserted_len = inserted_range.len(); - let deleted_len = old_text[old_start..] - .find(following_text) - .expect("invalid edit"); - - let old_range = old_start..old_start + deleted_len; - edits.push((old_range, new_text[inserted_range].to_string())); - delta += inserted_len as isize - deleted_len as isize; - } - - assert_eq!( - old_text.len() as isize + delta, - new_text.len() as isize, - "invalid edit" - ); - - buffer.edit(edits); - } - pub fn string_contains_sequence(text: &str, parts: &[&str]) -> bool { let mut last_part_end = 0; for part in parts { diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 914023f305..c7d36e29de 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1372,6 +1372,57 @@ impl Buffer { #[cfg(any(test, feature = "test-support"))] impl Buffer { + pub fn edit_via_marked_text(&mut self, marked_string: &str) { + let edits = self.edits_for_marked_text(marked_string); + self.edit(edits); + } + + pub fn edits_for_marked_text(&self, marked_string: &str) -> Vec<(Range, String)> { + let old_text = self.text(); + let (new_text, mut ranges) = util::test::marked_text_ranges(marked_string, false); + if ranges.is_empty() { + ranges.push(0..new_text.len()); + } + + assert_eq!( + old_text[..ranges[0].start], + new_text[..ranges[0].start], + "invalid edit" + ); + + let mut delta = 0; + let mut edits = Vec::new(); + let mut ranges = ranges.into_iter().peekable(); + + while let Some(inserted_range) = ranges.next() { + let new_start = inserted_range.start; + let old_start = (new_start as isize - delta) as usize; + + let following_text = if let Some(next_range) = ranges.peek() { + &new_text[inserted_range.end..next_range.start] + } else { + &new_text[inserted_range.end..] + }; + + let inserted_len = inserted_range.len(); + let deleted_len = old_text[old_start..] + .find(following_text) + .expect("invalid edit"); + + let old_range = old_start..old_start + deleted_len; + edits.push((old_range, new_text[inserted_range].to_string())); + delta += inserted_len as isize - deleted_len as isize; + } + + assert_eq!( + old_text.len() as isize + delta, + new_text.len() as isize, + "invalid edit" + ); + + edits + } + pub fn check_invariants(&self) { // Ensure every fragment is ordered by locator in the fragment tree and corresponds // to an insertion fragment in the insertions tree. From 8dcaa81aadb31038ed5771ec3d5c8a43ca7949d9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 18:19:24 -0800 Subject: [PATCH 83/83] switch return type of accepts_first_mouse --- crates/gpui/src/platform/mac/window.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index e93e63be92..6126533644 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1438,7 +1438,11 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL { unsafe { let state = get_window_state(this); let state_borrow = state.as_ref().borrow(); - return state_borrow.kind == WindowKind::PopUp; + return if state_borrow.kind == WindowKind::PopUp { + YES + } else { + NO + }; } }