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 f090b384a44ae1c0a0e5743ed9060e14ccf6d052..26bd0931fe2d1d738406cf79406b8afea11df0c1 100644 --- a/crates/terminal_view/src/persistence.rs +++ b/crates/terminal_view/src/persistence.rs @@ -42,10 +42,10 @@ impl TerminalDb { } query! { - pub async fn take_working_directory(item_id: ItemId, workspace_id: WorkspaceId) -> Result> { - DELETE FROM terminals + pub fn get_working_directory(item_id: ItemId, workspace_id: WorkspaceId) -> Result> { + SELECT working_directory + FROM terminals WHERE item_id = ? AND workspace_id = ? - RETURNING working_directory } } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index a4f90a8d72c095146b9a28527dd92f6f4069bae1..e02a9758dd24f4568cce6b33da0099f53c259fd0 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -706,8 +706,7 @@ impl Item for TerminalView { let window_id = cx.window_id(); cx.spawn(|pane, mut cx| async move { let cwd = TERMINAL_DB - .take_working_directory(item_id, workspace_id) - .await + .get_working_directory(item_id, workspace_id) .log_err() .flatten(); From 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 | 101 ++++++++++++++++++++++++++----- 2 files changed, 94 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index 31ada105af9e8220f80e03433152959ed688f4df..375bd77ff0a832eb597738493fe5acc9590e46ab 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -15,8 +15,13 @@ define_connection!( FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE ON UPDATE CASCADE - ) STRICT; - )]; + ) STRICT; + ), + sql! ( + ALTER TABLE editors ADD COLUMN scroll_top_row INTEGER; + ALTER TABLE editors ADD COLUMN scroll_vertical_offset REAL; + ALTER TABLE editors ADD COLUMN scroll_horizontal_offset REAL; + )]; ); impl EditorDb { diff --git a/crates/sqlez/src/connection.rs b/crates/sqlez/src/connection.rs index 3c6cb31196b881054f0a503d41242039c51bd037..55ba5f8294ef3765eb04416bdebc5f9f210a6555 100644 --- a/crates/sqlez/src/connection.rs +++ b/crates/sqlez/src/connection.rs @@ -93,36 +93,77 @@ impl Connection { let sql_start = remaining_sql.as_ptr(); unsafe { + let mut alter_table = None; while { let remaining_sql_str = remaining_sql.to_str().unwrap().trim(); - remaining_sql_str != ";" && !remaining_sql_str.is_empty() + let any_remaining_sql = remaining_sql_str != ";" && !remaining_sql_str.is_empty(); + if any_remaining_sql { + alter_table = parse_alter_table(remaining_sql_str); + } + any_remaining_sql } { let mut raw_statement = ptr::null_mut::(); let mut remaining_sql_ptr = ptr::null(); - sqlite3_prepare_v2( - self.sqlite3, - remaining_sql.as_ptr(), - -1, - &mut raw_statement, - &mut remaining_sql_ptr, - ); - - let res = sqlite3_errcode(self.sqlite3); - let offset = sqlite3_error_offset(self.sqlite3); - let message = sqlite3_errmsg(self.sqlite3); + + let (res, offset, message, _conn) = if let Some(table_to_alter) = alter_table { + // ALTER TABLE is a weird statement. When preparing the statement the table's + // existence is checked *before* syntax checking any other part of the statement. + // Therefore, we need to make sure that the table has been created before calling + // prepare. As we don't want to trash whatever database this is connected to, we + // create a new in-memory DB to test. + + let temp_connection = Connection::open_memory(None); + //This should always succeed, if it doesn't then you really should know about it + temp_connection + .exec(&format!( + "CREATE TABLE {table_to_alter}(__place_holder_column_for_syntax_checking)" + )) + .unwrap()() + .unwrap(); + + sqlite3_prepare_v2( + temp_connection.sqlite3, + remaining_sql.as_ptr(), + -1, + &mut raw_statement, + &mut remaining_sql_ptr, + ); + + ( + sqlite3_errcode(temp_connection.sqlite3), + sqlite3_error_offset(temp_connection.sqlite3), + sqlite3_errmsg(temp_connection.sqlite3), + Some(temp_connection), + ) + } else { + sqlite3_prepare_v2( + self.sqlite3, + remaining_sql.as_ptr(), + -1, + &mut raw_statement, + &mut remaining_sql_ptr, + ); + ( + sqlite3_errcode(self.sqlite3), + sqlite3_error_offset(self.sqlite3), + sqlite3_errmsg(self.sqlite3), + None, + ) + }; sqlite3_finalize(raw_statement); if res == 1 && offset >= 0 { + let sub_statement_correction = + remaining_sql.as_ptr() as usize - sql_start as usize; let err_msg = String::from_utf8_lossy(CStr::from_ptr(message as *const _).to_bytes()) .into_owned(); - let sub_statement_correction = - remaining_sql.as_ptr() as usize - sql_start as usize; return Some((err_msg, offset as usize + sub_statement_correction)); } remaining_sql = CStr::from_ptr(remaining_sql_ptr); + alter_table = None; } } None @@ -162,6 +203,25 @@ impl Connection { } } +fn parse_alter_table(remaining_sql_str: &str) -> Option { + let remaining_sql_str = remaining_sql_str.to_lowercase(); + if remaining_sql_str.starts_with("alter") { + if let Some(table_offset) = remaining_sql_str.find("table") { + let after_table_offset = table_offset + "table".len(); + let table_to_alter = remaining_sql_str + .chars() + .skip(after_table_offset) + .skip_while(|c| c.is_whitespace()) + .take_while(|c| !c.is_whitespace()) + .collect::(); + if !table_to_alter.is_empty() { + return Some(table_to_alter); + } + } + } + None +} + impl Drop for Connection { fn drop(&mut self) { unsafe { sqlite3_close(self.sqlite3) }; @@ -331,4 +391,17 @@ mod test { assert_eq!(res, Some(first_stmt.len() + second_offset + 1)); } + + #[test] + fn test_alter_table_syntax() { + let connection = Connection::open_memory(Some("test_alter_table_syntax")); + + assert!(connection + .sql_has_syntax_error("ALTER TABLE test ADD x TEXT") + .is_none()); + + assert!(connection + .sql_has_syntax_error("ALTER TABLE test AAD x TEXT") + .is_some()); + } } From ebbe6e7aa95da46f6a3db0fb48d9990e868ee35f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 9 Jan 2023 14:06:40 -0800 Subject: [PATCH 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 | 116 +++++++++++++++++++++++++------ crates/sqlez/src/bindable.rs | 35 ++++++++++ 5 files changed, 182 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9feb3f11d08663d41cbfc4ecb241fbbd310d4ae9..f11061cb84893a05fff4629ed3c0881eb354c56c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3629,9 +3629,7 @@ impl Editor { } pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext) { - dbg!("undo"); if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) { - dbg!(tx_id); if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() { self.change_selections(None, cx, |s| { s.select_anchors(selections.to_vec()); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index ec678c6180121bac4768821242a56b91d4d1494b..f8fa3c9df0e68b0c813616e6480a9a98382866c9 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -8,6 +8,7 @@ use anyhow::{anyhow, Context, Result}; use collections::HashSet; use futures::future::try_join_all; use futures::FutureExt; + use gpui::{ elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -765,6 +766,7 @@ impl Item for Editor { fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext) { let workspace_id = workspace.database_id(); let item_id = cx.view_id(); + self.workspace_id = Some(workspace_id); fn serialize( buffer: ModelHandle, @@ -836,7 +838,11 @@ impl Item for Editor { .context("Project item at stored path was not a buffer")?; Ok(cx.update(|cx| { - cx.add_view(pane, |cx| Editor::for_buffer(buffer, Some(project), cx)) + cx.add_view(pane, |cx| { + let mut editor = Editor::for_buffer(buffer, Some(project), cx); + editor.read_scroll_position_from_db(item_id, workspace_id, cx); + editor + }) })) }) }) diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index 375bd77ff0a832eb597738493fe5acc9590e46ab..2d8d1a74fd152b11092eaa74e017d10f1816bc59 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -2,9 +2,19 @@ use std::path::PathBuf; use db::sqlez_macros::sql; use db::{define_connection, query}; + use workspace::{ItemId, WorkspaceDb, WorkspaceId}; define_connection!( + // Current table shape using pseudo-rust syntax: + // editors( + // item_id: usize, + // workspace_id: usize, + // path: PathBuf, + // scroll_top_row: usize, + // scroll_vertical_offset: f32, + // scroll_horizontal_offset: f32, + // ) pub static ref DB: EditorDb = &[sql! ( CREATE TABLE editors( @@ -18,9 +28,9 @@ define_connection!( ) STRICT; ), sql! ( - ALTER TABLE editors ADD COLUMN scroll_top_row INTEGER; - ALTER TABLE editors ADD COLUMN scroll_vertical_offset REAL; - ALTER TABLE editors ADD COLUMN scroll_horizontal_offset REAL; + ALTER TABLE editors ADD COLUMN scroll_top_row INTEGER NOT NULL DEFAULT 0; + ALTER TABLE editors ADD COLUMN scroll_horizontal_offset REAL NOT NULL DEFAULT 0; + ALTER TABLE editors ADD COLUMN scroll_vertical_offset REAL NOT NULL DEFAULT 0; )]; ); @@ -34,8 +44,40 @@ impl EditorDb { query! { pub async fn save_path(item_id: ItemId, workspace_id: WorkspaceId, path: PathBuf) -> Result<()> { - INSERT OR REPLACE INTO editors(item_id, workspace_id, path) - VALUES (?, ?, ?) + INSERT INTO editors + (item_id, workspace_id, path) + VALUES + (?1, ?2, ?3) + ON CONFLICT DO UPDATE SET + item_id = ?1, + workspace_id = ?2, + path = ?3 + } + } + + // Returns the scroll top row, and offset + query! { + pub fn get_scroll_position(item_id: ItemId, workspace_id: WorkspaceId) -> Result> { + SELECT scroll_top_row, scroll_horizontal_offset, scroll_vertical_offset + FROM editors + WHERE item_id = ? AND workspace_id = ? + } + } + + query! { + pub async fn save_scroll_position( + item_id: ItemId, + workspace_id: WorkspaceId, + top_row: u32, + vertical_offset: f32, + horizontal_offset: f32 + ) -> Result<()> { + UPDATE OR IGNORE editors + SET + scroll_top_row = ?3, + scroll_horizontal_offset = ?4, + scroll_vertical_offset = ?5 + WHERE item_id = ?1 AND workspace_id = ?2 } } } diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index eb89bfeec206d44af2a60602919309c353f3c7e1..0ca837a843ab7c0c56610641095068cf4a7843f6 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -11,11 +11,14 @@ use gpui::{ geometry::vector::{vec2f, Vector2F}, Axis, MutableAppContext, Task, ViewContext, }; -use language::Bias; +use language::{Bias, Point}; +use util::ResultExt; +use workspace::WorkspaceId; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, + persistence::DB, Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint, }; @@ -170,37 +173,68 @@ impl ScrollManager { scroll_position: Vector2F, map: &DisplaySnapshot, local: bool, + workspace_id: Option, cx: &mut ViewContext, ) { - let new_anchor = if scroll_position.y() <= 0. { - ScrollAnchor { - top_anchor: Anchor::min(), - offset: scroll_position.max(vec2f(0., 0.)), - } + let (new_anchor, top_row) = if scroll_position.y() <= 0. { + ( + ScrollAnchor { + top_anchor: Anchor::min(), + offset: scroll_position.max(vec2f(0., 0.)), + }, + 0, + ) } else { - let scroll_top_buffer_offset = - DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right); + let scroll_top_buffer_point = + DisplayPoint::new(scroll_position.y() as u32, 0).to_point(&map); let top_anchor = map .buffer_snapshot - .anchor_at(scroll_top_buffer_offset, Bias::Right); - - ScrollAnchor { - top_anchor, - offset: vec2f( - scroll_position.x(), - scroll_position.y() - top_anchor.to_display_point(&map).row() as f32, - ), - } + .anchor_at(scroll_top_buffer_point, Bias::Right); + + ( + ScrollAnchor { + top_anchor, + offset: vec2f( + scroll_position.x(), + scroll_position.y() - top_anchor.to_display_point(&map).row() as f32, + ), + }, + scroll_top_buffer_point.row, + ) }; - self.set_anchor(new_anchor, local, cx); + self.set_anchor(new_anchor, top_row, local, workspace_id, cx); } - fn set_anchor(&mut self, anchor: ScrollAnchor, local: bool, cx: &mut ViewContext) { + fn set_anchor( + &mut self, + anchor: ScrollAnchor, + top_row: u32, + local: bool, + workspace_id: Option, + cx: &mut ViewContext, + ) { self.anchor = anchor; cx.emit(Event::ScrollPositionChanged { local }); self.show_scrollbar(cx); self.autoscroll_request.take(); + if let Some(workspace_id) = workspace_id { + let item_id = cx.view_id(); + + cx.background() + .spawn(async move { + DB.save_scroll_position( + item_id, + workspace_id, + top_row, + anchor.offset.x(), + anchor.offset.y(), + ) + .await + .log_err() + }) + .detach() + } cx.notify(); } @@ -274,8 +308,13 @@ impl Editor { let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); hide_hover(self, cx); - self.scroll_manager - .set_scroll_position(scroll_position, &map, local, cx); + self.scroll_manager.set_scroll_position( + scroll_position, + &map, + local, + self.workspace_id, + cx, + ); } pub fn scroll_position(&self, cx: &mut ViewContext) -> Vector2F { @@ -285,7 +324,12 @@ impl Editor { pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext) { hide_hover(self, cx); - self.scroll_manager.set_anchor(scroll_anchor, true, cx); + let top_row = scroll_anchor + .top_anchor + .to_point(&self.buffer().read(cx).snapshot(cx)) + .row; + self.scroll_manager + .set_anchor(scroll_anchor, top_row, true, self.workspace_id, cx); } pub(crate) fn set_scroll_anchor_remote( @@ -294,7 +338,12 @@ impl Editor { cx: &mut ViewContext, ) { hide_hover(self, cx); - self.scroll_manager.set_anchor(scroll_anchor, false, cx); + let top_row = scroll_anchor + .top_anchor + .to_point(&self.buffer().read(cx).snapshot(cx)) + .row; + self.scroll_manager + .set_anchor(scroll_anchor, top_row, false, self.workspace_id, cx); } pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext) { @@ -345,4 +394,25 @@ impl Editor { Ordering::Greater } + + pub fn read_scroll_position_from_db( + &mut self, + item_id: usize, + workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) { + let scroll_position = DB.get_scroll_position(item_id, workspace_id); + if let Ok(Some((top_row, x, y))) = scroll_position { + let top_anchor = self + .buffer() + .read(cx) + .snapshot(cx) + .anchor_at(Point::new(top_row as u32, 0), Bias::Left); + let scroll_anchor = ScrollAnchor { + offset: Vector2F::new(x, y), + top_anchor, + }; + self.set_scroll_anchor(scroll_anchor, cx); + } + } } diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 3649037e502ca34b4a99b7a6f53de3ab4ba03ef3..62212d8f18c66a9c59a4941b0fbb9268a4e5dc90 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -89,6 +89,26 @@ impl Column for f64 { } } +impl Bind for f32 { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + statement + .bind_double(start_index, *self as f64) + .with_context(|| format!("Failed to bind f64 at index {start_index}"))?; + Ok(start_index + 1) + } +} + +impl Column for f32 { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let result = statement + .column_double(start_index) + .with_context(|| format!("Failed to parse f32 at index {start_index}"))? + as f32; + + Ok((result, start_index + 1)) + } +} + impl Bind for i32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -122,6 +142,21 @@ impl Column for i64 { } } +impl Bind for u32 { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + (*self as i64) + .bind(statement, start_index) + .with_context(|| format!("Failed to bind usize at index {start_index}")) + } +} + +impl Column for u32 { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let result = statement.column_int64(start_index)?; + Ok((result as u32, start_index + 1)) + } +} + impl Bind for usize { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) From 176738d674d177d44ff0d71dfaa4ae78e4217d89 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Mon, 9 Jan 2023 16:18:04 -0800 Subject: [PATCH 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 cd425c3a2861ecd025bbc4e3b9eab9cb9068159d..03a866f2f6cf362a2592e4bd8ced681aae5fd17c 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -216,7 +216,9 @@ impl WorkspaceDb { let mut result = Vec::new(); let mut delete_tasks = Vec::new(); for (id, location) in self.recent_workspaces()? { - if location.paths().iter().all(|path| path.exists()) { + if location.paths().iter().all(|path| path.exists()) + && location.paths().iter().any(|path| path.is_dir()) + { result.push((id, location)); } else { delete_tasks.push(self.delete_stale_workspace(id)); @@ -227,14 +229,13 @@ impl WorkspaceDb { Ok(result) } - query! { - pub fn last_workspace() -> Result> { - SELECT workspace_location - FROM workspaces - WHERE workspace_location IS NOT NULL - ORDER BY timestamp DESC - LIMIT 1 - } + pub async fn last_workspace(&self) -> Result> { + Ok(self + .recent_workspaces_on_disk() + .await? + .into_iter() + .next() + .map(|(_, location)| location)) } fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8bf4cbce328f7800c2d297241faf8a0696280dca..ac6a9f38edd0d695679a8f49c6299797b1baf5f0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2680,8 +2680,8 @@ pub fn activate_workspace_for_project( None } -pub fn last_opened_workspace_paths() -> Option { - DB.last_workspace().log_err().flatten() +pub async fn last_opened_workspace_paths() -> Option { + DB.last_workspace().await.log_err().flatten() } #[allow(clippy::type_complexity)] diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f4fa919c48e5b2e2640adcde991af6a1447904c7..3861c53e792eb0fd943ede744a69ed1ab392e471 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -170,7 +170,8 @@ fn main() { cx.platform().activate(true); let paths = collect_path_args(); if paths.is_empty() { - restore_or_create_workspace(cx); + cx.spawn(|cx| async move { restore_or_create_workspace(cx).await }) + .detach() } else { cx.dispatch_global_action(OpenPaths { paths }); } @@ -179,7 +180,8 @@ fn main() { cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) .detach(); } else { - restore_or_create_workspace(cx); + cx.spawn(|cx| async move { restore_or_create_workspace(cx).await }) + .detach() } cx.spawn(|cx| async move { while let Some(connection) = cli_connections_rx.next().await { @@ -203,13 +205,17 @@ fn main() { }); } -fn restore_or_create_workspace(cx: &mut gpui::MutableAppContext) { - if let Some(location) = workspace::last_opened_workspace_paths() { - cx.dispatch_global_action(OpenPaths { - paths: location.paths().as_ref().clone(), - }) +async fn restore_or_create_workspace(mut cx: AsyncAppContext) { + if let Some(location) = workspace::last_opened_workspace_paths().await { + cx.update(|cx| { + cx.dispatch_global_action(OpenPaths { + paths: location.paths().as_ref().clone(), + }) + }); } else { - cx.dispatch_global_action(NewFile); + cx.update(|cx| { + cx.dispatch_global_action(NewFile); + }); } } From 866f0e1344f1818aec4186a54b316b4bdfd1dc0a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 15:06:48 -0800 Subject: [PATCH 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 5b73d7643c84d344bd8f133f2b52427c0b952adf..3924e84d61a051c26b0b5e34719f71a53b92d631 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -79,6 +79,13 @@ "hard_tabs": false, // How many columns a tab should occupy. "tab_size": 4, + // Control what info Zed sends to our servers + "telemetry": { + // Send debug info like crash reports. + "diagnostics": true, + // Send anonymized usage data like what languages you're using Zed with. + "metrics": true + }, // Git gutter behavior configuration. "git": { // Control whether the git gutter is shown. May take 2 values: diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index f0c64a1bb995f3a710301de16fd8c00e02dc0088..dd2be6e3185a53e1d75adaec7ef3c9d97c054d07 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -51,9 +51,17 @@ pub struct Settings { pub language_overrides: HashMap, EditorSettings>, pub lsp: HashMap, LspSettings>, pub theme: Arc, + pub telemetry_defaults: TelemetrySettings, + pub telemetry_overrides: TelemetrySettings, pub staff_mode: bool, } +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct TelemetrySettings { + diagnostics: Option, + metrics: Option, +} + #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct FeatureFlags { pub experimental_themes: bool, @@ -302,6 +310,8 @@ pub struct SettingsFileContent { #[serde(default)] pub theme: Option, #[serde(default)] + pub telemetry: TelemetrySettings, + #[serde(default)] pub staff_mode: Option, } @@ -312,6 +322,7 @@ pub struct LspSettings { } impl Settings { + /// Fill out the settings corresponding to the default.json file, overrides will be set later pub fn defaults( assets: impl AssetSource, font_cache: &FontCache, @@ -363,11 +374,13 @@ impl Settings { language_overrides: Default::default(), lsp: defaults.lsp.clone(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), - + telemetry_defaults: defaults.telemetry, + telemetry_overrides: Default::default(), staff_mode: false, } } + // Fill out the overrride and etc. settings from the user's settings.json pub fn set_user_settings( &mut self, data: SettingsFileContent, @@ -419,6 +432,7 @@ impl Settings { self.terminal_overrides.copy_on_select = data.terminal.copy_on_select; self.terminal_overrides = data.terminal; self.language_overrides = data.languages; + self.telemetry_overrides = data.telemetry; self.lsp = data.lsp; } @@ -489,6 +503,20 @@ impl Settings { .unwrap_or_else(|| R::default()) } + pub fn telemetry_diagnostics(&self) -> bool { + self.telemetry_overrides + .diagnostics + .or(self.telemetry_defaults.diagnostics) + .expect("missing default") + } + + pub fn telemetry_metrics(&self) -> bool { + self.telemetry_overrides + .metrics + .or(self.telemetry_defaults.metrics) + .expect("missing default") + } + pub fn terminal_scroll(&self) -> AlternateScroll { self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.as_ref()) } @@ -540,6 +568,8 @@ impl Settings { lsp: Default::default(), projects_online_by_default: true, theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), + telemetry_defaults: Default::default(), + telemetry_overrides: Default::default(), staff_mode: false, } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 3861c53e792eb0fd943ede744a69ed1ab392e471..72115022d723d975629b26ac2159b0a55535d0de 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -18,7 +18,7 @@ use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, }; -use gpui::{executor::Background, App, AssetSource, AsyncAppContext, Task, ViewContext}; +use gpui::{App, AssetSource, AsyncAppContext, MutableAppContext, Task, ViewContext}; use isahc::{config::Configurable, Request}; use language::LanguageRegistry; use log::LevelFilter; @@ -50,10 +50,13 @@ fn main() { log::info!("========== starting zed =========="); let mut app = gpui::App::new(Assets).unwrap(); + let app_version = ZED_APP_VERSION .or_else(|| app.platform().app_version().ok()) .map_or("dev".to_string(), |v| v.to_string()); - init_panic_hook(app_version, http.clone(), app.background()); + init_panic_hook(app_version); + + app.background(); load_embedded_fonts(&app); @@ -61,7 +64,6 @@ fn main() { let themes = ThemeRegistry::new(Assets, app.font_cache()); let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes); - let config_files = load_config_files(&app, fs.clone()); let login_shell_env_loaded = if stdout_is_a_pty() { @@ -88,15 +90,6 @@ fn main() { cx.set_global(*RELEASE_CHANNEL); cx.set_global(HomeDir(paths::HOME.to_path_buf())); - let client = client::Client::new(http.clone(), cx); - let mut languages = LanguageRegistry::new(login_shell_env_loaded); - languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); - let languages = Arc::new(languages); - let init_languages = cx - .background() - .spawn(languages::init(languages.clone(), cx.background().clone())); - let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); - let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap(); //Setup settings global before binding actions @@ -105,7 +98,19 @@ fn main() { settings_file_content.clone(), fs.clone(), )); + watch_settings_file(default_settings, settings_file_content, themes.clone(), cx); + upload_previous_panics(http.clone(), cx); + + let client = client::Client::new(http.clone(), cx); + let mut languages = LanguageRegistry::new(login_shell_env_loaded); + languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); + let languages = Arc::new(languages); + let init_languages = cx + .background() + .spawn(languages::init(languages.clone(), cx.background().clone())); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); + watch_keymap_file(keymap_file, cx); context_menu::init(cx); @@ -251,65 +256,7 @@ fn init_logger() { } } -fn init_panic_hook(app_version: String, http: Arc, background: Arc) { - background - .spawn({ - async move { - let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); - let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; - while let Some(child) = children.next().await { - let child = child?; - let child_path = child.path(); - if child_path.extension() != Some(OsStr::new("panic")) { - continue; - } - let filename = if let Some(filename) = child_path.file_name() { - filename.to_string_lossy() - } else { - continue; - }; - - let mut components = filename.split('-'); - if components.next() != Some("zed") { - continue; - } - let version = if let Some(version) = components.next() { - version - } else { - continue; - }; - - let text = smol::fs::read_to_string(&child_path) - .await - .context("error reading panic file")?; - let body = serde_json::to_string(&json!({ - "text": text, - "version": version, - "token": ZED_SECRET_CLIENT_TOKEN, - })) - .unwrap(); - let request = Request::post(&panic_report_url) - .redirect_policy(isahc::config::RedirectPolicy::Follow) - .header("Content-Type", "application/json") - .body(body.into())?; - let response = http.send(request).await.context("error sending panic")?; - if response.status().is_success() { - std::fs::remove_file(child_path) - .context("error removing panic after sending it successfully") - .log_err(); - } else { - return Err(anyhow!( - "error uploading panic to server: {}", - response.status() - )); - } - } - Ok::<_, anyhow::Error>(()) - } - .log_err() - }) - .detach(); - +fn init_panic_hook(app_version: String) { let is_pty = stdout_is_a_pty(); panic::set_hook(Box::new(move |info| { let backtrace = Backtrace::new(); @@ -358,6 +305,69 @@ fn init_panic_hook(app_version: String, http: Arc, background: A })); } +fn upload_previous_panics(http: Arc, cx: &mut MutableAppContext) { + let diagnostics_telemetry = cx.global::().telemetry_diagnostics(); + + cx.background() + .spawn({ + async move { + let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); + let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; + while let Some(child) = children.next().await { + let child = child?; + let child_path = child.path(); + + if child_path.extension() != Some(OsStr::new("panic")) { + continue; + } + let filename = if let Some(filename) = child_path.file_name() { + filename.to_string_lossy() + } else { + continue; + }; + + let mut components = filename.split('-'); + if components.next() != Some("zed") { + continue; + } + let version = if let Some(version) = components.next() { + version + } else { + continue; + }; + + if diagnostics_telemetry { + let text = smol::fs::read_to_string(&child_path) + .await + .context("error reading panic file")?; + let body = serde_json::to_string(&json!({ + "text": text, + "version": version, + "token": ZED_SECRET_CLIENT_TOKEN, + })) + .unwrap(); + let request = Request::post(&panic_report_url) + .redirect_policy(isahc::config::RedirectPolicy::Follow) + .header("Content-Type", "application/json") + .body(body.into())?; + let response = http.send(request).await.context("error sending panic")?; + if !response.status().is_success() { + log::error!("Error uploading panic to server: {}", response.status()); + } + } + + // We've done what we can, delete the file + std::fs::remove_file(child_path) + .context("error removing panic") + .log_err(); + } + Ok::<_, anyhow::Error>(()) + } + .log_err() + }) + .detach(); +} + async fn load_login_shell_environment() -> Result<()> { let marker = "ZED_LOGIN_SHELL_START"; let shell = env::var("SHELL").context( From 551dc1f3187daea5e2b2ee4b581d4e9135730934 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Jan 2023 15:32:14 -0800 Subject: [PATCH 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 19fed4bf59004f917d86943f96cd45f8e0c6c4ab..ecc8d3f7b39b57080c2245fc460c613e0b55c03c 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -623,6 +623,20 @@ mod tests { cx.assert_dock_pane_active(); } + #[gpui::test] + async fn test_activate_next_and_prev_pane(cx: &mut TestAppContext) { + let mut cx = DockTestContext::new(cx).await; + + cx.move_dock(DockAnchor::Right); + cx.assert_dock_pane_active(); + + cx.update_workspace(|workspace, cx| workspace.activate_next_pane(cx)); + cx.assert_dock_pane_active(); + + cx.update_workspace(|workspace, cx| workspace.activate_previous_pane(cx)); + cx.assert_dock_pane_active(); + } + struct DockTestContext<'a> { pub cx: &'a mut TestAppContext, pub window_id: usize, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ac6a9f38edd0d695679a8f49c6299797b1baf5f0..027b9ba86a7fc8b4709944bb8eba5fae6e1f0e91 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -44,6 +44,7 @@ use language::LanguageRegistry; use std::{ any::TypeId, borrow::Cow, + cmp, future::Future, path::{Path, PathBuf}, sync::Arc, @@ -1415,29 +1416,21 @@ impl Workspace { } pub fn activate_next_pane(&mut self, cx: &mut ViewContext) { - let next_pane = { - let panes = self.center.panes(); - let ix = panes - .iter() - .position(|pane| **pane == self.active_pane) - .unwrap(); + let panes = self.center.panes(); + if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { let next_ix = (ix + 1) % panes.len(); - panes[next_ix].clone() - }; - cx.focus(next_pane); + let next_pane = panes[next_ix].clone(); + cx.focus(next_pane); + } } pub fn activate_previous_pane(&mut self, cx: &mut ViewContext) { - let prev_pane = { - let panes = self.center.panes(); - let ix = panes - .iter() - .position(|pane| **pane == self.active_pane) - .unwrap(); - let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 }; - panes[prev_ix].clone() - }; - cx.focus(prev_pane); + let panes = self.center.panes(); + if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { + let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1); + let prev_pane = panes[prev_ix].clone(); + cx.focus(prev_pane); + } } fn handle_pane_focused(&mut self, pane: ViewHandle, cx: &mut ViewContext) { From 37a4de1a848ab9e2b0e3e0f8174b49f5454a5371 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 15:49:54 -0800 Subject: [PATCH 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 aa46d64fcb250d30ac4c41c97497bccd40b7ad74..213b2cf0424a42f8a6c8de30f251ce8395f9a2a8 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -25,6 +25,7 @@ use postage::watch; use rand::prelude::*; use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage}; use serde::Deserialize; +use settings::{Settings, TelemetrySettings}; use std::{ any::TypeId, collections::HashMap, @@ -423,7 +424,9 @@ impl Client { })); } Status::SignedOut | Status::UpgradeRequired => { - self.telemetry.set_authenticated_user_info(None, false); + let telemetry_settings = cx.read(|cx| cx.global::().telemetry()); + self.telemetry + .set_authenticated_user_info(None, false, telemetry_settings); state._reconnect_task.take(); } _ => {} @@ -706,7 +709,13 @@ impl Client { credentials = read_credentials_from_keychain(cx); read_from_keychain = credentials.is_some(); if read_from_keychain { - self.report_event("read credentials from keychain", Default::default()); + cx.read(|cx| { + self.report_event( + "read credentials from keychain", + Default::default(), + cx.global::().telemetry(), + ); + }); } } if credentials.is_none() { @@ -997,6 +1006,8 @@ impl Client { let executor = cx.background(); let telemetry = self.telemetry.clone(); let http = self.http.clone(); + let metrics_enabled = cx.read(|cx| cx.global::().telemetry()); + executor.clone().spawn(async move { // Generate a pair of asymmetric encryption keys. The public key will be used by the // zed server to encrypt the user's access token, so that it can'be intercepted by @@ -1079,7 +1090,11 @@ impl Client { .context("failed to decrypt access token")?; platform.activate(true); - telemetry.report_event("authenticate with browser", Default::default()); + telemetry.report_event( + "authenticate with browser", + Default::default(), + metrics_enabled, + ); Ok(Credentials { user_id: user_id.parse()?, @@ -1287,8 +1302,14 @@ impl Client { self.telemetry.start(); } - pub fn report_event(&self, kind: &str, properties: Value) { - self.telemetry.report_event(kind, properties.clone()); + pub fn report_event( + &self, + kind: &str, + properties: Value, + telemetry_settings: TelemetrySettings, + ) { + self.telemetry + .report_event(kind, properties.clone(), telemetry_settings); } pub fn telemetry_log_file_path(&self) -> Option { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index ce8b713996be0c3ce993cb7b2473bd874a8583b0..e5d2dad41e9a0de40063b1ac891e6a32d4889158 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -10,6 +10,7 @@ use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; use serde_json::json; +use settings::TelemetrySettings; use std::{ io::Write, mem, @@ -184,11 +185,18 @@ impl Telemetry { .detach(); } + /// This method takes the entire TelemetrySettings struct in order to force client code + /// to pull the struct out of the settings global. Do not remove! pub fn set_authenticated_user_info( self: &Arc, metrics_id: Option, is_staff: bool, + telemetry_settings: TelemetrySettings, ) { + if !telemetry_settings.metrics() { + return; + } + let this = self.clone(); let mut state = self.state.lock(); let device_id = state.device_id.clone(); @@ -221,7 +229,16 @@ impl Telemetry { } } - pub fn report_event(self: &Arc, kind: &str, properties: Value) { + pub fn report_event( + self: &Arc, + kind: &str, + properties: Value, + telemetry_settings: TelemetrySettings, + ) { + if !telemetry_settings.metrics() { + return; + } + let mut state = self.state.lock(); let event = MixpanelEvent { event: kind.to_string(), diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 4d29669c2f87015ddb15557c183b8e853e3d5534..1201665571e16b63b92d2397043e7d0266548e85 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -5,6 +5,7 @@ use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt}; use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task}; use postage::{sink::Sink, watch}; use rpc::proto::{RequestMessage, UsersResponse}; +use settings::Settings; use std::sync::{Arc, Weak}; use util::TryFutureExt as _; @@ -141,14 +142,11 @@ impl UserStore { let fetch_metrics_id = client.request(proto::GetPrivateUserInfo {}).log_err(); let (user, info) = futures::join!(fetch_user, fetch_metrics_id); - if let Some(info) = info { - client.telemetry.set_authenticated_user_info( - Some(info.metrics_id.clone()), - info.staff, - ); - } else { - client.telemetry.set_authenticated_user_info(None, false); - } + client.telemetry.set_authenticated_user_info( + info.as_ref().map(|info| info.metrics_id.clone()), + info.as_ref().map(|info| info.staff).unwrap_or(false), + cx.read(|cx| cx.global::().telemetry()), + ); current_user_tx.send(user).await.ok(); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f11061cb84893a05fff4629ed3c0881eb354c56c..8847c01af7aeecd449137b7b0843e306bb4e8df4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6087,10 +6087,11 @@ impl Editor { let extension = Path::new(file.file_name(cx)) .extension() .and_then(|e| e.to_str()); - project - .read(cx) - .client() - .report_event(name, json!({ "File Extension": extension })); + project.read(cx).client().report_event( + name, + json!({ "File Extension": extension }), + cx.global::().telemetry(), + ); } } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index dd2be6e3185a53e1d75adaec7ef3c9d97c054d07..e3e8516508d9d9048757fdc2780fe1703cdb7242 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -62,6 +62,15 @@ pub struct TelemetrySettings { metrics: Option, } +impl TelemetrySettings { + pub fn metrics(&self) -> bool { + self.metrics.unwrap() + } + pub fn diagnostics(&self) -> bool { + self.diagnostics.unwrap() + } +} + #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct FeatureFlags { pub experimental_themes: bool, @@ -503,6 +512,13 @@ impl Settings { .unwrap_or_else(|| R::default()) } + pub fn telemetry(&self) -> TelemetrySettings { + TelemetrySettings { + diagnostics: Some(self.telemetry_diagnostics()), + metrics: Some(self.telemetry_metrics()), + } + } + pub fn telemetry_diagnostics(&self) -> bool { self.telemetry_overrides .diagnostics diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 72115022d723d975629b26ac2159b0a55535d0de..bf86bb96528b16c1529389038b199f59352da510 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -148,7 +148,11 @@ fn main() { .detach(); client.start_telemetry(); - client.report_event("start app", Default::default()); + client.report_event( + "start app", + Default::default(), + cx.global::().telemetry(), + ); let app_state = Arc::new(AppState { languages, From 41ff42ddec3936306d7bdfc3e11310e7a55e0130 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Jan 2023 16:27:13 -0800 Subject: [PATCH 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 608abc5815612b1c180b9dd0d674d4464e4c7e46..478271118c2c1be477fb6df8e4d44ff36ebe167f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2081,6 +2081,7 @@ impl Project { .buffer_snapshots .entry(buffer.remote_id()) .or_insert_with(|| vec![(0, buffer.text_snapshot())]); + let (version, initial_snapshot) = versions.last().unwrap(); let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); language_server @@ -2617,6 +2618,7 @@ impl Project { worktree_id: worktree.read(cx).id(), path: relative_path.into(), }; + if let Some(buffer) = self.get_open_buffer(&project_path, cx) { self.update_buffer_diagnostics(&buffer, diagnostics.clone(), version, cx)?; } @@ -6124,25 +6126,20 @@ impl Project { .buffer_snapshots .get_mut(&buffer_id) .ok_or_else(|| anyhow!("no snapshot found for buffer {}", buffer_id))?; - let mut found_snapshot = None; - snapshots.retain(|(snapshot_version, snapshot)| { - if snapshot_version + OLD_VERSIONS_TO_RETAIN < version { - false - } else { - if *snapshot_version == version { - found_snapshot = Some(snapshot.clone()); - } - true - } + let found_snapshot = snapshots + .binary_search_by_key(&version, |e| e.0) + .map(|ix| snapshots[ix].1.clone()) + .map_err(|_| { + anyhow!( + "snapshot not found for buffer {} at version {}", + buffer_id, + version + ) + })?; + snapshots.retain(|(snapshot_version, _)| { + snapshot_version + OLD_VERSIONS_TO_RETAIN >= version }); - - found_snapshot.ok_or_else(|| { - anyhow!( - "snapshot not found for buffer {} at version {}", - buffer_id, - version - ) - }) + Ok(found_snapshot) } else { Ok((buffer.read(cx)).text_snapshot()) } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index a36831857f0ffed548fabe2d57ddb5f28aaac5fb..c9e159f391a1af7b8ea5a9d92ccb1932710fccc2 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -806,6 +806,55 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC }); } +#[gpui::test] +async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let mut language = Language::new( + LanguageConfig { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-lsp", + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.background()); + fs.insert_tree("/dir", json!({ "a.rs": "" })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // Before restarting the server, report diagnostics with an unknown buffer version. + let fake_server = fake_servers.next().await.unwrap(); + fake_server.notify::(lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), + version: Some(10000), + diagnostics: Vec::new(), + }); + cx.foreground().run_until_parked(); + + project.update(cx, |project, cx| { + project.restart_language_servers_for_buffers([buffer.clone()], cx); + }); + let mut fake_server = fake_servers.next().await.unwrap(); + let notification = fake_server + .receive_notification::() + .await + .text_document; + assert_eq!(notification.version, 0); +} + #[gpui::test] async fn test_toggling_enable_language_server( deterministic: Arc, From a3da41bfad3a6df94d1a93f37cfb120193bce5f0 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 16:38:07 -0800 Subject: [PATCH 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 b067cac5ff596fb74837b565c6264ffc7ccbc31b..11c62005c2d2b4115899046bdf784468123c72c8 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -18,6 +18,7 @@ use rand::{ distributions::{Alphanumeric, DistString}, prelude::*, }; +use settings::Settings; use std::{env, ffi::OsStr, path::PathBuf, sync::Arc}; #[gpui::test(iterations = 100)] @@ -104,6 +105,8 @@ async fn test_random_collaboration( cx.function_name.clone(), ); + client_cx.update(|cx| cx.set_global(Settings::test(cx))); + let op_start_signal = futures::channel::mpsc::unbounded(); let client = server.create_client(&mut client_cx, &username).await; user_ids.push(client.current_user_id(&client_cx)); @@ -173,6 +176,7 @@ async fn test_random_collaboration( available_users.push((removed_user_id, client.username.clone())); client_cx.update(|cx| { cx.clear_globals(); + cx.set_global(Settings::test(cx)); drop(client); }); @@ -401,6 +405,7 @@ async fn test_random_collaboration( for (client, mut cx) in clients { cx.update(|cx| { cx.clear_globals(); + cx.set_global(Settings::test(cx)); drop(client); }); } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index e3e8516508d9d9048757fdc2780fe1703cdb7242..e29a98e775f528ee6242c16da24364e3bf7b62e8 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -584,7 +584,10 @@ impl Settings { lsp: Default::default(), projects_online_by_default: true, theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), - telemetry_defaults: Default::default(), + telemetry_defaults: TelemetrySettings { + diagnostics: Some(true), + metrics: Some(true), + }, telemetry_overrides: Default::default(), staff_mode: false, } From bfb43c67f84c99c2a40ab7f6e35b8a8bb449306c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 10 Jan 2023 16:50:54 -0800 Subject: [PATCH 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 19fed4bf59004f917d86943f96cd45f8e0c6c4ab..d830f3474499ccb3e8b9ed9439eb4fe747de1153 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -470,7 +470,7 @@ mod tests { use super::*; use crate::{ dock, - item::test::TestItem, + item::{self, test::TestItem}, persistence::model::{ SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, }, @@ -492,7 +492,7 @@ mod tests { Settings::test_async(cx); cx.update(|cx| { - register_deserializable_item::(cx); + register_deserializable_item::(cx); }); let serialized_workspace = SerializedWorkspace { @@ -508,7 +508,7 @@ mod tests { children: vec![SerializedItem { active: true, item_id: 0, - kind: "test".into(), + kind: "TestItem".into(), }], }, left_sidebar_open: false, diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 0a8311fd5cb3998a392908c522fc7f5e1efbe792..19385038e4a81bd06c4fb2f63d78cca9840b0c4a 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -919,7 +919,7 @@ pub(crate) mod test { } fn serialized_item_kind() -> Option<&'static str> { - None + Some("TestItem") } fn deserialize( From db831c3fbb8a2fdc2d25ce23f5f7696bcdf6cf07 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Jan 2023 17:38:34 -0800 Subject: [PATCH 11/83] Remove roadmap from readme --- README.md | 53 ----------------------------------------------------- 1 file changed, 53 deletions(-) diff --git a/README.md b/README.md index 9153d12bbbb0776d30c4917820fb8d5288cf0206..24614e97c271176f6939b3199c9a3e7613fc504b 100644 --- a/README.md +++ b/README.md @@ -83,56 +83,3 @@ rustup target add wasm32-wasi ``` Plugins can be found in the `plugins` folder in the root. For more information about how plugins work, check the [Plugin Guide](./crates/plugin_runtime/README.md) in `crates/plugin_runtime/README.md`. - -## Roadmap - -We will organize our efforts around the following major milestones. We'll create tracking issues for each of these milestones to detail the individual tasks that comprise them. - -### Minimal text editor - -[Tracking issue](https://github.com/zed-industries/zed/issues/2) - -Ship a minimal text editor to investors and other insiders. It should be extremely fast and stable, but all it can do is open, edit, and save text files, making it potentially useful for basic editing but not for real coding. - -Establish basic infrastructure for building the app bundle and uploading an artifact. Once this is released, we should regularly distribute updates as features land. - -### Collaborative code editor for internal use - -[Tracking issue](https://github.com/zed-industries/zed/issues/6) - -Turn the minimal text editor into a collaborative _code_ editor. This will include the minimal features that the Zed team needs to collaborate in Zed to build Zed without net loss in developer productivity. This includes productivity-critical features such as: - -- Syntax highlighting and syntax-aware editing and navigation -- The ability to see and edit non-local working copies of a repository -- Language server support for Rust code navigation, refactoring, diagnostics, etc. -- Project browsing and project-wide search and replace - -We want to tackle collaboration fairly early so that the rest of the design of the product can flow around that assumption. We could probably produce a single-player code editor more quickly, but at the risk of having collaboration feel more "bolted on" when we eventually add it. - -### Private alpha for Rust teams on macOS - -The "minimal" milestones were about getting Zed to a point where the Zed team could use Zed productively to build Zed. What features are required for someone outside the company to use Zed to productively work on another project that is also written in Rust? - -This includes infrastructure like auto-updates, error reporting, and metrics collection. It also includes some amount of polish to make the tool more discoverable for someone that didn't write it, such as a UI for updating settings and key bindings. We may also need to enhance the server to support user authentication and related concerns. - -The initial target audience is like us. A small team working in Rust that's potentially interested in collaborating. As the alpha proceeds, we can work with teams of different sizes. - -### Private beta for Rust teams on macOS - -Once we're getting sufficiently positive feedback from our initial alpha users, we widen the audience by letting people share invites. Now may be a good time to get Zed running on the web, so that it's extremely easy for a Zed user to share a link and be collaborating in seconds. Once someone is using Zed on the Web, we'll let them register for the private beta and download the native binary if they're on macOS. - -### Expand to other languages - -Depending on how the Rust beta is going, focus hard on dominating another niche language such as Elixr or getting a foothold within a niche of a larger language, such as React/Typescript. Alternatively, go wide at this point and add decent support several widely-used languages such as Python, Ruby, Typescript, etc. This would entail taking 1-2 weeks per language and making sure we ship a solid experience based on a publicly-available language server. Each language has slightly different development practices, so we need to make sure Zed's UX meshes well with those practices. - -### Future directions - -Each of these sections could probably broken into multiple milestones, but this part of the roadmap is too far in the future to go into that level of detail at this point. - -#### Expand to other platforms - -Support Linux and Windows. We'll probably want to hire at least one person that prefers to work on each respective platform and have them spearhead the effort to port Zed to that platform. Once they've done so, they can join the general development effort while ensuring the user experience stays good on that platform. - -#### Expand on collaboration - -To start with, we'll focus on synchronous collaboration because that's where we're most differentiated, but there's no reason we have to limit ourselves to that. How can our tool facilitate collaboration generally, whether it's sync or async? What would it take for a team to go 100% Zed and collaborate fully within the tool? If we haven't added it already, basic Git support would be nice. From 9c627e82a09d5a652347c4710d9f94baa72dafb5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 11 Jan 2023 10:34:11 -0800 Subject: [PATCH 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 14574cc19a7f1327d6dd3c47a3578f8a715f21a0..d615546104ec99072cbd79a90f13f4e2a5e3d459 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8187,7 +8187,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.69.0" +version = "0.70.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d295deea717c3d8ac9529c8fbc50431757b22946..38fa2ad4befffe33901f5499b1ab890a83e20bcc 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.69.0" +version = "0.70.0" [lib] name = "zed" From 048da9ddce8dc8fd60c8be3ac425dc1afc842c14 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 11 Jan 2023 10:50:16 -0800 Subject: [PATCH 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 d615546104ec99072cbd79a90f13f4e2a5e3d459..fb090ac835f58a7349f6ee86e51345979a209dc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.4.2" +version = "0.5.0" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 261289b9b325f1c8e291a35538ab7e2c5a3d0f64..1e26e6f5273004d40e7a11e713bb0fcfadd2c12e 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.4.2" +version = "0.5.0" [[bin]] name = "collab" From 14eec66e38b634fad59ce878e35b4cf8f90865a1 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Mon, 9 Jan 2023 15:44:40 -0800 Subject: [PATCH 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 bef6f48cb4a3776a85e7e90ffe313e1db6f0405d..12873a3e4e6a4515a02fe161dce4c57a49efabe4 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -209,6 +209,10 @@ "ctrl-e": [ "vim::Scroll", "LineDown" + ], + "r": [ + "vim::PushOperator", + "Replace" ] } }, @@ -294,7 +298,11 @@ "d": "vim::VisualDelete", "x": "vim::VisualDelete", "y": "vim::VisualYank", - "p": "vim::VisualPaste" + "p": "vim::VisualPaste", + "r": [ + "vim::PushOperator", + "Replace" + ] } }, { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 9089eebcb5654c34cd5cbd5c91c479b2134ed51e..62b30730e8ea8d4a3a4d9f28120359872c5e8211 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -3,7 +3,7 @@ use editor::{ display_map::{DisplaySnapshot, ToDisplayPoint}, movement, Bias, CharKind, DisplayPoint, }; -use gpui::{actions, impl_actions, keymap_matcher::KeyPressed, MutableAppContext}; +use gpui::{actions, impl_actions, MutableAppContext}; use language::{Point, Selection, SelectionGoal}; use serde::Deserialize; use workspace::Workspace; @@ -109,27 +109,6 @@ pub fn init(cx: &mut MutableAppContext) { &PreviousWordStart { ignore_punctuation }: &PreviousWordStart, cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) }, ); - cx.add_action( - |_: &mut Workspace, KeyPressed { keystroke }: &KeyPressed, cx| match Vim::read(cx) - .active_operator() - { - Some(Operator::FindForward { before }) => motion( - Motion::FindForward { - before, - character: keystroke.key.chars().next().unwrap(), - }, - cx, - ), - Some(Operator::FindBackward { after }) => motion( - Motion::FindBackward { - after, - character: keystroke.key.chars().next().unwrap(), - }, - cx, - ), - _ => cx.propagate_action(), - }, - ) } pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) { diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index d88d496ee92a6da2323f4b51fed02b6a0dbeee60..d2ab40c7729a4941300be0d267cf57026d283372 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -424,6 +424,25 @@ fn scroll(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext "c", Operator::Delete => "d", Operator::Yank => "y", + Operator::Replace => "r", Operator::FindForward { before: false } => "f", Operator::FindForward { before: true } => "t", Operator::FindBackward { after: false } => "F", @@ -127,7 +129,9 @@ impl Operator { pub fn context_flags(&self) -> &'static [&'static str] { match self { Operator::Object { .. } => &["VimObject"], - Operator::FindForward { .. } | Operator::FindBackward { .. } => &["VimWaiting"], + Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => { + &["VimWaiting"] + } _ => &[], } } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 4d582fea6b25fab4a8d9833ddfb8e5676d78b0a6..8c5dae1e01fa7db5c4d5e477d1c08874e1feaad3 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -13,11 +13,18 @@ mod visual; use collections::HashMap; use command_palette::CommandPaletteFilter; use editor::{Bias, Cancel, Editor}; -use gpui::{impl_actions, MutableAppContext, Subscription, ViewContext, WeakViewHandle}; +use gpui::{ + impl_actions, + keymap_matcher::{KeyPressed, Keystroke}, + MutableAppContext, Subscription, ViewContext, WeakViewHandle, +}; use language::CursorShape; +use motion::Motion; +use normal::normal_replace; use serde::Deserialize; use settings::Settings; use state::{Mode, Operator, VimState}; +use visual::visual_replace; use workspace::{self, Workspace}; #[derive(Clone, Deserialize, PartialEq)] @@ -51,6 +58,11 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(|_: &mut Workspace, n: &Number, cx: _| { Vim::update(cx, |vim, cx| vim.push_number(n, cx)); }); + cx.add_action( + |_: &mut Workspace, KeyPressed { keystroke }: &KeyPressed, cx| { + Vim::key_pressed(keystroke, cx); + }, + ); // Editor Actions cx.add_action(|_: &mut Editor, _: &Cancel, cx| { @@ -208,6 +220,27 @@ impl Vim { self.state.operator_stack.last().copied() } + fn key_pressed(keystroke: &Keystroke, cx: &mut ViewContext) { + match Vim::read(cx).active_operator() { + Some(Operator::FindForward { before }) => { + if let Some(character) = keystroke.key.chars().next() { + motion::motion(Motion::FindForward { before, character }, cx) + } + } + Some(Operator::FindBackward { after }) => { + if let Some(character) = keystroke.key.chars().next() { + motion::motion(Motion::FindBackward { after, character }, cx) + } + } + Some(Operator::Replace) => match Vim::read(cx).state.mode { + Mode::Normal => normal_replace(&keystroke.key, cx), + Mode::Visual { line } => visual_replace(&keystroke.key, line, cx), + _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)), + }, + _ => cx.propagate_action(), + } + } + fn set_enabled(&mut self, enabled: bool, cx: &mut MutableAppContext) { if self.enabled != enabled { self.enabled = enabled; diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index ef5bb6ddd83f45e552e2c903363e9a64a3651e9c..3eba8699ef36d70894fc45c70389297b684c5382 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -313,6 +313,40 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext }); } +pub(crate) fn visual_replace(text: &str, line: bool, cx: &mut MutableAppContext) { + Vim::update(cx, |vim, cx| { + vim.update_active_editor(cx, |editor, cx| { + editor.transact(cx, |editor, cx| { + let (display_map, selections) = editor.selections.all_adjusted_display(cx); + let mut new_selections = Vec::new(); + editor.buffer().update(cx, |buffer, cx| { + let mut edits = Vec::new(); + for selection in selections.iter() { + let mut selection = selection.clone(); + if !line && !selection.reversed { + // Head is at the end of the selection. Adjust the end position to + // to include the character under the cursor. + *selection.end.column_mut() = selection.end.column() + 1; + selection.end = display_map.clip_point(selection.end, Bias::Right); + } + + let range = selection + .map(|p| p.to_offset(&display_map, Bias::Right)) + .range(); + new_selections.push(range.start..range.start); + let text = text.repeat(range.len()); + edits.push((range, text)); + } + + buffer.edit(edits, None, cx); + }); + editor.change_selections(None, cx, |s| s.select_ranges(new_selections)); + }); + }); + vim.pop_operator(cx) + }); +} + #[cfg(test)] mod test { use indoc::indoc; From 49379924cb9a53a65d7a8140ed46f8232e09add5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 13:25:02 -0800 Subject: [PATCH 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 f89e46f8c923923bc69dcc02cae1707f2bcd770d..32254d5757da77f7b90f6c675b0a432418d32624 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -57,6 +57,7 @@ CREATE TABLE "worktrees" ( "abs_path" VARCHAR NOT NULL, "visible" BOOL NOT NULL, "scan_id" INTEGER NOT NULL, + "is_complete" BOOL NOT NULL DEFAULT FALSE, "completed_scan_id" INTEGER NOT NULL, PRIMARY KEY(project_id, id) ); diff --git a/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql b/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql index e0f301b2e0c44b801819e94bc463d20077f6f476..1894d888b92a89508981abe5de7f5fc3e710184f 100644 --- a/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql +++ b/crates/collab/migrations/20230103200902_replace_is_completed_with_completed_scan_id.sql @@ -1,3 +1,3 @@ ALTER TABLE worktrees - DROP COLUMN is_complete, + ALTER COLUMN is_complete SET DEFAULT FALSE, ADD COLUMN completed_scan_id INT8; From 74f8b493b23fe7df75174f8816ec74b53d8a0505 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 13:25:28 -0800 Subject: [PATCH 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 fb090ac835f58a7349f6ee86e51345979a209dc3..9a51f872a6038690458649ef96dedacf192b5bb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.0" +version = "0.5.1" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 1e26e6f5273004d40e7a11e713bb0fcfadd2c12e..dbb5a1cc995453939d1ef8bbceb8fb91d210cbe9 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.0" +version = "0.5.1" [[bin]] name = "collab" From fe27f135c0a00cf2f545350f65866d94fd4fec73 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 14:00:16 -0800 Subject: [PATCH 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 7fc59d86fbccfa8ab787d3eed12063bb94fa7ac4..39cdea71651ac8c127a73f10208a31c787126a2f 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 44; +pub const PROTOCOL_VERSION: u32 = 45; From 02f6928328e69ffceef9cd77dd7013ba11f044b6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Jan 2023 14:00:44 -0800 Subject: [PATCH 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 9a51f872a6038690458649ef96dedacf192b5bb5..3e79a68fc4041329aa868975fe0b8a767682eca9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.1" +version = "0.5.2" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index dbb5a1cc995453939d1ef8bbceb8fb91d210cbe9..9c3f0f61c4e2d8e503bfab4d51007cd0a7e6ffcb 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.1" +version = "0.5.2" [[bin]] name = "collab" From 216b1aec08f687e3e84cc4a973d88c040b0d2df4 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 11 Jan 2023 14:57:40 -0800 Subject: [PATCH 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 96b2065823915e039b93a6627dd04b1d83ccef9d..0ede6186da4a668dd80ac3368c7f78ad1ea35e22 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -352,6 +352,29 @@ pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range< start..end } +pub fn split_display_range_by_lines( + map: &DisplaySnapshot, + range: Range, +) -> Vec> { + let mut result = Vec::new(); + + let mut start = range.start; + // Loop over all the covered rows until the one containing the range end + for row in range.start.row()..range.end.row() { + let row_end_column = map.line_len(row); + let end = map.clip_point(DisplayPoint::new(row, row_end_column), Bias::Left); + if start != end { + result.push(start..end); + } + start = map.clip_point(DisplayPoint::new(row + 1, 0), Bias::Left); + } + + // Add the final range from the start of the last end to the original range end. + result.push(start..range.end); + + result +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index d2ab40c7729a4941300be0d267cf57026d283372..d6391353cf7fc43980dd2866f69adf4d379721c8 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -429,14 +429,42 @@ pub(crate) fn normal_replace(text: &str, cx: &mut MutableAppContext) { vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(None, cx, |s| { - s.move_with(|map, selection| { - *selection.end.column_mut() += 1; - selection.end = map.clip_point(selection.end, Bias::Right); - }); + let (map, display_selections) = editor.selections.all_display(cx); + // Selections are biased right at the start. So we need to store + // anchors that are biased left so that we can restore the selections + // after the change + let stable_anchors = editor + .selections + .disjoint_anchors() + .into_iter() + .map(|selection| { + let start = selection.start.bias_left(&map.buffer_snapshot); + start..start + }) + .collect::>(); + + let edits = display_selections + .into_iter() + .map(|selection| { + let mut range = selection.range(); + *range.end.column_mut() += 1; + range.end = map.clip_point(range.end, Bias::Right); + + ( + range.start.to_offset(&map, Bias::Left) + ..range.end.to_offset(&map, Bias::Left), + text, + ) + }) + .collect::>(); + + editor.buffer().update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); }); - editor.insert(text, cx); editor.set_clip_at_line_ends(true, cx); + editor.change_selections(None, cx, |s| { + s.select_anchor_ranges(stable_anchors); + }); }); }); vim.pop_operator(cx) @@ -487,6 +515,16 @@ mod test { .await; } + // #[gpui::test] + // async fn test_enter(cx: &mut gpui::TestAppContext) { + // let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]); + // cx.assert_all(indoc! {" + // ˇThe qˇuick broˇwn + // ˇfox jumps" + // }) + // .await; + // } + #[gpui::test] async fn test_k(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await.binding(["k"]); diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 3eba8699ef36d70894fc45c70389297b684c5382..ac8771f969ef40d7f37f6b66f429cde7b3085247 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use collections::HashMap; use editor::{ - display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection, + display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection, }; use gpui::{actions, MutableAppContext, ViewContext}; use language::{AutoindentMode, SelectionGoal}; @@ -318,32 +318,47 @@ pub(crate) fn visual_replace(text: &str, line: bool, cx: &mut MutableAppContext) vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { let (display_map, selections) = editor.selections.all_adjusted_display(cx); - let mut new_selections = Vec::new(); - editor.buffer().update(cx, |buffer, cx| { - let mut edits = Vec::new(); - for selection in selections.iter() { - let mut selection = selection.clone(); - if !line && !selection.reversed { - // Head is at the end of the selection. Adjust the end position to - // to include the character under the cursor. - *selection.end.column_mut() = selection.end.column() + 1; - selection.end = display_map.clip_point(selection.end, Bias::Right); - } - let range = selection - .map(|p| p.to_offset(&display_map, Bias::Right)) - .range(); - new_selections.push(range.start..range.start); + // Selections are biased right at the start. So we need to store + // anchors that are biased left so that we can restore the selections + // after the change + let stable_anchors = editor + .selections + .disjoint_anchors() + .into_iter() + .map(|selection| { + let start = selection.start.bias_left(&display_map.buffer_snapshot); + start..start + }) + .collect::>(); + + let mut edits = Vec::new(); + for selection in selections.iter() { + let mut selection = selection.clone(); + if !line && !selection.reversed { + // Head is at the end of the selection. Adjust the end position to + // to include the character under the cursor. + *selection.end.column_mut() = selection.end.column() + 1; + selection.end = display_map.clip_point(selection.end, Bias::Right); + } + + for row_range in + movement::split_display_range_by_lines(&display_map, selection.range()) + { + let range = row_range.start.to_offset(&display_map, Bias::Right) + ..row_range.end.to_offset(&display_map, Bias::Right); let text = text.repeat(range.len()); edits.push((range, text)); } + } + editor.buffer().update(cx, |buffer, cx| { buffer.edit(edits, None, cx); }); - editor.change_selections(None, cx, |s| s.select_ranges(new_selections)); + editor.change_selections(None, cx, |s| s.select_ranges(stable_anchors)); }); }); - vim.pop_operator(cx) + vim.switch_mode(Mode::Normal, false, cx); }); } From febf992a430e496f195b6bb7f8a17f59b80af9d2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 11 Jan 2023 16:35:49 -0800 Subject: [PATCH 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 2f3316469068f0134fb0a959bacc3878e0a10621..6f358aad3939d3f5dbb4be3dba52e3d5020874ce 100644 --- a/crates/gpui/src/keymap_matcher/keymap.rs +++ b/crates/gpui/src/keymap_matcher/keymap.rs @@ -43,7 +43,7 @@ impl Keymap { pub(crate) fn add_bindings>(&mut self, bindings: T) { for binding in bindings { self.binding_indices_by_action_type - .entry(binding.action().type_id()) + .entry(binding.action().as_any().type_id()) .or_default() .push(self.bindings.len()); self.bindings.push(binding); From 8e02266d0769b60d6b2eec541514a09d9923fc22 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Sat, 14 Jan 2023 02:30:21 -0500 Subject: [PATCH 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 3db4f7b467ba7cbc0f91e33da1a46d4fed2e39ab..a5949127f5a9f3f7b96c2372c3bea40951de0a9a 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -21,6 +21,15 @@ jobs: ${{ github.event.release.body }} ``` + discourse_release: + runs-on: ubuntu-latest + steps: + - name: Install Node + uses: actions/setup-node@v2 + if: ${{ ! github.event.release.prerelease }} + with: + node-version: '16' + - run: script/discourse_release ${{ secrets.DISCOURSE_RELEASES_API_KEY }} ${{ github.event.release.tag_name }} ${{ github.event.release.body }} mixpanel_release: runs-on: ubuntu-latest steps: diff --git a/script/discourse_release b/script/discourse_release new file mode 100755 index 0000000000000000000000000000000000000000..c233bf18725dac4b0c5d543526d813a66503452f --- /dev/null +++ b/script/discourse_release @@ -0,0 +1,38 @@ +#!/usr/bin/env node --redirect-warnings=/dev/null + +main(); + +async function main() { + const apiKey = process.argv[2] + const zedVersion = process.argv[3] + const releaseNotes = process.argv[4] + const postBody = ` + 📣 Zed ${zedVersion} was just released! + + Restart your Zed or head to the [releases page](https://zed.dev/releases/latest) to grab it. + + --- + + ${releaseNotes} + ` + + const title = `${zedVersion} Release Notes` + + const options = { + method: "POST", + headers: { + "Api-Key": apiKey, + "Api-Username": "system" + }, + body: new URLSearchParams({ + title: title, + raw: postBody, + category: "8" + }) + }; + + fetch("https://forum.zed.dev/posts.json", options) + .then(response => response.json()) + .then(response => console.log(response)) + .catch(err => console.error(err)); +} \ No newline at end of file From 9779663c6b18fa35ecf9742c92c3f32a2a8d955d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Jan 2023 16:50:30 +0100 Subject: [PATCH 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 f54b09ceda55c5e94666fe16bbd7b29fc9fe0002..01b62577ad030deca39225c26c5b054d88a8303d 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -12,7 +12,8 @@ use util::ResultExt; pub struct TypeScriptLspAdapter; impl TypeScriptLspAdapter { - const BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js"; + const OLD_BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js"; + const NEW_BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs"; } struct Versions { @@ -57,7 +58,7 @@ impl LspAdapter for TypeScriptLspAdapter { fs::create_dir_all(&version_dir) .await .context("failed to create version directory")?; - let binary_path = version_dir.join(Self::BIN_PATH); + let binary_path = version_dir.join(Self::NEW_BIN_PATH); if fs::metadata(&binary_path).await.is_err() { npm_install_packages( @@ -98,9 +99,12 @@ impl LspAdapter for TypeScriptLspAdapter { } } let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let bin_path = last_version_dir.join(Self::BIN_PATH); - if bin_path.exists() { - Ok(bin_path) + let old_bin_path = last_version_dir.join(Self::OLD_BIN_PATH); + let new_bin_path = last_version_dir.join(Self::NEW_BIN_PATH); + if new_bin_path.exists() { + Ok(new_bin_path) + } else if old_bin_path.exists() { + Ok(old_bin_path) } else { Err(anyhow!( "missing executable in directory {:?}", From 2c1fd7b0bf635107e37d5d3ef41f64d7b83ed9a9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Jan 2023 16:51:45 +0100 Subject: [PATCH 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 40edbb88d7462798e0a5a41739bb73c9c50c03b6..c5aff17e566b69b33e788ded686fd2eb8acef1d3 100644 --- a/crates/zed/src/languages/installation.rs +++ b/crates/zed/src/languages/installation.rs @@ -37,6 +37,8 @@ pub(crate) struct GithubReleaseAsset { pub async fn npm_package_latest_version(name: &str) -> Result { let output = smol::process::Command::new("npm") + .args(["-fetch-retry-mintimeout", "2000"]) + .args(["-fetch-retry-maxtimeout", "5000"]) .args(["info", name, "--json"]) .output() .await @@ -60,6 +62,8 @@ pub async fn npm_install_packages( directory: &Path, ) -> Result<()> { let output = smol::process::Command::new("npm") + .args(["-fetch-retry-mintimeout", "2000"]) + .args(["-fetch-retry-maxtimeout", "5000"]) .arg("install") .arg("--prefix") .arg(directory) From b90e1012bf504f6d9320f6d365bfce3ab45d0cf3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 10:24:17 -0800 Subject: [PATCH 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 618c650e02a538b53a70b0bd3083847f4d1e0425..bd355b8b3e75465c17267468202eb8dbffa906cc 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1452,7 +1452,11 @@ impl View for Pane { 0, self.active_item_index + 1, false, - Some(100.), + if self.docked.is_some() { + None + } else { + Some(100.) + }, cx, { let toolbar = self.toolbar.clone(); From aa9710f7c3184ca58676bb841aacdf8b485b92b7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 11:46:43 -0800 Subject: [PATCH 25/83] Avoid unwrapping pane split in SplitWithProjectEntry Also, implement pane-splitting operations more consistently. --- crates/workspace/src/workspace.rs | 130 +++++++++++++----------------- 1 file changed, 55 insertions(+), 75 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 027b9ba86a7fc8b4709944bb8eba5fae6e1f0e91..eefce67371e33099c5b478f2f95b0999e5b7188b 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -231,54 +231,8 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { workspace.toggle_sidebar(SidebarSide::Right, cx); }); cx.add_action(Workspace::activate_pane_at_index); - cx.add_action( - |workspace: &mut Workspace, - SplitWithItem { - from, - pane_to_split, - item_id_to_move, - split_direction, - }: &_, - cx| { - workspace.split_pane_with_item( - from.clone(), - pane_to_split.clone(), - *item_id_to_move, - *split_direction, - cx, - ) - }, - ); - - cx.add_async_action( - |workspace: &mut Workspace, - SplitWithProjectEntry { - pane_to_split, - split_direction, - project_entry, - }: &_, - cx| { - pane_to_split.upgrade(cx).and_then(|pane_to_split| { - let new_pane = workspace.add_pane(cx); - workspace - .center - .split(&pane_to_split, &new_pane, *split_direction) - .unwrap(); - - workspace - .project - .read(cx) - .path_for_entry(*project_entry, cx) - .map(|path| { - let task = workspace.open_path(path, Some(new_pane.downgrade()), true, cx); - cx.foreground().spawn(async move { - task.await?; - Ok(()) - }) - }) - }) - }, - ); + cx.add_action(Workspace::split_pane_with_item); + cx.add_action(Workspace::split_pane_with_project_entry); cx.add_async_action( |workspace: &mut Workspace, @@ -1525,38 +1479,64 @@ impl Workspace { return None; } - pane.read(cx).active_item().map(|item| { - let new_pane = self.add_pane(cx); - if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) { - Pane::add_item(self, &new_pane, clone, true, true, None, cx); - } - self.center.split(&pane, &new_pane, direction).unwrap(); - cx.notify(); - new_pane - }) + let item = pane.read(cx).active_item()?; + let new_pane = self.add_pane(cx); + if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) { + Pane::add_item(self, &new_pane, clone, true, true, None, cx); + } + self.center.split(&pane, &new_pane, direction).unwrap(); + cx.notify(); + Some(new_pane) } - pub fn split_pane_with_item( + pub fn split_pane_with_item(&mut self, action: &SplitWithItem, cx: &mut ViewContext) { + let Some(pane_to_split) = action.pane_to_split.upgrade(cx) else { return; }; + let Some(from) = action.from.upgrade(cx) else { return; }; + if &pane_to_split == self.dock_pane() { + warn!("Can't split dock pane."); + return; + } + + let new_pane = self.add_pane(cx); + Pane::move_item( + self, + from.clone(), + new_pane.clone(), + action.item_id_to_move, + 0, + cx, + ); + self.center + .split(&pane_to_split, &new_pane, action.split_direction) + .unwrap(); + cx.notify(); + } + + pub fn split_pane_with_project_entry( &mut self, - from: WeakViewHandle, - pane_to_split: WeakViewHandle, - item_id_to_move: usize, - split_direction: SplitDirection, + action: &SplitWithProjectEntry, cx: &mut ViewContext, - ) { - if let Some((pane_to_split, from)) = pane_to_split.upgrade(cx).zip(from.upgrade(cx)) { - if &pane_to_split == self.dock_pane() { - warn!("Can't split dock pane."); - return; - } - - let new_pane = self.add_pane(cx); - Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx); - self.center - .split(&pane_to_split, &new_pane, split_direction) - .unwrap(); - cx.notify(); + ) -> Option>> { + let pane_to_split = action.pane_to_split.upgrade(cx)?; + if &pane_to_split == self.dock_pane() { + warn!("Can't split dock pane."); + return None; } + + let new_pane = self.add_pane(cx); + self.center + .split(&pane_to_split, &new_pane, action.split_direction) + .unwrap(); + + let path = self + .project + .read(cx) + .path_for_entry(action.project_entry, cx)?; + let task = self.open_path(path, Some(new_pane.downgrade()), true, cx); + Some(cx.foreground().spawn(async move { + task.await?; + Ok(()) + })) } fn remove_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { From 765773cfe68e6022746b980263c567d2117b0ca8 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 16 Jan 2023 16:34:10 -0500 Subject: [PATCH 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 bf86bb96528b16c1529389038b199f59352da510..6baf08b01e0191d312a26d8990fc838894db7998 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -75,6 +75,7 @@ fn main() { }; let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded(); + let (open_paths_tx, open_paths_rx) = mpsc::unbounded(); app.on_open_urls(move |urls, _| { if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { if let Some(cli_connection) = connect_to_cli(server_name).log_err() { @@ -83,6 +84,16 @@ fn main() { .map_err(|_| anyhow!("no listener for cli connections")) .log_err(); }; + } else { + let paths: Vec<_> = urls + .iter() + .flat_map(|url| url.strip_prefix("file://")) + .map(|path| PathBuf::from(path)) + .collect(); + open_paths_tx + .unbounded_send(paths) + .map_err(|_| anyhow!("no listener for open urls requests")) + .log_err(); } }); @@ -175,6 +186,9 @@ fn main() { cx.set_menus(menus::menus()); + cx.spawn(|cx| handle_open_paths(open_paths_rx, app_state.clone(), cx)) + .detach(); + if stdout_is_a_pty() { cx.platform().activate(true); let paths = collect_path_args(); @@ -500,6 +514,17 @@ fn load_config_files( rx } +async fn handle_open_paths( + mut rx: mpsc::UnboundedReceiver>, + app_state: Arc, + mut cx: AsyncAppContext, +) { + while let Some(paths) = rx.next().await { + cx.update(|cx| workspace::open_paths(&paths, &app_state, cx)) + .detach(); + } +} + fn connect_to_cli( server_name: &str, ) -> Result<(mpsc::Receiver, IpcSender)> { From f62d13de2119f600fbd2be63ce3d1dc097a3805a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 14:41:24 -0800 Subject: [PATCH 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 23f6f04992ea65e0f63603558d23d8f2a6a242a8..095d5065d9a6a8fe7dcaa3ca4f08594305f3b703 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -6,7 +6,6 @@ use std::{ fn main() { generate_dispatch_bindings(); - compile_context_predicate_parser(); compile_metal_shaders(); generate_shader_bindings(); } @@ -30,17 +29,6 @@ fn generate_dispatch_bindings() { .expect("couldn't write dispatch bindings"); } -fn compile_context_predicate_parser() { - let dir = PathBuf::from("./grammars/context-predicate/src"); - let parser_c = dir.join("parser.c"); - - println!("cargo:rerun-if-changed={}", &parser_c.to_str().unwrap()); - cc::Build::new() - .include(&dir) - .file(parser_c) - .compile("tree_sitter_context_predicate"); -} - const SHADER_HEADER_PATH: &str = "./src/platform/mac/shaders/shaders.h"; fn compile_metal_shaders() { diff --git a/crates/gpui/grammars/context-predicate/.gitignore b/crates/gpui/grammars/context-predicate/.gitignore deleted file mode 100644 index 563ba71918c780f0f945deff80e4f9d913156779..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/node_modules -/build \ No newline at end of file diff --git a/crates/gpui/grammars/context-predicate/Cargo.toml b/crates/gpui/grammars/context-predicate/Cargo.toml deleted file mode 100644 index a3cb19799608e4934a1e0c6895786dc9b4b390f8..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "tree-sitter-context-predicate" -description = "context-predicate grammar for the tree-sitter parsing library" -version = "0.0.1" -keywords = ["incremental", "parsing", "context-predicate"] -categories = ["parsing", "text-editors"] -repository = "https://github.com/tree-sitter/tree-sitter-javascript" -edition = "2021" -license = "MIT" -build = "bindings/rust/build.rs" -include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*"] - -[lib] -path = "bindings/rust/lib.rs" - -[dependencies] -tree-sitter = "0.20" - -[build-dependencies] -cc = "1.0" diff --git a/crates/gpui/grammars/context-predicate/binding.gyp b/crates/gpui/grammars/context-predicate/binding.gyp deleted file mode 100644 index 16f3d1af27b9c9c711e3301e7f048eb33abe3aa8..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/binding.gyp +++ /dev/null @@ -1,18 +0,0 @@ -{ - "targets": [ - { - "target_name": "tree_sitter_context_predicate_binding", - "include_dirs": [ - " - -using namespace v8; - -extern "C" TSLanguage *tree_sitter_context_predicate(); - -namespace { - -NAN_METHOD(New) {} - -void Init(Local exports, Local module) { - Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New("Language").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - Local constructor = Nan::GetFunction(tpl).ToLocalChecked(); - Local instance = - constructor->NewInstance(Nan::GetCurrentContext()).ToLocalChecked(); - Nan::SetInternalFieldPointer(instance, 0, tree_sitter_context_predicate()); - - Nan::Set(instance, Nan::New("name").ToLocalChecked(), - Nan::New("context_predicate").ToLocalChecked()); - Nan::Set(module, Nan::New("exports").ToLocalChecked(), instance); -} - -NODE_MODULE(tree_sitter_context_predicate_binding, Init) - -} // namespace diff --git a/crates/gpui/grammars/context-predicate/bindings/node/index.js b/crates/gpui/grammars/context-predicate/bindings/node/index.js deleted file mode 100644 index 3bad018a56ca5528443aac5afa9f67e0637f2ab1..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/bindings/node/index.js +++ /dev/null @@ -1,19 +0,0 @@ -try { - module.exports = require("../../build/Release/tree_sitter_context_predicate_binding"); -} catch (error1) { - if (error1.code !== 'MODULE_NOT_FOUND') { - throw error1; - } - try { - module.exports = require("../../build/Debug/tree_sitter_context_predicate_binding"); - } catch (error2) { - if (error2.code !== 'MODULE_NOT_FOUND') { - throw error2; - } - throw error1 - } -} - -try { - module.exports.nodeTypeInfo = require("../../src/node-types.json"); -} catch (_) {} diff --git a/crates/gpui/grammars/context-predicate/bindings/rust/build.rs b/crates/gpui/grammars/context-predicate/bindings/rust/build.rs deleted file mode 100644 index c6061f0995320f044faeac56bcac458a09747f1d..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/bindings/rust/build.rs +++ /dev/null @@ -1,40 +0,0 @@ -fn main() { - let src_dir = std::path::Path::new("src"); - - let mut c_config = cc::Build::new(); - c_config.include(&src_dir); - c_config - .flag_if_supported("-Wno-unused-parameter") - .flag_if_supported("-Wno-unused-but-set-variable") - .flag_if_supported("-Wno-trigraphs"); - let parser_path = src_dir.join("parser.c"); - c_config.file(&parser_path); - - // If your language uses an external scanner written in C, - // then include this block of code: - - /* - let scanner_path = src_dir.join("scanner.c"); - c_config.file(&scanner_path); - println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); - */ - - c_config.compile("parser"); - println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); - - // If your language uses an external scanner written in C++, - // then include this block of code: - - /* - let mut cpp_config = cc::Build::new(); - cpp_config.cpp(true); - cpp_config.include(&src_dir); - cpp_config - .flag_if_supported("-Wno-unused-parameter") - .flag_if_supported("-Wno-unused-but-set-variable"); - let scanner_path = src_dir.join("scanner.cc"); - cpp_config.file(&scanner_path); - cpp_config.compile("scanner"); - println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); - */ -} diff --git a/crates/gpui/grammars/context-predicate/bindings/rust/lib.rs b/crates/gpui/grammars/context-predicate/bindings/rust/lib.rs deleted file mode 100644 index 41962c960d333c12c2bbccedbf07fc13215a10b5..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/bindings/rust/lib.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! This crate provides context_predicate language support for the [tree-sitter][] parsing library. -//! -//! Typically, you will use the [language][language func] function to add this language to a -//! tree-sitter [Parser][], and then use the parser to parse some code: -//! -//! ``` -//! let code = ""; -//! let mut parser = tree_sitter::Parser::new(); -//! parser.set_language(tree_sitter_context_predicate::language()).expect("Error loading context_predicate grammar"); -//! let tree = parser.parse(code, None).unwrap(); -//! ``` -//! -//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html -//! [language func]: fn.language.html -//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html -//! [tree-sitter]: https://tree-sitter.github.io/ - -use tree_sitter::Language; - -extern "C" { - fn tree_sitter_context_predicate() -> Language; -} - -/// Get the tree-sitter [Language][] for this grammar. -/// -/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html -pub fn language() -> Language { - unsafe { tree_sitter_context_predicate() } -} - -/// The content of the [`node-types.json`][] file for this grammar. -/// -/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types -pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json"); - -// Uncomment these to include any queries that this grammar contains - -// pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm"); -// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm"); -// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm"); -// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm"); - -#[cfg(test)] -mod tests { - #[test] - fn test_can_load_grammar() { - let mut parser = tree_sitter::Parser::new(); - parser - .set_language(super::language()) - .expect("Error loading context_predicate language"); - } -} diff --git a/crates/gpui/grammars/context-predicate/corpus/expressions.txt b/crates/gpui/grammars/context-predicate/corpus/expressions.txt deleted file mode 100644 index b53bb105a8772ab9d5228545dc008b4120411af5..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/corpus/expressions.txt +++ /dev/null @@ -1,49 +0,0 @@ -================== -Identifiers -================== - -abc12 - ---- - -(source (identifier)) - -================== -Negation -================== - -!abc - ---- - -(source (not (identifier))) - -================== -And/Or -================== - -a || b && c && d - ---- - -(source - (or - (identifier) - (and - (and (identifier) (identifier)) - (identifier)))) - -================== -Expressions -================== - -a && (b == c || d != e) - ---- - -(source - (and - (identifier) - (parenthesized (or - (equal (identifier) (identifier)) - (not_equal (identifier) (identifier)))))) diff --git a/crates/gpui/grammars/context-predicate/grammar.js b/crates/gpui/grammars/context-predicate/grammar.js deleted file mode 100644 index 9a649359170c80e85cd69c5b1e6373eeae9489f4..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/grammar.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = grammar({ - name: 'context_predicate', - - rules: { - source: $ => $._expression, - - _expression: $ => choice( - $.identifier, - $.not, - $.and, - $.or, - $.equal, - $.not_equal, - $.parenthesized, - ), - - identifier: $ => /[A-Za-z0-9_-]+/, - - not: $ => prec(3, seq("!", field("expression", $._expression))), - - and: $ => prec.left(2, seq(field("left", $._expression), "&&", field("right", $._expression))), - - or: $ => prec.left(1, seq(field("left", $._expression), "||", field("right", $._expression))), - - equal: $ => seq(field("left", $.identifier), "==", field("right", $.identifier)), - - not_equal: $ => seq(field("left", $.identifier), "!=", field("right", $.identifier)), - - parenthesized: $ => seq("(", field("expression", $._expression), ")"), - } -}); diff --git a/crates/gpui/grammars/context-predicate/package-lock.json b/crates/gpui/grammars/context-predicate/package-lock.json deleted file mode 100644 index 1da584a856dad08115cb6790c828956fc9e76169..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/package-lock.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "tree-sitter-context-predicate", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "tree-sitter-context-predicate", - "dependencies": { - "nan": "^2.14.0" - }, - "devDependencies": { - "tree-sitter-cli": "^0.19.5" - } - }, - "node_modules/nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" - }, - "node_modules/tree-sitter-cli": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.19.5.tgz", - "integrity": "sha512-kRzKrUAwpDN9AjA3b0tPBwT1hd8N2oQvvvHup2OEsX6mdsSMLmAvR+NSqK9fe05JrRbVvG8mbteNUQsxlMQohQ==", - "dev": true, - "hasInstallScript": true, - "bin": { - "tree-sitter": "cli.js" - } - } - }, - "dependencies": { - "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" - }, - "tree-sitter-cli": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.19.5.tgz", - "integrity": "sha512-kRzKrUAwpDN9AjA3b0tPBwT1hd8N2oQvvvHup2OEsX6mdsSMLmAvR+NSqK9fe05JrRbVvG8mbteNUQsxlMQohQ==", - "dev": true - } - } -} diff --git a/crates/gpui/grammars/context-predicate/package.json b/crates/gpui/grammars/context-predicate/package.json deleted file mode 100644 index 298e34a6b56d76ef55b4944f85226cc66c441cf5..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "tree-sitter-context-predicate", - "main": "bindings/node", - "devDependencies": { - "tree-sitter-cli": "^0.19.5" - }, - "dependencies": { - "nan": "^2.14.0" - } -} diff --git a/crates/gpui/grammars/context-predicate/src/grammar.json b/crates/gpui/grammars/context-predicate/src/grammar.json deleted file mode 100644 index 9ccb50fe79c841227fc44817611f9cfd7a4e9401..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/src/grammar.json +++ /dev/null @@ -1,208 +0,0 @@ -{ - "name": "context_predicate", - "rules": { - "source": { - "type": "SYMBOL", - "name": "_expression" - }, - "_expression": { - "type": "CHOICE", - "members": [ - { - "type": "SYMBOL", - "name": "identifier" - }, - { - "type": "SYMBOL", - "name": "not" - }, - { - "type": "SYMBOL", - "name": "and" - }, - { - "type": "SYMBOL", - "name": "or" - }, - { - "type": "SYMBOL", - "name": "equal" - }, - { - "type": "SYMBOL", - "name": "not_equal" - }, - { - "type": "SYMBOL", - "name": "parenthesized" - } - ] - }, - "identifier": { - "type": "PATTERN", - "value": "[A-Za-z0-9_-]+" - }, - "not": { - "type": "PREC", - "value": 3, - "content": { - "type": "SEQ", - "members": [ - { - "type": "STRING", - "value": "!" - }, - { - "type": "FIELD", - "name": "expression", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - } - ] - } - }, - "and": { - "type": "PREC_LEFT", - "value": 2, - "content": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - }, - { - "type": "STRING", - "value": "&&" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - } - ] - } - }, - "or": { - "type": "PREC_LEFT", - "value": 1, - "content": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - }, - { - "type": "STRING", - "value": "||" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - } - ] - } - }, - "equal": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - }, - { - "type": "STRING", - "value": "==" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - } - ] - }, - "not_equal": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - }, - { - "type": "STRING", - "value": "!=" - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - } - ] - }, - "parenthesized": { - "type": "SEQ", - "members": [ - { - "type": "STRING", - "value": "(" - }, - { - "type": "FIELD", - "name": "expression", - "content": { - "type": "SYMBOL", - "name": "_expression" - } - }, - { - "type": "STRING", - "value": ")" - } - ] - } - }, - "extras": [ - { - "type": "PATTERN", - "value": "\\s" - } - ], - "conflicts": [], - "precedences": [], - "externals": [], - "inline": [], - "supertypes": [] -} - diff --git a/crates/gpui/grammars/context-predicate/src/node-types.json b/crates/gpui/grammars/context-predicate/src/node-types.json deleted file mode 100644 index 2e8868b1f1c37c37e5449efe6a81dd0be2df997b..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/src/node-types.json +++ /dev/null @@ -1,353 +0,0 @@ -[ - { - "type": "and", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "equal", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - } - } - }, - { - "type": "not", - "named": true, - "fields": { - "expression": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "not_equal", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "identifier", - "named": true - } - ] - } - } - }, - { - "type": "or", - "named": true, - "fields": { - "left": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - }, - "right": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "parenthesized", - "named": true, - "fields": { - "expression": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - } - }, - { - "type": "source", - "named": true, - "fields": {}, - "children": { - "multiple": false, - "required": true, - "types": [ - { - "type": "and", - "named": true - }, - { - "type": "equal", - "named": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "not", - "named": true - }, - { - "type": "not_equal", - "named": true - }, - { - "type": "or", - "named": true - }, - { - "type": "parenthesized", - "named": true - } - ] - } - }, - { - "type": "!", - "named": false - }, - { - "type": "!=", - "named": false - }, - { - "type": "&&", - "named": false - }, - { - "type": "(", - "named": false - }, - { - "type": ")", - "named": false - }, - { - "type": "==", - "named": false - }, - { - "type": "identifier", - "named": true - }, - { - "type": "||", - "named": false - } -] \ No newline at end of file diff --git a/crates/gpui/grammars/context-predicate/src/parser.c b/crates/gpui/grammars/context-predicate/src/parser.c deleted file mode 100644 index 57a7364a43df051cc26975720751d54d37c1ffcb..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/src/parser.c +++ /dev/null @@ -1,584 +0,0 @@ -#include - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - -#define LANGUAGE_VERSION 13 -#define STATE_COUNT 18 -#define LARGE_STATE_COUNT 6 -#define SYMBOL_COUNT 17 -#define ALIAS_COUNT 0 -#define TOKEN_COUNT 9 -#define EXTERNAL_TOKEN_COUNT 0 -#define FIELD_COUNT 3 -#define MAX_ALIAS_SEQUENCE_LENGTH 3 -#define PRODUCTION_ID_COUNT 3 - -enum { - sym_identifier = 1, - anon_sym_BANG = 2, - anon_sym_AMP_AMP = 3, - anon_sym_PIPE_PIPE = 4, - anon_sym_EQ_EQ = 5, - anon_sym_BANG_EQ = 6, - anon_sym_LPAREN = 7, - anon_sym_RPAREN = 8, - sym_source = 9, - sym__expression = 10, - sym_not = 11, - sym_and = 12, - sym_or = 13, - sym_equal = 14, - sym_not_equal = 15, - sym_parenthesized = 16, -}; - -static const char *const ts_symbol_names[] = { - [ts_builtin_sym_end] = "end", - [sym_identifier] = "identifier", - [anon_sym_BANG] = "!", - [anon_sym_AMP_AMP] = "&&", - [anon_sym_PIPE_PIPE] = "||", - [anon_sym_EQ_EQ] = "==", - [anon_sym_BANG_EQ] = "!=", - [anon_sym_LPAREN] = "(", - [anon_sym_RPAREN] = ")", - [sym_source] = "source", - [sym__expression] = "_expression", - [sym_not] = "not", - [sym_and] = "and", - [sym_or] = "or", - [sym_equal] = "equal", - [sym_not_equal] = "not_equal", - [sym_parenthesized] = "parenthesized", -}; - -static const TSSymbol ts_symbol_map[] = { - [ts_builtin_sym_end] = ts_builtin_sym_end, - [sym_identifier] = sym_identifier, - [anon_sym_BANG] = anon_sym_BANG, - [anon_sym_AMP_AMP] = anon_sym_AMP_AMP, - [anon_sym_PIPE_PIPE] = anon_sym_PIPE_PIPE, - [anon_sym_EQ_EQ] = anon_sym_EQ_EQ, - [anon_sym_BANG_EQ] = anon_sym_BANG_EQ, - [anon_sym_LPAREN] = anon_sym_LPAREN, - [anon_sym_RPAREN] = anon_sym_RPAREN, - [sym_source] = sym_source, - [sym__expression] = sym__expression, - [sym_not] = sym_not, - [sym_and] = sym_and, - [sym_or] = sym_or, - [sym_equal] = sym_equal, - [sym_not_equal] = sym_not_equal, - [sym_parenthesized] = sym_parenthesized, -}; - -static const TSSymbolMetadata ts_symbol_metadata[] = { - [ts_builtin_sym_end] = - { - .visible = false, - .named = true, - }, - [sym_identifier] = - { - .visible = true, - .named = true, - }, - [anon_sym_BANG] = - { - .visible = true, - .named = false, - }, - [anon_sym_AMP_AMP] = - { - .visible = true, - .named = false, - }, - [anon_sym_PIPE_PIPE] = - { - .visible = true, - .named = false, - }, - [anon_sym_EQ_EQ] = - { - .visible = true, - .named = false, - }, - [anon_sym_BANG_EQ] = - { - .visible = true, - .named = false, - }, - [anon_sym_LPAREN] = - { - .visible = true, - .named = false, - }, - [anon_sym_RPAREN] = - { - .visible = true, - .named = false, - }, - [sym_source] = - { - .visible = true, - .named = true, - }, - [sym__expression] = - { - .visible = false, - .named = true, - }, - [sym_not] = - { - .visible = true, - .named = true, - }, - [sym_and] = - { - .visible = true, - .named = true, - }, - [sym_or] = - { - .visible = true, - .named = true, - }, - [sym_equal] = - { - .visible = true, - .named = true, - }, - [sym_not_equal] = - { - .visible = true, - .named = true, - }, - [sym_parenthesized] = - { - .visible = true, - .named = true, - }, -}; - -enum { - field_expression = 1, - field_left = 2, - field_right = 3, -}; - -static const char *const ts_field_names[] = { - [0] = NULL, - [field_expression] = "expression", - [field_left] = "left", - [field_right] = "right", -}; - -static const TSFieldMapSlice ts_field_map_slices[PRODUCTION_ID_COUNT] = { - [1] = {.index = 0, .length = 1}, - [2] = {.index = 1, .length = 2}, -}; - -static const TSFieldMapEntry ts_field_map_entries[] = { - [0] = {field_expression, 1}, - [1] = {field_left, 0}, - {field_right, 2}, -}; - -static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT] - [MAX_ALIAS_SEQUENCE_LENGTH] = { - [0] = {0}, -}; - -static const uint16_t ts_non_terminal_alias_map[] = { - 0, -}; - -static bool ts_lex(TSLexer *lexer, TSStateId state) { - START_LEXER(); - eof = lexer->eof(lexer); - switch (state) { - case 0: - if (eof) - ADVANCE(7); - if (lookahead == '!') - ADVANCE(10); - if (lookahead == '&') - ADVANCE(2); - if (lookahead == '(') - ADVANCE(15); - if (lookahead == ')') - ADVANCE(16); - if (lookahead == '=') - ADVANCE(4); - if (lookahead == '|') - ADVANCE(5); - if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' || - lookahead == ' ') - SKIP(0) - if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || - ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) - ADVANCE(8); - END_STATE(); - case 1: - if (lookahead == '!') - ADVANCE(9); - if (lookahead == '(') - ADVANCE(15); - if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' || - lookahead == ' ') - SKIP(1) - if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || - ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) - ADVANCE(8); - END_STATE(); - case 2: - if (lookahead == '&') - ADVANCE(11); - END_STATE(); - case 3: - if (lookahead == '=') - ADVANCE(14); - END_STATE(); - case 4: - if (lookahead == '=') - ADVANCE(13); - END_STATE(); - case 5: - if (lookahead == '|') - ADVANCE(12); - END_STATE(); - case 6: - if (eof) - ADVANCE(7); - if (lookahead == '!') - ADVANCE(3); - if (lookahead == '&') - ADVANCE(2); - if (lookahead == ')') - ADVANCE(16); - if (lookahead == '=') - ADVANCE(4); - if (lookahead == '|') - ADVANCE(5); - if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' || - lookahead == ' ') - SKIP(6) - END_STATE(); - case 7: - ACCEPT_TOKEN(ts_builtin_sym_end); - END_STATE(); - case 8: - ACCEPT_TOKEN(sym_identifier); - if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || - ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) - ADVANCE(8); - END_STATE(); - case 9: - ACCEPT_TOKEN(anon_sym_BANG); - END_STATE(); - case 10: - ACCEPT_TOKEN(anon_sym_BANG); - if (lookahead == '=') - ADVANCE(14); - END_STATE(); - case 11: - ACCEPT_TOKEN(anon_sym_AMP_AMP); - END_STATE(); - case 12: - ACCEPT_TOKEN(anon_sym_PIPE_PIPE); - END_STATE(); - case 13: - ACCEPT_TOKEN(anon_sym_EQ_EQ); - END_STATE(); - case 14: - ACCEPT_TOKEN(anon_sym_BANG_EQ); - END_STATE(); - case 15: - ACCEPT_TOKEN(anon_sym_LPAREN); - END_STATE(); - case 16: - ACCEPT_TOKEN(anon_sym_RPAREN); - END_STATE(); - default: - return false; - } -} - -static const TSLexMode ts_lex_modes[STATE_COUNT] = { - [0] = {.lex_state = 0}, [1] = {.lex_state = 1}, [2] = {.lex_state = 1}, - [3] = {.lex_state = 1}, [4] = {.lex_state = 1}, [5] = {.lex_state = 1}, - [6] = {.lex_state = 6}, [7] = {.lex_state = 0}, [8] = {.lex_state = 0}, - [9] = {.lex_state = 0}, [10] = {.lex_state = 0}, [11] = {.lex_state = 0}, - [12] = {.lex_state = 0}, [13] = {.lex_state = 0}, [14] = {.lex_state = 0}, - [15] = {.lex_state = 0}, [16] = {.lex_state = 0}, [17] = {.lex_state = 0}, -}; - -static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { - [0] = - { - [ts_builtin_sym_end] = ACTIONS(1), - [sym_identifier] = ACTIONS(1), - [anon_sym_BANG] = ACTIONS(1), - [anon_sym_AMP_AMP] = ACTIONS(1), - [anon_sym_PIPE_PIPE] = ACTIONS(1), - [anon_sym_EQ_EQ] = ACTIONS(1), - [anon_sym_BANG_EQ] = ACTIONS(1), - [anon_sym_LPAREN] = ACTIONS(1), - [anon_sym_RPAREN] = ACTIONS(1), - }, - [1] = - { - [sym_source] = STATE(15), - [sym__expression] = STATE(13), - [sym_not] = STATE(13), - [sym_and] = STATE(13), - [sym_or] = STATE(13), - [sym_equal] = STATE(13), - [sym_not_equal] = STATE(13), - [sym_parenthesized] = STATE(13), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [2] = - { - [sym__expression] = STATE(7), - [sym_not] = STATE(7), - [sym_and] = STATE(7), - [sym_or] = STATE(7), - [sym_equal] = STATE(7), - [sym_not_equal] = STATE(7), - [sym_parenthesized] = STATE(7), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [3] = - { - [sym__expression] = STATE(14), - [sym_not] = STATE(14), - [sym_and] = STATE(14), - [sym_or] = STATE(14), - [sym_equal] = STATE(14), - [sym_not_equal] = STATE(14), - [sym_parenthesized] = STATE(14), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [4] = - { - [sym__expression] = STATE(11), - [sym_not] = STATE(11), - [sym_and] = STATE(11), - [sym_or] = STATE(11), - [sym_equal] = STATE(11), - [sym_not_equal] = STATE(11), - [sym_parenthesized] = STATE(11), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, - [5] = - { - [sym__expression] = STATE(12), - [sym_not] = STATE(12), - [sym_and] = STATE(12), - [sym_or] = STATE(12), - [sym_equal] = STATE(12), - [sym_not_equal] = STATE(12), - [sym_parenthesized] = STATE(12), - [sym_identifier] = ACTIONS(3), - [anon_sym_BANG] = ACTIONS(5), - [anon_sym_LPAREN] = ACTIONS(7), - }, -}; - -static const uint16_t ts_small_parse_table[] = { - [0] = 3, - ACTIONS(11), - 1, - anon_sym_EQ_EQ, - ACTIONS(13), - 1, - anon_sym_BANG_EQ, - ACTIONS(9), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [13] = 1, - ACTIONS(15), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [20] = 1, - ACTIONS(17), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [27] = 1, - ACTIONS(19), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [34] = 1, - ACTIONS(21), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [41] = 1, - ACTIONS(23), - 4, - ts_builtin_sym_end, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [48] = 2, - ACTIONS(27), - 1, - anon_sym_AMP_AMP, - ACTIONS(25), - 3, - ts_builtin_sym_end, - anon_sym_PIPE_PIPE, - anon_sym_RPAREN, - [57] = 3, - ACTIONS(27), - 1, - anon_sym_AMP_AMP, - ACTIONS(29), - 1, - ts_builtin_sym_end, - ACTIONS(31), - 1, - anon_sym_PIPE_PIPE, - [67] = 3, - ACTIONS(27), - 1, - anon_sym_AMP_AMP, - ACTIONS(31), - 1, - anon_sym_PIPE_PIPE, - ACTIONS(33), - 1, - anon_sym_RPAREN, - [77] = 1, - ACTIONS(35), - 1, - ts_builtin_sym_end, - [81] = 1, - ACTIONS(37), - 1, - sym_identifier, - [85] = 1, - ACTIONS(39), - 1, - sym_identifier, -}; - -static const uint32_t ts_small_parse_table_map[] = { - [SMALL_STATE(6)] = 0, [SMALL_STATE(7)] = 13, [SMALL_STATE(8)] = 20, - [SMALL_STATE(9)] = 27, [SMALL_STATE(10)] = 34, [SMALL_STATE(11)] = 41, - [SMALL_STATE(12)] = 48, [SMALL_STATE(13)] = 57, [SMALL_STATE(14)] = 67, - [SMALL_STATE(15)] = 77, [SMALL_STATE(16)] = 81, [SMALL_STATE(17)] = 85, -}; - -static const TSParseActionEntry ts_parse_actions[] = { - [0] = {.entry = {.count = 0, .reusable = false}}, - [1] = {.entry = {.count = 1, .reusable = false}}, - RECOVER(), - [3] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(6), - [5] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(2), - [7] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(3), - [9] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym__expression, 1), - [11] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(16), - [13] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(17), - [15] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_not, 2, .production_id = 1), - [17] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_equal, 3, .production_id = 2), - [19] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_not_equal, 3, .production_id = 2), - [21] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_parenthesized, 3, .production_id = 1), - [23] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_and, 3, .production_id = 2), - [25] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_or, 3, .production_id = 2), - [27] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(4), - [29] = {.entry = {.count = 1, .reusable = true}}, - REDUCE(sym_source, 1), - [31] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(5), - [33] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(10), - [35] = {.entry = {.count = 1, .reusable = true}}, - ACCEPT_INPUT(), - [37] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(8), - [39] = {.entry = {.count = 1, .reusable = true}}, - SHIFT(9), -}; - -#ifdef __cplusplus -extern "C" { -#endif -#ifdef _WIN32 -#define extern __declspec(dllexport) -#endif - -extern const TSLanguage *tree_sitter_context_predicate(void) { - static const TSLanguage language = { - .version = LANGUAGE_VERSION, - .symbol_count = SYMBOL_COUNT, - .alias_count = ALIAS_COUNT, - .token_count = TOKEN_COUNT, - .external_token_count = EXTERNAL_TOKEN_COUNT, - .state_count = STATE_COUNT, - .large_state_count = LARGE_STATE_COUNT, - .production_id_count = PRODUCTION_ID_COUNT, - .field_count = FIELD_COUNT, - .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH, - .parse_table = &ts_parse_table[0][0], - .small_parse_table = ts_small_parse_table, - .small_parse_table_map = ts_small_parse_table_map, - .parse_actions = ts_parse_actions, - .symbol_names = ts_symbol_names, - .field_names = ts_field_names, - .field_map_slices = ts_field_map_slices, - .field_map_entries = ts_field_map_entries, - .symbol_metadata = ts_symbol_metadata, - .public_symbol_map = ts_symbol_map, - .alias_map = ts_non_terminal_alias_map, - .alias_sequences = &ts_alias_sequences[0][0], - .lex_modes = ts_lex_modes, - .lex_fn = ts_lex, - }; - return &language; -} -#ifdef __cplusplus -} -#endif diff --git a/crates/gpui/grammars/context-predicate/src/tree_sitter/parser.h b/crates/gpui/grammars/context-predicate/src/tree_sitter/parser.h deleted file mode 100644 index cbbc7b4ee3c5d0d594d304c8f1c6b44377b3793e..0000000000000000000000000000000000000000 --- a/crates/gpui/grammars/context-predicate/src/tree_sitter/parser.h +++ /dev/null @@ -1,223 +0,0 @@ -#ifndef TREE_SITTER_PARSER_H_ -#define TREE_SITTER_PARSER_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#define ts_builtin_sym_error ((TSSymbol)-1) -#define ts_builtin_sym_end 0 -#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 - -typedef uint16_t TSStateId; - -#ifndef TREE_SITTER_API_H_ -typedef uint16_t TSSymbol; -typedef uint16_t TSFieldId; -typedef struct TSLanguage TSLanguage; -#endif - -typedef struct { - TSFieldId field_id; - uint8_t child_index; - bool inherited; -} TSFieldMapEntry; - -typedef struct { - uint16_t index; - uint16_t length; -} TSFieldMapSlice; - -typedef struct { - bool visible; - bool named; - bool supertype; -} TSSymbolMetadata; - -typedef struct TSLexer TSLexer; - -struct TSLexer { - int32_t lookahead; - TSSymbol result_symbol; - void (*advance)(TSLexer *, bool); - void (*mark_end)(TSLexer *); - uint32_t (*get_column)(TSLexer *); - bool (*is_at_included_range_start)(const TSLexer *); - bool (*eof)(const TSLexer *); -}; - -typedef enum { - TSParseActionTypeShift, - TSParseActionTypeReduce, - TSParseActionTypeAccept, - TSParseActionTypeRecover, -} TSParseActionType; - -typedef union { - struct { - uint8_t type; - TSStateId state; - bool extra; - bool repetition; - } shift; - struct { - uint8_t type; - uint8_t child_count; - TSSymbol symbol; - int16_t dynamic_precedence; - uint16_t production_id; - } reduce; - uint8_t type; -} TSParseAction; - -typedef struct { - uint16_t lex_state; - uint16_t external_lex_state; -} TSLexMode; - -typedef union { - TSParseAction action; - struct { - uint8_t count; - bool reusable; - } entry; -} TSParseActionEntry; - -struct TSLanguage { - uint32_t version; - uint32_t symbol_count; - uint32_t alias_count; - uint32_t token_count; - uint32_t external_token_count; - uint32_t state_count; - uint32_t large_state_count; - uint32_t production_id_count; - uint32_t field_count; - uint16_t max_alias_sequence_length; - const uint16_t *parse_table; - const uint16_t *small_parse_table; - const uint32_t *small_parse_table_map; - const TSParseActionEntry *parse_actions; - const char * const *symbol_names; - const char * const *field_names; - const TSFieldMapSlice *field_map_slices; - const TSFieldMapEntry *field_map_entries; - const TSSymbolMetadata *symbol_metadata; - const TSSymbol *public_symbol_map; - const uint16_t *alias_map; - const TSSymbol *alias_sequences; - const TSLexMode *lex_modes; - bool (*lex_fn)(TSLexer *, TSStateId); - bool (*keyword_lex_fn)(TSLexer *, TSStateId); - TSSymbol keyword_capture_token; - struct { - const bool *states; - const TSSymbol *symbol_map; - void *(*create)(void); - void (*destroy)(void *); - bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); - unsigned (*serialize)(void *, char *); - void (*deserialize)(void *, const char *, unsigned); - } external_scanner; -}; - -/* - * Lexer Macros - */ - -#define START_LEXER() \ - bool result = false; \ - bool skip = false; \ - bool eof = false; \ - int32_t lookahead; \ - goto start; \ - next_state: \ - lexer->advance(lexer, skip); \ - start: \ - skip = false; \ - lookahead = lexer->lookahead; - -#define ADVANCE(state_value) \ - { \ - state = state_value; \ - goto next_state; \ - } - -#define SKIP(state_value) \ - { \ - skip = true; \ - state = state_value; \ - goto next_state; \ - } - -#define ACCEPT_TOKEN(symbol_value) \ - result = true; \ - lexer->result_symbol = symbol_value; \ - lexer->mark_end(lexer); - -#define END_STATE() return result; - -/* - * Parse Table Macros - */ - -#define SMALL_STATE(id) id - LARGE_STATE_COUNT - -#define STATE(id) id - -#define ACTIONS(id) id - -#define SHIFT(state_value) \ - {{ \ - .shift = { \ - .type = TSParseActionTypeShift, \ - .state = state_value \ - } \ - }} - -#define SHIFT_REPEAT(state_value) \ - {{ \ - .shift = { \ - .type = TSParseActionTypeShift, \ - .state = state_value, \ - .repetition = true \ - } \ - }} - -#define SHIFT_EXTRA() \ - {{ \ - .shift = { \ - .type = TSParseActionTypeShift, \ - .extra = true \ - } \ - }} - -#define REDUCE(symbol_val, child_count_val, ...) \ - {{ \ - .reduce = { \ - .type = TSParseActionTypeReduce, \ - .symbol = symbol_val, \ - .child_count = child_count_val, \ - __VA_ARGS__ \ - }, \ - }} - -#define RECOVER() \ - {{ \ - .type = TSParseActionTypeRecover \ - }} - -#define ACCEPT_INPUT() \ - {{ \ - .type = TSParseActionTypeAccept \ - }} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_PARSER_H_ diff --git a/crates/gpui/src/keymap_matcher/keymap_context.rs b/crates/gpui/src/keymap_matcher/keymap_context.rs index ad7ce929fef602468a5ffbb4a709927d767ec753..b1dbd0e92d9cef855d0476c2bca1b7a07f162d88 100644 --- a/crates/gpui/src/keymap_matcher/keymap_context.rs +++ b/crates/gpui/src/keymap_matcher/keymap_context.rs @@ -1,11 +1,5 @@ -use anyhow::anyhow; - +use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet}; -use tree_sitter::{Language, Node, Parser}; - -extern "C" { - fn tree_sitter_context_predicate() -> Language; -} #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct KeymapContext { @@ -35,70 +29,13 @@ pub enum KeymapContextPredicate { } impl KeymapContextPredicate { - pub fn parse(source: &str) -> anyhow::Result { - let mut parser = Parser::new(); - let language = unsafe { tree_sitter_context_predicate() }; - parser.set_language(language).unwrap(); - let source = source.as_bytes(); - let tree = parser.parse(source, None).unwrap(); - Self::from_node(tree.root_node(), source) - } - - fn from_node(node: Node, source: &[u8]) -> anyhow::Result { - let parse_error = "error parsing context predicate"; - let kind = node.kind(); - - match kind { - "source" => Self::from_node(node.child(0).ok_or_else(|| anyhow!(parse_error))?, source), - "identifier" => Ok(Self::Identifier(node.utf8_text(source)?.into())), - "not" => { - let child = Self::from_node( - node.child_by_field_name("expression") - .ok_or_else(|| anyhow!(parse_error))?, - source, - )?; - Ok(Self::Not(Box::new(child))) - } - "and" | "or" => { - let left = Box::new(Self::from_node( - node.child_by_field_name("left") - .ok_or_else(|| anyhow!(parse_error))?, - source, - )?); - let right = Box::new(Self::from_node( - node.child_by_field_name("right") - .ok_or_else(|| anyhow!(parse_error))?, - source, - )?); - if kind == "and" { - Ok(Self::And(left, right)) - } else { - Ok(Self::Or(left, right)) - } - } - "equal" | "not_equal" => { - let left = node - .child_by_field_name("left") - .ok_or_else(|| anyhow!(parse_error))? - .utf8_text(source)? - .into(); - let right = node - .child_by_field_name("right") - .ok_or_else(|| anyhow!(parse_error))? - .utf8_text(source)? - .into(); - if kind == "equal" { - Ok(Self::Equal(left, right)) - } else { - Ok(Self::NotEqual(left, right)) - } - } - "parenthesized" => Self::from_node( - node.child_by_field_name("expression") - .ok_or_else(|| anyhow!(parse_error))?, - source, - ), - _ => Err(anyhow!(parse_error)), + pub fn parse(source: &str) -> Result { + let source = Self::skip_whitespace(source); + let (predicate, rest) = Self::parse_expr(source, 0)?; + if let Some(next) = rest.chars().next() { + Err(anyhow!("unexpected character {next:?}")) + } else { + Ok(predicate) } } @@ -120,4 +57,236 @@ impl KeymapContextPredicate { Self::Or(left, right) => left.eval(context) || right.eval(context), } } + + fn parse_expr( + mut source: &str, + min_precedence: u32, + ) -> anyhow::Result<(KeymapContextPredicate, &str)> { + type Op = + fn(KeymapContextPredicate, KeymapContextPredicate) -> Result; + + let (mut predicate, rest) = Self::parse_primary(source)?; + source = rest; + + 'parse: loop { + for (operator, precedence, constructor) in [ + ("&&", PRECEDENCE_AND, KeymapContextPredicate::new_and as Op), + ("||", PRECEDENCE_OR, KeymapContextPredicate::new_or as Op), + ("==", PRECEDENCE_EQ, KeymapContextPredicate::new_eq as Op), + ("!=", PRECEDENCE_EQ, KeymapContextPredicate::new_neq as Op), + ] { + if source.starts_with(operator) && precedence >= min_precedence { + source = Self::skip_whitespace(&source[operator.len()..]); + let (right, rest) = Self::parse_expr(source, precedence + 1)?; + predicate = constructor(predicate, right)?; + source = rest; + continue 'parse; + } + } + break; + } + + Ok((predicate, source)) + } + + fn parse_primary(mut source: &str) -> anyhow::Result<(KeymapContextPredicate, &str)> { + let next = source + .chars() + .next() + .ok_or_else(|| anyhow!("unexpected eof"))?; + match next { + '(' => { + source = Self::skip_whitespace(&source[1..]); + let (predicate, rest) = Self::parse_expr(source, 0)?; + if rest.starts_with(')') { + source = Self::skip_whitespace(&rest[1..]); + Ok((predicate, source)) + } else { + Err(anyhow!("expected a ')'")) + } + } + '!' => { + let source = Self::skip_whitespace(&source[1..]); + let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?; + Ok((KeymapContextPredicate::Not(Box::new(predicate)), source)) + } + _ if next.is_alphanumeric() || next == '_' => { + let len = source + .find(|c: char| !(c.is_alphanumeric() || c == '_')) + .unwrap_or(source.len()); + let (identifier, rest) = source.split_at(len); + source = Self::skip_whitespace(rest); + Ok(( + KeymapContextPredicate::Identifier(identifier.into()), + source, + )) + } + _ => Err(anyhow!("unexpected character {next:?}")), + } + } + + fn skip_whitespace(source: &str) -> &str { + let len = source + .find(|c: char| !c.is_whitespace()) + .unwrap_or(source.len()); + &source[len..] + } + + fn new_or(self, other: Self) -> Result { + Ok(Self::Or(Box::new(self), Box::new(other))) + } + + fn new_and(self, other: Self) -> Result { + Ok(Self::And(Box::new(self), Box::new(other))) + } + + fn new_eq(self, other: Self) -> Result { + if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) { + Ok(Self::Equal(left, right)) + } else { + Err(anyhow!("operands must be identifiers")) + } + } + + fn new_neq(self, other: Self) -> Result { + if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) { + Ok(Self::NotEqual(left, right)) + } else { + Err(anyhow!("operands must be identifiers")) + } + } +} + +const PRECEDENCE_OR: u32 = 1; +const PRECEDENCE_AND: u32 = 2; +const PRECEDENCE_EQ: u32 = 3; +const PRECEDENCE_NOT: u32 = 4; + +#[cfg(test)] +mod tests { + use super::KeymapContextPredicate::{self, *}; + + #[test] + fn test_parse_identifiers() { + // Identifiers + assert_eq!( + KeymapContextPredicate::parse("abc12").unwrap(), + Identifier("abc12".into()) + ); + assert_eq!( + KeymapContextPredicate::parse("_1a").unwrap(), + Identifier("_1a".into()) + ); + } + + #[test] + fn test_parse_negations() { + assert_eq!( + KeymapContextPredicate::parse("!abc").unwrap(), + Not(Box::new(Identifier("abc".into()))) + ); + assert_eq!( + KeymapContextPredicate::parse(" ! ! abc").unwrap(), + Not(Box::new(Not(Box::new(Identifier("abc".into()))))) + ); + } + + #[test] + fn test_parse_equality_operators() { + assert_eq!( + KeymapContextPredicate::parse("a == b").unwrap(), + Equal("a".into(), "b".into()) + ); + assert_eq!( + KeymapContextPredicate::parse("c!=d").unwrap(), + NotEqual("c".into(), "d".into()) + ); + assert_eq!( + KeymapContextPredicate::parse("c == !d") + .unwrap_err() + .to_string(), + "operands must be identifiers" + ); + } + + #[test] + fn test_parse_boolean_operators() { + assert_eq!( + KeymapContextPredicate::parse("a || b").unwrap(), + Or( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a || !b && c").unwrap(), + Or( + Box::new(Identifier("a".into())), + Box::new(And( + Box::new(Not(Box::new(Identifier("b".into())))), + Box::new(Identifier("c".into())) + )) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a && b || c&&d").unwrap(), + Or( + Box::new(And( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())) + )), + Box::new(And( + Box::new(Identifier("c".into())), + Box::new(Identifier("d".into())) + )) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a == b && c || d == e && f").unwrap(), + Or( + Box::new(And( + Box::new(Equal("a".into(), "b".into())), + Box::new(Identifier("c".into())) + )), + Box::new(And( + Box::new(Equal("d".into(), "e".into())), + Box::new(Identifier("f".into())) + )) + ) + ); + assert_eq!( + KeymapContextPredicate::parse("a && b && c && d").unwrap(), + And( + Box::new(And( + Box::new(And( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())) + )), + Box::new(Identifier("c".into())), + )), + Box::new(Identifier("d".into())) + ), + ); + } + + #[test] + fn test_parse_parenthesized_expressions() { + assert_eq!( + KeymapContextPredicate::parse("a && (b == c || d != e)").unwrap(), + And( + Box::new(Identifier("a".into())), + Box::new(Or( + Box::new(Equal("b".into(), "c".into())), + Box::new(NotEqual("d".into(), "e".into())), + )), + ), + ); + assert_eq!( + KeymapContextPredicate::parse(" ( a || b ) ").unwrap(), + Or( + Box::new(Identifier("a".into())), + Box::new(Identifier("b".into())), + ) + ); + } } From 373902d9333ccecba5eafcd9e45b695f488ec099 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 16:00:46 -0800 Subject: [PATCH 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 6b784833c70a5dcf7c66990463ff070fc99afffe..6a4086c5eb15cc1b126ce343c78b00d745302eb3 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1349,21 +1349,24 @@ impl MutableAppContext { /// Return keystrokes that would dispatch the given action closest to the focused view, if there are any. pub(crate) fn keystrokes_for_action( - &self, + &mut self, window_id: usize, - dispatch_path: &[usize], + view_stack: &[usize], action: &dyn Action, ) -> Option> { - for view_id in dispatch_path.iter().rev() { + self.keystroke_matcher.contexts.clear(); + for view_id in view_stack.iter().rev() { let view = self .cx .views .get(&(window_id, *view_id)) .expect("view in responder chain does not exist"); - let keymap_context = view.keymap_context(self.as_ref()); + self.keystroke_matcher + .contexts + .push(view.keymap_context(self.as_ref())); let keystrokes = self .keystroke_matcher - .keystrokes_for_action(action, &keymap_context); + .keystrokes_for_action(action, &self.keystroke_matcher.contexts); if keystrokes.is_some() { return keystrokes; } @@ -6681,7 +6684,7 @@ mod tests { view_3 }); - // This keymap's only binding dispatches an action on view 2 because that view will have + // This binding only dispatches an action on view 2 because that view will have // "a" and "b" in its context, but not "c". cx.add_bindings(vec![Binding::new( "a", @@ -6691,16 +6694,31 @@ mod tests { cx.add_bindings(vec![Binding::new("b", Action("b".to_string()), None)]); + // This binding only dispatches an action on views 2 and 3, because they have + // a parent view with a in its context + cx.add_bindings(vec![Binding::new( + "c", + Action("c".to_string()), + Some("b > c"), + )]); + + // This binding only dispatches an action on view 2, because they have + // a parent view with a in its context + cx.add_bindings(vec![Binding::new( + "d", + Action("d".to_string()), + Some("a && !b > b"), + )]); + let actions = Rc::new(RefCell::new(Vec::new())); cx.add_action({ let actions = actions.clone(); move |view: &mut View, action: &Action, cx| { - if action.0 == "a" { - actions.borrow_mut().push(format!("{} a", view.id)); - } else { - actions - .borrow_mut() - .push(format!("{} {}", view.id, action.0)); + actions + .borrow_mut() + .push(format!("{} {}", view.id, action.0)); + + if action.0 == "b" { cx.propagate_action(); } } @@ -6714,14 +6732,20 @@ mod tests { }); cx.dispatch_keystroke(window_id, &Keystroke::parse("a").unwrap()); - assert_eq!(&*actions.borrow(), &["2 a"]); - actions.borrow_mut().clear(); cx.dispatch_keystroke(window_id, &Keystroke::parse("b").unwrap()); - assert_eq!(&*actions.borrow(), &["3 b", "2 b", "1 b", "global b"]); + actions.borrow_mut().clear(); + + cx.dispatch_keystroke(window_id, &Keystroke::parse("c").unwrap()); + assert_eq!(&*actions.borrow(), &["3 c"]); + actions.borrow_mut().clear(); + + cx.dispatch_keystroke(window_id, &Keystroke::parse("d").unwrap()); + assert_eq!(&*actions.borrow(), &["2 d"]); + actions.borrow_mut().clear(); } #[crate::test(self)] diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index e007605cff5fe7005d25d16b015f163f6fecc139..c7de0352328d287b1248b80699c06df7fd07ae0e 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -25,6 +25,7 @@ pub struct KeyPressed { impl_actions!(gpui, [KeyPressed]); pub struct KeymapMatcher { + pub contexts: Vec, pending_views: HashMap, pending_keystrokes: Vec, keymap: Keymap, @@ -33,6 +34,7 @@ pub struct KeymapMatcher { impl KeymapMatcher { pub fn new(keymap: Keymap) -> Self { Self { + contexts: Vec::new(), pending_views: Default::default(), pending_keystrokes: Vec::new(), keymap, @@ -70,7 +72,7 @@ impl KeymapMatcher { pub fn push_keystroke( &mut self, keystroke: Keystroke, - dispatch_path: Vec<(usize, KeymapContext)>, + mut dispatch_path: Vec<(usize, KeymapContext)>, ) -> MatchResult { let mut any_pending = false; let mut matched_bindings: Vec<(usize, Box)> = Vec::new(); @@ -78,7 +80,11 @@ impl KeymapMatcher { let first_keystroke = self.pending_keystrokes.is_empty(); self.pending_keystrokes.push(keystroke.clone()); - for (view_id, context) in dispatch_path { + self.contexts.clear(); + self.contexts + .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1))); + + for (i, (view_id, _)) in dispatch_path.into_iter().enumerate() { // Don't require pending view entry if there are no pending keystrokes if !first_keystroke && !self.pending_views.contains_key(&view_id) { continue; @@ -87,14 +93,15 @@ impl KeymapMatcher { // If there is a previous view context, invalidate that view if it // has changed if let Some(previous_view_context) = self.pending_views.remove(&view_id) { - if previous_view_context != context { + if previous_view_context != self.contexts[i] { continue; } } // Find the bindings which map the pending keystrokes and current context for binding in self.keymap.bindings().iter().rev() { - match binding.match_keys_and_context(&self.pending_keystrokes, &context) { + match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..]) + { BindingMatchResult::Complete(mut action) => { // Swap in keystroke for special KeyPressed action if action.name() == "KeyPressed" && action.namespace() == "gpui" { @@ -105,7 +112,7 @@ impl KeymapMatcher { matched_bindings.push((view_id, action)) } BindingMatchResult::Partial => { - self.pending_views.insert(view_id, context.clone()); + self.pending_views.insert(view_id, self.contexts[i].clone()); any_pending = true; } _ => {} @@ -129,13 +136,13 @@ impl KeymapMatcher { pub fn keystrokes_for_action( &self, action: &dyn Action, - context: &KeymapContext, + contexts: &[KeymapContext], ) -> Option> { self.keymap .bindings() .iter() .rev() - .find_map(|binding| binding.keystrokes_for_action(action, context)) + .find_map(|binding| binding.keystrokes_for_action(action, contexts)) } } @@ -349,27 +356,70 @@ mod tests { } #[test] - fn test_context_predicate_eval() -> Result<()> { - let predicate = KeymapContextPredicate::parse("a && b || c == d")?; + fn test_context_predicate_eval() { + let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap(); let mut context = KeymapContext::default(); context.set.insert("a".into()); - assert!(!predicate.eval(&context)); + assert!(!predicate.eval(&[context])); + let mut context = KeymapContext::default(); + context.set.insert("a".into()); context.set.insert("b".into()); - assert!(predicate.eval(&context)); + assert!(predicate.eval(&[context])); - context.set.remove("b"); + let mut context = KeymapContext::default(); + context.set.insert("a".into()); context.map.insert("c".into(), "x".into()); - assert!(!predicate.eval(&context)); + assert!(!predicate.eval(&[context])); + let mut context = KeymapContext::default(); + context.set.insert("a".into()); context.map.insert("c".into(), "d".into()); - assert!(predicate.eval(&context)); + assert!(predicate.eval(&[context])); - let predicate = KeymapContextPredicate::parse("!a")?; - assert!(predicate.eval(&KeymapContext::default())); + let predicate = KeymapContextPredicate::parse("!a").unwrap(); + assert!(predicate.eval(&[KeymapContext::default()])); + } - Ok(()) + #[test] + fn test_context_child_predicate_eval() { + let predicate = KeymapContextPredicate::parse("a && b > c").unwrap(); + let contexts = [ + context_set(&["e", "f"]), + context_set(&["c", "d"]), // match this context + context_set(&["a", "b"]), + ]; + + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); + + let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap(); + let contexts = [ + context_set(&["f"]), + context_set(&["e"]), // only match this context + context_set(&["c"]), + context_set(&["a", "b"]), + context_set(&["e"]), + context_set(&["c", "d"]), + context_set(&["a", "b"]), + ]; + + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); + assert!(!predicate.eval(&contexts[3..])); + assert!(!predicate.eval(&contexts[4..])); + assert!(!predicate.eval(&contexts[5..])); + assert!(!predicate.eval(&contexts[6..])); + + fn context_set(names: &[&str]) -> KeymapContext { + KeymapContext { + set: names.iter().copied().map(str::to_string).collect(), + ..Default::default() + } + } } #[test] diff --git a/crates/gpui/src/keymap_matcher/binding.rs b/crates/gpui/src/keymap_matcher/binding.rs index b16b7f15523a3bd684aad1c3de5397be59f5df68..afd65d4f0424e0031090852d2441d1a6f8bd9420 100644 --- a/crates/gpui/src/keymap_matcher/binding.rs +++ b/crates/gpui/src/keymap_matcher/binding.rs @@ -41,24 +41,24 @@ impl Binding { }) } - fn match_context(&self, context: &KeymapContext) -> bool { + fn match_context(&self, contexts: &[KeymapContext]) -> bool { self.context_predicate .as_ref() - .map(|predicate| predicate.eval(context)) + .map(|predicate| predicate.eval(contexts)) .unwrap_or(true) } pub fn match_keys_and_context( &self, pending_keystrokes: &Vec, - context: &KeymapContext, + contexts: &[KeymapContext], ) -> BindingMatchResult { if self .keystrokes .as_ref() .map(|keystrokes| keystrokes.starts_with(&pending_keystrokes)) .unwrap_or(true) - && self.match_context(context) + && self.match_context(contexts) { // If the binding is completed, push it onto the matches list if self @@ -79,9 +79,9 @@ impl Binding { pub fn keystrokes_for_action( &self, action: &dyn Action, - context: &KeymapContext, + contexts: &[KeymapContext], ) -> Option> { - if self.action.eq(action) && self.match_context(context) { + if self.action.eq(action) && self.match_context(contexts) { self.keystrokes.clone() } else { None diff --git a/crates/gpui/src/keymap_matcher/keymap_context.rs b/crates/gpui/src/keymap_matcher/keymap_context.rs index b1dbd0e92d9cef855d0476c2bca1b7a07f162d88..28f5f80c8337696f06b47ccdb8ba595a3270d5df 100644 --- a/crates/gpui/src/keymap_matcher/keymap_context.rs +++ b/crates/gpui/src/keymap_matcher/keymap_context.rs @@ -23,6 +23,7 @@ pub enum KeymapContextPredicate { Identifier(String), Equal(String, String), NotEqual(String, String), + Child(Box, Box), Not(Box), And(Box, Box), Or(Box, Box), @@ -39,7 +40,8 @@ impl KeymapContextPredicate { } } - pub fn eval(&self, context: &KeymapContext) -> bool { + pub fn eval(&self, contexts: &[KeymapContext]) -> bool { + let Some(context) = contexts.first() else { return false }; match self { Self::Identifier(name) => context.set.contains(name.as_str()), Self::Equal(left, right) => context @@ -52,16 +54,14 @@ impl KeymapContextPredicate { .get(left) .map(|value| value != right) .unwrap_or(true), - Self::Not(pred) => !pred.eval(context), - Self::And(left, right) => left.eval(context) && right.eval(context), - Self::Or(left, right) => left.eval(context) || right.eval(context), + Self::Not(pred) => !pred.eval(contexts), + Self::Child(parent, child) => parent.eval(&contexts[1..]) && child.eval(contexts), + Self::And(left, right) => left.eval(contexts) && right.eval(contexts), + Self::Or(left, right) => left.eval(contexts) || right.eval(contexts), } } - fn parse_expr( - mut source: &str, - min_precedence: u32, - ) -> anyhow::Result<(KeymapContextPredicate, &str)> { + fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> { type Op = fn(KeymapContextPredicate, KeymapContextPredicate) -> Result; @@ -70,10 +70,11 @@ impl KeymapContextPredicate { 'parse: loop { for (operator, precedence, constructor) in [ - ("&&", PRECEDENCE_AND, KeymapContextPredicate::new_and as Op), - ("||", PRECEDENCE_OR, KeymapContextPredicate::new_or as Op), - ("==", PRECEDENCE_EQ, KeymapContextPredicate::new_eq as Op), - ("!=", PRECEDENCE_EQ, KeymapContextPredicate::new_neq as Op), + (">", PRECEDENCE_CHILD, Self::new_child as Op), + ("&&", PRECEDENCE_AND, Self::new_and as Op), + ("||", PRECEDENCE_OR, Self::new_or as Op), + ("==", PRECEDENCE_EQ, Self::new_eq as Op), + ("!=", PRECEDENCE_EQ, Self::new_neq as Op), ] { if source.starts_with(operator) && precedence >= min_precedence { source = Self::skip_whitespace(&source[operator.len()..]); @@ -89,7 +90,7 @@ impl KeymapContextPredicate { Ok((predicate, source)) } - fn parse_primary(mut source: &str) -> anyhow::Result<(KeymapContextPredicate, &str)> { + fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> { let next = source .chars() .next() @@ -140,6 +141,10 @@ impl KeymapContextPredicate { Ok(Self::And(Box::new(self), Box::new(other))) } + fn new_child(self, other: Self) -> Result { + Ok(Self::Child(Box::new(self), Box::new(other))) + } + fn new_eq(self, other: Self) -> Result { if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) { Ok(Self::Equal(left, right)) @@ -157,10 +162,11 @@ impl KeymapContextPredicate { } } -const PRECEDENCE_OR: u32 = 1; -const PRECEDENCE_AND: u32 = 2; -const PRECEDENCE_EQ: u32 = 3; -const PRECEDENCE_NOT: u32 = 4; +const PRECEDENCE_CHILD: u32 = 1; +const PRECEDENCE_OR: u32 = 2; +const PRECEDENCE_AND: u32 = 3; +const PRECEDENCE_EQ: u32 = 4; +const PRECEDENCE_NOT: u32 = 5; #[cfg(test)] mod tests { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 5c13f7467aa1e5e0abd589babc49250a113d1329..bbaf1ed0bbce3ec2a096e37719354ce100d00db6 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -573,7 +573,7 @@ pub struct LayoutContext<'a> { impl<'a> LayoutContext<'a> { pub(crate) fn keystrokes_for_action( - &self, + &mut self, action: &dyn Action, ) -> Option> { self.app From 3312a063688f047566cf95f7f5c9972e10f5da5b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 16:01:15 -0800 Subject: [PATCH 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 fbbb517c936b194318d409bd56c965e81d99d9a7..a0f437cf9182c2876197fb8b12a5be222712c481 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -186,10 +186,10 @@ } }, { - "context": "BufferSearchBar", + "context": "BufferSearchBar > Editor", "bindings": { "escape": "buffer_search::Dismiss", - "cmd-f": "buffer_search::FocusEditor", + "tab": "buffer_search::FocusEditor", "enter": "search::SelectNextMatch", "shift-enter": "search::SelectPrevMatch" } From 6810490bf4f856f187e53ca2af7cf1a0c04299d4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 16:11:13 -0800 Subject: [PATCH 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 3e79a68fc4041329aa868975fe0b8a767682eca9..d9976fbba7aa16fac83b7943cde97b04ca3b2895 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2563,7 +2563,6 @@ dependencies = [ "sum_tree", "time 0.3.17", "tiny-skia", - "tree-sitter", "usvg", "util", "waker-fn", diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 683e3bdfcd05f7794f326cafd1b20baea8462e99..5153c1f7c166e68081d4edaabd9afe36fcbc7e9f 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -45,7 +45,6 @@ smallvec = { version = "1.6", features = ["union"] } smol = "1.2" time = { version = "0.3", features = ["serde", "serde-well-known"] } tiny-skia = "0.5" -tree-sitter = "0.20" usvg = "0.14" waker-fn = "1.1.0" From 244f2593314e6c83c2c890af6c5351fea0ec037b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 17:42:00 -0800 Subject: [PATCH 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 8847c01af7aeecd449137b7b0843e306bb4e8df4..fb8e37077ec8996b41bdbbebafd1e6a69aa6d3f1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2020,7 +2020,9 @@ impl Editor { old_selections .iter() .map(|s| (s.start..s.end, text.clone())), - Some(AutoindentMode::EachLine), + Some(AutoindentMode::Block { + original_indent_columns: Vec::new(), + }), cx, ); anchors diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 9ceb6b32a994398307211b40e1b31a5880648f30..d1e7316f1b0c66b3cbdcb46c57bbd184ea6353b8 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1332,13 +1332,6 @@ impl Buffer { let edit_id = edit_operation.local_timestamp(); if let Some((before_edit, mode)) = autoindent_request { - let (start_columns, is_block_mode) = match mode { - AutoindentMode::Block { - original_indent_columns: start_columns, - } => (start_columns, true), - AutoindentMode::EachLine => (Default::default(), false), - }; - let mut delta = 0isize; let entries = edits .into_iter() @@ -1352,7 +1345,7 @@ impl Buffer { let mut range_of_insertion_to_indent = 0..new_text_len; let mut first_line_is_new = false; - let mut start_column = None; + let mut original_indent_column = None; // When inserting an entire line at the beginning of an existing line, // treat the insertion as new. @@ -1364,14 +1357,23 @@ impl Buffer { // When inserting text starting with a newline, avoid auto-indenting the // previous line. - if new_text[range_of_insertion_to_indent.clone()].starts_with('\n') { + if new_text.starts_with('\n') { range_of_insertion_to_indent.start += 1; first_line_is_new = true; } // Avoid auto-indenting after the insertion. - if is_block_mode { - start_column = start_columns.get(ix).copied(); + if let AutoindentMode::Block { + original_indent_columns, + } = &mode + { + original_indent_column = + Some(original_indent_columns.get(ix).copied().unwrap_or_else(|| { + indent_size_for_text( + new_text[range_of_insertion_to_indent.clone()].chars(), + ) + .len + })); if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') { range_of_insertion_to_indent.end -= 1; } @@ -1379,7 +1381,7 @@ impl Buffer { AutoindentRequestEntry { first_line_is_new, - original_indent_column: start_column, + original_indent_column, indent_size: before_edit.language_indent_size_at(range.start, cx), range: self.anchor_before(new_start + range_of_insertion_to_indent.start) ..self.anchor_after(new_start + range_of_insertion_to_indent.end), @@ -1390,7 +1392,7 @@ impl Buffer { self.autoindent_requests.push(Arc::new(AutoindentRequest { before_edit, entries, - is_block_mode, + is_block_mode: matches!(mode, AutoindentMode::Block { .. }), })); } @@ -2397,7 +2399,7 @@ impl BufferSnapshot { } } -pub fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize { +fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize { indent_size_for_text(text.chars_at(Point::new(row, 0))) } From 0bd6f9b6ceb09d639e8bb9aec886f5acb2008305 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 18:06:58 -0800 Subject: [PATCH 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 09ccc5d62138e1a351fcc60b9b5cb02088f17fb8..b7f35e9ad5cc8b20671faf49632339445af8c727 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -995,12 +995,17 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { .unindent(); let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + // When this text was copied, both of the quotation marks were at the same + // indent level, but the indentation of the first line was not included in + // the copied text. This information is retained in the + // 'original_indent_columns' vector. + let original_indent_columns = vec![4]; let inserted_text = r#" " - c - d - e - " + c + d + e + " "# .unindent(); @@ -1009,7 +1014,7 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { buffer.edit( [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())], Some(AutoindentMode::Block { - original_indent_columns: vec![0], + original_indent_columns: original_indent_columns.clone(), }), cx, ); @@ -1037,7 +1042,7 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { buffer.edit( [(Point::new(2, 8)..Point::new(2, 8), inserted_text)], Some(AutoindentMode::Block { - original_indent_columns: vec![0], + original_indent_columns: original_indent_columns.clone(), }), cx, ); @@ -1060,6 +1065,84 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) { }); } +#[gpui::test] +fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut MutableAppContext) { + cx.set_global(Settings::test(cx)); + cx.add_model(|cx| { + let text = r#" + fn a() { + if b() { + + } + } + "# + .unindent(); + let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + + // The original indent columns are not known, so this text is + // auto-indented in a block as if the first line was copied in + // its entirety. + let original_indent_columns = Vec::new(); + let inserted_text = " c\n .d()\n .e();"; + + // Insert the block at column zero. The entire block is indented + // so that the first line matches the previous line's indentation. + buffer.edit( + [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())], + Some(AutoindentMode::Block { + original_indent_columns: original_indent_columns.clone(), + }), + cx, + ); + assert_eq!( + buffer.text(), + r#" + fn a() { + if b() { + c + .d() + .e(); + } + } + "# + .unindent() + ); + + // Grouping is disabled in tests, so we need 2 undos + buffer.undo(cx); // Undo the auto-indent + buffer.undo(cx); // Undo the original edit + + // Insert the block at a deeper indent level. The entire block is outdented. + buffer.edit( + [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))], + None, + cx, + ); + buffer.edit( + [(Point::new(2, 12)..Point::new(2, 12), inserted_text)], + Some(AutoindentMode::Block { + original_indent_columns: Vec::new(), + }), + cx, + ); + assert_eq!( + buffer.text(), + r#" + fn a() { + if b() { + c + .d() + .e(); + } + } + "# + .unindent() + ); + + buffer + }); +} + #[gpui::test] fn test_autoindent_language_without_indents_query(cx: &mut MutableAppContext) { cx.set_global(Settings::test(cx)); From 467e5691b968134e8d29ee6c4a2ad2c73fcf134c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 16 Jan 2023 16:22:47 +0100 Subject: [PATCH 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 9ceb6b32a994398307211b40e1b31a5880648f30..b92f0a7c3e005f2be6de2ae1d133d0df405a6308 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -385,6 +385,11 @@ impl Buffer { rpc::proto::LineEnding::from_i32(message.line_ending) .ok_or_else(|| anyhow!("missing line_ending"))?, )); + this.saved_version_fingerprint = message.saved_version_fingerprint; + this.saved_mtime = message + .saved_mtime + .ok_or_else(|| anyhow!("invalid saved_mtime"))? + .into(); Ok(this) } @@ -395,6 +400,8 @@ impl Buffer { base_text: self.base_text().to_string(), diff_base: self.diff_base.as_ref().map(|h| h.to_string()), line_ending: proto::serialize_line_ending(self.line_ending()) as i32, + saved_version_fingerprint: self.saved_version_fingerprint.clone(), + saved_mtime: Some(self.saved_mtime.into()), } } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 8626806abe852a3c7d8a1d5ff5f46b125ff55534..86494df05b30dd18fa75f44f58cce537d7a6ecf8 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -975,6 +975,8 @@ message BufferState { string base_text = 3; optional string diff_base = 4; LineEnding line_ending = 5; + string saved_version_fingerprint = 6; + Timestamp saved_mtime = 7; } message BufferChunk { From 2cd9db1cfeb4dd787d88a806aca1301d69060136 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:20:09 +0100 Subject: [PATCH 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 11c62005c2d2b4115899046bdf784468123c72c8..85173857983de9462dc25494f55b953c52d386a0 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -10,7 +10,7 @@ use collections::BTreeMap; use fs::{FakeFs, Fs as _}; use futures::StreamExt as _; use gpui::{executor::Deterministic, ModelHandle, TestAppContext}; -use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16}; +use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16, Rope}; use lsp::FakeLanguageServer; use parking_lot::Mutex; use project::{search::SearchQuery, Project}; @@ -19,7 +19,12 @@ use rand::{ prelude::*, }; use settings::Settings; -use std::{env, ffi::OsStr, path::PathBuf, sync::Arc}; +use std::{ + env, + ffi::OsStr, + path::{Path, PathBuf}, + sync::Arc, +}; #[gpui::test(iterations = 100)] async fn test_random_collaboration( @@ -398,6 +403,14 @@ async fn test_random_collaboration( let guest_diff_base = guest_buffer .read_with(client_cx, |b, _| b.diff_base().map(ToString::to_string)); assert_eq!(guest_diff_base, host_diff_base); + + let host_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); + let guest_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); + assert_eq!(guest_is_dirty, host_is_dirty); + + let host_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); + let guest_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); + assert_eq!(guest_has_conflict, host_has_conflict); } } } @@ -638,14 +651,7 @@ async fn randomly_mutate_git(client: &mut TestClient, rng: &Mutex) { client.fs.create_dir(&git_dir_path).await.unwrap(); } - let mut child_paths = client.fs.read_dir(&dir_path).await.unwrap(); - let mut child_file_paths = Vec::new(); - while let Some(child_path) = child_paths.next().await { - let child_path = child_path.unwrap(); - if client.fs.is_file(&child_path).await { - child_file_paths.push(child_path); - } - } + let mut child_file_paths = child_file_paths(client, &dir_path).await; let count = rng.lock().gen_range(0..=child_file_paths.len()); child_file_paths.shuffle(&mut *rng.lock()); child_file_paths.truncate(count); @@ -669,26 +675,63 @@ async fn randomly_mutate_git(client: &mut TestClient, rng: &Mutex) { } async fn randomly_mutate_fs(client: &mut TestClient, rng: &Mutex) { - let is_dir = rng.lock().gen::(); - let mut new_path = client + let parent_dir_path = client .fs .directories() .await .choose(&mut *rng.lock()) .unwrap() .clone(); - new_path.push(gen_file_name(rng)); + + let is_dir = rng.lock().gen::(); if is_dir { - log::info!("{}: creating local dir at {:?}", client.username, new_path); - client.fs.create_dir(&new_path).await.unwrap(); + let mut dir_path = parent_dir_path.clone(); + dir_path.push(gen_file_name(rng)); + log::info!("{}: creating local dir at {:?}", client.username, dir_path); + client.fs.create_dir(&dir_path).await.unwrap(); } else { - new_path.set_extension("rs"); - log::info!("{}: creating local file at {:?}", client.username, new_path); - client - .fs - .create_file(&new_path, Default::default()) - .await - .unwrap(); + let child_file_paths = child_file_paths(client, &parent_dir_path).await; + let create_new_file = child_file_paths.is_empty() || rng.lock().gen(); + let text = Alphanumeric.sample_string(&mut *rng.lock(), 16); + if create_new_file { + let mut file_path = parent_dir_path.clone(); + file_path.push(gen_file_name(rng)); + file_path.set_extension("rs"); + log::info!( + "{}: creating local file at {:?}", + client.username, + file_path + ); + client + .fs + .create_file(&file_path, Default::default()) + .await + .unwrap(); + log::info!( + "{}: setting local file {:?} text to {:?}", + client.username, + file_path, + text + ); + client + .fs + .save(&file_path, &Rope::from(text.as_str()), fs::LineEnding::Unix) + .await + .unwrap(); + } else { + let file_path = child_file_paths.choose(&mut *rng.lock()).unwrap(); + log::info!( + "{}: setting local file {:?} text to {:?}", + client.username, + file_path, + text + ); + client + .fs + .save(file_path, &Rope::from(text.as_str()), fs::LineEnding::Unix) + .await + .unwrap(); + } } } @@ -1154,3 +1197,15 @@ fn gen_file_name(rng: &Mutex) -> String { } name } + +async fn child_file_paths(client: &TestClient, dir_path: &Path) -> Vec { + let mut child_paths = client.fs.read_dir(dir_path).await.unwrap(); + let mut child_file_paths = Vec::new(); + while let Some(child_path) = child_paths.next().await { + let child_path = child_path.unwrap(); + if client.fs.is_file(&child_path).await { + child_file_paths.push(child_path); + } + } + child_file_paths +} From bb200aa082f7bb760463fd55eebb562dc86d1601 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:20:50 +0100 Subject: [PATCH 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 b92f0a7c3e005f2be6de2ae1d133d0df405a6308..0e546d4ba0b471014c1e468a9ad4f8f11f42ff16 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -385,6 +385,7 @@ impl Buffer { rpc::proto::LineEnding::from_i32(message.line_ending) .ok_or_else(|| anyhow!("missing line_ending"))?, )); + this.saved_version = proto::deserialize_version(message.saved_version); this.saved_version_fingerprint = message.saved_version_fingerprint; this.saved_mtime = message .saved_mtime @@ -400,6 +401,7 @@ impl Buffer { base_text: self.base_text().to_string(), diff_base: self.diff_base.as_ref().map(|h| h.to_string()), line_ending: proto::serialize_line_ending(self.line_ending()) as i32, + saved_version: proto::serialize_version(&self.saved_version), saved_version_fingerprint: self.saved_version_fingerprint.clone(), saved_mtime: Some(self.saved_mtime.into()), } @@ -556,6 +558,14 @@ impl Buffer { &self.saved_version } + pub fn saved_version_fingerprint(&self) -> &str { + &self.saved_version_fingerprint + } + + pub fn saved_mtime(&self) -> SystemTime { + self.saved_mtime + } + pub fn set_language(&mut self, language: Option>, cx: &mut ModelContext) { self.syntax_map.lock().clear(); self.language = language; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 478271118c2c1be477fb6df8e4d44ff36ebe167f..d3b3c11f2eb09a3584ba5063acd001de9674f18e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -5209,6 +5209,19 @@ impl Project { }) .log_err(); + client + .send(proto::BufferReloaded { + project_id, + buffer_id, + version: language::proto::serialize_version(buffer.saved_version()), + mtime: Some(buffer.saved_mtime().into()), + fingerprint: buffer.saved_version_fingerprint().into(), + line_ending: language::proto::serialize_line_ending( + buffer.line_ending(), + ) as i32, + }) + .log_err(); + cx.background() .spawn( async move { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 86494df05b30dd18fa75f44f58cce537d7a6ecf8..ba481ce45ba7ab148d2f9b347d5d483e448b44f6 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -975,8 +975,9 @@ message BufferState { string base_text = 3; optional string diff_base = 4; LineEnding line_ending = 5; - string saved_version_fingerprint = 6; - Timestamp saved_mtime = 7; + repeated VectorClockEntry saved_version = 6; + string saved_version_fingerprint = 7; + Timestamp saved_mtime = 8; } message BufferChunk { From fcf97ab41eabfa6719138a368e0d0301e3daa030 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:21:20 +0100 Subject: [PATCH 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 39cdea71651ac8c127a73f10208a31c787126a2f..b05bc17906063fe51dc4ec349496f26a1338a9dc 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 45; +pub const PROTOCOL_VERSION: u32 = 46; From 7726a9ec3d8e2390f55fb0c10585c8861626d5f7 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 17 Jan 2023 10:42:53 -0500 Subject: [PATCH 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 8847c01af7aeecd449137b7b0843e306bb4e8df4..e2bf3c775ba2e0090552ddb5b7f6e825df5620f9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4799,7 +4799,7 @@ impl Editor { if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() { let (group_id, jump_to) = popover.activation_info(); if self.activate_diagnostics(group_id, cx) { - self.change_selections(Some(Autoscroll::center()), cx, |s| { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { let mut new_selection = s.newest_anchor().clone(); new_selection.collapse_to(jump_to, SelectionGoal::None); s.select_anchors(vec![new_selection.clone()]); @@ -4845,7 +4845,7 @@ impl Editor { if let Some((primary_range, group_id)) = group { if self.activate_diagnostics(group_id, cx) { - self.change_selections(Some(Autoscroll::center()), cx, |s| { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select(vec![Selection { id: selection.id, start: primary_range.start, @@ -4920,7 +4920,7 @@ impl Editor { .dedup(); if let Some(hunk) = hunks.next() { - this.change_selections(Some(Autoscroll::center()), cx, |s| { + this.change_selections(Some(Autoscroll::fit()), cx, |s| { let row = hunk.start_display_row(); let point = DisplayPoint::new(row, 0); s.select_display_ranges([point..point]); @@ -5018,7 +5018,7 @@ impl Editor { if editor_handle != target_editor_handle { pane.update(cx, |pane, _| pane.disable_history()); } - target_editor.change_selections(Some(Autoscroll::center()), cx, |s| { + target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges([range]); }); From cc788dc5f74164a9bab5eeefed015e7be165157f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 16:46:06 +0100 Subject: [PATCH 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 85173857983de9462dc25494f55b953c52d386a0..34346e2128a09fc7e338a7e82b9c4bf3f28bc66a 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -404,6 +404,25 @@ async fn test_random_collaboration( .read_with(client_cx, |b, _| b.diff_base().map(ToString::to_string)); assert_eq!(guest_diff_base, host_diff_base); + let host_saved_version = + host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); + let guest_saved_version = + host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); + assert_eq!(guest_saved_version, host_saved_version); + + let host_saved_version_fingerprint = host_buffer + .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); + let guest_saved_version_fingerprint = host_buffer + .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); + assert_eq!( + guest_saved_version_fingerprint, + host_saved_version_fingerprint + ); + + let host_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); + let guest_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); + assert_eq!(guest_saved_mtime, host_saved_mtime); + let host_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); let guest_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); assert_eq!(guest_is_dirty, host_is_dirty); From dc88a67f502dc77273b0170d7358ce7c45f79453 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 17 Jan 2023 18:09:45 +0100 Subject: [PATCH 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 34346e2128a09fc7e338a7e82b9c4bf3f28bc66a..ed8f984d274228b7c777635c73e6ac2686407991 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -407,28 +407,28 @@ async fn test_random_collaboration( let host_saved_version = host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); let guest_saved_version = - host_buffer.read_with(host_cx, |b, _| b.saved_version().clone()); + guest_buffer.read_with(client_cx, |b, _| b.saved_version().clone()); assert_eq!(guest_saved_version, host_saved_version); let host_saved_version_fingerprint = host_buffer .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); - let guest_saved_version_fingerprint = host_buffer - .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); + let guest_saved_version_fingerprint = guest_buffer + .read_with(client_cx, |b, _| b.saved_version_fingerprint().to_string()); assert_eq!( guest_saved_version_fingerprint, host_saved_version_fingerprint ); let host_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); - let guest_saved_mtime = host_buffer.read_with(host_cx, |b, _| b.saved_mtime()); + let guest_saved_mtime = guest_buffer.read_with(client_cx, |b, _| b.saved_mtime()); assert_eq!(guest_saved_mtime, host_saved_mtime); let host_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); - let guest_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty()); + let guest_is_dirty = guest_buffer.read_with(client_cx, |b, _| b.is_dirty()); assert_eq!(guest_is_dirty, host_is_dirty); let host_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); - let guest_has_conflict = host_buffer.read_with(host_cx, |b, _| b.has_conflict()); + let guest_has_conflict = guest_buffer.read_with(client_cx, |b, _| b.has_conflict()); assert_eq!(guest_has_conflict, host_has_conflict); } } From c3b102f5a82efa010b6d0fd9824b76f562a198ca Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 17 Jan 2023 16:46:01 -0500 Subject: [PATCH 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 921b4189e824dab10e6692991654c2de223ac096..235ed66424487ce3b3af84e88378c0007c3d01d5 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -353,6 +353,8 @@ pub struct CreateInviteFromCodeParams { invite_code: String, email_address: String, device_id: Option, + #[serde(default)] + added_to_mailing_list: bool, } async fn create_invite_from_code( @@ -365,6 +367,7 @@ async fn create_invite_from_code( ¶ms.invite_code, ¶ms.email_address, params.device_id.as_deref(), + params.added_to_mailing_list, ) .await?, )) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 1bbfac85d3d7673fce279cd8ddc52879fe6fe934..63ea7fdd9e128ee76206f7bcf7829bc05bee8d52 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -882,6 +882,7 @@ impl Database { code: &str, email_address: &str, device_id: Option<&str>, + added_to_mailing_list: bool, ) -> Result { self.transaction(|tx| async move { let existing_user = user::Entity::find() @@ -933,6 +934,7 @@ impl Database { platform_windows: ActiveValue::set(false), platform_unknown: ActiveValue::set(true), device_id: ActiveValue::set(device_id.map(|device_id| device_id.into())), + added_to_mailing_list: ActiveValue::set(added_to_mailing_list), ..Default::default() }) .on_conflict( diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index 9d42c11f8bba88bd164eaab443857d910b97230d..1e271675458114bda3039f2ef272eb74eaecc2b2 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -567,7 +567,12 @@ async fn test_invite_codes() { // User 2 redeems the invite code and becomes a contact of user 1. let user2_invite = db - .create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id")) + .create_invite_from_code( + &invite_code, + "user2@example.com", + Some("user-2-device-id"), + true, + ) .await .unwrap(); let NewUserResult { @@ -617,7 +622,7 @@ async fn test_invite_codes() { // User 3 redeems the invite code and becomes a contact of user 1. let user3_invite = db - .create_invite_from_code(&invite_code, "user3@example.com", None) + .create_invite_from_code(&invite_code, "user3@example.com", None, true) .await .unwrap(); let NewUserResult { @@ -672,9 +677,14 @@ async fn test_invite_codes() { ); // Trying to reedem the code for the third time results in an error. - db.create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id")) - .await - .unwrap_err(); + db.create_invite_from_code( + &invite_code, + "user4@example.com", + Some("user-4-device-id"), + true, + ) + .await + .unwrap_err(); // Invite count can be updated after the code has been created. db.set_invite_count_for_user(user1, 2).await.unwrap(); @@ -684,7 +694,12 @@ async fn test_invite_codes() { // User 4 can now redeem the invite code and becomes a contact of user 1. let user4_invite = db - .create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id")) + .create_invite_from_code( + &invite_code, + "user4@example.com", + Some("user-4-device-id"), + true, + ) .await .unwrap(); let user4 = db @@ -739,9 +754,14 @@ async fn test_invite_codes() { ); // An existing user cannot redeem invite codes. - db.create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id")) - .await - .unwrap_err(); + db.create_invite_from_code( + &invite_code, + "user2@example.com", + Some("user-2-device-id"), + true, + ) + .await + .unwrap_err(); let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap(); assert_eq!(invite_count, 1); @@ -763,7 +783,7 @@ async fn test_invite_codes() { db.set_invite_count_for_user(user5, 5).await.unwrap(); let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap(); let user5_invite_to_user1 = db - .create_invite_from_code(&user5_invite_code, "user1@different.com", None) + .create_invite_from_code(&user5_invite_code, "user1@different.com", None, true) .await .unwrap(); let user1_2 = db From 292708573fc2e85602e452bd4551acaeb7f64c7d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 17 Jan 2023 16:13:26 -0800 Subject: [PATCH 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 f8fa3c9df0e68b0c813616e6480a9a98382866c9..1fe0329405741d87cb7da4da686dfda37aca1116 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -565,12 +565,14 @@ impl Item for Editor { } fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - self.buffer - .read(cx) - .files(cx) - .into_iter() - .filter_map(|file| File::from_dyn(Some(file))?.project_entry_id(cx)) - .collect() + let mut result = SmallVec::new(); + self.buffer.read(cx).for_each_buffer(|buffer| { + let buffer = buffer.read(cx); + if let Some(file) = File::from_dyn(buffer.file()) { + result.extend(file.project_entry_id(cx)); + } + }); + result } fn is_singleton(&self, cx: &AppContext) -> bool { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 2347d9a63d1fbaa8d88555c45eb8fe434530138a..c8a62c6a57326683b21515c06f3b3923135c0732 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -9,11 +9,10 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, - DiagnosticEntry, File, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, + DiagnosticEntry, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; -use smallvec::SmallVec; use std::{ borrow::Cow, cell::{Ref, RefCell}, @@ -1311,12 +1310,11 @@ impl MultiBuffer { .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset)) } - pub fn files<'a>(&'a self, cx: &'a AppContext) -> SmallVec<[&'a Arc; 2]> { - let buffers = self.buffers.borrow(); - buffers + pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle)) { + self.buffers + .borrow() .values() - .filter_map(|buffer| buffer.buffer.read(cx).file()) - .collect() + .for_each(|state| f(&state.buffer)) } pub fn title<'a>(&'a self, cx: &'a AppContext) -> Cow<'a, str> { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index bd355b8b3e75465c17267468202eb8dbffa906cc..735e165c7d644d4842d15e6ae1dfa276585084c8 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -482,7 +482,7 @@ impl Pane { ) -> Box { let existing_item = pane.update(cx, |pane, cx| { for (index, item) in pane.items.iter().enumerate() { - if item.project_path(cx).is_some() + if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [project_entry_id] { let item = item.boxed_clone(); From c9a306b4ac0995aa27d3f2ff8f984bf462446b1f Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 17 Jan 2023 16:58:55 -0800 Subject: [PATCH 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 69c6db51ab3a4a3fbb28e91fc20b825d2ff6ec31..747541f87d6faaed9a3e3883a9a4d0e5f5072403 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -2,8 +2,10 @@ use collections::HashMap; use gpui::{ actions, elements::{ChildView, Container, Empty, MouseEventHandler, ParentElement, Side, Stack, Svg}, + geometry::vector::Vector2F, impl_internal_actions, Border, CursorStyle, Element, ElementBox, Entity, MouseButton, - MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle, + MutableAppContext, RenderContext, SizeConstraint, View, ViewContext, ViewHandle, + WeakViewHandle, }; use serde::Deserialize; use settings::{DockAnchor, Settings}; @@ -312,7 +314,27 @@ impl Dock { } }); - resizable.flex(5., false).boxed() + if anchor == DockAnchor::Right { + resizable + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new(cx.window_size.x() * 0.8, constraint.max.y()), + ) + }) + .boxed() + } else { + resizable + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(constraint.min.x(), 50.), + Vector2F::new(constraint.max.x(), cx.window_size.y() * 0.8), + ) + }) + .boxed() + } } DockAnchor::Expanded => { enum ExpandedDockWash {} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index eefce67371e33099c5b478f2f95b0999e5b7188b..de5b50b468ece25dd293f64719af27a37275b54c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -32,12 +32,13 @@ use futures::{ use gpui::{ actions, elements::*, + geometry::vector::Vector2F, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, - MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -2471,7 +2472,16 @@ impl View for Workspace { if self.left_sidebar.read(cx).active_item().is_some() { Some( ChildView::new(&self.left_sidebar, cx) - .flex(0.8, false) + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new( + cx.window_size.x() * 0.8, + constraint.max.y(), + ), + ) + }) .boxed(), ) } else { @@ -2508,7 +2518,16 @@ impl View for Workspace { if self.right_sidebar.read(cx).active_item().is_some() { Some( ChildView::new(&self.right_sidebar, cx) - .flex(0.8, false) + .constrained() + .dynamically(|constraint, cx| { + SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new( + cx.window_size.x() * 0.8, + constraint.max.y(), + ), + ) + }) .boxed(), ) } else { From 8651320c9f129ea2bf12f26ea425d36f439e9313 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 17 Jan 2023 17:21:06 -0800 Subject: [PATCH 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 729da6d10980a6d8b8a7a335fbd16bd01d0b06e3..3f2a777f87927e0f9bb5b75c533a20a184e76284 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -32,7 +32,9 @@ use std::{ sync::Arc, }; use unindent::Unindent as _; -use workspace::{item::Item, shared_screen::SharedScreen, SplitDirection, ToggleFollow, Workspace}; +use workspace::{ + item::ItemHandle as _, shared_screen::SharedScreen, SplitDirection, ToggleFollow, Workspace, +}; #[ctor::ctor] fn init_logger() { @@ -5602,7 +5604,7 @@ async fn test_following( }); assert!(cx_b.read(|cx| editor_b2.is_focused(cx))); assert_eq!( - editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)), + cx_b.read(|cx| editor_b2.project_path(cx)), Some((worktree_id, "2.txt").into()) ); assert_eq!( diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index e98aa645d562b8f2ec76c6e85e49ad565e733f56..d3078bce81493e749c9156635c12d895eea1bd30 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -21,7 +21,6 @@ use language::{ use project::{DiagnosticSummary, Project, ProjectPath}; use serde_json::json; use settings::Settings; -use smallvec::SmallVec; use std::{ any::{Any, TypeId}, cmp::Ordering, @@ -521,12 +520,8 @@ impl Item for ProjectDiagnosticsEditor { ) } - fn project_path(&self, _: &AppContext) -> Option { - None - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - self.editor.project_entry_ids(cx) + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.editor.for_each_project_item(cx, f) } fn is_singleton(&self, _: &AppContext) -> bool { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 1fe0329405741d87cb7da4da686dfda37aca1116..6568fe78dd01bcd1b7dcd1ef756c1f5bd79407a9 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -14,11 +14,10 @@ use gpui::{ RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::proto::serialize_anchor as serialize_text_anchor; -use language::{Bias, Buffer, File as _, OffsetRangeExt, Point, SelectionGoal}; -use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath}; +use language::{Bias, Buffer, OffsetRangeExt, Point, SelectionGoal}; +use project::{FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; -use smallvec::SmallVec; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -555,24 +554,10 @@ impl Item for Editor { .boxed() } - fn project_path(&self, cx: &AppContext) -> Option { - let buffer = self.buffer.read(cx).as_singleton()?; - let file = buffer.read(cx).file(); - File::from_dyn(file).map(|file| ProjectPath { - worktree_id: file.worktree_id(cx), - path: file.path().clone(), - }) - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - let mut result = SmallVec::new(); - self.buffer.read(cx).for_each_buffer(|buffer| { - let buffer = buffer.read(cx); - if let Some(file) = File::from_dyn(buffer.file()) { - result.extend(file.project_entry_id(cx)); - } - }); - result + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.buffer + .read(cx) + .for_each_buffer(|buffer| f(buffer.id(), buffer.read(cx))); } fn is_singleton(&self, cx: &AppContext) -> bool { @@ -609,7 +594,12 @@ impl Item for Editor { } fn can_save(&self, cx: &AppContext) -> bool { - !self.buffer().read(cx).is_singleton() || self.project_path(cx).is_some() + let buffer = &self.buffer().read(cx); + if let Some(buffer) = buffer.as_singleton() { + buffer.read(cx).project_path(cx).is_some() + } else { + true + } } fn save( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d3b3c11f2eb09a3584ba5063acd001de9674f18e..66b016e27c328d1e91e74dd305ff93d3a9994893 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -67,8 +67,9 @@ use util::{debug_panic, defer, post_inc, ResultExt, TryFutureExt as _}; pub use fs::*; pub use worktree::*; -pub trait Item: Entity { +pub trait Item { fn entry_id(&self, cx: &AppContext) -> Option; + fn project_path(&self, cx: &AppContext) -> Option; } // Language server state is stored across 3 collections: @@ -6401,4 +6402,11 @@ impl Item for Buffer { fn entry_id(&self, cx: &AppContext) -> Option { File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx)) } + + fn project_path(&self, cx: &AppContext) -> Option { + File::from_dyn(self.file()).map(|file| ProjectPath { + worktree_id: file.worktree_id(cx), + path: file.path().clone(), + }) + } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 9d2f31e2690c38f1ece7bbf475fcaeb4c79311d8..d30f4d804c7d362c2054e365a7d4061f0607a9a5 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -15,7 +15,6 @@ use gpui::{ use menu::Confirm; use project::{search::SearchQuery, Project}; use settings::Settings; -use smallvec::SmallVec; use std::{ any::{Any, TypeId}, ops::Range, @@ -264,12 +263,8 @@ impl Item for ProjectSearchView { .boxed() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - None - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - self.results_editor.project_entry_ids(cx) + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.results_editor.for_each_project_item(cx, f) } fn is_singleton(&self, _: &AppContext) -> bool { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index e02a9758dd24f4568cce6b33da0099f53c259fd0..847dfc5ee5c6710a7faefda6fe80131812733954 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -18,10 +18,9 @@ use gpui::{ AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use project::{LocalWorktree, Project, ProjectPath}; +use project::{LocalWorktree, Project}; use serde::Deserialize; use settings::{Settings, TerminalBlink, WorkingDirectory}; -use smallvec::SmallVec; use smol::Timer; use terminal::{ alacritty_terminal::{ @@ -616,13 +615,7 @@ impl Item for TerminalView { None } - fn project_path(&self, _cx: &gpui::AppContext) -> Option { - None - } - - fn project_entry_ids(&self, _cx: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - SmallVec::new() - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} fn is_singleton(&self, _cx: &gpui::AppContext) -> bool { false diff --git a/crates/theme_testbench/src/theme_testbench.rs b/crates/theme_testbench/src/theme_testbench.rs index cf9f03de45f351cae59a89eaca78654074daff85..3cda5d3e51078857d622478262b399400538f222 100644 --- a/crates/theme_testbench/src/theme_testbench.rs +++ b/crates/theme_testbench/src/theme_testbench.rs @@ -6,12 +6,11 @@ use gpui::{ Padding, ParentElement, }, fonts::TextStyle, - Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + AppContext, Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task, + View, ViewContext, ViewHandle, WeakViewHandle, }; -use project::{Project, ProjectEntryId, ProjectPath}; +use project::Project; use settings::Settings; -use smallvec::SmallVec; use theme::{ColorScheme, Layer, Style, StyleSet}; use workspace::{ item::{Item, ItemEvent}, @@ -306,7 +305,7 @@ impl Item for ThemeTestbench { &self, _: Option, style: &theme::Tab, - _: &gpui::AppContext, + _: &AppContext, ) -> gpui::ElementBox { Label::new("Theme Testbench".into(), style.label.clone()) .aligned() @@ -314,21 +313,15 @@ impl Item for ThemeTestbench { .boxed() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - None - } - - fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> { - SmallVec::new() - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - fn is_singleton(&self, _: &gpui::AppContext) -> bool { + fn is_singleton(&self, _: &AppContext) -> bool { false } fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} - fn can_save(&self, _: &gpui::AppContext) -> bool { + fn can_save(&self, _: &AppContext) -> bool { false } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 19385038e4a81bd06c4fb2f63d78cca9840b0c4a..9d5b94bddb8a99e4f7be34d62ac88d913c131f4e 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -49,8 +49,7 @@ pub trait Item: View { } fn tab_content(&self, detail: Option, style: &theme::Tab, cx: &AppContext) -> ElementBox; - fn project_path(&self, cx: &AppContext) -> Option; - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)); fn is_singleton(&self, cx: &AppContext) -> bool; fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext); fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option @@ -147,6 +146,8 @@ pub trait ItemHandle: 'static + fmt::Debug { -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; + fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>; + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)); fn is_singleton(&self, cx: &AppContext) -> bool; fn boxed_clone(&self) -> Box; fn clone_on_split( @@ -240,11 +241,36 @@ impl ItemHandle for ViewHandle { } fn project_path(&self, cx: &AppContext) -> Option { - self.read(cx).project_path(cx) + let this = self.read(cx); + let mut result = None; + if this.is_singleton(cx) { + this.for_each_project_item(cx, &mut |_, item| { + result = item.project_path(cx); + }); + } + result } fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - self.read(cx).project_entry_ids(cx) + let mut result = SmallVec::new(); + self.read(cx).for_each_project_item(cx, &mut |_, item| { + if let Some(id) = item.entry_id(cx) { + result.push(id); + } + }); + result + } + + fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]> { + let mut result = SmallVec::new(); + self.read(cx).for_each_project_item(cx, &mut |id, _| { + result.push(id); + }); + result + } + + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.read(cx).for_each_project_item(cx, f) } fn is_singleton(&self, cx: &AppContext) -> bool { @@ -582,7 +608,7 @@ impl WeakItemHandle for WeakViewHandle { } pub trait ProjectItem: Item { - type Item: project::Item; + type Item: project::Item + gpui::Entity; fn for_project_item( project: ModelHandle, @@ -690,18 +716,18 @@ impl FollowableItemHandle for ViewHandle { #[cfg(test)] pub(crate) mod test { - use std::{any::Any, borrow::Cow, cell::Cell}; - + use super::{Item, ItemEvent}; + use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ - elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext, Task, - View, ViewContext, ViewHandle, WeakViewHandle, + elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, + RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use project::{Project, ProjectEntryId, ProjectPath}; - use smallvec::SmallVec; - - use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; + use std::{any::Any, borrow::Cow, cell::Cell}; - use super::{Item, ItemEvent}; + pub struct TestProjectItem { + pub entry_id: Option, + } pub struct TestItem { pub workspace_id: WorkspaceId, @@ -713,13 +739,27 @@ pub(crate) mod test { pub is_dirty: bool, pub is_singleton: bool, pub has_conflict: bool, - pub project_entry_ids: Vec, + pub project_items: Vec>, pub project_path: Option, pub nav_history: Option, pub tab_descriptions: Option>, pub tab_detail: Cell>, } + impl Entity for TestProjectItem { + type Event = (); + } + + impl project::Item for TestProjectItem { + fn entry_id(&self, _: &AppContext) -> Option { + self.entry_id + } + + fn project_path(&self, _: &AppContext) -> Option { + None + } + } + pub enum TestItemEvent { Edit, } @@ -735,7 +775,7 @@ pub(crate) mod test { is_dirty: self.is_dirty, is_singleton: self.is_singleton, has_conflict: self.has_conflict, - project_entry_ids: self.project_entry_ids.clone(), + project_items: self.project_items.clone(), project_path: self.project_path.clone(), nav_history: None, tab_descriptions: None, @@ -755,7 +795,7 @@ pub(crate) mod test { reload_count: 0, is_dirty: false, has_conflict: false, - project_entry_ids: Vec::new(), + project_items: Vec::new(), project_path: None, is_singleton: true, nav_history: None, @@ -781,13 +821,26 @@ pub(crate) mod test { self } - pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self { - self.project_entry_ids.extend( - project_entry_ids - .iter() - .copied() - .map(ProjectEntryId::from_proto), - ); + pub fn with_dirty(mut self, dirty: bool) -> Self { + self.is_dirty = dirty; + self + } + + pub fn with_conflict(mut self, has_conflict: bool) -> Self { + self.has_conflict = has_conflict; + self + } + + pub fn with_project_entry_ids( + mut self, + project_entry_ids: &[u64], + cx: &mut MutableAppContext, + ) -> Self { + self.project_items + .extend(project_entry_ids.iter().copied().map(|id| { + let id = ProjectEntryId::from_proto(id); + cx.add_model(|_| TestProjectItem { entry_id: Some(id) }) + })); self } @@ -830,12 +883,14 @@ pub(crate) mod test { Empty::new().boxed() } - fn project_path(&self, _: &AppContext) -> Option { - self.project_path.clone() - } - - fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - self.project_entry_ids.iter().copied().collect() + fn for_each_project_item( + &self, + cx: &AppContext, + f: &mut dyn FnMut(usize, &dyn project::Item), + ) { + self.project_items + .iter() + .for_each(|item| f(item.id(), item.read(cx))) } fn is_singleton(&self, _: &AppContext) -> bool { @@ -880,7 +935,7 @@ pub(crate) mod test { } fn can_save(&self, _: &AppContext) -> bool { - !self.project_entry_ids.is_empty() + !self.project_items.is_empty() } fn save( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 735e165c7d644d4842d15e6ae1dfa276585084c8..4d5a73b2b33785dcbeaffc2f82efc83a328b42e2 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -809,8 +809,8 @@ impl Pane { // Find the item's current index and its set of project entries. Avoid // storing these in advance, in case they have changed since this task // was started. - let (item_ix, mut project_entry_ids) = pane.read_with(&cx, |pane, cx| { - (pane.index_for_item(&*item), item.project_entry_ids(cx)) + let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { + (pane.index_for_item(&*item), item.project_item_model_ids(cx)) }); let item_ix = if let Some(ix) = item_ix { ix @@ -823,25 +823,20 @@ impl Pane { // any project entries that are not open anywhere else in the workspace, // AND that the user has not already been prompted to save. If there are // any such project entries, prompt the user to save this item. - let should_save = if project_entry_ids.is_empty() { - true - } else { - workspace.read_with(&cx, |workspace, cx| { - for item in workspace.items(cx) { - if !items_to_close - .iter() - .any(|item_to_close| item_to_close.id() == item.id()) - { - let other_project_entry_ids = item.project_entry_ids(cx); - project_entry_ids - .retain(|id| !other_project_entry_ids.contains(id)); - } + workspace.read_with(&cx, |workspace, cx| { + for item in workspace.items(cx) { + if !items_to_close + .iter() + .any(|item_to_close| item_to_close.id() == item.id()) + { + let other_project_item_ids = item.project_item_model_ids(cx); + project_item_ids.retain(|id| !other_project_item_ids.contains(id)); } - }); - project_entry_ids - .iter() - .any(|id| saved_project_entry_ids.insert(*id)) - }; + } + }); + let should_save = project_item_ids + .iter() + .any(|id| saved_project_entry_ids.insert(*id)); if should_save && !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx) @@ -1866,7 +1861,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, @@ -1885,7 +1880,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, @@ -1904,7 +1899,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 2") - .with_project_entry_ids(&[2]); + .with_project_entry_ids(&[2], cx); Pane::add_item( workspace, @@ -1923,7 +1918,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, @@ -1942,7 +1937,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1b") - .with_project_entry_ids(&[1]); + .with_project_entry_ids(&[1], cx); Pane::add_item( workspace, diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index d292ece3d5fb821f07e8ed80a0931ebb44bf0e96..b76535f6ed226fc0e63fa490779a731ef0dd089f 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -8,12 +8,11 @@ use futures::StreamExt; use gpui::{ elements::*, geometry::{rect::RectF, vector::vec2f}, - Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext, ViewHandle, - WeakViewHandle, + AppContext, Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext, + ViewHandle, WeakViewHandle, }; use project::Project; use settings::Settings; -use smallvec::SmallVec; use std::{ path::PathBuf, sync::{Arc, Weak}, @@ -106,7 +105,7 @@ impl Item for SharedScreen { &self, _: Option, style: &theme::Tab, - _: &gpui::AppContext, + _: &AppContext, ) -> gpui::ElementBox { Flex::row() .with_child( @@ -130,15 +129,9 @@ impl Item for SharedScreen { .boxed() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - Default::default() - } - - fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - Default::default() - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - fn is_singleton(&self, _: &gpui::AppContext) -> bool { + fn is_singleton(&self, _: &AppContext) -> bool { false } @@ -155,7 +148,7 @@ impl Item for SharedScreen { Some(Self::new(&track, self.peer_id, self.user.clone(), cx)) } - fn can_save(&self, _: &gpui::AppContext) -> bool { + fn can_save(&self, _: &AppContext) -> bool { false } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index eefce67371e33099c5b478f2f95b0999e5b7188b..c7aa0798a3d81f304013f3fd4287a456f06de9b3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2943,16 +2943,11 @@ mod tests { // When there are dirty untitled items, prompt to save each one. If the user // cancels any prompt, then abort. - let item2 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item - }); - let item3 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item2 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); + let item3 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_project_entry_ids(&[1], cx) }); workspace.update(cx, |w, cx| { w.add_item(Box::new(item2.clone()), cx); @@ -2977,31 +2972,24 @@ mod tests { Workspace::new(Default::default(), 0, project, default_item_factory, cx) }); - let item1 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item1 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_project_entry_ids(&[1], cx) }); - let item2 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.has_conflict = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(2)]; - item + let item2 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_conflict(true) + .with_project_entry_ids(&[2], cx) }); - let item3 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.has_conflict = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(3)]; - item - }); - let item4 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.is_dirty = true; - item + let item3 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_conflict(true) + .with_project_entry_ids(&[3], cx) }); + let item4 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); let pane = workspace.update(cx, |workspace, cx| { workspace.add_item(Box::new(item1.clone()), cx); workspace.add_item(Box::new(item2.clone()), cx); @@ -3078,29 +3066,26 @@ mod tests { // workspace items with multiple project entries. let single_entry_items = (0..=4) .map(|project_entry_id| { - let mut item = TestItem::new(); - item.is_dirty = true; - item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)]; - item.is_singleton = true; - item + cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_singleton(true) + .with_project_entry_ids(&[project_entry_id], cx) + }) }) .collect::>(); - let item_2_3 = { - let mut item = TestItem::new(); - item.is_dirty = true; - item.is_singleton = false; - item.project_entry_ids = - vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)]; - item - }; - let item_3_4 = { - let mut item = TestItem::new(); - item.is_dirty = true; - item.is_singleton = false; - item.project_entry_ids = - vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)]; - item - }; + let item_2_3 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_singleton(false) + .with_project_entry_ids(&[2, 3], cx) + }); + let item_3_4 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_singleton(false) + .with_project_entry_ids(&[3, 4], cx) + }); // Create two panes that contain the following project entries: // left pane: @@ -3111,9 +3096,9 @@ mod tests { // multi-entry items: (3, 4) let left_pane = workspace.update(cx, |workspace, cx| { let left_pane = workspace.active_pane().clone(); - workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx); - for item in &single_entry_items { - workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx); + workspace.add_item(Box::new(item_2_3.clone()), cx); + for item in single_entry_items { + workspace.add_item(Box::new(item), cx); } left_pane.update(cx, |pane, cx| { pane.activate_item(2, true, true, cx); @@ -3128,7 +3113,7 @@ mod tests { //Need to cause an effect flush in order to respect new focus workspace.update(cx, |workspace, cx| { - workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx); + workspace.add_item(Box::new(item_3_4.clone()), cx); cx.focus(left_pane.clone()); }); @@ -3177,10 +3162,8 @@ mod tests { Workspace::new(Default::default(), 0, project, default_item_factory, cx) }); - let item = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_entry_ids(&[1], cx) }); let item_id = item.id(); workspace.update(cx, |workspace, cx| { @@ -3265,7 +3248,9 @@ mod tests { workspace.add_item(Box::new(item.clone()), cx); }); item.update(cx, |item, cx| { - item.project_entry_ids = Default::default(); + item.project_items[0].update(cx, |item, _| { + item.entry_id = None; + }); item.is_dirty = true; cx.blur(); }); @@ -3296,10 +3281,8 @@ mod tests { Workspace::new(Default::default(), 0, project, default_item_factory, cx) }); - let item = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; - item + let item = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_entry_ids(&[1], cx) }); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); From c3518cefe832535a8ba2a3cd7c8a7a8ebb9a0b27 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 17 Jan 2023 17:32:10 -0800 Subject: [PATCH 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 7b777a50edd9d69253688797f239c5c16e91f268..c526e3b1dc7eba5a2edbed82bcaf7408bf0574d8 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -37,9 +37,7 @@ fn editor_focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppCont let editor_mode = editor.mode(); let newest_selection_empty = editor.selections.newest::(cx).is_empty(); - if editor_mode != EditorMode::Full { - vim.switch_mode(Mode::Insert, true, cx); - } else if !newest_selection_empty { + if editor_mode == EditorMode::Full && !newest_selection_empty { vim.switch_mode(Mode::Visual { line: false }, true, cx); } }); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 8c5dae1e01fa7db5c4d5e477d1c08874e1feaad3..9f799ef37f9f3dbcecb168420d6892e3b8534587 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -12,7 +12,7 @@ mod visual; use collections::HashMap; use command_palette::CommandPaletteFilter; -use editor::{Bias, Cancel, Editor}; +use editor::{Bias, Cancel, Editor, EditorMode}; use gpui::{ impl_actions, keymap_matcher::{KeyPressed, Keystroke}, @@ -267,7 +267,7 @@ impl Vim { for editor in self.editors.values() { if let Some(editor) = editor.upgrade(cx) { editor.update(cx, |editor, cx| { - if self.enabled { + if self.enabled && editor.mode() == EditorMode::Full { editor.set_cursor_shape(cursor_shape, cx); editor.set_clip_at_line_ends(state.clip_at_line_end(), cx); editor.set_input_enabled(!state.vim_controlled()); From cf193154e10266c35dfc5539702d04a0cc1c7979 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 17 Jan 2023 17:35:39 -0800 Subject: [PATCH 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 e320962cfa30cf6ca91e39628c3b19f81237bc53..0214806e1195bd5d18f97f6eb15de8e023bb17aa 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -84,9 +84,6 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) { ); cx.simulate_keystroke("/"); - // We now use a weird insert mode with selection when jumping to a single line editor - assert_eq!(cx.mode(), Mode::Insert); - let search_bar = cx.workspace(|workspace, cx| { workspace .active_pane() From a0a50cb412ba23e97f010013153719132582676a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 17 Jan 2023 17:37:52 -0800 Subject: [PATCH 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 9d5b94bddb8a99e4f7be34d62ac88d913c131f4e..1bc713b5fb45c2b5beca9d5a4e8c539d1ba5d53d 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -722,11 +722,12 @@ pub(crate) mod test { elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; - use project::{Project, ProjectEntryId, ProjectPath}; - use std::{any::Any, borrow::Cow, cell::Cell}; + use project::{Project, ProjectEntryId, ProjectPath, WorktreeId}; + use std::{any::Any, borrow::Cow, cell::Cell, path::Path}; pub struct TestProjectItem { pub entry_id: Option, + pub project_path: Option, } pub struct TestItem { @@ -740,7 +741,6 @@ pub(crate) mod test { pub is_singleton: bool, pub has_conflict: bool, pub project_items: Vec>, - pub project_path: Option, pub nav_history: Option, pub tab_descriptions: Option>, pub tab_detail: Cell>, @@ -756,7 +756,7 @@ pub(crate) mod test { } fn project_path(&self, _: &AppContext) -> Option { - None + self.project_path.clone() } } @@ -776,7 +776,6 @@ pub(crate) mod test { is_singleton: self.is_singleton, has_conflict: self.has_conflict, project_items: self.project_items.clone(), - project_path: self.project_path.clone(), nav_history: None, tab_descriptions: None, tab_detail: Default::default(), @@ -796,7 +795,6 @@ pub(crate) mod test { is_dirty: false, has_conflict: false, project_items: Vec::new(), - project_path: None, is_singleton: true, nav_history: None, tab_descriptions: None, @@ -831,15 +829,21 @@ pub(crate) mod test { self } - pub fn with_project_entry_ids( + pub fn with_project_items( mut self, - project_entry_ids: &[u64], + items: &[(u64, &str)], cx: &mut MutableAppContext, ) -> Self { self.project_items - .extend(project_entry_ids.iter().copied().map(|id| { + .extend(items.iter().copied().map(|(id, path)| { let id = ProjectEntryId::from_proto(id); - cx.add_model(|_| TestProjectItem { entry_id: Some(id) }) + cx.add_model(|_| TestProjectItem { + entry_id: Some(id), + project_path: Some(ProjectPath { + worktree_id: WorktreeId::from_usize(0), + path: Path::new(path).into(), + }), + }) })); self } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 4d5a73b2b33785dcbeaffc2f82efc83a328b42e2..de298e7be10cf01609c84de064967ad1aa764377 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1861,7 +1861,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "one.txt")], cx); Pane::add_item( workspace, @@ -1880,7 +1880,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "1.txt")], cx); Pane::add_item( workspace, @@ -1899,7 +1899,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 2") - .with_project_entry_ids(&[2], cx); + .with_project_items(&[(2, "2.txt")], cx); Pane::add_item( workspace, @@ -1918,7 +1918,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "1.txt")], cx); Pane::add_item( workspace, @@ -1937,7 +1937,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1b") - .with_project_entry_ids(&[1], cx); + .with_project_items(&[(1, "1.txt")], cx); Pane::add_item( workspace, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c7aa0798a3d81f304013f3fd4287a456f06de9b3..a521d02b5fbbdcecd1f438d06ae1b169e887e2e3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2834,15 +2834,11 @@ mod tests { project.worktrees(cx).next().unwrap().read(cx).id() }); - let item1 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_path = Some((worktree_id, "one.txt").into()); - item + let item1 = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_items(&[(1, "one.txt")], cx) }); - let item2 = cx.add_view(&workspace, |_| { - let mut item = TestItem::new(); - item.project_path = Some((worktree_id, "two.txt").into()); - item + let item2 = cx.add_view(&workspace, |cx| { + TestItem::new().with_project_items(&[(2, "two.txt")], cx) }); // Add an item to an empty pane @@ -2947,7 +2943,7 @@ mod tests { let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_entry_ids(&[1], cx) + .with_project_items(&[(1, "1.txt")], cx) }); workspace.update(cx, |w, cx| { w.add_item(Box::new(item2.clone()), cx); @@ -2975,19 +2971,19 @@ mod tests { let item1 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_entry_ids(&[1], cx) + .with_project_items(&[(1, "1.txt")], cx) }); let item2 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_entry_ids(&[2], cx) + .with_project_items(&[(2, "2.txt")], cx) }); let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_entry_ids(&[3], cx) + .with_project_items(&[(3, "3.txt")], cx) }); let item4 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); let pane = workspace.update(cx, |workspace, cx| { @@ -3067,10 +3063,10 @@ mod tests { let single_entry_items = (0..=4) .map(|project_entry_id| { cx.add_view(&workspace, |cx| { - TestItem::new() - .with_dirty(true) - .with_singleton(true) - .with_project_entry_ids(&[project_entry_id], cx) + TestItem::new().with_dirty(true).with_project_items( + &[(project_entry_id, &format!("{project_entry_id}.txt"))], + cx, + ) }) }) .collect::>(); @@ -3078,13 +3074,13 @@ mod tests { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_entry_ids(&[2, 3], cx) + .with_project_items(&[(2, "2.txt"), (3, "3.txt")], cx) }); let item_3_4 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_entry_ids(&[3, 4], cx) + .with_project_items(&[(3, "3.txt"), (4, "4.txt")], cx) }); // Create two panes that contain the following project entries: @@ -3163,7 +3159,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_entry_ids(&[1], cx) + TestItem::new().with_project_items(&[(1, "1.txt")], cx) }); let item_id = item.id(); workspace.update(cx, |workspace, cx| { @@ -3282,7 +3278,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_entry_ids(&[1], cx) + TestItem::new().with_project_items(&[(1, "1.txt")], cx) }); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); From 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 d9976fbba7aa16fac83b7943cde97b04ca3b2895..66b0d88b229b03a34c27bda7376cb3f16504b6bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -739,8 +739,7 @@ dependencies = [ [[package]] name = "bromberg_sl2" version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ed88064f69518b7e3ea50ecfc1b61d43f19248618a377b95ae5c8b611134d4d" +source = "git+https://github.com/zed-industries/bromberg_sl2?rev=dac565a90e8f9245f48ff46225c915dc50f76920#dac565a90e8f9245f48ff46225c915dc50f76920" dependencies = [ "digest 0.9.0", "lazy_static", diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 4b79009a68ea31f4350e5bae5447613d7e8b748e..972a33c0b785c10ecfbe8052a1effb814b213a7b 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -60,7 +60,7 @@ pub struct Buffer { git_diff_status: GitDiffStatus, file: Option>, saved_version: clock::Global, - saved_version_fingerprint: String, + saved_version_fingerprint: RopeFingerprint, saved_mtime: SystemTime, transaction_depth: usize, was_dirty_before_starting_transaction: Option, @@ -221,7 +221,7 @@ pub trait File: Send + Sync { version: clock::Global, line_ending: LineEnding, cx: &mut MutableAppContext, - ) -> Task>; + ) -> Task>; fn as_any(&self) -> &dyn Any; @@ -238,7 +238,7 @@ pub trait LocalFile: File { &self, buffer_id: u64, version: &clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, line_ending: LineEnding, mtime: SystemTime, cx: &mut MutableAppContext, @@ -386,7 +386,8 @@ impl Buffer { .ok_or_else(|| anyhow!("missing line_ending"))?, )); this.saved_version = proto::deserialize_version(message.saved_version); - this.saved_version_fingerprint = message.saved_version_fingerprint; + this.saved_version_fingerprint = + proto::deserialize_fingerprint(&message.saved_version_fingerprint)?; this.saved_mtime = message .saved_mtime .ok_or_else(|| anyhow!("invalid saved_mtime"))? @@ -402,7 +403,7 @@ impl Buffer { diff_base: self.diff_base.as_ref().map(|h| h.to_string()), line_ending: proto::serialize_line_ending(self.line_ending()) as i32, saved_version: proto::serialize_version(&self.saved_version), - saved_version_fingerprint: self.saved_version_fingerprint.clone(), + saved_version_fingerprint: proto::serialize_fingerprint(self.saved_version_fingerprint), saved_mtime: Some(self.saved_mtime.into()), } } @@ -530,7 +531,7 @@ impl Buffer { pub fn save( &mut self, cx: &mut ModelContext, - ) -> Task> { + ) -> Task> { let file = if let Some(file) = self.file.as_ref() { file } else { @@ -548,7 +549,7 @@ impl Buffer { cx.spawn(|this, mut cx| async move { let (version, fingerprint, mtime) = save.await?; this.update(&mut cx, |this, cx| { - this.did_save(version.clone(), fingerprint.clone(), mtime, None, cx); + this.did_save(version.clone(), fingerprint, mtime, None, cx); }); Ok((version, fingerprint, mtime)) }) @@ -558,8 +559,8 @@ impl Buffer { &self.saved_version } - pub fn saved_version_fingerprint(&self) -> &str { - &self.saved_version_fingerprint + pub fn saved_version_fingerprint(&self) -> RopeFingerprint { + self.saved_version_fingerprint } pub fn saved_mtime(&self) -> SystemTime { @@ -581,7 +582,7 @@ impl Buffer { pub fn did_save( &mut self, version: clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, mtime: SystemTime, new_file: Option>, cx: &mut ModelContext, @@ -630,7 +631,7 @@ impl Buffer { pub fn did_reload( &mut self, version: clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, line_ending: LineEnding, mtime: SystemTime, cx: &mut ModelContext, @@ -643,7 +644,7 @@ impl Buffer { file.buffer_reloaded( self.remote_id(), &self.saved_version, - self.saved_version_fingerprint.clone(), + self.saved_version_fingerprint, self.line_ending(), self.saved_mtime, cx, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 9612deb5bd81a475f95911f35378d6e60238cd16..1b95e3ace9a21615bb803d3ad4f4015e5d17188b 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -11,6 +11,15 @@ use text::*; pub use proto::{BufferState, Operation}; +pub fn serialize_fingerprint(fingerprint: RopeFingerprint) -> String { + fingerprint.to_hex() +} + +pub fn deserialize_fingerprint(fingerprint: &str) -> Result { + RopeFingerprint::from_hex(fingerprint) + .map_err(|error| anyhow!("invalid fingerprint: {}", error)) +} + pub fn deserialize_line_ending(message: proto::LineEnding) -> fs::LineEnding { match message { proto::LineEnding::Unix => fs::LineEnding::Unix, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d3b3c11f2eb09a3584ba5063acd001de9674f18e..ce7296994a8ff62c567ede76e44dd8a020292732 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -22,8 +22,8 @@ use gpui::{ use language::{ point_to_lsp, proto::{ - deserialize_anchor, deserialize_line_ending, deserialize_version, serialize_anchor, - serialize_version, + deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, + serialize_anchor, serialize_version, }, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, @@ -5123,7 +5123,7 @@ impl Project { buffer_id, version: serialize_version(&saved_version), mtime: Some(mtime.into()), - fingerprint, + fingerprint: language::proto::serialize_fingerprint(fingerprint), }) } @@ -5215,7 +5215,9 @@ impl Project { buffer_id, version: language::proto::serialize_version(buffer.saved_version()), mtime: Some(buffer.saved_mtime().into()), - fingerprint: buffer.saved_version_fingerprint().into(), + fingerprint: language::proto::serialize_fingerprint( + buffer.saved_version_fingerprint(), + ), line_ending: language::proto::serialize_line_ending( buffer.line_ending(), ) as i32, @@ -5970,6 +5972,7 @@ impl Project { _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { + let fingerprint = deserialize_fingerprint(&envelope.payload.fingerprint)?; let version = deserialize_version(envelope.payload.version); let mtime = envelope .payload @@ -5984,7 +5987,7 @@ impl Project { .and_then(|buffer| buffer.upgrade(cx)); if let Some(buffer) = buffer { buffer.update(cx, |buffer, cx| { - buffer.did_save(version, envelope.payload.fingerprint, mtime, None, cx); + buffer.did_save(version, fingerprint, mtime, None, cx); }); } Ok(()) @@ -5999,6 +6002,7 @@ impl Project { ) -> Result<()> { let payload = envelope.payload; let version = deserialize_version(payload.version); + let fingerprint = deserialize_fingerprint(&payload.fingerprint)?; let line_ending = deserialize_line_ending( proto::LineEnding::from_i32(payload.line_ending) .ok_or_else(|| anyhow!("missing line ending"))?, @@ -6014,7 +6018,7 @@ impl Project { .and_then(|buffer| buffer.upgrade(cx)); if let Some(buffer) = buffer { buffer.update(cx, |buffer, cx| { - buffer.did_reload(version, payload.fingerprint, line_ending, mtime, cx); + buffer.did_reload(version, fingerprint, line_ending, mtime, cx); }); } Ok(()) diff --git a/crates/rope/Cargo.toml b/crates/rope/Cargo.toml index bd1dc690db8b34b78846f96cbb06d36ec70cc65b..e9ddcc8195a1c940369b9148b9f5b9fc93ff6e2f 100644 --- a/crates/rope/Cargo.toml +++ b/crates/rope/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" path = "src/rope.rs" [dependencies] -bromberg_sl2 = "0.6" +bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "dac565a90e8f9245f48ff46225c915dc50f76920" } smallvec = { version = "1.6", features = ["union"] } sum_tree = { path = "../sum_tree" } arrayvec = "0.7.1" diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 53713e3f7a35f7e0d1f3c463575f60f0c5af0efe..74cc9a6789e779f1d5176e8bc66fd4640d1e0052 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -4,7 +4,7 @@ mod point_utf16; mod unclipped; use arrayvec::ArrayString; -use bromberg_sl2::{DigestString, HashMatrix}; +use bromberg_sl2::HashMatrix; use smallvec::SmallVec; use std::{ cmp, fmt, io, mem, @@ -25,6 +25,8 @@ const CHUNK_BASE: usize = 6; #[cfg(not(test))] const CHUNK_BASE: usize = 16; +pub type RopeFingerprint = HashMatrix; + #[derive(Clone, Default, Debug)] pub struct Rope { chunks: SumTree, @@ -361,8 +363,8 @@ impl Rope { .column } - pub fn fingerprint(&self) -> String { - self.chunks.summary().fingerprint.to_hex() + pub fn fingerprint(&self) -> RopeFingerprint { + self.chunks.summary().fingerprint } } From 8ca0f9ac99457095e3a0a6a181a65c1a34fea579 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Jan 2023 13:53:48 +0100 Subject: [PATCH 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 ed8f984d274228b7c777635c73e6ac2686407991..e0170f6648e3d03eb5fd1d81cad444ce498534dc 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -410,10 +410,10 @@ async fn test_random_collaboration( guest_buffer.read_with(client_cx, |b, _| b.saved_version().clone()); assert_eq!(guest_saved_version, host_saved_version); - let host_saved_version_fingerprint = host_buffer - .read_with(host_cx, |b, _| b.saved_version_fingerprint().to_string()); - let guest_saved_version_fingerprint = guest_buffer - .read_with(client_cx, |b, _| b.saved_version_fingerprint().to_string()); + let host_saved_version_fingerprint = + host_buffer.read_with(host_cx, |b, _| b.saved_version_fingerprint()); + let guest_saved_version_fingerprint = + guest_buffer.read_with(client_cx, |b, _| b.saved_version_fingerprint()); assert_eq!( guest_saved_version_fingerprint, host_saved_version_fingerprint diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index f8fa3c9df0e68b0c813616e6480a9a98382866c9..cce6d338f0278ff0d32e5db89a6ad73bd2dff6db 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -13,8 +13,10 @@ use gpui::{ elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use language::proto::serialize_anchor as serialize_text_anchor; -use language::{Bias, Buffer, File as _, OffsetRangeExt, Point, SelectionGoal}; +use language::{ + proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, File as _, OffsetRangeExt, + Point, SelectionGoal, +}; use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; @@ -1165,9 +1167,11 @@ fn path_for_file<'a>( mod tests { use super::*; use gpui::MutableAppContext; + use language::RopeFingerprint; use std::{ path::{Path, PathBuf}, sync::Arc, + time::SystemTime, }; #[gpui::test] @@ -1197,7 +1201,7 @@ mod tests { todo!() } - fn mtime(&self) -> std::time::SystemTime { + fn mtime(&self) -> SystemTime { todo!() } @@ -1216,7 +1220,7 @@ mod tests { _: clock::Global, _: project::LineEnding, _: &mut MutableAppContext, - ) -> gpui::Task> { + ) -> gpui::Task> { todo!() } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index f22e91578565492f1b1a14ac8981f3f393f5eb8c..b65cf9e39bd96367a3e0b0d45989fcbdce76cf9b 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -20,10 +20,12 @@ use gpui::{ executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, }; -use language::Unclipped; use language::{ - proto::{deserialize_version, serialize_line_ending, serialize_version}, - Buffer, DiagnosticEntry, PointUtf16, Rope, + proto::{ + deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending, + serialize_version, + }, + Buffer, DiagnosticEntry, PointUtf16, Rope, RopeFingerprint, Unclipped, }; use parking_lot::Mutex; use postage::{ @@ -1863,7 +1865,7 @@ impl language::File for File { version: clock::Global, line_ending: LineEnding, cx: &mut MutableAppContext, - ) -> Task> { + ) -> Task> { self.worktree.update(cx, |worktree, cx| match worktree { Worktree::Local(worktree) => { let rpc = worktree.client.clone(); @@ -1878,7 +1880,7 @@ impl language::File for File { buffer_id, version: serialize_version(&version), mtime: Some(entry.mtime.into()), - fingerprint: fingerprint.clone(), + fingerprint: serialize_fingerprint(fingerprint), })?; } Ok((version, fingerprint, entry.mtime)) @@ -1896,11 +1898,12 @@ impl language::File for File { }) .await?; let version = deserialize_version(response.version); + let fingerprint = deserialize_fingerprint(&response.fingerprint)?; let mtime = response .mtime .ok_or_else(|| anyhow!("missing mtime"))? .into(); - Ok((version, response.fingerprint, mtime)) + Ok((version, fingerprint, mtime)) }) } }) @@ -1943,7 +1946,7 @@ impl language::LocalFile for File { &self, buffer_id: u64, version: &clock::Global, - fingerprint: String, + fingerprint: RopeFingerprint, line_ending: LineEnding, mtime: SystemTime, cx: &mut MutableAppContext, @@ -1957,7 +1960,7 @@ impl language::LocalFile for File { buffer_id, version: serialize_version(version), mtime: Some(mtime.into()), - fingerprint, + fingerprint: serialize_fingerprint(fingerprint), line_ending: serialize_line_ending(line_ending) as i32, }) .log_err(); From f8d092fdc6dd0059fe61d5397b18fce6a0da8071 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 18 Jan 2023 15:22:20 +0200 Subject: [PATCH 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 0042695950f0642bb22aaee4240e576d3221fd14..73b558bd5bc0443bcaa7ddcbcb7fb6cbbdf8c628 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1176,13 +1176,15 @@ impl ProjectPanel { ) }) .on_click(MouseButton::Left, move |e, cx| { - if kind == EntryKind::Dir { - cx.dispatch_action(ToggleExpanded(entry_id)) - } else { - cx.dispatch_action(Open { - entry_id, - change_focus: e.click_count > 1, - }) + if !show_editor { + if kind == EntryKind::Dir { + cx.dispatch_action(ToggleExpanded(entry_id)) + } else { + cx.dispatch_action(Open { + entry_id, + change_focus: e.click_count > 1, + }) + } } }) .on_down(MouseButton::Right, move |e, cx| { From a8f466b42269bf507ec8600d69608bdc7c0a1d99 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 18 Jan 2023 14:22:23 +0100 Subject: [PATCH 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 66b0d88b229b03a34c27bda7376cb3f16504b6bb..bbe77d8c7568c8f1b2bd1c891c7335fc5332d3c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5520,6 +5520,7 @@ dependencies = [ "serde_json", "settings", "smallvec", + "smol", "theme", "unindent", "util", diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 1f6c9582beb1f70df03f4f77b396264f87dc38ea..30802801a676756017ee60ca6c8a29d5d058a92e 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -23,6 +23,7 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] } postage = { version = "0.4.1", features = ["futures-traits"] } serde = { version = "1.0", features = ["derive", "rc"] } smallvec = { version = "1.6", features = ["union"] } +smol = "1.2" [dev-dependencies] editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 9d2f31e2690c38f1ece7bbf475fcaeb4c79311d8..a0f29738b01fc8d9af965772c7a95496dab81d02 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -18,6 +18,7 @@ use settings::Settings; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, + mem, ops::Range, path::PathBuf, sync::Arc, @@ -67,6 +68,7 @@ struct ProjectSearch { pending_search: Option>>, match_ranges: Vec>, active_query: Option, + search_id: usize, } pub struct ProjectSearchView { @@ -78,6 +80,7 @@ pub struct ProjectSearchView { regex: bool, query_contains_error: bool, active_match_index: Option, + search_id: usize, } pub struct ProjectSearchBar { @@ -98,6 +101,7 @@ impl ProjectSearch { pending_search: Default::default(), match_ranges: Default::default(), active_query: None, + search_id: 0, } } @@ -110,6 +114,7 @@ impl ProjectSearch { pending_search: Default::default(), match_ranges: self.match_ranges.clone(), active_query: self.active_query.clone(), + search_id: self.search_id, }) } @@ -117,21 +122,25 @@ impl ProjectSearch { let search = self .project .update(cx, |project, cx| project.search(query.clone(), cx)); + self.search_id += 1; self.active_query = Some(query); self.match_ranges.clear(); self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move { let matches = search.await.log_err()?; - if let Some(this) = this.upgrade(&cx) { + let this = this.upgrade(&cx)?; + let mut matches = matches.into_iter().collect::>(); + this.update(&mut cx, |this, cx| { + this.match_ranges.clear(); + matches.sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); + this.excerpts.update(cx, |excerpts, cx| excerpts.clear(cx)); + }); + + for matches_chunk in matches.chunks(100) { this.update(&mut cx, |this, cx| { - this.match_ranges.clear(); - let mut matches = matches.into_iter().collect::>(); - matches - .sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); this.excerpts.update(cx, |excerpts, cx| { - excerpts.clear(cx); - for (buffer, buffer_matches) in matches { + for (buffer, buffer_matches) in matches_chunk { let ranges_to_highlight = excerpts.push_excerpts_with_context_lines( - buffer, + buffer.clone(), buffer_matches.clone(), 1, cx, @@ -139,10 +148,19 @@ impl ProjectSearch { this.match_ranges.extend(ranges_to_highlight); } }); - this.pending_search.take(); + cx.notify(); }); + + // Don't starve the main thread when adding lots of excerpts. + smol::future::yield_now().await; } + + this.update(&mut cx, |this, cx| { + this.pending_search.take(); + cx.notify(); + }); + None })); cx.notify(); @@ -398,7 +416,7 @@ impl ProjectSearchView { whole_word = active_query.whole_word(); } } - cx.observe(&model, |this, _, cx| this.model_changed(true, cx)) + cx.observe(&model, |this, _, cx| this.model_changed(cx)) .detach(); let query_editor = cx.add_view(|cx| { @@ -433,6 +451,7 @@ impl ProjectSearchView { .detach(); let mut this = ProjectSearchView { + search_id: model.read(cx).search_id, model, query_editor, results_editor, @@ -442,7 +461,7 @@ impl ProjectSearchView { query_contains_error: false, active_match_index: None, }; - this.model_changed(false, cx); + this.model_changed(cx); this } @@ -562,11 +581,13 @@ impl ProjectSearchView { cx.focus(&self.results_editor); } - fn model_changed(&mut self, reset_selections: bool, cx: &mut ViewContext) { + fn model_changed(&mut self, cx: &mut ViewContext) { let match_ranges = self.model.read(cx).match_ranges.clone(); if match_ranges.is_empty() { self.active_match_index = None; } else { + let prev_search_id = mem::replace(&mut self.search_id, self.model.read(cx).search_id); + let reset_selections = self.search_id != prev_search_id; self.results_editor.update(cx, |editor, cx| { if reset_selections { editor.change_selections(Some(Autoscroll::fit()), cx, |s| { From 27a6951403cd11234edaa62c339e52678b9a2b86 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 18 Jan 2023 17:35:21 +0200 Subject: [PATCH 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 73b558bd5bc0443bcaa7ddcbcb7fb6cbbdf8c628..e59353aae40e06773e4467731976e878baff1cbb 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -763,7 +763,6 @@ impl ProjectPanel { ix += 1; } - self.clipboard_entry.take(); if clipboard_entry.is_cut() { if let Some(task) = self.project.update(cx, |project, cx| { project.rename_entry(clipboard_entry.entry_id(), new_path, cx) From b0fb5913b6aa9fd734f1ee40a1cfa5d322b19d1e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 18 Jan 2023 12:39:38 -0800 Subject: [PATCH 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 d9976fbba7aa16fac83b7943cde97b04ca3b2895..62674acf3d674e19e8befa82d90d77dd33aa9634 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8186,7 +8186,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.70.0" +version = "0.71.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 38fa2ad4befffe33901f5499b1ab890a83e20bcc..63719b70031a6543a7b72d8a67ef77795d33f5c9 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.70.0" +version = "0.71.0" [lib] name = "zed" From 203f569f2eda03cb1179f600ea2e6eb321bdbe52 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 18 Jan 2023 12:52:58 -0800 Subject: [PATCH 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 62674acf3d674e19e8befa82d90d77dd33aa9634..c0b97a6b3ebdd6fccc11d301f8efccf865eda8bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.2" +version = "0.5.3" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 9c3f0f61c4e2d8e503bfab4d51007cd0a7e6ffcb..8c21af7273775ebee32b79327aa65e465fc15f9e 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.2" +version = "0.5.3" [[bin]] name = "collab" From 06c31a0daad2a3a8b6017a5843e1e4174808b5bb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Jan 2023 15:00:40 -0800 Subject: [PATCH 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 1bc713b5fb45c2b5beca9d5a4e8c539d1ba5d53d..b1888bb243c41001d330f108225893a1f9fb6208 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -784,6 +784,27 @@ pub(crate) mod test { } } + impl TestProjectItem { + pub fn new(id: u64, path: &str, cx: &mut MutableAppContext) -> ModelHandle { + let entry_id = Some(ProjectEntryId::from_proto(id)); + let project_path = Some(ProjectPath { + worktree_id: WorktreeId::from_usize(0), + path: Path::new(path).into(), + }); + cx.add_model(|_| Self { + entry_id, + project_path, + }) + } + + pub fn new_untitled(cx: &mut MutableAppContext) -> ModelHandle { + cx.add_model(|_| Self { + project_path: None, + entry_id: None, + }) + } + } + impl TestItem { pub fn new() -> Self { Self { @@ -829,22 +850,9 @@ pub(crate) mod test { self } - pub fn with_project_items( - mut self, - items: &[(u64, &str)], - cx: &mut MutableAppContext, - ) -> Self { - self.project_items - .extend(items.iter().copied().map(|(id, path)| { - let id = ProjectEntryId::from_proto(id); - cx.add_model(|_| TestProjectItem { - entry_id: Some(id), - project_path: Some(ProjectPath { - worktree_id: WorktreeId::from_usize(0), - path: Path::new(path).into(), - }), - }) - })); + pub fn with_project_items(mut self, items: &[ModelHandle]) -> Self { + self.project_items.clear(); + self.project_items.extend(items.iter().cloned()); self } @@ -938,8 +946,12 @@ pub(crate) mod test { self.has_conflict } - fn can_save(&self, _: &AppContext) -> bool { + fn can_save(&self, cx: &AppContext) -> bool { !self.project_items.is_empty() + && self + .project_items + .iter() + .all(|item| item.read(cx).entry_id.is_some()) } fn save( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index de298e7be10cf01609c84de064967ad1aa764377..05d6de1ee22b6eae9f424c84de89dde9dd05fe0f 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -804,7 +804,7 @@ impl Pane { items_to_close.sort_by_key(|item| !item.is_singleton(cx)); cx.spawn(|workspace, mut cx| async move { - let mut saved_project_entry_ids = HashSet::default(); + let mut saved_project_items_ids = HashSet::default(); for item in items_to_close.clone() { // Find the item's current index and its set of project entries. Avoid // storing these in advance, in case they have changed since this task @@ -836,7 +836,7 @@ impl Pane { }); let should_save = project_item_ids .iter() - .any(|id| saved_project_entry_ids.insert(*id)); + .any(|id| saved_project_items_ids.insert(*id)); if should_save && !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx) @@ -1672,7 +1672,7 @@ mod tests { use std::sync::Arc; use super::*; - use crate::item::test::TestItem; + use crate::item::test::{TestItem, TestProjectItem}; use gpui::{executor::Deterministic, TestAppContext}; use project::FakeFs; @@ -1861,7 +1861,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_items(&[(1, "one.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]); Pane::add_item( workspace, @@ -1880,7 +1880,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, @@ -1899,7 +1899,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 2") - .with_project_items(&[(2, "2.txt")], cx); + .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]); Pane::add_item( workspace, @@ -1918,7 +1918,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, @@ -1937,7 +1937,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1b") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a521d02b5fbbdcecd1f438d06ae1b169e887e2e3..47e2be29dfc7aa6b53420b0e7cde247d62082c32 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2727,7 +2727,7 @@ pub fn open_new(app_state: &Arc, cx: &mut MutableAppContext) -> Task<( mod tests { use std::{cell::RefCell, rc::Rc}; - use crate::item::test::{TestItem, TestItemEvent}; + use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; use super::*; use fs::FakeFs; @@ -2835,10 +2835,10 @@ mod tests { }); let item1 = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "one.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) }); let item2 = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(2, "two.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)]) }); // Add an item to an empty pane @@ -2943,7 +2943,7 @@ mod tests { let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_items(&[(1, "1.txt")], cx) + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); workspace.update(cx, |w, cx| { w.add_item(Box::new(item2.clone()), cx); @@ -2971,21 +2971,25 @@ mod tests { let item1 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_items(&[(1, "1.txt")], cx) + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let item2 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_items(&[(2, "2.txt")], cx) + .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]) }); let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_items(&[(3, "3.txt")], cx) + .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)]) + }); + let item4 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_project_items(&[TestProjectItem::new_untitled(cx)]) }); - let item4 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); let pane = workspace.update(cx, |workspace, cx| { workspace.add_item(Box::new(item1.clone()), cx); workspace.add_item(Box::new(item2.clone()), cx); @@ -3007,15 +3011,20 @@ mod tests { [item1_id, item3_id, item4_id].contains(&id) }) }); - cx.foreground().run_until_parked(); + + // There's a prompt to save item 1. pane.read_with(cx, |pane, _| { assert_eq!(pane.items_len(), 4); assert_eq!(pane.active_item().unwrap().id(), item1.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Confirm saving item 1. cx.simulate_prompt_answer(window_id, 0); cx.foreground().run_until_parked(); + + // Item 1 is saved. There's a prompt to save item 3. pane.read_with(cx, |pane, cx| { assert_eq!(item1.read(cx).save_count, 1); assert_eq!(item1.read(cx).save_as_count, 0); @@ -3023,9 +3032,13 @@ mod tests { assert_eq!(pane.items_len(), 3); assert_eq!(pane.active_item().unwrap().id(), item3.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Cancel saving item 3. cx.simulate_prompt_answer(window_id, 1); cx.foreground().run_until_parked(); + + // Item 3 is reloaded. There's a prompt to save item 4. pane.read_with(cx, |pane, cx| { assert_eq!(item3.read(cx).save_count, 0); assert_eq!(item3.read(cx).save_as_count, 0); @@ -3033,11 +3046,17 @@ mod tests { assert_eq!(pane.items_len(), 2); assert_eq!(pane.active_item().unwrap().id(), item4.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Confirm saving item 4. cx.simulate_prompt_answer(window_id, 0); cx.foreground().run_until_parked(); + + // There's a prompt for a path for item 4. cx.simulate_new_path_selection(|_| Some(Default::default())); close_items.await.unwrap(); + + // The requested items are closed. pane.read_with(cx, |pane, cx| { assert_eq!(item4.read(cx).save_count, 0); assert_eq!(item4.read(cx).save_as_count, 1); @@ -3063,10 +3082,13 @@ mod tests { let single_entry_items = (0..=4) .map(|project_entry_id| { cx.add_view(&workspace, |cx| { - TestItem::new().with_dirty(true).with_project_items( - &[(project_entry_id, &format!("{project_entry_id}.txt"))], - cx, - ) + TestItem::new() + .with_dirty(true) + .with_project_items(&[TestProjectItem::new( + project_entry_id, + &format!("{project_entry_id}.txt"), + cx, + )]) }) }) .collect::>(); @@ -3074,13 +3096,19 @@ mod tests { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_items(&[(2, "2.txt"), (3, "3.txt")], cx) + .with_project_items(&[ + single_entry_items[2].read(cx).project_items[0].clone(), + single_entry_items[3].read(cx).project_items[0].clone(), + ]) }); let item_3_4 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_items(&[(3, "3.txt"), (4, "4.txt")], cx) + .with_project_items(&[ + single_entry_items[3].read(cx).project_items[0].clone(), + single_entry_items[4].read(cx).project_items[0].clone(), + ]) }); // Create two panes that contain the following project entries: @@ -3159,7 +3187,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "1.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let item_id = item.id(); workspace.update(cx, |workspace, cx| { @@ -3278,7 +3306,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "1.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); From 062e7a03a9604c39ec8ead4542d0493b469f9a19 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Jan 2023 15:17:44 -0800 Subject: [PATCH 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 05d6de1ee22b6eae9f424c84de89dde9dd05fe0f..8d0d7315517b9930c74026c51097c07ec8269cd3 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -806,7 +806,7 @@ impl Pane { cx.spawn(|workspace, mut cx| async move { let mut saved_project_items_ids = HashSet::default(); for item in items_to_close.clone() { - // Find the item's current index and its set of project entries. Avoid + // Find the item's current index and its set of project item models. Avoid // storing these in advance, in case they have changed since this task // was started. let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { @@ -818,11 +818,9 @@ impl Pane { continue; }; - // If an item hasn't yet been associated with a project entry, then always - // prompt to save it before closing it. Otherwise, check if the item has - // any project entries that are not open anywhere else in the workspace, - // AND that the user has not already been prompted to save. If there are - // any such project entries, prompt the user to save this item. + // Check if this view has any project items that are not open anywhere else + // in the workspace, AND that the user has not already been prompted to save. + // If there are any such project entries, prompt the user to save this item. workspace.read_with(&cx, |workspace, cx| { for item in workspace.items(cx) { if !items_to_close From d9948bf77273df94f92d0cca7cb8f38ea0ef46b5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Jan 2023 16:43:18 -0800 Subject: [PATCH 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 4b79009a68ea31f4350e5bae5447613d7e8b748e..a52ee34962e5f13e4686f119585cfe27cdcf9c56 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2168,8 +2168,6 @@ impl BufferSnapshot { continue; } - // TODO - move later, after processing captures - let mut text = String::new(); let mut name_ranges = Vec::new(); let mut highlight_ranges = Vec::new(); @@ -2183,7 +2181,13 @@ impl BufferSnapshot { continue; } - let range = capture.node.start_byte()..capture.node.end_byte(); + let mut range = capture.node.start_byte()..capture.node.end_byte(); + let start = capture.node.start_position(); + if capture.node.end_position().row > start.row { + range.end = + range.start + self.line_len(start.row as u32) as usize - start.column; + } + if !text.is_empty() { text.push(' '); } diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index b7f35e9ad5cc8b20671faf49632339445af8c727..5aa915220520d6ef8b491742c598b2e5e40b3405 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -455,6 +455,32 @@ async fn test_outline(cx: &mut gpui::TestAppContext) { } } +#[gpui::test] +async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) { + let text = r#" + impl A for B< + C + > { + }; + "# + .unindent(); + + let buffer = + cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx)); + let outline = buffer + .read_with(cx, |buffer, _| buffer.snapshot().outline(None)) + .unwrap(); + + assert_eq!( + outline + .items + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect::>(), + &[("impl A for B<", 0)] + ); +} + #[gpui::test] async fn test_symbols_containing(cx: &mut gpui::TestAppContext) { let text = r#" diff --git a/crates/zed/src/languages/go/outline.scm b/crates/zed/src/languages/go/outline.scm index 849d2bdebf681ed932101a7c0c28b0c8451c29eb..2ff7ef25a0a9c9623f329369bfeccd2e8c1f2fd5 100644 --- a/crates/zed/src/languages/go/outline.scm +++ b/crates/zed/src/languages/go/outline.scm @@ -40,5 +40,4 @@ ")" @context)) @item (field_declaration - name: (_) @name - type: (_) @context) @item \ No newline at end of file + name: (_) @name) @item \ No newline at end of file From 5ce065ac92381ad629dcd8e6bd4ee5ab2a1155bd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 15:42:14 +0100 Subject: [PATCH 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 bbe77d8c7568c8f1b2bd1c891c7335fc5332d3c3..7be9d4529373cd767958139308fca6aaa05ad03a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5510,6 +5510,7 @@ dependencies = [ "anyhow", "collections", "editor", + "futures 0.3.25", "gpui", "language", "log", diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 2347d9a63d1fbaa8d88555c45eb8fe434530138a..4d576883d39c3dcb1fbaa43f45ed253a56a6c567 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -4,6 +4,7 @@ pub use anchor::{Anchor, AnchorRangeExt}; use anyhow::Result; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet}; +use futures::{channel::mpsc, SinkExt}; use git::diff::DiffHunk; use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; @@ -764,6 +765,63 @@ impl MultiBuffer { None } + pub fn stream_excerpts_with_context_lines( + &mut self, + excerpts: Vec<(ModelHandle, Vec>)>, + context_line_count: u32, + cx: &mut ModelContext, + ) -> (Task<()>, mpsc::Receiver>) { + let (mut tx, rx) = mpsc::channel(256); + let task = cx.spawn(|this, mut cx| async move { + for (buffer, ranges) in excerpts { + let buffer_id = buffer.id(); + let buffer_snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot()); + + let mut excerpt_ranges = Vec::new(); + let mut range_counts = Vec::new(); + cx.background() + .scoped(|scope| { + scope.spawn(async { + let (ranges, counts) = + build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); + excerpt_ranges = ranges; + range_counts = counts; + }); + }) + .await; + + let mut ranges = ranges.into_iter(); + let mut range_counts = range_counts.into_iter(); + for excerpt_ranges in excerpt_ranges.chunks(100) { + let excerpt_ids = this.update(&mut cx, |this, cx| { + this.push_excerpts(buffer.clone(), excerpt_ranges.iter().cloned(), cx) + }); + + for (excerpt_id, range_count) in + excerpt_ids.into_iter().zip(range_counts.by_ref()) + { + for range in ranges.by_ref().take(range_count) { + let start = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: range.start, + }; + let end = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: range.end, + }; + if tx.send(start..end).await.is_err() { + break; + } + } + } + } + } + }); + (task, rx) + } + pub fn push_excerpts( &mut self, buffer: ModelHandle, @@ -788,39 +846,8 @@ impl MultiBuffer { { let buffer_id = buffer.id(); let buffer_snapshot = buffer.read(cx).snapshot(); - let max_point = buffer_snapshot.max_point(); - - let mut range_counts = Vec::new(); - let mut excerpt_ranges = Vec::new(); - let mut range_iter = ranges - .iter() - .map(|range| { - range.start.to_point(&buffer_snapshot)..range.end.to_point(&buffer_snapshot) - }) - .peekable(); - while let Some(range) = range_iter.next() { - let excerpt_start = Point::new(range.start.row.saturating_sub(context_line_count), 0); - let mut excerpt_end = - Point::new(range.end.row + 1 + context_line_count, 0).min(max_point); - let mut ranges_in_excerpt = 1; - - while let Some(next_range) = range_iter.peek() { - if next_range.start.row <= excerpt_end.row + context_line_count { - excerpt_end = - Point::new(next_range.end.row + 1 + context_line_count, 0).min(max_point); - ranges_in_excerpt += 1; - range_iter.next(); - } else { - break; - } - } - - excerpt_ranges.push(ExcerptRange { - context: excerpt_start..excerpt_end, - primary: Some(range), - }); - range_counts.push(ranges_in_excerpt); - } + let (excerpt_ranges, range_counts) = + build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); let excerpt_ids = self.push_excerpts(buffer, excerpt_ranges, cx); @@ -3605,6 +3632,47 @@ impl ToPointUtf16 for PointUtf16 { } } +fn build_excerpt_ranges( + buffer: &BufferSnapshot, + ranges: &[Range], + context_line_count: u32, +) -> (Vec>, Vec) +where + T: text::ToPoint, +{ + let max_point = buffer.max_point(); + let mut range_counts = Vec::new(); + let mut excerpt_ranges = Vec::new(); + let mut range_iter = ranges + .iter() + .map(|range| range.start.to_point(buffer)..range.end.to_point(buffer)) + .peekable(); + while let Some(range) = range_iter.next() { + let excerpt_start = Point::new(range.start.row.saturating_sub(context_line_count), 0); + let mut excerpt_end = Point::new(range.end.row + 1 + context_line_count, 0).min(max_point); + let mut ranges_in_excerpt = 1; + + while let Some(next_range) = range_iter.peek() { + if next_range.start.row <= excerpt_end.row + context_line_count { + excerpt_end = + Point::new(next_range.end.row + 1 + context_line_count, 0).min(max_point); + ranges_in_excerpt += 1; + range_iter.next(); + } else { + break; + } + } + + excerpt_ranges.push(ExcerptRange { + context: excerpt_start..excerpt_end, + primary: Some(range), + }); + range_counts.push(ranges_in_excerpt); + } + + (excerpt_ranges, range_counts) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 30802801a676756017ee60ca6c8a29d5d058a92e..3a5d9468fc028a44ba7349e0f7549ba14a9540b8 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -19,6 +19,7 @@ theme = { path = "../theme" } util = { path = "../util" } workspace = { path = "../workspace" } anyhow = "1.0" +futures = "0.3" log = { version = "0.4.16", features = ["kv_unstable_serde"] } postage = { version = "0.4.1", features = ["futures-traits"] } serde = { version = "1.0", features = ["derive", "rc"] } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a0f29738b01fc8d9af965772c7a95496dab81d02..11318b1c4362b5a4ff22f9ff33776fb96b3f3239 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -7,6 +7,7 @@ use editor::{ items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN, }; +use futures::StreamExt; use gpui::{ actions, elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, @@ -129,31 +130,23 @@ impl ProjectSearch { let matches = search.await.log_err()?; let this = this.upgrade(&cx)?; let mut matches = matches.into_iter().collect::>(); - this.update(&mut cx, |this, cx| { + let (_rebuild, mut match_ranges) = this.update(&mut cx, |this, cx| { this.match_ranges.clear(); matches.sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); - this.excerpts.update(cx, |excerpts, cx| excerpts.clear(cx)); + this.excerpts.update(cx, |excerpts, cx| { + excerpts.clear(cx); + excerpts.stream_excerpts_with_context_lines(matches, 1, cx) + }) }); - for matches_chunk in matches.chunks(100) { + while let Some(match_range) = match_ranges.next().await { this.update(&mut cx, |this, cx| { - this.excerpts.update(cx, |excerpts, cx| { - for (buffer, buffer_matches) in matches_chunk { - let ranges_to_highlight = excerpts.push_excerpts_with_context_lines( - buffer.clone(), - buffer_matches.clone(), - 1, - cx, - ); - this.match_ranges.extend(ranges_to_highlight); - } - }); - + this.match_ranges.push(match_range); + while let Ok(Some(match_range)) = match_ranges.try_next() { + this.match_ranges.push(match_range); + } cx.notify(); }); - - // Don't starve the main thread when adding lots of excerpts. - smol::future::yield_now().await; } this.update(&mut cx, |this, cx| { From c124caeb0dccad8073f6e25413f6cb54143f5ca7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 15:52:59 +0100 Subject: [PATCH 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 4d576883d39c3dcb1fbaa43f45ed253a56a6c567..7812c347812af0a20885cdf0151ecf1d9280e7bb 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -3676,6 +3676,7 @@ where #[cfg(test)] mod tests { use super::*; + use futures::StreamExt; use gpui::{MutableAppContext, TestAppContext}; use language::{Buffer, Rope}; use rand::prelude::*; @@ -4080,6 +4081,44 @@ mod tests { ); } + #[gpui::test] + async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) { + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(20, 3, 'a'), cx)); + let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); + let (task, anchor_ranges) = multibuffer.update(cx, |multibuffer, cx| { + let snapshot = buffer.read(cx); + let ranges = vec![ + snapshot.anchor_before(Point::new(3, 2))..snapshot.anchor_before(Point::new(4, 2)), + snapshot.anchor_before(Point::new(7, 1))..snapshot.anchor_before(Point::new(7, 3)), + snapshot.anchor_before(Point::new(15, 0)) + ..snapshot.anchor_before(Point::new(15, 0)), + ]; + multibuffer.stream_excerpts_with_context_lines(vec![(buffer.clone(), ranges)], 2, cx) + }); + + let anchor_ranges = anchor_ranges.collect::>().await; + // Ensure task is finished when stream completes. + task.await; + + let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx)); + assert_eq!( + snapshot.text(), + "bbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\n\nnnn\nooo\nppp\nqqq\nrrr\n" + ); + + assert_eq!( + anchor_ranges + .iter() + .map(|range| range.to_point(&snapshot)) + .collect::>(), + vec![ + Point::new(2, 2)..Point::new(3, 2), + Point::new(6, 1)..Point::new(6, 3), + Point::new(12, 0)..Point::new(12, 0) + ] + ); + } + #[gpui::test] fn test_empty_multibuffer(cx: &mut MutableAppContext) { let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 11318b1c4362b5a4ff22f9ff33776fb96b3f3239..68c2f33a7518b7eb4d07021689dfc9aa4205bc02 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -130,7 +130,7 @@ impl ProjectSearch { let matches = search.await.log_err()?; let this = this.upgrade(&cx)?; let mut matches = matches.into_iter().collect::>(); - let (_rebuild, mut match_ranges) = this.update(&mut cx, |this, cx| { + let (_task, mut match_ranges) = this.update(&mut cx, |this, cx| { this.match_ranges.clear(); matches.sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); this.excerpts.update(cx, |excerpts, cx| { From 0a7111d21607cbf780cef0159729602265ac429f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 16:26:27 +0100 Subject: [PATCH 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 a315cf0c5f1945b00019265578cd84006720087d..d3d5c437c5e6f6675d30ebf501322afd96d5d5d2 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -949,13 +949,13 @@ impl ToolbarItemView for ProjectSearchBar { mod tests { use super::*; use editor::DisplayPoint; - use gpui::{color::Color, TestAppContext}; + use gpui::{color::Color, executor::Deterministic, TestAppContext}; use project::FakeFs; use serde_json::json; use std::sync::Arc; #[gpui::test] - async fn test_project_search(cx: &mut TestAppContext) { + async fn test_project_search(deterministic: Arc, cx: &mut TestAppContext) { let fonts = cx.font_cache(); let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default); theme.search.match_background = Color::red(); @@ -987,7 +987,7 @@ mod tests { .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx)); search_view.search(cx); }); - search_view.next_notification(cx).await; + deterministic.run_until_parked(); search_view.update(cx, |search_view, cx| { assert_eq!( search_view From 1f649e52dedd1d46fb034749f111d31ab60f71c4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 19 Jan 2023 17:25:59 +0100 Subject: [PATCH 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 74cc9a6789e779f1d5176e8bc66fd4640d1e0052..d0796cfc6acb1d906698cb13af0906c3737765b6 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -25,6 +25,10 @@ const CHUNK_BASE: usize = 6; #[cfg(not(test))] const CHUNK_BASE: usize = 16; +/// Type alias to [HashMatrix], an implementation of a homomorphic hash function. Two [Rope] instances +/// containing the same text will produce the same fingerprint. This hash function is special in that +/// it allows us to hash individual chunks and aggregate them up the [Rope]'s tree, with the resulting +/// hash being equivalent to hashing all the text contained in the [Rope] at once. pub type RopeFingerprint = HashMatrix; #[derive(Clone, Default, Debug)] @@ -858,7 +862,7 @@ impl sum_tree::Item for Chunk { #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct ChunkSummary { text: TextSummary, - fingerprint: HashMatrix, + fingerprint: RopeFingerprint, } impl<'a> From<&'a str> for ChunkSummary { From f088de5947f6b467171a1f1950239761d525b544 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Thu, 19 Jan 2023 19:05:17 +0200 Subject: [PATCH 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 6baf08b01e0191d312a26d8990fc838894db7998..16fd0c1eeab1dc3670ffb3a2e1ed66d46e85388e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -29,6 +29,7 @@ use settings::{ self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent, WorkingDirectory, }; +use simplelog::ConfigBuilder; use smol::process::Command; use std::fs::OpenOptions; use std::{env, ffi::OsStr, panic, path::PathBuf, sync::Arc, thread, time::Duration}; @@ -269,8 +270,12 @@ fn init_logger() { .append(true) .open(&*paths::LOG) .expect("could not open logfile"); - simplelog::WriteLogger::init(level, simplelog::Config::default(), log_file) - .expect("could not initialize logger"); + + let config = ConfigBuilder::new() + .set_time_format_str("%Y-%m-%dT%T") //All timestamps are UTC + .build(); + + simplelog::WriteLogger::init(level, config, log_file).expect("could not initialize logger"); } } From ad1947fa50d1561fd9741dafb1e9744480941c8f Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 19 Jan 2023 12:34:13 -0500 Subject: [PATCH 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 7d69d3833c9ba4e26dfaed64f8901b043de6bb84..5c34b9838047187f8c0507e8669ab7a4e39ec813 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -190,6 +190,9 @@ impl EditorElement { } } }) + .on_move_out(|e, cx| { + println!("on move out"); + }) .on_scroll({ let position_map = position_map.clone(); move |e, cx| { diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 132a915f0b6f99313720b0b6e21ac134d862639f..761e134262542268fbc866fe97dcc40aa4d50fe8 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -7,7 +7,7 @@ use crate::{ platform::CursorStyle, scene::{ CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover, - MouseMove, MouseScrollWheel, MouseUp, MouseUpOut, + MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, }, DebugContext, Element, ElementBox, EventContext, LayoutContext, MeasurementContext, MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View, @@ -82,6 +82,14 @@ impl MouseEventHandler { self } + pub fn on_move_out( + mut self, + handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_move_out(handler); + self + } + pub fn on_down( mut self, button: MouseButton, diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index bbaf1ed0bbce3ec2a096e37719354ce100d00db6..1573b10a10035925414479b0ea6707d29d3ae1f9 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -8,7 +8,7 @@ use crate::{ platform::{CursorStyle, Event}, scene::{ CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, - MouseMove, MouseScrollWheel, MouseUp, MouseUpOut, Scene, + MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene, }, text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, Appearance, @@ -245,8 +245,11 @@ impl Presenter { // -> Also updates mouse-related state match &event { Event::KeyDown(e) => return cx.dispatch_key_down(self.window_id, e), + Event::KeyUp(e) => return cx.dispatch_key_up(self.window_id, e), + Event::ModifiersChanged(e) => return cx.dispatch_modifiers_changed(self.window_id, e), + Event::MouseDown(e) => { // Click events are weird because they can be fired after a drag event. // MDN says that browsers handle this by starting from 'the most @@ -279,6 +282,7 @@ impl Presenter { platform_event: e.clone(), })); } + Event::MouseUp(e) => { // NOTE: The order of event pushes is important! MouseUp events MUST be fired // before click events, and so the MouseUp events need to be pushed before @@ -296,6 +300,7 @@ impl Presenter { platform_event: e.clone(), })); } + Event::MouseMoved( e @ MouseMovedEvent { position, @@ -347,9 +352,13 @@ impl Presenter { platform_event: e.clone(), started: false, })); + mouse_events.push(MouseEvent::MoveOut(MouseMoveOut { + region: Default::default(), + })); self.last_mouse_moved_event = Some(event.clone()); } + Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel { region: Default::default(), platform_event: e.clone(), @@ -407,6 +416,7 @@ impl Presenter { } } } + MouseEvent::Down(_) | MouseEvent::Up(_) => { for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(self.mouse_position) { @@ -417,6 +427,7 @@ impl Presenter { } } } + MouseEvent::Click(e) => { // Only raise click events if the released button is the same as the one stored if self @@ -439,6 +450,7 @@ impl Presenter { } } } + MouseEvent::Drag(_) => { for (mouse_region, _) in self.mouse_regions.iter().rev() { if self.clicked_region_ids.contains(&mouse_region.id()) { @@ -447,7 +459,7 @@ impl Presenter { } } - MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => { + MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => { for (mouse_region, _) in self.mouse_regions.iter().rev() { // NOT contains if !mouse_region.bounds.contains_point(self.mouse_position) { @@ -455,6 +467,7 @@ impl Presenter { } } } + _ => { for (mouse_region, _) in self.mouse_regions.iter().rev() { // Contains diff --git a/crates/gpui/src/scene/mouse_event.rs b/crates/gpui/src/scene/mouse_event.rs index 00d1ddbf8bf61ee0457186d30f449d1c3ed95ef4..2effb8ab537f7bd3eda979f4e477b5804ac8ad5f 100644 --- a/crates/gpui/src/scene/mouse_event.rs +++ b/crates/gpui/src/scene/mouse_event.rs @@ -21,6 +21,11 @@ impl Deref for MouseMove { } } +#[derive(Debug, Default, Clone)] +pub struct MouseMoveOut { + pub region: RectF, +} + #[derive(Debug, Default, Clone)] pub struct MouseDrag { pub region: RectF, @@ -138,6 +143,7 @@ impl Deref for MouseScrollWheel { #[derive(Debug, Clone)] pub enum MouseEvent { Move(MouseMove), + MoveOut(MouseMoveOut), Drag(MouseDrag), Hover(MouseHover), Down(MouseDown), @@ -152,6 +158,7 @@ impl MouseEvent { pub fn set_region(&mut self, region: RectF) { match self { MouseEvent::Move(r) => r.region = region, + MouseEvent::MoveOut(r) => r.region = region, MouseEvent::Drag(r) => r.region = region, MouseEvent::Hover(r) => r.region = region, MouseEvent::Down(r) => r.region = region, @@ -168,6 +175,7 @@ impl MouseEvent { pub fn is_capturable(&self) -> bool { match self { MouseEvent::Move(_) => true, + MouseEvent::MoveOut(_) => false, MouseEvent::Drag(_) => true, MouseEvent::Hover(_) => false, MouseEvent::Down(_) => true, @@ -185,6 +193,10 @@ impl MouseEvent { discriminant(&MouseEvent::Move(Default::default())) } + pub fn move_out_disc() -> Discriminant { + discriminant(&MouseEvent::MoveOut(Default::default())) + } + pub fn drag_disc() -> Discriminant { discriminant(&MouseEvent::Drag(Default::default())) } @@ -220,6 +232,7 @@ impl MouseEvent { pub fn handler_key(&self) -> HandlerKey { match self { MouseEvent::Move(_) => HandlerKey::new(Self::move_disc(), None), + MouseEvent::MoveOut(_) => HandlerKey::new(Self::move_out_disc(), None), MouseEvent::Drag(e) => HandlerKey::new(Self::drag_disc(), e.pressed_button), MouseEvent::Hover(_) => HandlerKey::new(Self::hover_disc(), None), MouseEvent::Down(e) => HandlerKey::new(Self::down_disc(), Some(e.button)), diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 0fdc76ebbfefe8204983cd58e4a9f42a310d8024..7cbbc5b89746b5c8ddb81d0b89021e2876213e7c 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -12,7 +12,7 @@ use super::{ MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, MouseMove, MouseUp, MouseUpOut, }, - MouseScrollWheel, + MouseMoveOut, MouseScrollWheel, }; #[derive(Clone)] @@ -124,6 +124,14 @@ impl MouseRegion { self } + pub fn on_move_out( + mut self, + handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_move_out(handler); + self + } + pub fn on_scroll( mut self, handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static, @@ -289,6 +297,23 @@ impl HandlerSet { self } + pub fn on_move_out( + mut self, + handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, + ) -> Self { + self.insert(MouseEvent::move_out_disc(), None, + Rc::new(move |region_event, cx| { + if let MouseEvent::MoveOut(e) = region_event { + handler(e, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::MoveOut, found {:?}", + region_event); + } + })); + self + } + pub fn on_down( mut self, button: MouseButton, From 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 07c0bce6b6502b0550b84c27753e70f454601c24..a6ce5626318b519593fa3de624610d7a817d8625 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1086,6 +1086,8 @@ impl Editor { let blink_manager = cx.add_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx)); + let soft_wrap_mode_override = + (mode == EditorMode::SingleLine).then(|| settings::SoftWrap::None); let mut this = Self { handle: cx.weak_handle(), buffer: buffer.clone(), @@ -1101,7 +1103,7 @@ impl Editor { select_larger_syntax_node_stack: Vec::new(), ime_transaction: Default::default(), active_diagnostics: None, - soft_wrap_mode_override: None, + soft_wrap_mode_override, get_field_editor_theme, project, focused: false, From 4a46227909820fe6f5014f72b7758a3242d67e6c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 11:43:46 -0800 Subject: [PATCH 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 a51fb4891d20ee303d35992ef1c2dbc298dd1562..daf93ce6a213b6083915d4d390b67194309dea80 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -48,6 +48,7 @@ pub fn init(cx: &mut MutableAppContext) { }, |_| IncomingCallNotification::new(incoming_call.clone()), ); + cx.activate_window(window_id); notification_windows.push(window_id); } } From 1851e2e77cd0a1e59ec9c1487000d01311714f05 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 19 Jan 2023 12:31:34 -0800 Subject: [PATCH 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 a6ce5626318b519593fa3de624610d7a817d8625..81ab73e24ea34008b9163f7da0147055771a5dc8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1737,7 +1737,7 @@ impl Editor { for (selection, autoclose_region) in self.selections_with_autoclose_regions(selections, &snapshot) { - if let Some(language) = snapshot.language_at(selection.head()) { + if let Some(language) = snapshot.language_config_at(selection.head()) { // Determine if the inserted text matches the opening or closing // bracket of any of this language's bracket pairs. let mut bracket_pair = None; @@ -1898,7 +1898,7 @@ impl Editor { let end = selection.end; let mut insert_extra_newline = false; - if let Some(language) = buffer.language_at(start) { + if let Some(language) = buffer.language_config_at(start) { let leading_whitespace_len = buffer .reversed_chars_at(start) .take_while(|c| c.is_whitespace() && *c != '\n') @@ -4533,7 +4533,10 @@ impl Editor { // TODO: Handle selections that cross excerpts for selection in &mut selections { - let language = if let Some(language) = snapshot.language_at(selection.start) { + let start_column = snapshot.indent_size_for_line(selection.start.row).len; + let language = if let Some(language) = + snapshot.language_config_at(Point::new(selection.start.row, start_column)) + { language } else { continue; diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 6b5262c16ab97eff73491322a3f3607bcbb0b1d8..57fe5411cd732e39660b84ee65a6f0da79bb2532 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -10,9 +10,9 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, - DiagnosticEntry, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, - Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, - ToPointUtf16 as _, TransactionId, Unclipped, + DiagnosticEntry, IndentSize, Language, LanguageConfigYeet, OffsetRangeExt, OffsetUtf16, + Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, + ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; use std::{ borrow::Cow, @@ -2691,6 +2691,11 @@ impl MultiBufferSnapshot { .and_then(|(buffer, offset)| buffer.language_at(offset)) } + pub fn language_config_at<'a, T: ToOffset>(&'a self, point: T) -> Option { + self.point_to_buffer_offset(point) + .and_then(|(buffer, offset)| buffer.language_config_at(offset)) + } + pub fn is_dirty(&self) -> bool { self.is_dirty } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index ad3bdb6821d2c80b781a52b44c3ab7a8c567041e..5593241e70ba305d59855dc475631b36f872a020 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -9,7 +9,7 @@ use crate::{ syntax_map::{ SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint, }, - CodeLabel, Outline, + CodeLabel, LanguageConfigYeet, Outline, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -2015,6 +2015,27 @@ impl BufferSnapshot { .or(self.language.as_ref()) } + pub fn language_config_at(&self, position: D) -> Option { + let offset = position.to_offset(self); + + if let Some(layer_info) = self + .syntax + .layers_for_range(offset..offset, &self.text) + .filter(|l| l.node.end_byte() > offset) + .last() + { + Some(LanguageConfigYeet { + language: layer_info.language.clone(), + override_id: layer_info.override_id(offset, &self.text), + }) + } else { + self.language.clone().map(|language| LanguageConfigYeet { + language, + override_id: None, + }) + } + } + pub fn surrounding_word(&self, start: T) -> (Range, Option) { let mut start = start.to_offset(self); let mut end = start; diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c3f2c3716b43c208408f67218f191354fcbe0c7b..3c8b2b987b10f71f2f3a8894ae9f587bf402c958 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -22,7 +22,10 @@ use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; -use serde::{de, Deserialize, Deserializer}; +use serde::{ + de::{self}, + Deserialize, Deserializer, +}; use serde_json::Value; use std::{ any::Any, @@ -243,6 +246,45 @@ pub struct LanguageConfig { pub line_comment: Option>, #[serde(default)] pub block_comment: Option<(Arc, Arc)>, + #[serde(default)] + pub overrides: HashMap, +} + +#[derive(Clone)] +pub struct LanguageConfigYeet { + language: Arc, + override_id: Option, +} + +#[derive(Deserialize)] +pub struct LanguageConfigOverride { + #[serde(default)] + pub line_comment: Override>, + #[serde(default)] + pub block_comment: Override<(Arc, Arc)>, +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum Override { + Remove { remove: bool }, + Set(T), +} + +impl Default for Override { + fn default() -> Self { + Override::Remove { remove: false } + } +} + +impl Override { + fn as_option<'a>(this: Option<&'a Self>, original: &'a Option) -> Option<&'a T> { + match this { + Some(Self::Set(value)) => Some(value), + Some(Self::Remove { remove: true }) => None, + Some(Self::Remove { remove: false }) | None => original.as_ref(), + } + } } impl Default for LanguageConfig { @@ -257,6 +299,7 @@ impl Default for LanguageConfig { autoclose_before: Default::default(), line_comment: Default::default(), block_comment: Default::default(), + overrides: Default::default(), } } } @@ -311,6 +354,7 @@ pub struct Grammar { pub(crate) indents_config: Option, pub(crate) outline_config: Option, pub(crate) injection_config: Option, + pub(crate) override_config: Option, pub(crate) highlight_map: Mutex, } @@ -336,6 +380,11 @@ struct InjectionConfig { patterns: Vec, } +struct OverrideConfig { + query: Query, + values: HashMap, +} + #[derive(Default, Clone)] struct InjectionPatternConfig { language: Option>, @@ -635,6 +684,7 @@ impl Language { outline_config: None, indents_config: None, injection_config: None, + override_config: None, ts_language, highlight_map: Default::default(), }) @@ -775,6 +825,25 @@ impl Language { Ok(self) } + pub fn with_override_query(mut self, source: &str) -> Result { + let query = Query::new(self.grammar_mut().ts_language, source)?; + + let mut values = HashMap::default(); + for (ix, name) in query.capture_names().iter().enumerate() { + if let Some(override_name) = name.strip_prefix("override.") { + let value = self + .config + .overrides + .remove(override_name) + .ok_or_else(|| anyhow!("no such override {override_name}"))?; + values.insert(ix as u32, value); + } + } + + self.grammar_mut().override_config = Some(OverrideConfig { query, values }); + Ok(self) + } + fn grammar_mut(&mut self) -> &mut Grammar { Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap() } @@ -800,17 +869,6 @@ impl Language { self.config.name.clone() } - pub fn line_comment_prefix(&self) -> Option<&Arc> { - self.config.line_comment.as_ref() - } - - pub fn block_comment_delimiters(&self) -> Option<(&Arc, &Arc)> { - self.config - .block_comment - .as_ref() - .map(|(start, end)| (start, end)) - } - pub async fn disk_based_diagnostic_sources(&self) -> &[String] { match self.adapter.as_ref() { Some(adapter) => &adapter.disk_based_diagnostic_sources, @@ -886,10 +944,6 @@ impl Language { result } - pub fn brackets(&self) -> &[BracketPair] { - &self.config.brackets - } - pub fn path_suffixes(&self) -> &[String] { &self.config.path_suffixes } @@ -912,6 +966,43 @@ impl Language { } } +impl LanguageConfigYeet { + pub fn line_comment_prefix(&self) -> Option<&Arc> { + Override::as_option( + self.over_ride().map(|o| &o.line_comment), + &self.language.config.line_comment, + ) + } + + pub fn block_comment_delimiters(&self) -> Option<(&Arc, &Arc)> { + Override::as_option( + self.over_ride().map(|o| &o.block_comment), + &self.language.config.block_comment, + ) + .map(|e| (&e.0, &e.1)) + } + + pub fn brackets(&self) -> &[BracketPair] { + &self.language.config.brackets + } + + pub fn should_autoclose_before(&self, c: char) -> bool { + c.is_whitespace() || self.language.config.autoclose_before.contains(c) + } + + fn over_ride(&self) -> Option<&LanguageConfigOverride> { + self.override_id.and_then(|id| { + self.language + .grammar + .as_ref()? + .override_config + .as_ref()? + .values + .get(&id) + }) + } +} + impl Hash for Language { fn hash(&self, state: &mut H) { self.id().hash(state) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 65d01e949317bf64ab7ee65258df3c0c848e5602..458ffd8bc23a147a241ea719636e65389bdafafa 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1127,6 +1127,41 @@ fn splice_included_ranges( ranges } +impl<'a> SyntaxLayerInfo<'a> { + pub(crate) fn override_id(&self, offset: usize, text: &text::BufferSnapshot) -> Option { + let text = TextProvider(text.as_rope()); + let config = self.language.grammar.as_ref()?.override_config.as_ref()?; + + let mut query_cursor = QueryCursorHandle::new(); + query_cursor.set_byte_range(offset..offset); + + let mut smallest_match: Option<(u32, Range)> = None; + for mat in query_cursor.matches(&config.query, self.node, text) { + for capture in mat.captures { + if !config.values.contains_key(&capture.index) { + continue; + } + + let range = capture.node.byte_range(); + if offset <= range.start || offset >= range.end { + continue; + } + + if let Some((_, smallest_range)) = &smallest_match { + if range.len() < smallest_range.len() { + smallest_match = Some((capture.index, range)) + } + continue; + } + + smallest_match = Some((capture.index, range)); + } + } + + smallest_match.map(|(index, _)| index) + } +} + impl std::ops::Deref for SyntaxMap { type Target = SyntaxSnapshot; diff --git a/crates/zed/src/languages/javascript/contexts.scm b/crates/zed/src/languages/javascript/contexts.scm new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/crates/zed/src/languages/tsx/config.toml b/crates/zed/src/languages/tsx/config.toml index 7baa12385c89d684eb80a708497d02693017eca3..4f466cc961e3836ee8d458cdcc4d863f61ab92a5 100644 --- a/crates/zed/src/languages/tsx/config.toml +++ b/crates/zed/src/languages/tsx/config.toml @@ -12,3 +12,12 @@ brackets = [ { start = "`", end = "`", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.element] +line_comment = { remove = true } +block_comment = ["{/* ", " */}"] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/tsx/overrides.scm b/crates/zed/src/languages/tsx/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..aa617b30862add7e3efbdeb9a2644e0b1b28ac05 --- /dev/null +++ b/crates/zed/src/languages/tsx/overrides.scm @@ -0,0 +1,2 @@ +(jsx_element) @override.element +(string) @override.string From 4eeb1aec50f45a07ce3101bfa12eacc950235ee2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 14:22:12 -0800 Subject: [PATCH 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 3351fb9eb9c14c7f82f0f4d27243776a5b419161..ffafa2d142c427614bc87daab65d85a947d92673 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -417,9 +417,13 @@ impl CollabTitlebarItem { theme: &Theme, cx: &mut RenderContext, ) -> ElementBox { - let is_followed = peer.map_or(false, |(peer_id, _, _)| { - workspace.read(cx).is_following(peer_id) + let (is_followed, is_following) = peer.map_or((false, false), |(peer_id, _, _)| { + ( + workspace.read(cx).is_following(peer_id), + workspace.read(cx).is_followed(peer_id), + ) }); + // my color, around their avatar. let mut avatar_style; if let Some((_, _, location)) = peer.as_ref() { @@ -442,6 +446,10 @@ impl CollabTitlebarItem { replica_color = Some(color); if is_followed { avatar_style.border = Border::all(1.0, color); + } else if is_following { + let our_id = workspace.read(cx).project().read(cx).replica_id(); + let our_color = theme.editor.replica_selection_style(our_id).cursor; + avatar_style.border = Border::all(1.0, our_color); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 58517346097af8b6f79d1fe0bb60b837d037af60..6a8fdaa17b4e54d49d8868e7ebbbc3cdc8e32c21 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1623,6 +1623,7 @@ impl Workspace { project_id, leader_id: Some(leader_id), }); + Some(cx.spawn_weak(|this, mut cx| async move { let response = request.await?; if let Some(this) = this.upgrade(&cx) { @@ -1719,6 +1720,10 @@ impl Workspace { self.follower_states_by_leader.contains_key(&peer_id) } + pub fn is_followed(&self, peer_id: PeerId) -> bool { + self.leader_state.followers.contains(&peer_id) + } + fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { let project = &self.project.read(cx); let mut worktree_root_names = String::new(); @@ -1896,6 +1901,9 @@ impl Workspace { .to_proto(), ) }); + + cx.notify(); + Ok(proto::FollowResponse { active_view_id, views: this @@ -1928,10 +1936,11 @@ impl Workspace { _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { - this.update(&mut cx, |this, _| { + this.update(&mut cx, |this, cx| { this.leader_state .followers .remove(&envelope.original_sender_id()?); + cx.notify(); Ok(()) }) } From 2967b46a17b9e5bb64dda54fe6fb5a68dee0b539 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 19 Jan 2023 15:04:27 -0800 Subject: [PATCH 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 81ab73e24ea34008b9163f7da0147055771a5dc8..0d62c1f6257935455341877de1de49fe26f01f22 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1737,7 +1737,7 @@ impl Editor { for (selection, autoclose_region) in self.selections_with_autoclose_regions(selections, &snapshot) { - if let Some(language) = snapshot.language_config_at(selection.head()) { + if let Some(language) = snapshot.language_scope_at(selection.head()) { // Determine if the inserted text matches the opening or closing // bracket of any of this language's bracket pairs. let mut bracket_pair = None; @@ -1898,7 +1898,7 @@ impl Editor { let end = selection.end; let mut insert_extra_newline = false; - if let Some(language) = buffer.language_config_at(start) { + if let Some(language) = buffer.language_scope_at(start) { let leading_whitespace_len = buffer .reversed_chars_at(start) .take_while(|c| c.is_whitespace() && *c != '\n') @@ -4535,7 +4535,7 @@ impl Editor { for selection in &mut selections { let start_column = snapshot.indent_size_for_line(selection.start.row).len; let language = if let Some(language) = - snapshot.language_config_at(Point::new(selection.start.row, start_column)) + snapshot.language_scope_at(Point::new(selection.start.row, start_column)) { language } else { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 57fe5411cd732e39660b84ee65a6f0da79bb2532..7079d197f9dfca676e84b595a77a8387c0f8e776 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -10,9 +10,9 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, - DiagnosticEntry, IndentSize, Language, LanguageConfigYeet, OffsetRangeExt, OffsetUtf16, - Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, - ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, + DiagnosticEntry, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, + OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, + ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; use std::{ borrow::Cow, @@ -2691,9 +2691,9 @@ impl MultiBufferSnapshot { .and_then(|(buffer, offset)| buffer.language_at(offset)) } - pub fn language_config_at<'a, T: ToOffset>(&'a self, point: T) -> Option { + pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option { self.point_to_buffer_offset(point) - .and_then(|(buffer, offset)| buffer.language_config_at(offset)) + .and_then(|(buffer, offset)| buffer.language_scope_at(offset)) } pub fn is_dirty(&self) -> bool { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5593241e70ba305d59855dc475631b36f872a020..7ef78459527fecd2310d41abac1e5bc12ae8ff67 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -9,7 +9,7 @@ use crate::{ syntax_map::{ SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint, }, - CodeLabel, LanguageConfigYeet, Outline, + CodeLabel, LanguageScope, Outline, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -2015,7 +2015,7 @@ impl BufferSnapshot { .or(self.language.as_ref()) } - pub fn language_config_at(&self, position: D) -> Option { + pub fn language_scope_at(&self, position: D) -> Option { let offset = position.to_offset(self); if let Some(layer_info) = self @@ -2024,12 +2024,12 @@ impl BufferSnapshot { .filter(|l| l.node.end_byte() > offset) .last() { - Some(LanguageConfigYeet { + Some(LanguageScope { language: layer_info.language.clone(), override_id: layer_info.override_id(offset, &self.text), }) } else { - self.language.clone().map(|language| LanguageConfigYeet { + self.language.clone().map(|language| LanguageScope { language, override_id: None, }) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 5aa915220520d6ef8b491742c598b2e5e40b3405..07198e8090c612df4adb142704dd797da8845e26 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1369,6 +1369,89 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut MutableAppContext) { }); } +#[gpui::test] +fn test_language_config_at(cx: &mut MutableAppContext) { + cx.set_global(Settings::test(cx)); + cx.add_model(|cx| { + let language = Language::new( + LanguageConfig { + name: "JavaScript".into(), + line_comment: Some("// ".into()), + brackets: vec![ + BracketPair { + start: "{".into(), + end: "}".into(), + close: true, + newline: false, + }, + BracketPair { + start: "'".into(), + end: "'".into(), + close: true, + newline: false, + }, + ], + overrides: [ + ( + "element".into(), + LanguageConfigOverride { + line_comment: Override::Remove { remove: true }, + block_comment: Override::Set(("{/*".into(), "*/}".into())), + ..Default::default() + }, + ), + ( + "string".into(), + LanguageConfigOverride { + brackets: Override::Set(vec![BracketPair { + start: "{".into(), + end: "}".into(), + close: true, + newline: false, + }]), + ..Default::default() + }, + ), + ] + .into_iter() + .collect(), + ..Default::default() + }, + Some(tree_sitter_javascript::language()), + ) + .with_override_query( + r#" + (jsx_element) @override.element + (string) @override.string + "#, + ) + .unwrap(); + + let text = r#"a["b"] = ;"#; + + let buffer = Buffer::new(0, text, cx).with_language(Arc::new(language), cx); + let snapshot = buffer.snapshot(); + + let config = snapshot.language_scope_at(0).unwrap(); + assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// "); + assert_eq!(config.brackets().len(), 2); + + let string_config = snapshot.language_scope_at(3).unwrap(); + assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// "); + assert_eq!(string_config.brackets().len(), 1); + + let element_config = snapshot.language_scope_at(10).unwrap(); + assert_eq!(element_config.line_comment_prefix(), None); + assert_eq!( + element_config.block_comment_delimiters(), + Some((&"{/*".into(), &"*/}".into())) + ); + assert_eq!(element_config.brackets().len(), 2); + + buffer + }); +} + #[gpui::test] fn test_serialization(cx: &mut gpui::MutableAppContext) { let mut now = Instant::now(); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 3c8b2b987b10f71f2f3a8894ae9f587bf402c958..928e20862746c4935db279c81d31eb389964be58 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -22,10 +22,7 @@ use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; -use serde::{ - de::{self}, - Deserialize, Deserializer, -}; +use serde::{de, Deserialize, Deserializer}; use serde_json::Value; use std::{ any::Any, @@ -251,20 +248,22 @@ pub struct LanguageConfig { } #[derive(Clone)] -pub struct LanguageConfigYeet { +pub struct LanguageScope { language: Arc, override_id: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, Default, Debug)] pub struct LanguageConfigOverride { #[serde(default)] pub line_comment: Override>, #[serde(default)] pub block_comment: Override<(Arc, Arc)>, + #[serde(default)] + pub brackets: Override>, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(untagged)] pub enum Override { Remove { remove: bool }, @@ -278,11 +277,11 @@ impl Default for Override { } impl Override { - fn as_option<'a>(this: Option<&'a Self>, original: &'a Option) -> Option<&'a T> { + fn as_option<'a>(this: Option<&'a Self>, original: Option<&'a T>) -> Option<&'a T> { match this { Some(Self::Set(value)) => Some(value), Some(Self::Remove { remove: true }) => None, - Some(Self::Remove { remove: false }) | None => original.as_ref(), + Some(Self::Remove { remove: false }) | None => original, } } } @@ -966,40 +965,39 @@ impl Language { } } -impl LanguageConfigYeet { +impl LanguageScope { pub fn line_comment_prefix(&self) -> Option<&Arc> { Override::as_option( - self.over_ride().map(|o| &o.line_comment), - &self.language.config.line_comment, + self.config_override().map(|o| &o.line_comment), + self.language.config.line_comment.as_ref(), ) } pub fn block_comment_delimiters(&self) -> Option<(&Arc, &Arc)> { Override::as_option( - self.over_ride().map(|o| &o.block_comment), - &self.language.config.block_comment, + self.config_override().map(|o| &o.block_comment), + self.language.config.block_comment.as_ref(), ) .map(|e| (&e.0, &e.1)) } pub fn brackets(&self) -> &[BracketPair] { - &self.language.config.brackets + Override::as_option( + self.config_override().map(|o| &o.brackets), + Some(&self.language.config.brackets), + ) + .map_or(&[], Vec::as_slice) } pub fn should_autoclose_before(&self, c: char) -> bool { c.is_whitespace() || self.language.config.autoclose_before.contains(c) } - fn over_ride(&self) -> Option<&LanguageConfigOverride> { - self.override_id.and_then(|id| { - self.language - .grammar - .as_ref()? - .override_config - .as_ref()? - .values - .get(&id) - }) + fn config_override(&self) -> Option<&LanguageConfigOverride> { + let id = self.override_id?; + let grammar = self.language.grammar.as_ref()?; + let override_config = grammar.override_config.as_ref()?; + override_config.values.get(&id) } } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 0250c53684675c5b5791048b658df91b32f817f0..d7fa67b1d451c13fc103cb5210650fd8efae9d4c 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -173,6 +173,11 @@ pub(crate) fn language( .with_injection_query(query.as_ref()) .expect("failed to load injection query"); } + if let Some(query) = load_query(name, "/overrides") { + language = language + .with_override_query(query.as_ref()) + .expect("failed to load override query"); + } if let Some(lsp_adapter) = lsp_adapter { language = language.with_lsp_adapter(lsp_adapter) } diff --git a/crates/zed/src/languages/tsx/overrides.scm b/crates/zed/src/languages/tsx/overrides.scm index aa617b30862add7e3efbdeb9a2644e0b1b28ac05..183f7b4f98db4e016c0981b8c2f4509e213bfb97 100644 --- a/crates/zed/src/languages/tsx/overrides.scm +++ b/crates/zed/src/languages/tsx/overrides.scm @@ -1,2 +1,7 @@ -(jsx_element) @override.element +[ + (jsx_element) + (jsx_fragment) + (jsx_self_closing_element) + (jsx_expression) +] @override.element (string) @override.string From 88170df7f0dfcb445f3c3dc3d34c3d34054ce89e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 15:21:26 -0800 Subject: [PATCH 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 ffafa2d142c427614bc87daab65d85a947d92673..3351fb9eb9c14c7f82f0f4d27243776a5b419161 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -417,13 +417,9 @@ impl CollabTitlebarItem { theme: &Theme, cx: &mut RenderContext, ) -> ElementBox { - let (is_followed, is_following) = peer.map_or((false, false), |(peer_id, _, _)| { - ( - workspace.read(cx).is_following(peer_id), - workspace.read(cx).is_followed(peer_id), - ) + let is_followed = peer.map_or(false, |(peer_id, _, _)| { + workspace.read(cx).is_following(peer_id) }); - // my color, around their avatar. let mut avatar_style; if let Some((_, _, location)) = peer.as_ref() { @@ -446,10 +442,6 @@ impl CollabTitlebarItem { replica_color = Some(color); if is_followed { avatar_style.border = Border::all(1.0, color); - } else if is_following { - let our_id = workspace.read(cx).project().read(cx).replica_id(); - let our_color = theme.editor.replica_selection_style(our_id).cursor; - avatar_style.border = Border::all(1.0, our_color); } } diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index daf93ce6a213b6083915d4d390b67194309dea80..f0daa3c7e8617040d10a616b2f6353b068310ece 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -45,10 +45,11 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), + accepts_first_mouse: true, }, |_| IncomingCallNotification::new(incoming_call.clone()), ); - cx.activate_window(window_id); + notification_windows.push(window_id); } } @@ -226,6 +227,7 @@ impl View for IncomingCallNotification { .theme .incoming_call_notification .background; + Flex::row() .with_child(self.render_caller(cx)) .with_child(self.render_buttons(cx)) diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 0815d9c8d8c02c6756f65c61ca0b4c5187d0b016..858886a0e3e5ba7669e476ed6202ba20f72c011f 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -44,6 +44,7 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), + accepts_first_mouse: true, }, |_| { ProjectSharedNotification::new( diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index d027218040909e2412248221de1371fbd22d573a..ad4d7542d300ce9c64db6fb0d2de7f628f56337e 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -156,6 +156,7 @@ pub struct WindowOptions<'a> { pub kind: WindowKind, pub is_movable: bool, pub screen: Option>, + pub accepts_first_mouse: bool, } #[derive(Debug)] @@ -301,6 +302,7 @@ impl<'a> Default for WindowOptions<'a> { kind: WindowKind::Normal, is_movable: true, screen: None, + accepts_first_mouse: false, } } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 958ac2ebd67ea294c942dea7dfba7fc525e1f2d4..a929ba08a333458bf85f9b4b262b063506e6b704 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -252,6 +252,11 @@ unsafe fn build_classes() { do_command_by_selector as extern "C" fn(&Object, Sel, Sel), ); + decl.add_method( + sel!(acceptsFirstMouse:), + accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL, + ); + decl.register() }; } @@ -338,6 +343,7 @@ struct WindowState { ime_state: ImeState, //Retains the last IME Text ime_text: Option, + accepts_first_mouse: bool, } struct InsertText { @@ -444,6 +450,7 @@ impl Window { previous_modifiers_changed_event: None, ime_state: ImeState::None, ime_text: None, + accepts_first_mouse: options.accepts_first_mouse, }))); (*native_window).set_ivar( @@ -1404,6 +1411,14 @@ extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) { } } +extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL { + unsafe { + let state = get_window_state(this); + let state_borrow = state.as_ref().borrow(); + return state_borrow.accepts_first_mouse as BOOL; + } +} + async fn synthetic_drag( window_state: Weak>, drag_id: usize, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c3af91306dce4b1ba79bf46f52fdce79daa34cff..248bab5fdacb58fdf5f5f715932cf4d0598b7691 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -408,6 +408,7 @@ pub fn build_window_options() -> WindowOptions<'static> { kind: WindowKind::Normal, is_movable: true, screen: None, + accepts_first_mouse: false, } } From 131f3471fc5b16f3972ad0bac072b32c6fe17be2 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 20 Jan 2023 09:50:02 -0500 Subject: [PATCH 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 958ac2ebd67ea294c942dea7dfba7fc525e1f2d4..ca233ccf655dc75eaf23399f9b7e437e5562e48c 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -66,12 +66,6 @@ const NSNormalWindowLevel: NSInteger = 0; #[allow(non_upper_case_globals)] const NSPopUpWindowLevel: NSInteger = 101; #[allow(non_upper_case_globals)] -const NSTrackingMouseMoved: NSUInteger = 0x02; -#[allow(non_upper_case_globals)] -const NSTrackingActiveAlways: NSUInteger = 0x80; -#[allow(non_upper_case_globals)] -const NSTrackingInVisibleRect: NSUInteger = 0x200; -#[allow(non_upper_case_globals)] const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4; #[repr(C)] @@ -469,15 +463,7 @@ impl Window { native_window.setTitlebarAppearsTransparent_(YES); } - let tracking_area: id = msg_send![class!(NSTrackingArea), alloc]; - let _: () = msg_send![ - tracking_area, - initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)) - options: NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect - owner: native_view - userInfo: nil - ]; - let _: () = msg_send![native_view, addTrackingArea: tracking_area.autorelease()]; + native_window.setAcceptsMouseMovedEvents_(YES); native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable); native_view.setWantsBestResolutionOpenGLSurface_(YES); @@ -873,11 +859,10 @@ extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) { extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL { let window_state = unsafe { get_window_state(this) }; - let mut window_state_borrow = window_state.as_ref().borrow_mut(); - let event = - unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) }; + let window_height = window_state_borrow.content_size().y(); + let event = unsafe { Event::from_native(native_event, Some(window_height)) }; if let Some(event) = event { if key_equivalent { @@ -902,6 +887,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: function_is_held = event.keystroke.function; Some((event, None)) } + _ => return NO, }; @@ -968,9 +954,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let window_state = unsafe { get_window_state(this) }; let weak_window_state = Rc::downgrade(&window_state); let mut window_state_borrow = window_state.as_ref().borrow_mut(); + let is_active = unsafe { window_state_borrow.native_window.isKeyWindow() == YES }; + + let window_height = window_state_borrow.content_size().y(); + let event = unsafe { Event::from_native(native_event, Some(window_height)) }; - let event = - unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) }; if let Some(event) = event { match &event { Event::MouseMoved( @@ -989,12 +977,16 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { )) .detach(); } + + Event::MouseMoved(_) if !is_active => return, + Event::MouseUp(MouseButtonEvent { button: MouseButton::Left, .. }) => { window_state_borrow.synthetic_drag_counter += 1; } + Event::ModifiersChanged(ModifiersChangedEvent { modifiers }) => { // Only raise modifiers changed event when they have actually changed if let Some(Event::ModifiersChanged(ModifiersChangedEvent { @@ -1008,6 +1000,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { window_state_borrow.previous_modifiers_changed_event = Some(event.clone()); } + _ => {} } From 467e3dc50ac846260bbf44a51d458e3028311a5b Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 20 Jan 2023 10:16:24 -0500 Subject: [PATCH 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 07c0bce6b6502b0550b84c27753e70f454601c24..057a86e7f36501cb530df6cd04a5b057ca266361 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -44,7 +44,7 @@ use gpui::{ ViewContext, ViewHandle, WeakViewHandle, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; -use hover_popover::{hide_hover, HoverState}; +use hover_popover::{hide_hover, HideHover, HoverState}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -1319,7 +1319,7 @@ impl Editor { } } - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); if old_cursor_position.to_display_point(&display_map).row() != new_cursor_position.to_display_point(&display_map).row() @@ -1694,7 +1694,7 @@ impl Editor { return; } - if hide_hover(self, cx) { + if hide_hover(self, &HideHover, cx) { return; } @@ -6174,7 +6174,7 @@ impl View for Editor { cx.defer(move |cx| { if let Some(editor) = handle.upgrade(cx) { editor.update(cx, |editor, cx| { - hide_hover(editor, cx); + hide_hover(editor, &HideHover, cx); hide_link_definition(editor, cx); }) } @@ -6223,7 +6223,7 @@ impl View for Editor { self.buffer .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); self.hide_context_menu(cx); - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); cx.emit(Event::Blurred); cx.notify(); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 5c34b9838047187f8c0507e8669ab7a4e39ec813..bce63ca0cf796ef0e9537ff7f40f241f196c5331 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -7,7 +7,7 @@ use crate::{ display_map::{BlockStyle, DisplaySnapshot, TransformBlock}, git::{diff_hunk_to_display, DisplayDiffHunk}, hover_popover::{ - HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, + HideHover, HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, }, link_go_to_definition::{ GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink, @@ -114,6 +114,7 @@ impl EditorElement { fn attach_mouse_handlers( view: &WeakViewHandle, position_map: &Arc, + has_popovers: bool, visible_bounds: RectF, text_bounds: RectF, gutter_bounds: RectF, @@ -190,8 +191,10 @@ impl EditorElement { } } }) - .on_move_out(|e, cx| { - println!("on move out"); + .on_move_out(move |_, cx| { + if has_popovers { + cx.dispatch_action(HideHover); + } }) .on_scroll({ let position_map = position_map.clone(); @@ -1873,6 +1876,7 @@ impl Element for EditorElement { Self::attach_mouse_handlers( &self.view, &layout.position_map, + layout.hover_popovers.is_some(), visible_bounds, text_bounds, gutter_bounds, diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 7369b0a6f40b5a20718d354e3a1ece9fe5c16aa4..6d003cae5dbaf42d54a85b56ea941d0851816538 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -29,12 +29,16 @@ pub struct HoverAt { pub point: Option, } +#[derive(Copy, Clone, PartialEq)] +pub struct HideHover; + actions!(editor, [Hover]); -impl_internal_actions!(editor, [HoverAt]); +impl_internal_actions!(editor, [HoverAt, HideHover]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(hover); cx.add_action(hover_at); + cx.add_action(hide_hover); } /// Bindable action which uses the most recent selection head to trigger a hover @@ -50,7 +54,7 @@ pub fn hover_at(editor: &mut Editor, action: &HoverAt, cx: &mut ViewContext) -> bool { +pub fn hide_hover(editor: &mut Editor, _: &HideHover, cx: &mut ViewContext) -> bool { let did_hide = editor.hover_state.info_popover.take().is_some() | editor.hover_state.diagnostic_popover.take().is_some(); @@ -67,6 +71,10 @@ pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext) -> bool { editor.clear_background_highlights::(cx); + if did_hide { + cx.notify(); + } + did_hide } @@ -121,7 +129,7 @@ fn show_hover( // Hover triggered from same location as last time. Don't show again. return; } else { - hide_hover(editor, cx); + hide_hover(editor, &HideHover, cx); } } } diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 0ca837a843ab7c0c56610641095068cf4a7843f6..1b3fd96dbd99fb0d3363d5a9fcd5827aea921f91 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -17,7 +17,7 @@ use workspace::WorkspaceId; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, - hover_popover::hide_hover, + hover_popover::{hide_hover, HideHover}, persistence::DB, Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint, }; @@ -307,7 +307,7 @@ impl Editor { ) { let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); self.scroll_manager.set_scroll_position( scroll_position, &map, @@ -323,7 +323,7 @@ impl Editor { } pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext) { - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); let top_row = scroll_anchor .top_anchor .to_point(&self.buffer().read(cx).snapshot(cx)) @@ -337,7 +337,7 @@ impl Editor { scroll_anchor: ScrollAnchor, cx: &mut ViewContext, ) { - hide_hover(self, cx); + hide_hover(self, &HideHover, cx); let top_row = scroll_anchor .top_anchor .to_point(&self.buffer().read(cx).snapshot(cx)) From 95e661a78c38947c83f59f69e82ba9497924db5a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 19 Jan 2023 15:21:26 -0800 Subject: [PATCH 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 a929ba08a333458bf85f9b4b262b063506e6b704..f5a21c46440a4b4f1e45276a181f37e3b018dfe0 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -443,6 +443,7 @@ impl Window { scene_to_render: Default::default(), renderer: Renderer::new(true, fonts), last_fresh_keydown: None, + accepts_first_mouse: matches!(options.kind, WindowKind::PopUp), traffic_light_position: options .titlebar .as_ref() diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index bbaf1ed0bbce3ec2a096e37719354ce100d00db6..4ccc319219c33c4f882d2a27ed2c0b0d43796e5f 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -156,6 +156,7 @@ impl Presenter { self.cursor_regions = scene.cursor_regions(); self.mouse_regions = scene.mouse_regions(); + // window.is_topmost for the mouse moved event's postion? if cx.window_is_active(self.window_id) { if let Some(event) = self.last_mouse_moved_event.clone() { self.dispatch_event(event, true, cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6a8fdaa17b4e54d49d8868e7ebbbc3cdc8e32c21..9665db6be60955af11c10979cad87bbc23a29ed5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,14 +31,18 @@ use futures::{ }; use gpui::{ actions, + color::Color, elements::*, - geometry::vector::Vector2F, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, + Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -98,7 +102,8 @@ actions!( ToggleLeftSidebar, ToggleRightSidebar, NewTerminal, - NewSearch + NewSearch, + ShowNotif, ] ); From 97203e1e02fdf3c25d5ffd037d495947a74ac6d5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 09:19:58 -0800 Subject: [PATCH 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 f0daa3c7e8617040d10a616b2f6353b068310ece..6ad533665e7477494e221da71f85a31646671447 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -45,7 +45,6 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), - accepts_first_mouse: true, }, |_| IncomingCallNotification::new(incoming_call.clone()), ); diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 858886a0e3e5ba7669e476ed6202ba20f72c011f..0815d9c8d8c02c6756f65c61ca0b4c5187d0b016 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -44,7 +44,6 @@ pub fn init(cx: &mut MutableAppContext) { kind: WindowKind::PopUp, is_movable: false, screen: Some(screen), - accepts_first_mouse: true, }, |_| { ProjectSharedNotification::new( diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index ad4d7542d300ce9c64db6fb0d2de7f628f56337e..99d607e4070c5eebb2039dba3754e6100a59b30a 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -156,7 +156,6 @@ pub struct WindowOptions<'a> { pub kind: WindowKind, pub is_movable: bool, pub screen: Option>, - pub accepts_first_mouse: bool, } #[derive(Debug)] @@ -180,7 +179,7 @@ impl Default for Appearance { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum WindowKind { Normal, PopUp, @@ -302,7 +301,6 @@ impl<'a> Default for WindowOptions<'a> { kind: WindowKind::Normal, is_movable: true, screen: None, - accepts_first_mouse: false, } } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index f5a21c46440a4b4f1e45276a181f37e3b018dfe0..26305e86da7fe08e645b12536581934e18e15a38 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -443,7 +443,7 @@ impl Window { scene_to_render: Default::default(), renderer: Renderer::new(true, fonts), last_fresh_keydown: None, - accepts_first_mouse: matches!(options.kind, WindowKind::PopUp), + accepts_first_mouse: options.kind == WindowKind::PopUp, traffic_light_position: options .titlebar .as_ref() @@ -451,7 +451,6 @@ impl Window { previous_modifiers_changed_event: None, ime_state: ImeState::None, ime_text: None, - accepts_first_mouse: options.accepts_first_mouse, }))); (*native_window).set_ivar( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9665db6be60955af11c10979cad87bbc23a29ed5..8735f33fbe6cfd3197bd825e8c5abc01652eea46 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,18 +31,14 @@ use futures::{ }; use gpui::{ actions, - color::Color, elements::*, - geometry::{ - rect::RectF, - vector::{vec2f, Vector2F}, - }, + geometry::vector::Vector2F, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 248bab5fdacb58fdf5f5f715932cf4d0598b7691..c3af91306dce4b1ba79bf46f52fdce79daa34cff 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -408,7 +408,6 @@ pub fn build_window_options() -> WindowOptions<'static> { kind: WindowKind::Normal, is_movable: true, screen: None, - accepts_first_mouse: false, } } From 8af1294ba51d6c8b114d06d3f8a1aa7cfbcc20ae Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 09:37:09 -0800 Subject: [PATCH 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 4104b0ebf893d7d57ef6a9f82327b140eba16b1c..0da2e4351e477476300a0ebfbfa3cc20e8a55324 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -316,6 +316,7 @@ enum ImeState { struct WindowState { id: usize, native_window: id, + kind: WindowKind, event_callback: Option bool>>, activate_callback: Option>, resize_callback: Option>, @@ -337,7 +338,6 @@ struct WindowState { ime_state: ImeState, //Retains the last IME Text ime_text: Option, - accepts_first_mouse: bool, } struct InsertText { @@ -422,6 +422,7 @@ impl Window { let window = Self(Rc::new(RefCell::new(WindowState { id, native_window, + kind: options.kind, event_callback: None, resize_callback: None, should_close_callback: None, @@ -437,7 +438,6 @@ impl Window { scene_to_render: Default::default(), renderer: Renderer::new(true, fonts), last_fresh_keydown: None, - accepts_first_mouse: options.kind == WindowKind::PopUp, traffic_light_position: options .titlebar .as_ref() @@ -985,7 +985,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { .detach(); } - Event::MouseMoved(_) if !is_active => return, + Event::MouseMoved(_) + if !(is_active || window_state_borrow.kind == WindowKind::PopUp) => + { + return + } Event::MouseUp(MouseButtonEvent { button: MouseButton::Left, @@ -1408,7 +1412,7 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL { unsafe { let state = get_window_state(this); let state_borrow = state.as_ref().borrow(); - return state_borrow.accepts_first_mouse as BOOL; + return state_borrow.kind == WindowKind::PopUp; } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8735f33fbe6cfd3197bd825e8c5abc01652eea46..9665db6be60955af11c10979cad87bbc23a29ed5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,14 +31,18 @@ use futures::{ }; use gpui::{ actions, + color::Color, elements::*, - geometry::vector::Vector2F, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, + Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; From 2aa7a9e95bcbab7533e8f9930d88980ac2f35027 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 10:39:31 -0800 Subject: [PATCH 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 928e20862746c4935db279c81d31eb389964be58..766e181671e37722a1d6f851ab62b6fc38f45490 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -830,15 +830,24 @@ impl Language { let mut values = HashMap::default(); for (ix, name) in query.capture_names().iter().enumerate() { if let Some(override_name) = name.strip_prefix("override.") { - let value = self - .config - .overrides - .remove(override_name) - .ok_or_else(|| anyhow!("no such override {override_name}"))?; + let value = self.config.overrides.remove(override_name).ok_or_else(|| { + anyhow!( + "language {:?} has override in query but not in config: {override_name:?}", + self.config.name + ) + })?; values.insert(ix as u32, value); } } + if !self.config.overrides.is_empty() { + let keys = self.config.overrides.keys().collect::>(); + Err(anyhow!( + "language {:?} has overrides in config not in query: {keys:?}", + self.config.name + ))?; + } + self.grammar_mut().override_config = Some(OverrideConfig { query, values }); Ok(self) } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index d7fa67b1d451c13fc103cb5210650fd8efae9d4c..240d1dc49e5ac6603b1c0760a2e11f748274b276 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use gpui::executor::Background; pub use language::*; use lazy_static::lazy_static; @@ -145,7 +146,9 @@ pub(crate) fn language( .unwrap() .data, ) + .with_context(|| format!("failed to load config.toml for language {name:?}")) .unwrap(); + let mut language = Language::new(config, Some(grammar)); if let Some(query) = load_query(name, "/highlights") { diff --git a/crates/zed/src/languages/c/config.toml b/crates/zed/src/languages/c/config.toml index 5b4841561043ad5c3aa6ed42d786ef50419e067b..1a5fe9339ad9fa3a0c9da1198ae5bebfd8600819 100644 --- a/crates/zed/src/languages/c/config.toml +++ b/crates/zed/src/languages/c/config.toml @@ -7,5 +7,20 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/c/overrides.scm b/crates/zed/src/languages/c/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..0d14e6c205e3f4c624d8bfa3352d852cdcfb9193 --- /dev/null +++ b/crates/zed/src/languages/c/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string_literal) @override.string diff --git a/crates/zed/src/languages/cpp/config.toml b/crates/zed/src/languages/cpp/config.toml index e9a793ec3c97b7ac7ee941e5e57cb3886ccec202..c83adfb067f2d2da8819b32b144c90f1c40992b4 100644 --- a/crates/zed/src/languages/cpp/config.toml +++ b/crates/zed/src/languages/cpp/config.toml @@ -7,5 +7,20 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/cpp/overrides.scm b/crates/zed/src/languages/cpp/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..0d14e6c205e3f4c624d8bfa3352d852cdcfb9193 --- /dev/null +++ b/crates/zed/src/languages/cpp/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string_literal) @override.string diff --git a/crates/zed/src/languages/css/config.toml b/crates/zed/src/languages/css/config.toml index 28def3abd53d8bbd7fdcb7465cc402d4b38871bf..cf8d4f1e2f88682139fb6a4bf28623ca4826e338 100644 --- a/crates/zed/src/languages/css/config.toml +++ b/crates/zed/src/languages/css/config.toml @@ -5,5 +5,20 @@ brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, - { start = "\"", end = "\"", close = true, newline = false } + { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, ] diff --git a/crates/zed/src/languages/css/overrides.scm b/crates/zed/src/languages/css/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..9617249d3c4b1f99750b752f51493dbacc740267 --- /dev/null +++ b/crates/zed/src/languages/css/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string_value) @override.string diff --git a/crates/zed/src/languages/elixir/config.toml b/crates/zed/src/languages/elixir/config.toml index 4e1af93943d2e63763181b83590a4ff72f809c23..0185d8eec35a2173eb802f0e0618e43b23fd19dd 100644 --- a/crates/zed/src/languages/elixir/config.toml +++ b/crates/zed/src/languages/elixir/config.toml @@ -6,5 +6,20 @@ brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, - { start = "\"", end = "\"", close = true, newline = false } + { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, ] diff --git a/crates/zed/src/languages/elixir/overrides.scm b/crates/zed/src/languages/elixir/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..ed1b20d44b262c5ce9dca529fc6f49425441d0e4 --- /dev/null +++ b/crates/zed/src/languages/elixir/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +[(string) (charlist)] @override.string diff --git a/crates/zed/src/languages/erb/config.toml b/crates/zed/src/languages/erb/config.toml index 280219a1191c1638190a6d52e1d7e717daa665fd..9cfcef0c8ba8ff4b3a5aedd574f01f6050c5798a 100644 --- a/crates/zed/src/languages/erb/config.toml +++ b/crates/zed/src/languages/erb/config.toml @@ -4,5 +4,4 @@ autoclose_before = ">})" brackets = [ { start = "<", end = ">", close = true, newline = true }, ] - -block_comment = ["<%#", "%>"] \ No newline at end of file +block_comment = ["<%#", "%>"] diff --git a/crates/zed/src/languages/go/config.toml b/crates/zed/src/languages/go/config.toml index fc6167e311b10b000bcba0ff8645e40bdfa14c8d..3f9464594cd76528ff6bf0e80b75ad6d3a08d4cf 100644 --- a/crates/zed/src/languages/go/config.toml +++ b/crates/zed/src/languages/go/config.toml @@ -7,5 +7,20 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/go/overrides.scm b/crates/zed/src/languages/go/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..fc32c47f681d48d2adfb4f94b918305136507d50 --- /dev/null +++ b/crates/zed/src/languages/go/overrides.scm @@ -0,0 +1,6 @@ +(comment) @override.comment +[ + (interpreted_string_literal) + (raw_string_literal) + (rune_literal) +] @override.string diff --git a/crates/zed/src/languages/html/config.toml b/crates/zed/src/languages/html/config.toml index 3e618da25e06f5f37c3106db12bd1d6abf2b1a4c..f4f0ba26eac95488c79c6db91afb5700ebe6a4dd 100644 --- a/crates/zed/src/languages/html/config.toml +++ b/crates/zed/src/languages/html/config.toml @@ -9,4 +9,18 @@ brackets = [ { start = "!--", end = " --", close = true, newline = false }, ] -block_comment = [""] \ No newline at end of file +block_comment = [""] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/html/overrides.scm b/crates/zed/src/languages/html/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..8f6a1f9eeae3dca51d2dc4473eae93215fd54472 --- /dev/null +++ b/crates/zed/src/languages/html/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(quoted_attribute_value) @override.string \ No newline at end of file diff --git a/crates/zed/src/languages/javascript/config.toml b/crates/zed/src/languages/javascript/config.toml index d7612d13f08ec2eac41d30bef1da7cc41eccbd20..a5a656393dcb6aa338a8431eb63d8dc5ca4898d5 100644 --- a/crates/zed/src/languages/javascript/config.toml +++ b/crates/zed/src/languages/javascript/config.toml @@ -12,3 +12,21 @@ brackets = [ { start = "`", end = "`", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.element] +line_comment = { remove = true } +block_comment = ["{/* ", " */}"] diff --git a/crates/zed/src/languages/javascript/overrides.scm b/crates/zed/src/languages/javascript/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..f19c1d9203a697d2a34101f031e74afb8101b3be --- /dev/null +++ b/crates/zed/src/languages/javascript/overrides.scm @@ -0,0 +1,9 @@ +(comment) @override.comment +(string) @override.string + +[ + (jsx_element) + (jsx_fragment) + (jsx_self_closing_element) + (jsx_expression) +] @override.element diff --git a/crates/zed/src/languages/json/config.toml b/crates/zed/src/languages/json/config.toml index cb36279358a50dd138b0e9d0632b559895c19cd3..48a1fb0c993f0481d0862eb4a1820c922adcb12d 100644 --- a/crates/zed/src/languages/json/config.toml +++ b/crates/zed/src/languages/json/config.toml @@ -7,3 +7,9 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, ] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/json/overrides.scm b/crates/zed/src/languages/json/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..83911ae4a514eed107152d3b8ad52c1a56f85d53 --- /dev/null +++ b/crates/zed/src/languages/json/overrides.scm @@ -0,0 +1,2 @@ +(comment) @comment +(string) @override.string \ No newline at end of file diff --git a/crates/zed/src/languages/python/config.toml b/crates/zed/src/languages/python/config.toml index c6b41ed700a5ef089499088e7b217924c1952e3c..a817de8e3ba87b2619d4935dbe536060d3522137 100644 --- a/crates/zed/src/languages/python/config.toml +++ b/crates/zed/src/languages/python/config.toml @@ -12,4 +12,18 @@ brackets = [ auto_indent_using_last_non_empty_line = false increase_indent_pattern = ":$" -decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:" \ No newline at end of file +decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:" + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/python/overrides.scm b/crates/zed/src/languages/python/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..3a7ad5f035efa113afe29444f8c75e40585aafcd --- /dev/null +++ b/crates/zed/src/languages/python/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/ruby/config.toml b/crates/zed/src/languages/ruby/config.toml index 5600266de3a2a43fc0740cc6c1249bcf7d00149b..4fd21d8bd0a2598f30a006e5ccd232ec089475af 100644 --- a/crates/zed/src/languages/ruby/config.toml +++ b/crates/zed/src/languages/ruby/config.toml @@ -7,5 +7,19 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, - { start = "'", end = "'", close = false, newline = false }, -] \ No newline at end of file + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/ruby/overrides.scm b/crates/zed/src/languages/ruby/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..3a7ad5f035efa113afe29444f8c75e40585aafcd --- /dev/null +++ b/crates/zed/src/languages/ruby/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/rust/config.toml b/crates/zed/src/languages/rust/config.toml index 971ed27ebc3147ecee9ac6884712a3937ff1b7ba..f9f6b597173e49243c8552ab6a3cd64f7a7d1957 100644 --- a/crates/zed/src/languages/rust/config.toml +++ b/crates/zed/src/languages/rust/config.toml @@ -11,3 +11,19 @@ brackets = [ { start = "'", end = "'", close = false, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = false, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = false, newline = true }, +] diff --git a/crates/zed/src/languages/rust/overrides.scm b/crates/zed/src/languages/rust/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..29128d98e7f970f7f25622835ca14afd5da5c814 --- /dev/null +++ b/crates/zed/src/languages/rust/overrides.scm @@ -0,0 +1,8 @@ +[ + (string_literal) + (raw_string_literal) +] @override.string +[ + (line_comment) + (block_comment) +] @override.comment diff --git a/crates/zed/src/languages/scheme/config.toml b/crates/zed/src/languages/scheme/config.toml index 7e63673834f201b63f77140d5449f08af2efa30c..46e11bdc1158cd89237c0c10ef35de3a6b8ceb14 100644 --- a/crates/zed/src/languages/scheme/config.toml +++ b/crates/zed/src/languages/scheme/config.toml @@ -7,3 +7,15 @@ brackets = [ { start = "(", end = ")", close = true, newline = false }, { start = "\"", end = "\"", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/scheme/overrides.scm b/crates/zed/src/languages/scheme/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..28a8a569dfd86c1f96f540777e0c3f074d2045c7 --- /dev/null +++ b/crates/zed/src/languages/scheme/overrides.scm @@ -0,0 +1,6 @@ +[ + (comment) + (block_comment) + (directive) +] @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/toml/config.toml b/crates/zed/src/languages/toml/config.toml index cd624aecef07aadf8c9354749479676ae3768fe4..30797bf141bf0a77a0dfcab431445104ee067095 100644 --- a/crates/zed/src/languages/toml/config.toml +++ b/crates/zed/src/languages/toml/config.toml @@ -6,4 +6,17 @@ brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, + { start = "'", end = "'", close = true, newline = false }, +] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, ] diff --git a/crates/zed/src/languages/toml/overrides.scm b/crates/zed/src/languages/toml/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..3a7ad5f035efa113afe29444f8c75e40585aafcd --- /dev/null +++ b/crates/zed/src/languages/toml/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string diff --git a/crates/zed/src/languages/typescript/config.toml b/crates/zed/src/languages/typescript/config.toml index 8e5886167c763c1bb86a5ee0e6e8d88d08ad0d7b..3c2146e0015254ac69cecae7acc07422bf1bce28 100644 --- a/crates/zed/src/languages/typescript/config.toml +++ b/crates/zed/src/languages/typescript/config.toml @@ -12,3 +12,17 @@ brackets = [ { start = "`", end = "`", close = true, newline = false }, { start = "/*", end = " */", close = true, newline = false }, ] + +[overrides.comment] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] + +[overrides.string] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/typescript/overrides.scm b/crates/zed/src/languages/typescript/overrides.scm new file mode 100644 index 0000000000000000000000000000000000000000..3a7ad5f035efa113afe29444f8c75e40585aafcd --- /dev/null +++ b/crates/zed/src/languages/typescript/overrides.scm @@ -0,0 +1,2 @@ +(comment) @override.comment +(string) @override.string From 8dcef46842ea329a1d47e5b6e2ba9be44fd2b1cf Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 10:44:33 -0800 Subject: [PATCH 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 07198e8090c612df4adb142704dd797da8845e26..382311e2040d85e0fe930600bc36446215a32d16 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1421,8 +1421,8 @@ fn test_language_config_at(cx: &mut MutableAppContext) { ) .with_override_query( r#" - (jsx_element) @override.element - (string) @override.string + (jsx_element) @element + (string) @string "#, ) .unwrap(); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 766e181671e37722a1d6f851ab62b6fc38f45490..e926a776bd47435f6d365da6f7d81630dc8ec4fd 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -829,10 +829,10 @@ impl Language { let mut values = HashMap::default(); for (ix, name) in query.capture_names().iter().enumerate() { - if let Some(override_name) = name.strip_prefix("override.") { - let value = self.config.overrides.remove(override_name).ok_or_else(|| { + if !name.starts_with('_') { + let value = self.config.overrides.remove(name).ok_or_else(|| { anyhow!( - "language {:?} has override in query but not in config: {override_name:?}", + "language {:?} has override in query but not in config: {name:?}", self.config.name ) })?; diff --git a/crates/zed/src/languages/c/overrides.scm b/crates/zed/src/languages/c/overrides.scm index 0d14e6c205e3f4c624d8bfa3352d852cdcfb9193..178355c67c9797b371be81de98c23cba9373c38d 100644 --- a/crates/zed/src/languages/c/overrides.scm +++ b/crates/zed/src/languages/c/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string_literal) @override.string +(comment) @comment +(string_literal) @string diff --git a/crates/zed/src/languages/cpp/overrides.scm b/crates/zed/src/languages/cpp/overrides.scm index 0d14e6c205e3f4c624d8bfa3352d852cdcfb9193..178355c67c9797b371be81de98c23cba9373c38d 100644 --- a/crates/zed/src/languages/cpp/overrides.scm +++ b/crates/zed/src/languages/cpp/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string_literal) @override.string +(comment) @comment +(string_literal) @string diff --git a/crates/zed/src/languages/css/overrides.scm b/crates/zed/src/languages/css/overrides.scm index 9617249d3c4b1f99750b752f51493dbacc740267..c0db9fe3274a7746ebb479618efbca117129bc60 100644 --- a/crates/zed/src/languages/css/overrides.scm +++ b/crates/zed/src/languages/css/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string_value) @override.string +(comment) @comment +(string_value) @string diff --git a/crates/zed/src/languages/elixir/overrides.scm b/crates/zed/src/languages/elixir/overrides.scm index ed1b20d44b262c5ce9dca529fc6f49425441d0e4..181254018126d5ee0faaad24911c3b71b1e30c5b 100644 --- a/crates/zed/src/languages/elixir/overrides.scm +++ b/crates/zed/src/languages/elixir/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -[(string) (charlist)] @override.string +(comment) @comment +[(string) (charlist)] @string diff --git a/crates/zed/src/languages/go/overrides.scm b/crates/zed/src/languages/go/overrides.scm index fc32c47f681d48d2adfb4f94b918305136507d50..9eb287df3f448b20848572f2bb81b9b4e9f80d3d 100644 --- a/crates/zed/src/languages/go/overrides.scm +++ b/crates/zed/src/languages/go/overrides.scm @@ -1,6 +1,6 @@ -(comment) @override.comment +(comment) @comment [ (interpreted_string_literal) (raw_string_literal) (rune_literal) -] @override.string +] @string diff --git a/crates/zed/src/languages/html/overrides.scm b/crates/zed/src/languages/html/overrides.scm index 8f6a1f9eeae3dca51d2dc4473eae93215fd54472..97accffd6721b6feaf6fefa61a1f9d64019b89f1 100644 --- a/crates/zed/src/languages/html/overrides.scm +++ b/crates/zed/src/languages/html/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(quoted_attribute_value) @override.string \ No newline at end of file +(comment) @comment +(quoted_attribute_value) @string \ No newline at end of file diff --git a/crates/zed/src/languages/javascript/overrides.scm b/crates/zed/src/languages/javascript/overrides.scm index f19c1d9203a697d2a34101f031e74afb8101b3be..5e43c4a94ad69e6b7d6c3afab091235b789c6e1d 100644 --- a/crates/zed/src/languages/javascript/overrides.scm +++ b/crates/zed/src/languages/javascript/overrides.scm @@ -1,9 +1,9 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string [ (jsx_element) (jsx_fragment) (jsx_self_closing_element) (jsx_expression) -] @override.element +] @element diff --git a/crates/zed/src/languages/json/overrides.scm b/crates/zed/src/languages/json/overrides.scm index 83911ae4a514eed107152d3b8ad52c1a56f85d53..746dbc5cd9548285649091aaa0694ee65164b0fc 100644 --- a/crates/zed/src/languages/json/overrides.scm +++ b/crates/zed/src/languages/json/overrides.scm @@ -1,2 +1 @@ -(comment) @comment -(string) @override.string \ No newline at end of file +(string) @string \ No newline at end of file diff --git a/crates/zed/src/languages/python/overrides.scm b/crates/zed/src/languages/python/overrides.scm index 3a7ad5f035efa113afe29444f8c75e40585aafcd..8a58e304e5c5185166a09bc78eb835527a246301 100644 --- a/crates/zed/src/languages/python/overrides.scm +++ b/crates/zed/src/languages/python/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string diff --git a/crates/zed/src/languages/ruby/overrides.scm b/crates/zed/src/languages/ruby/overrides.scm index 3a7ad5f035efa113afe29444f8c75e40585aafcd..8a58e304e5c5185166a09bc78eb835527a246301 100644 --- a/crates/zed/src/languages/ruby/overrides.scm +++ b/crates/zed/src/languages/ruby/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string diff --git a/crates/zed/src/languages/rust/overrides.scm b/crates/zed/src/languages/rust/overrides.scm index 29128d98e7f970f7f25622835ca14afd5da5c814..216a3951476509b79e35bec2f79b3feb9a1afa44 100644 --- a/crates/zed/src/languages/rust/overrides.scm +++ b/crates/zed/src/languages/rust/overrides.scm @@ -1,8 +1,8 @@ [ (string_literal) (raw_string_literal) -] @override.string +] @string [ (line_comment) (block_comment) -] @override.comment +] @comment diff --git a/crates/zed/src/languages/scheme/overrides.scm b/crates/zed/src/languages/scheme/overrides.scm index 28a8a569dfd86c1f96f540777e0c3f074d2045c7..8c0d41b046c6dbe72937d662a7c8ebdb023fe49e 100644 --- a/crates/zed/src/languages/scheme/overrides.scm +++ b/crates/zed/src/languages/scheme/overrides.scm @@ -2,5 +2,5 @@ (comment) (block_comment) (directive) -] @override.comment -(string) @override.string +] @comment +(string) @string diff --git a/crates/zed/src/languages/toml/overrides.scm b/crates/zed/src/languages/toml/overrides.scm index 3a7ad5f035efa113afe29444f8c75e40585aafcd..8a58e304e5c5185166a09bc78eb835527a246301 100644 --- a/crates/zed/src/languages/toml/overrides.scm +++ b/crates/zed/src/languages/toml/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string diff --git a/crates/zed/src/languages/tsx/overrides.scm b/crates/zed/src/languages/tsx/overrides.scm index 183f7b4f98db4e016c0981b8c2f4509e213bfb97..d86186a903373bb8f1351141b5611ba48aa090ca 100644 --- a/crates/zed/src/languages/tsx/overrides.scm +++ b/crates/zed/src/languages/tsx/overrides.scm @@ -3,5 +3,5 @@ (jsx_fragment) (jsx_self_closing_element) (jsx_expression) -] @override.element -(string) @override.string +] @element +(string) @string diff --git a/crates/zed/src/languages/typescript/overrides.scm b/crates/zed/src/languages/typescript/overrides.scm index 3a7ad5f035efa113afe29444f8c75e40585aafcd..8a58e304e5c5185166a09bc78eb835527a246301 100644 --- a/crates/zed/src/languages/typescript/overrides.scm +++ b/crates/zed/src/languages/typescript/overrides.scm @@ -1,2 +1,2 @@ -(comment) @override.comment -(string) @override.string +(comment) @comment +(string) @string From 2f42af2ac3ef922cfa87a0fce930b6360e452edf Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 13:02:38 -0800 Subject: [PATCH 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 3924e84d61a051c26b0b5e34719f71a53b92d631..1ef2ac8a16c9420d5ed019d36a05e1201236849f 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -13,6 +13,8 @@ // Whether to show the informational hover box when moving the mouse // over symbols in the editor. "hover_popover_enabled": true, + // Whether to confirm before quitting Zed. + "confirm_quit": false, // Whether the cursor blinks in the editor. "cursor_blink": true, // Whether to pop the completions menu while typing in an editor without diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 8dc29f3d606fdeaf0f74867863b197980dc5c43b..f257d9549358cdffcb932b535e11fb51e2ddf422 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -101,10 +101,7 @@ impl TestServer { async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient { cx.update(|cx| { cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf())); - - let mut settings = Settings::test(cx); - settings.projects_online_by_default = false; - cx.set_global(settings); + cx.set_global(Settings::test(cx)); }); let http = FakeHttpClient::with_404_response(); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 6a4086c5eb15cc1b126ce343c78b00d745302eb3..ad1fad85b1938220e4f2259ff623dc274748aac5 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -989,7 +989,7 @@ impl MutableAppContext { window.toggle_full_screen(); } - fn prompt( + pub fn prompt( &self, window_id: usize, level: PromptLevel, diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index e29a98e775f528ee6242c16da24364e3bf7b62e8..8b2c12a59bf8b673965838ed62319318a0574c6f 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -28,12 +28,12 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; #[derive(Clone)] pub struct Settings { pub experiments: FeatureFlags, - pub projects_online_by_default: bool, pub buffer_font_family: FamilyId, pub default_buffer_font_size: f32, pub buffer_font_size: f32, pub active_pane_magnification: f32, pub cursor_blink: bool, + pub confirm_quit: bool, pub hover_popover_enabled: bool, pub show_completions_on_input: bool, pub vim_mode: bool, @@ -294,6 +294,8 @@ pub struct SettingsFileContent { #[serde(default)] pub cursor_blink: Option, #[serde(default)] + pub confirm_quit: Option, + #[serde(default)] pub hover_popover_enabled: Option, #[serde(default)] pub show_completions_on_input: Option, @@ -356,10 +358,10 @@ impl Settings { buffer_font_size: defaults.buffer_font_size.unwrap(), active_pane_magnification: defaults.active_pane_magnification.unwrap(), default_buffer_font_size: defaults.buffer_font_size.unwrap(), + confirm_quit: defaults.confirm_quit.unwrap(), cursor_blink: defaults.cursor_blink.unwrap(), hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), show_completions_on_input: defaults.show_completions_on_input.unwrap(), - projects_online_by_default: defaults.projects_online_by_default.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), default_dock_anchor: defaults.default_dock_anchor.unwrap(), @@ -407,10 +409,6 @@ impl Settings { } } - merge( - &mut self.projects_online_by_default, - data.projects_online_by_default, - ); merge(&mut self.buffer_font_size, data.buffer_font_size); merge( &mut self.active_pane_magnification, @@ -418,6 +416,7 @@ impl Settings { ); merge(&mut self.default_buffer_font_size, data.buffer_font_size); merge(&mut self.cursor_blink, data.cursor_blink); + merge(&mut self.confirm_quit, data.confirm_quit); merge(&mut self.hover_popover_enabled, data.hover_popover_enabled); merge( &mut self.show_completions_on_input, @@ -557,6 +556,7 @@ impl Settings { buffer_font_size: 14., active_pane_magnification: 1., default_buffer_font_size: 14., + confirm_quit: false, cursor_blink: true, hover_popover_enabled: true, show_completions_on_input: true, @@ -582,7 +582,6 @@ impl Settings { language_defaults: Default::default(), language_overrides: Default::default(), lsp: Default::default(), - projects_online_by_default: true, theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), telemetry_defaults: TelemetrySettings { diagnostics: Some(true), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c3af91306dce4b1ba79bf46f52fdce79daa34cff..e765a9926b769bcf820c3b5d61d17c33f6d4df58 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -14,6 +14,7 @@ use collections::VecDeque; pub use editor; use editor::{Editor, MultiBuffer}; +use futures::StreamExt; use gpui::{ actions, geometry::{ @@ -22,7 +23,8 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, ClipboardItem, TitlebarOptions, ViewContext, WindowKind, + AssetSource, AsyncAppContext, ClipboardItem, PromptLevel, TitlebarOptions, ViewContext, + WindowKind, }; use language::Rope; use lazy_static::lazy_static; @@ -421,7 +423,25 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { // prompt in the active window before switching to a different window. workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id())); + let should_confirm = cx.global::().confirm_quit; cx.spawn(|mut cx| async move { + dbg!(should_confirm, workspaces.first()); + + if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { + let answer = cx + .prompt( + workspace.window_id(), + PromptLevel::Info, + "Are you sure you want to quit?", + &["Quit", "Cancel"], + ) + .next() + .await; + if answer != Some(0) { + return Ok(()); + } + } + // If the user cancels any save prompt, then keep the app open. for workspace in workspaces { if !workspace From 2e37c0ea4a8bcbd0629a42250a43d916e342b7e7 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 20 Jan 2023 13:28:13 -0800 Subject: [PATCH 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 cef32ff98cea92e0804caa983db638e3a0996e7b..4fbaaa80588edcd1cb329dc5481d9ee57ad4d344 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -62,7 +62,7 @@ pub use multi_buffer::{ }; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; -use project::{FormatTrigger, LocationLink, Project, ProjectPath, ProjectTransaction}; +use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -5012,13 +5012,15 @@ impl Editor { cx: &mut ViewContext, ) { let pane = workspace.active_pane().clone(); - for definition in definitions { + // If there is one definition, just open it directly + if let [definition] = definitions.as_slice() { let range = definition .target .range .to_offset(definition.target.buffer.read(cx)); - let target_editor_handle = workspace.open_project_item(definition.target.buffer, cx); + let target_editor_handle = + workspace.open_project_item(definition.target.buffer.clone(), cx); target_editor_handle.update(cx, |target_editor, cx| { // When selecting a definition in a different buffer, disable the nav history // to avoid creating a history entry at the previous cursor location. @@ -5031,6 +5033,28 @@ impl Editor { pane.update(cx, |pane, _| pane.enable_history()); }); + } else { + let replica_id = editor_handle.read(cx).replica_id(cx); + let title = definitions + .iter() + .find(|definition| definition.origin.is_some()) + .and_then(|definition| { + definition.origin.as_ref().map(|origin| { + let buffer = origin.buffer.read(cx); + format!( + "Definitions for {}", + buffer + .text_for_range(origin.range.clone()) + .collect::() + ) + }) + }) + .unwrap_or("Definitions".to_owned()); + let locations = definitions + .into_iter() + .map(|definition| definition.target) + .collect(); + Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx) } } @@ -5051,64 +5075,87 @@ impl Editor { let project = workspace.project().clone(); let references = project.update(cx, |project, cx| project.references(&buffer, head, cx)); Some(cx.spawn(|workspace, mut cx| async move { - let mut locations = references.await?; + let locations = references.await?; if locations.is_empty() { return Ok(()); } - locations.sort_by_key(|location| location.buffer.id()); - let mut locations = locations.into_iter().peekable(); - let mut ranges_to_highlight = Vec::new(); - - let excerpt_buffer = cx.add_model(|cx| { - let mut symbol_name = None; - let mut multibuffer = MultiBuffer::new(replica_id); - while let Some(location) = locations.next() { - let buffer = location.buffer.read(cx); - let mut ranges_for_buffer = Vec::new(); - let range = location.range.to_offset(buffer); - ranges_for_buffer.push(range.clone()); - if symbol_name.is_none() { - symbol_name = Some(buffer.text_for_range(range).collect::()); - } - - while let Some(next_location) = locations.peek() { - if next_location.buffer == location.buffer { - ranges_for_buffer.push(next_location.range.to_offset(buffer)); - locations.next(); - } else { - break; - } - } - - ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end))); - ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines( - location.buffer.clone(), - ranges_for_buffer, - 1, - cx, - )); - } - multibuffer.with_title(format!("References to `{}`", symbol_name.unwrap())) - }); - workspace.update(&mut cx, |workspace, cx| { - let editor = - cx.add_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx)); - editor.update(cx, |editor, cx| { - editor.highlight_background::( - ranges_to_highlight, - |theme| theme.editor.highlighted_line_background, - cx, - ); - }); - workspace.add_item(Box::new(editor), cx); + let title = locations + .first() + .as_ref() + .map(|location| { + let buffer = location.buffer.read(cx); + format!( + "References to `{}`", + buffer + .text_for_range(location.range.clone()) + .collect::() + ) + }) + .unwrap(); + Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx); }); Ok(()) })) } + /// Opens a multibuffer with the given project locations in it + pub fn open_locations_in_multibuffer( + workspace: &mut Workspace, + mut locations: Vec, + replica_id: ReplicaId, + title: String, + cx: &mut ViewContext, + ) { + // If there are multiple definitions, open them in a multibuffer + locations.sort_by_key(|location| location.buffer.id()); + let mut locations = locations.into_iter().peekable(); + let mut ranges_to_highlight = Vec::new(); + + let excerpt_buffer = cx.add_model(|cx| { + let mut multibuffer = MultiBuffer::new(replica_id); + while let Some(location) = locations.next() { + let buffer = location.buffer.read(cx); + let mut ranges_for_buffer = Vec::new(); + let range = location.range.to_offset(buffer); + ranges_for_buffer.push(range.clone()); + + while let Some(next_location) = locations.peek() { + if next_location.buffer == location.buffer { + ranges_for_buffer.push(next_location.range.to_offset(buffer)); + locations.next(); + } else { + break; + } + } + + ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end))); + ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines( + location.buffer.clone(), + ranges_for_buffer, + 1, + cx, + )) + } + + multibuffer.with_title(title) + }); + + let editor = cx.add_view(|cx| { + Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), cx) + }); + editor.update(cx, |editor, cx| { + editor.highlight_background::( + ranges_to_highlight, + |theme| theme.editor.highlighted_line_background, + cx, + ); + }); + workspace.add_item(Box::new(editor), cx); + } + pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext) -> Option>> { use language::ToOffset as _; From 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 7ef78459527fecd2310d41abac1e5bc12ae8ff67..5c966bbc723226e0a25156f13917a362452ed631 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -282,6 +282,7 @@ struct AutoindentRequestEntry { struct IndentSuggestion { basis_row: u32, delta: Ordering, + within_error: bool, } struct BufferChunkHighlights<'a> { @@ -937,7 +938,7 @@ impl Buffer { // Build a map containing the suggested indentation for each of the edited lines // with respect to the state of the buffer before these edits. This map is keyed // by the rows for these lines in the current state of the buffer. - let mut old_suggestions = BTreeMap::::default(); + let mut old_suggestions = BTreeMap::::default(); let old_edited_ranges = contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields); let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable(); @@ -963,14 +964,17 @@ impl Buffer { let suggested_indent = old_to_new_rows .get(&suggestion.basis_row) - .and_then(|from_row| old_suggestions.get(from_row).copied()) + .and_then(|from_row| { + Some(old_suggestions.get(from_row).copied()?.0) + }) .unwrap_or_else(|| { request .before_edit .indent_size_for_line(suggestion.basis_row) }) .with_delta(suggestion.delta, language_indent_size); - old_suggestions.insert(new_row, suggested_indent); + old_suggestions + .insert(new_row, (suggested_indent, suggestion.within_error)); } } yield_now().await; @@ -1016,12 +1020,13 @@ impl Buffer { snapshot.indent_size_for_line(suggestion.basis_row) }) .with_delta(suggestion.delta, language_indent_size); - if old_suggestions - .get(&new_row) - .map_or(true, |old_indentation| { + if old_suggestions.get(&new_row).map_or( + true, + |(old_indentation, was_within_error)| { suggested_indent != *old_indentation - }) - { + && (!suggestion.within_error || *was_within_error) + }, + ) { indent_sizes.insert(new_row, suggested_indent); } } @@ -1779,7 +1784,7 @@ impl BufferSnapshot { let start = Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0); let end = Point::new(row_range.end, 0); let range = (start..end).to_offset(&self.text); - let mut matches = self.syntax.matches(range, &self.text, |grammar| { + let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| { Some(&grammar.indents_config.as_ref()?.query) }); let indent_configs = matches @@ -1825,6 +1830,30 @@ impl BufferSnapshot { } } + let mut error_ranges = Vec::>::new(); + let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| { + Some(&grammar.error_query) + }); + while let Some(mat) = matches.peek() { + let node = mat.captures[0].node; + let start = Point::from_ts_point(node.start_position()); + let end = Point::from_ts_point(node.end_position()); + let range = start..end; + let ix = match error_ranges.binary_search_by_key(&range.start, |r| r.start) { + Ok(ix) | Err(ix) => ix, + }; + let mut end_ix = ix; + while let Some(existing_range) = error_ranges.get(end_ix) { + if existing_range.end < end { + end_ix += 1; + } else { + break; + } + } + error_ranges.splice(ix..end_ix, [range]); + matches.advance(); + } + outdent_positions.sort(); for outdent_position in outdent_positions { // find the innermost indent range containing this outdent_position @@ -1902,33 +1931,42 @@ impl BufferSnapshot { } } + let within_error = error_ranges + .iter() + .any(|e| e.start.row < row && e.end > row_start); + let suggestion = if outdent_to_row == prev_row || (outdent_from_prev_row && indent_from_prev_row) { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Equal, + within_error, }) } else if indent_from_prev_row { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Greater, + within_error, }) } else if outdent_to_row < prev_row { Some(IndentSuggestion { basis_row: outdent_to_row, delta: Ordering::Equal, + within_error, }) } else if outdent_from_prev_row { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Less, + within_error, }) } else if config.auto_indent_using_last_non_empty_line || !self.is_line_blank(prev_row) { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Equal, + within_error, }) } else { None diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e926a776bd47435f6d365da6f7d81630dc8ec4fd..045e8dcd6f510772c2050a55cbbbb228823f40f3 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -348,6 +348,7 @@ pub struct Language { pub struct Grammar { id: usize, pub(crate) ts_language: tree_sitter::Language, + pub(crate) error_query: Query, pub(crate) highlights_query: Option, pub(crate) brackets_config: Option, pub(crate) indents_config: Option, @@ -684,6 +685,7 @@ impl Language { indents_config: None, injection_config: None, override_config: None, + error_query: Query::new(ts_language, "(ERROR) @error").unwrap(), ts_language, highlight_map: Default::default(), }) From 9f74d6e4ac9f75ecf2c13ec3c3ac4897ae938f72 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 15:56:56 -0800 Subject: [PATCH 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 98ea1ee40ea1f3bebf13ae392e9c3192bbe79c51..b52a7a8affdef6cf85f455759342617e70e5b862 100644 --- a/crates/zed/src/languages/rust/highlights.scm +++ b/crates/zed/src/languages/rust/highlights.scm @@ -52,6 +52,7 @@ [ "as" "async" + "await" "break" "const" "continue" diff --git a/crates/zed/src/languages/rust/indents.scm b/crates/zed/src/languages/rust/indents.scm index 504c235d102894d44059ab0fa6051dbf1ae6eaee..9ab6b029083fd5d8e3249916c00a5f90648eb3e2 100644 --- a/crates/zed/src/languages/rust/indents.scm +++ b/crates/zed/src/languages/rust/indents.scm @@ -5,6 +5,7 @@ (assignment_expression) (let_declaration) (let_chain) + (await_expression) ] @indent (_ "[" "]" @end) @indent From 310d867aab01e6b0cc8b7f0954d13c68d88123c4 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 10:32:44 -0800 Subject: [PATCH 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 0c08af449785a08c86ddd1e3ae6d0751d706bcaf..c39c76dc34dab4af3d8521d23b6008b1af540d27 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -178,6 +178,21 @@ impl MouseMovedEvent { } } +#[derive(Clone, Copy, Debug, Default)] +pub struct MouseExitedEvent { + pub position: Vector2F, + pub pressed_button: Option, + pub modifiers: Modifiers, +} + +impl Deref for MouseExitedEvent { + type Target = Modifiers; + + fn deref(&self) -> &Self::Target { + &self.modifiers + } +} + #[derive(Clone, Debug)] pub enum Event { KeyDown(KeyDownEvent), @@ -186,6 +201,7 @@ pub enum Event { MouseDown(MouseButtonEvent), MouseUp(MouseButtonEvent), MouseMoved(MouseMovedEvent), + MouseExited(MouseExitedEvent), ScrollWheel(ScrollWheelEvent), } @@ -197,6 +213,7 @@ impl Event { Event::ModifiersChanged { .. } => None, Event::MouseDown(event) | Event::MouseUp(event) => Some(event.position), Event::MouseMoved(event) => Some(event.position), + Event::MouseExited(event) => Some(event.position), Event::ScrollWheel(event) => Some(event.position), } } diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index c527fe8d25f3197bd5bcf29df8a90d97c83af484..2f29898c26d2e9179560e40787d304e6320f33f6 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -3,7 +3,7 @@ use crate::{ keymap_matcher::Keystroke, platform::{Event, NavigationDirection}, KeyDownEvent, KeyUpEvent, Modifiers, ModifiersChangedEvent, MouseButton, MouseButtonEvent, - MouseMovedEvent, ScrollDelta, ScrollWheelEvent, TouchPhase, + MouseExitedEvent, MouseMovedEvent, ScrollDelta, ScrollWheelEvent, TouchPhase, }; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType}, @@ -221,6 +221,16 @@ impl Event { modifiers: read_modifiers(native_event), }) }), + NSEventType::NSMouseExited => window_height.map(|window_height| { + Self::MouseExited(MouseExitedEvent { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + pressed_button: None, + modifiers: read_modifiers(native_event), + }) + }), _ => None, } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 0da2e4351e477476300a0ebfbfa3cc20e8a55324..e93e63be921f3c50ecac1a2df49ea18cf55381c6 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -66,6 +66,14 @@ const NSNormalWindowLevel: NSInteger = 0; #[allow(non_upper_case_globals)] const NSPopUpWindowLevel: NSInteger = 101; #[allow(non_upper_case_globals)] +const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01; +#[allow(non_upper_case_globals)] +const NSTrackingMouseMoved: NSUInteger = 0x02; +#[allow(non_upper_case_globals)] +const NSTrackingActiveAlways: NSUInteger = 0x80; +#[allow(non_upper_case_globals)] +const NSTrackingInVisibleRect: NSUInteger = 0x200; +#[allow(non_upper_case_globals)] const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4; #[repr(C)] @@ -164,6 +172,10 @@ unsafe fn build_classes() { sel!(mouseMoved:), handle_view_event as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(mouseExited:), + handle_view_event as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(mouseDragged:), handle_view_event as extern "C" fn(&Object, Sel, id), @@ -470,8 +482,6 @@ impl Window { native_window.setTitlebarAppearsTransparent_(YES); } - native_window.setAcceptsMouseMovedEvents_(YES); - native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable); native_view.setWantsBestResolutionOpenGLSurface_(YES); @@ -494,8 +504,25 @@ impl Window { } match options.kind { - WindowKind::Normal => native_window.setLevel_(NSNormalWindowLevel), + WindowKind::Normal => { + native_window.setLevel_(NSNormalWindowLevel); + native_window.setAcceptsMouseMovedEvents_(YES); + } WindowKind::PopUp => { + // Use a tracking area to allow receiving MouseMoved events even when + // the window or application aren't active, which is often the case + // e.g. for notification windows. + let tracking_area: id = msg_send![class!(NSTrackingArea), alloc]; + let _: () = msg_send![ + tracking_area, + initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)) + options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect + owner: native_view + userInfo: nil + ]; + let _: () = + msg_send![native_view, addTrackingArea: tracking_area.autorelease()]; + native_window.setLevel_(NSPopUpWindowLevel); let _: () = msg_send![ native_window, @@ -965,7 +992,6 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let window_height = window_state_borrow.content_size().y(); let event = unsafe { Event::from_native(native_event, Some(window_height)) }; - if let Some(event) = event { match &event { Event::MouseMoved( diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 73e371c50bbc69ddb84790dc1a23ea37e7f9dfec..0909d95fd0a5ac4f53e5de600ca7bc5c540dfc00 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -360,6 +360,21 @@ impl Presenter { self.last_mouse_moved_event = Some(event.clone()); } + Event::MouseExited(event) => { + // When the platform sends a MouseExited event, synthesize + // a MouseMoved event whose position is outside the window's + // bounds so that hover and cursor state can be updated. + return self.dispatch_event( + Event::MouseMoved(MouseMovedEvent { + position: event.position, + pressed_button: event.pressed_button, + modifiers: event.modifiers, + }), + event_reused, + cx, + ); + } + Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel { region: Default::default(), platform_event: e.clone(), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9665db6be60955af11c10979cad87bbc23a29ed5..8735f33fbe6cfd3197bd825e8c5abc01652eea46 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,18 +31,14 @@ use futures::{ }; use gpui::{ actions, - color::Color, elements::*, - geometry::{ - rect::RectF, - vector::{vec2f, Vector2F}, - }, + geometry::vector::Vector2F, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, WindowKind, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; From bb24f1142fd11754eeba40c800f2c9b280c23232 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 16:47:23 -0800 Subject: [PATCH 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 e765a9926b769bcf820c3b5d61d17c33f6d4df58..b6b00e7869992af0d38e9d8395741b58098952f2 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -425,8 +425,6 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { let should_confirm = cx.global::().confirm_quit; cx.spawn(|mut cx| async move { - dbg!(should_confirm, workspaces.first()); - if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { let answer = cx .prompt( From 56080771e61f95b387b5f737bab158ca28fa05f8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 17:02:38 -0800 Subject: [PATCH 82/83] Add test for avoiding indent adjustment inside newly-created errors --- crates/language/src/buffer.rs | 10 ++ crates/language/src/buffer_tests.rs | 195 ++++++++++++++++++++-------- crates/language/src/syntax_map.rs | 48 +------ crates/text/src/text.rs | 51 ++++++++ 4 files changed, 206 insertions(+), 98 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5c966bbc723226e0a25156f13917a362452ed631..110e10564c35ecf6b04892c4d06dd6a53ee5e679 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1666,6 +1666,16 @@ impl Buffer { #[cfg(any(test, feature = "test-support"))] impl Buffer { + pub fn edit_via_marked_text( + &mut self, + marked_string: &str, + autoindent_mode: Option, + cx: &mut ModelContext, + ) { + let edits = self.edits_for_marked_text(marked_string); + self.edit(edits, autoindent_mode, cx); + } + pub fn set_group_interval(&mut self, group_interval: Duration) { self.text.set_group_interval(group_interval); } diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 382311e2040d85e0fe930600bc36446215a32d16..0b2ef1d7a782713159187b0a5262021afa34b8fe 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -800,23 +800,29 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta cx.set_global(settings); cx.add_model(|cx| { - let text = " + let mut buffer = Buffer::new( + 0, + " fn a() { c; d; } - " - .unindent(); - - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); // Lines 2 and 3 don't match the indentation suggestion. When editing these lines, // their indentation is not adjusted. - buffer.edit( - [ - (empty(Point::new(1, 1)), "()"), - (empty(Point::new(2, 1)), "()"), - ], + buffer.edit_via_marked_text( + &" + fn a() { + c«()»; + d«()»; + } + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -833,14 +839,22 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta // When appending new content after these lines, the indentation is based on the // preceding lines' actual indentation. - buffer.edit( - [ - (empty(Point::new(1, 1)), "\n.f\n.g"), - (empty(Point::new(2, 1)), "\n.f\n.g"), - ], + buffer.edit_via_marked_text( + &" + fn a() { + c« + .f + .g()»; + d« + .f + .g()»; + } + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); + assert_eq!( buffer.text(), " @@ -859,20 +873,90 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta }); cx.add_model(|cx| { - let text = " + let mut buffer = Buffer::new( + 0, + " + fn a() { + b(); + | + " + .replace("|", "") // marker to preserve trailing whitespace + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); + + // Insert a closing brace. It is outdented. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + «}» + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a() { + b(); + } + " + .unindent() + ); + + // Manually edit the leading whitespace. The edit is preserved. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + « »} + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " fn a() { - { - b()? + b(); } - Ok(()) + " + .unindent() + ); + buffer + }); +} + +#[gpui::test] +fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut MutableAppContext) { + let settings = Settings::test(cx); + cx.set_global(settings); + + cx.add_model(|cx| { + let mut buffer = Buffer::new( + 0, + " + fn a() { + i } - " - .unindent(); - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); - // Delete a closing curly brace changes the suggested indent for the line. - buffer.edit( - [(Point::new(3, 4)..Point::new(3, 5), "")], + // Regression test: line does not get outdented due to syntax error + buffer.edit_via_marked_text( + &" + fn a() { + i«f let Some(x) = y» + } + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -880,19 +964,19 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta buffer.text(), " fn a() { - { - b()? - | - Ok(()) + if let Some(x) = y } " - .replace('|', "") // included in the string to preserve trailing whites .unindent() ); - // Manually editing the leading whitespace - buffer.edit( - [(Point::new(3, 0)..Point::new(3, 12), "")], + buffer.edit_via_marked_text( + &" + fn a() { + if let Some(x) = y« {» + } + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -900,14 +984,12 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta buffer.text(), " fn a() { - { - b()? - - Ok(()) + if let Some(x) = y { } " .unindent() ); + buffer }); } @@ -916,27 +998,42 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) { cx.set_global(Settings::test(cx)); cx.add_model(|cx| { - let text = " + let mut buffer = Buffer::new( + 0, + " fn a() {} - " - .unindent(); - - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); - buffer.edit([(5..5, "\nb")], Some(AutoindentMode::EachLine), cx); + buffer.edit_via_marked_text( + &" + fn a(« + b») {} + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); assert_eq!( buffer.text(), " - fn a( - b) {} + fn a( + b) {} " .unindent() ); // The indentation suggestion changed because `@end` node (a close paren) // is now at the beginning of the line. - buffer.edit( - [(Point::new(1, 4)..Point::new(1, 5), "")], + buffer.edit_via_marked_text( + &" + fn a( + ˇ) {} + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -1894,7 +1991,3 @@ fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> Str layers[0].node.to_sexp() }) } - -fn empty(point: Point) -> Range { - point..point -} diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 458ffd8bc23a147a241ea719636e65389bdafafa..8d6673085494263970baf3fdbb3a0f6c21a939d0 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -2262,7 +2262,7 @@ mod tests { mutated_syntax_map.reparse(language.clone(), &buffer); for (i, marked_string) in steps.into_iter().enumerate() { - edit_buffer(&mut buffer, &marked_string.unindent()); + buffer.edit_via_marked_text(&marked_string.unindent()); // Reparse the syntax map mutated_syntax_map.interpolate(&buffer); @@ -2452,52 +2452,6 @@ mod tests { assert_eq!(actual_ranges, expected_ranges); } - fn edit_buffer(buffer: &mut Buffer, marked_string: &str) { - let old_text = buffer.text(); - let (new_text, mut ranges) = marked_text_ranges(marked_string, false); - if ranges.is_empty() { - ranges.push(0..new_text.len()); - } - - assert_eq!( - old_text[..ranges[0].start], - new_text[..ranges[0].start], - "invalid edit" - ); - - let mut delta = 0; - let mut edits = Vec::new(); - let mut ranges = ranges.into_iter().peekable(); - - while let Some(inserted_range) = ranges.next() { - let new_start = inserted_range.start; - let old_start = (new_start as isize - delta) as usize; - - let following_text = if let Some(next_range) = ranges.peek() { - &new_text[inserted_range.end..next_range.start] - } else { - &new_text[inserted_range.end..] - }; - - let inserted_len = inserted_range.len(); - let deleted_len = old_text[old_start..] - .find(following_text) - .expect("invalid edit"); - - let old_range = old_start..old_start + deleted_len; - edits.push((old_range, new_text[inserted_range].to_string())); - delta += inserted_len as isize - deleted_len as isize; - } - - assert_eq!( - old_text.len() as isize + delta, - new_text.len() as isize, - "invalid edit" - ); - - buffer.edit(edits); - } - pub fn string_contains_sequence(text: &str, parts: &[&str]) -> bool { let mut last_part_end = 0; for part in parts { diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 914023f305b4ba27aee8359205f3befc5245503a..c7d36e29def55aa2be84445ed14090b26a970b52 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1372,6 +1372,57 @@ impl Buffer { #[cfg(any(test, feature = "test-support"))] impl Buffer { + pub fn edit_via_marked_text(&mut self, marked_string: &str) { + let edits = self.edits_for_marked_text(marked_string); + self.edit(edits); + } + + pub fn edits_for_marked_text(&self, marked_string: &str) -> Vec<(Range, String)> { + let old_text = self.text(); + let (new_text, mut ranges) = util::test::marked_text_ranges(marked_string, false); + if ranges.is_empty() { + ranges.push(0..new_text.len()); + } + + assert_eq!( + old_text[..ranges[0].start], + new_text[..ranges[0].start], + "invalid edit" + ); + + let mut delta = 0; + let mut edits = Vec::new(); + let mut ranges = ranges.into_iter().peekable(); + + while let Some(inserted_range) = ranges.next() { + let new_start = inserted_range.start; + let old_start = (new_start as isize - delta) as usize; + + let following_text = if let Some(next_range) = ranges.peek() { + &new_text[inserted_range.end..next_range.start] + } else { + &new_text[inserted_range.end..] + }; + + let inserted_len = inserted_range.len(); + let deleted_len = old_text[old_start..] + .find(following_text) + .expect("invalid edit"); + + let old_range = old_start..old_start + deleted_len; + edits.push((old_range, new_text[inserted_range].to_string())); + delta += inserted_len as isize - deleted_len as isize; + } + + assert_eq!( + old_text.len() as isize + delta, + new_text.len() as isize, + "invalid edit" + ); + + edits + } + pub fn check_invariants(&self) { // Ensure every fragment is ordered by locator in the fragment tree and corresponds // to an insertion fragment in the insertions tree. From 8dcaa81aadb31038ed5771ec3d5c8a43ca7949d9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 18:19:24 -0800 Subject: [PATCH 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 e93e63be921f3c50ecac1a2df49ea18cf55381c6..6126533644578ca0a023657eef851f26c16432a3 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1438,7 +1438,11 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL { unsafe { let state = get_window_state(this); let state_borrow = state.as_ref().borrow(); - return state_borrow.kind == WindowKind::PopUp; + return if state_borrow.kind == WindowKind::PopUp { + YES + } else { + NO + }; } }