From c23feeab3ae6b8e6c3c7106cda953041f8a4e487 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 28 Mar 2023 14:10:43 -0700 Subject: [PATCH 01/11] :art: Make expand_tabs and collapse_tabs instance methods on TabSnapshot --- crates/editor/src/display_map.rs | 5 ++- crates/editor/src/display_map/tab_map.rs | 43 +++++++++++------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index f49b9e34b8..cdf4b7e3ad 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -19,7 +19,7 @@ use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; pub use suggestion_map::Suggestion; use suggestion_map::SuggestionMap; use sum_tree::{Bias, TreeMap}; -use tab_map::{TabMap, TabSnapshot}; +use tab_map::TabMap; use wrap_map::WrapMap; pub use block_map::{ @@ -637,7 +637,7 @@ impl DisplaySnapshot { let chars = buffer.chars_at(Point::new(range.start.row, 0)); let mut is_blank = false; - let indent_size = TabSnapshot::expand_tabs( + let indent_size = self.tab_snapshot.expand_tabs( chars.take_while(|c| { if *c == ' ' || *c == '\t' { true @@ -649,7 +649,6 @@ impl DisplaySnapshot { } }), buffer.line_len(buffer_row) as usize, // Never collapse - self.tab_snapshot.tab_size, ); (indent_size as u32, is_blank) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 45c92ea7b8..29dca3b1fb 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -245,7 +245,7 @@ impl TabSnapshot { let chars = self .suggestion_snapshot .chars_at(SuggestionPoint::new(input.row(), 0)); - let expanded = Self::expand_tabs(chars, input.column() as usize, self.tab_size); + let expanded = self.expand_tabs(chars, input.column() as usize); TabPoint::new(input.row(), expanded as u32) } @@ -259,7 +259,7 @@ impl TabSnapshot { .chars_at(SuggestionPoint::new(output.row(), 0)); let expanded = output.column() as usize; let (collapsed, expanded_char_column, to_next_stop) = - Self::collapse_tabs(chars, expanded, bias, self.tab_size); + self.collapse_tabs(chars, expanded, bias); ( SuggestionPoint::new(output.row(), collapsed as u32), expanded_char_column, @@ -282,11 +282,9 @@ impl TabSnapshot { fold_point.to_buffer_point(&self.suggestion_snapshot.fold_snapshot) } - pub fn expand_tabs( - chars: impl Iterator, - column: usize, - tab_size: NonZeroU32, - ) -> usize { + pub fn expand_tabs(&self, chars: impl Iterator, column: usize) -> usize { + let tab_size = self.tab_size.get() as usize; + let mut expanded_chars = 0; let mut expanded_bytes = 0; let mut collapsed_bytes = 0; @@ -295,7 +293,6 @@ impl TabSnapshot { break; } if c == '\t' { - let tab_size = tab_size.get() as usize; let tab_len = tab_size - expanded_chars % tab_size; expanded_bytes += tab_len; expanded_chars += tab_len; @@ -309,11 +306,13 @@ impl TabSnapshot { } fn collapse_tabs( + &self, chars: impl Iterator, column: usize, bias: Bias, - tab_size: NonZeroU32, ) -> (usize, usize, usize) { + let tab_size = self.tab_size.get() as usize; + let mut expanded_bytes = 0; let mut expanded_chars = 0; let mut collapsed_bytes = 0; @@ -323,7 +322,6 @@ impl TabSnapshot { } if c == '\t' { - let tab_size = tab_size.get() as usize; let tab_len = tab_size - (expanded_chars % tab_size); expanded_chars += tab_len; expanded_bytes += tab_len; @@ -508,20 +506,17 @@ mod tests { }; use rand::{prelude::StdRng, Rng}; - #[test] - fn test_expand_tabs() { - assert_eq!( - TabSnapshot::expand_tabs("\t".chars(), 0, 4.try_into().unwrap()), - 0 - ); - assert_eq!( - TabSnapshot::expand_tabs("\t".chars(), 1, 4.try_into().unwrap()), - 4 - ); - assert_eq!( - TabSnapshot::expand_tabs("\ta".chars(), 2, 4.try_into().unwrap()), - 5 - ); + #[gpui::test] + fn test_expand_tabs(cx: &mut gpui::MutableAppContext) { + let buffer = MultiBuffer::build_simple("", cx); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); + let (_, tabs_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + + assert_eq!(tabs_snapshot.expand_tabs("\t".chars(), 0), 0); + assert_eq!(tabs_snapshot.expand_tabs("\t".chars(), 1), 4); + assert_eq!(tabs_snapshot.expand_tabs("\ta".chars(), 2), 5); } #[gpui::test(iterations = 100)] From 08e93e93219da400c2412a1acb6f0964a3210624 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 28 Mar 2023 16:00:56 -0700 Subject: [PATCH 02/11] Only expand tabs up until a limited column --- crates/editor/src/display_map.rs | 2 +- .../editor/src/display_map/suggestion_map.rs | 26 +++ crates/editor/src/display_map/tab_map.rs | 178 ++++++++++++++---- crates/editor/src/display_map/wrap_map.rs | 3 +- 4 files changed, 166 insertions(+), 43 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index cdf4b7e3ad..cdad986b49 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -648,7 +648,7 @@ impl DisplaySnapshot { false } }), - buffer.line_len(buffer_row) as usize, // Never collapse + buffer.line_len(buffer_row), // Never collapse ); (indent_size as u32, is_blank) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 2d0225644f..5be4f6f0df 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -210,6 +210,32 @@ impl SuggestionSnapshot { } } + pub fn line_len(&self, row: u32) -> u32 { + if let Some(suggestion) = &self.suggestion { + let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; + let suggestion_end = suggestion_start + suggestion.text.max_point(); + + if row < suggestion_start.row { + self.fold_snapshot.line_len(row) + } else if row > suggestion_end.row { + self.fold_snapshot + .line_len(suggestion_start.row + (row - suggestion_end.row)) + } else { + let mut result = suggestion.text.line_len(row - suggestion_start.row); + if row == suggestion_start.row { + result += suggestion_start.column; + } + if row == suggestion_end.row { + result += + self.fold_snapshot.line_len(suggestion_start.row) - suggestion_start.column; + } + result + } + } else { + self.fold_snapshot.line_len(row) + } + } + pub fn clip_point(&self, point: SuggestionPoint, bias: Bias) -> SuggestionPoint { if let Some(suggestion) = self.suggestion.as_ref() { let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 29dca3b1fb..55b3425b24 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -8,6 +8,8 @@ use parking_lot::Mutex; use std::{cmp, mem, num::NonZeroU32, ops::Range}; use sum_tree::Bias; +const MAX_EXPANSION_COLUMN: u32 = 256; + pub struct TabMap(Mutex); impl TabMap { @@ -15,11 +17,18 @@ impl TabMap { let snapshot = TabSnapshot { suggestion_snapshot: input, tab_size, + max_expansion_column: MAX_EXPANSION_COLUMN, version: 0, }; (Self(Mutex::new(snapshot.clone())), snapshot) } + #[cfg(test)] + pub fn set_max_expansion_column(&self, column: u32) -> TabSnapshot { + self.0.lock().max_expansion_column = column; + self.0.lock().clone() + } + pub fn sync( &self, suggestion_snapshot: SuggestionSnapshot, @@ -30,6 +39,7 @@ impl TabMap { let mut new_snapshot = TabSnapshot { suggestion_snapshot, tab_size, + max_expansion_column: old_snapshot.max_expansion_column, version: old_snapshot.version, }; @@ -111,6 +121,7 @@ impl TabMap { pub struct TabSnapshot { pub suggestion_snapshot: SuggestionSnapshot, pub tab_size: NonZeroU32, + pub max_expansion_column: u32, pub version: usize, } @@ -122,14 +133,12 @@ impl TabSnapshot { pub fn line_len(&self, row: u32) -> u32 { let max_point = self.max_point(); if row < max_point.row() { - self.chunks( - TabPoint::new(row, 0)..TabPoint::new(row + 1, 0), - false, - None, - ) - .map(|chunk| chunk.text.len() as u32) - .sum::() - - 1 + self.to_tab_point(SuggestionPoint::new( + row, + self.suggestion_snapshot.line_len(row), + )) + .0 + .column } else { max_point.column() } @@ -191,12 +200,13 @@ impl TabSnapshot { ) -> TabChunks<'a> { let (input_start, expanded_char_column, to_next_stop) = self.to_suggestion_point(range.start, Bias::Left); + let input_column = input_start.column(); let input_start = self.suggestion_snapshot.to_offset(input_start); let input_end = self .suggestion_snapshot .to_offset(self.to_suggestion_point(range.end, Bias::Right).0); - let to_next_stop = if range.start.0 + Point::new(0, to_next_stop as u32) > range.end.0 { - (range.end.column() - range.start.column()) as usize + let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 { + range.end.column() - range.start.column() } else { to_next_stop }; @@ -207,12 +217,14 @@ impl TabSnapshot { language_aware, text_highlights, ), + input_column, column: expanded_char_column, + max_expansion_column: self.max_expansion_column, output_position: range.start.0, max_output_position: range.end.0, tab_size: self.tab_size, chunk: Chunk { - text: &SPACES[0..to_next_stop], + text: &SPACES[0..(to_next_stop as usize)], ..Default::default() }, skip_leading_tab: to_next_stop > 0, @@ -245,19 +257,15 @@ impl TabSnapshot { let chars = self .suggestion_snapshot .chars_at(SuggestionPoint::new(input.row(), 0)); - let expanded = self.expand_tabs(chars, input.column() as usize); - TabPoint::new(input.row(), expanded as u32) + let expanded = self.expand_tabs(chars, input.column()); + TabPoint::new(input.row(), expanded) } - pub fn to_suggestion_point( - &self, - output: TabPoint, - bias: Bias, - ) -> (SuggestionPoint, usize, usize) { + pub fn to_suggestion_point(&self, output: TabPoint, bias: Bias) -> (SuggestionPoint, u32, u32) { let chars = self .suggestion_snapshot .chars_at(SuggestionPoint::new(output.row(), 0)); - let expanded = output.column() as usize; + let expanded = output.column(); let (collapsed, expanded_char_column, to_next_stop) = self.collapse_tabs(chars, expanded, bias); ( @@ -282,14 +290,15 @@ impl TabSnapshot { fold_point.to_buffer_point(&self.suggestion_snapshot.fold_snapshot) } - pub fn expand_tabs(&self, chars: impl Iterator, column: usize) -> usize { - let tab_size = self.tab_size.get() as usize; + pub fn expand_tabs(&self, chars: impl Iterator, column: u32) -> u32 { + let tab_size = self.tab_size.get(); let mut expanded_chars = 0; let mut expanded_bytes = 0; let mut collapsed_bytes = 0; + let end_column = column.min(self.max_expansion_column); for c in chars { - if collapsed_bytes == column { + if collapsed_bytes >= end_column { break; } if c == '\t' { @@ -297,21 +306,21 @@ impl TabSnapshot { expanded_bytes += tab_len; expanded_chars += tab_len; } else { - expanded_bytes += c.len_utf8(); + expanded_bytes += c.len_utf8() as u32; expanded_chars += 1; } - collapsed_bytes += c.len_utf8(); + collapsed_bytes += c.len_utf8() as u32; } - expanded_bytes + expanded_bytes + column.saturating_sub(collapsed_bytes) } fn collapse_tabs( &self, chars: impl Iterator, - column: usize, + column: u32, bias: Bias, - ) -> (usize, usize, usize) { - let tab_size = self.tab_size.get() as usize; + ) -> (u32, u32, u32) { + let tab_size = self.tab_size.get(); let mut expanded_bytes = 0; let mut expanded_chars = 0; @@ -320,6 +329,9 @@ impl TabSnapshot { if expanded_bytes >= column { break; } + if collapsed_bytes >= self.max_expansion_column { + break; + } if c == '\t' { let tab_len = tab_size - (expanded_chars % tab_size); @@ -334,7 +346,7 @@ impl TabSnapshot { } } else { expanded_chars += 1; - expanded_bytes += c.len_utf8(); + expanded_bytes += c.len_utf8() as u32; } if expanded_bytes > column && matches!(bias, Bias::Left) { @@ -342,9 +354,13 @@ impl TabSnapshot { break; } - collapsed_bytes += c.len_utf8(); + collapsed_bytes += c.len_utf8() as u32; } - (collapsed_bytes, expanded_chars, 0) + ( + collapsed_bytes + column.saturating_sub(expanded_bytes), + expanded_chars, + 0, + ) } } @@ -432,8 +448,10 @@ const SPACES: &str = " "; pub struct TabChunks<'a> { suggestion_chunks: SuggestionChunks<'a>, chunk: Chunk<'a>, - column: usize, + column: u32, + max_expansion_column: u32, output_position: Point, + input_column: u32, max_output_position: Point, tab_size: NonZeroU32, skip_leading_tab: bool, @@ -467,14 +485,19 @@ impl<'a> Iterator for TabChunks<'a> { }); } else { self.chunk.text = &self.chunk.text[1..]; - let tab_size = self.tab_size.get() as u32; - let mut len = tab_size - self.column as u32 % tab_size; + let tab_size = if self.input_column < self.max_expansion_column { + self.tab_size.get() as u32 + } else { + 1 + }; + let mut len = tab_size - self.column % tab_size; let next_output_position = cmp::min( self.output_position + Point::new(0, len), self.max_output_position, ); len = next_output_position.column - self.output_position.column; - self.column += len as usize; + self.column += len; + self.input_column += 1; self.output_position = next_output_position; return Some(Chunk { text: &SPACES[0..len as usize], @@ -484,10 +507,12 @@ impl<'a> Iterator for TabChunks<'a> { } '\n' => { self.column = 0; + self.input_column = 0; self.output_position += Point::new(1, 0); } _ => { self.column += 1; + self.input_column += c.len_utf8() as u32; self.output_position.column += c.len_utf8() as u32; } } @@ -512,11 +537,76 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, tabs_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + let (_, tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); - assert_eq!(tabs_snapshot.expand_tabs("\t".chars(), 0), 0); - assert_eq!(tabs_snapshot.expand_tabs("\t".chars(), 1), 4); - assert_eq!(tabs_snapshot.expand_tabs("\ta".chars(), 2), 5); + assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0); + assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 1), 4); + assert_eq!(tab_snapshot.expand_tabs("\ta".chars(), 2), 5); + } + + #[gpui::test] + fn test_long_lines(cx: &mut gpui::MutableAppContext) { + let max_expansion_column = 12; + let input = "A\tBC\tDEF\tG\tHI\tJ\tK\tL\tM"; + let output = "A BC DEF G HI J K L M"; + + let buffer = MultiBuffer::build_simple(input, cx); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); + let (_, mut tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + + tab_snapshot.max_expansion_column = max_expansion_column; + assert_eq!(tab_snapshot.text(), output); + + for (ix, c) in input.char_indices() { + assert_eq!( + tab_snapshot + .chunks( + TabPoint::new(0, ix as u32)..tab_snapshot.max_point(), + false, + None + ) + .map(|c| c.text) + .collect::(), + &output[ix..], + "text from index {ix}" + ); + + if c != '\t' { + let input_point = Point::new(0, ix as u32); + let output_point = Point::new(0, output.find(c).unwrap() as u32); + assert_eq!( + tab_snapshot.to_tab_point(SuggestionPoint(input_point)), + TabPoint(output_point), + "to_tab_point({input_point:?})" + ); + assert_eq!( + tab_snapshot + .to_suggestion_point(TabPoint(output_point), Bias::Left) + .0, + SuggestionPoint(input_point), + "to_suggestion_point({output_point:?})" + ); + } + } + } + + #[gpui::test] + fn test_long_lines_with_character_spanning_max_expansion_column( + cx: &mut gpui::MutableAppContext, + ) { + let max_expansion_column = 8; + let input = "abcdefg⋯hij"; + + let buffer = MultiBuffer::build_simple(input, cx); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); + let (_, mut tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + + tab_snapshot.max_expansion_column = max_expansion_column; + assert_eq!(tab_snapshot.text(), input); } #[gpui::test(iterations = 100)] @@ -542,7 +632,9 @@ mod tests { let (suggestion_snapshot, _) = suggestion_map.randomly_mutate(&mut rng); log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (_, tabs_snapshot) = TabMap::new(suggestion_snapshot.clone(), tab_size); + let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size); + let tabs_snapshot = tab_map.set_max_expansion_column(32); + let text = text::Rope::from(tabs_snapshot.text().as_str()); log::info!( "TabMap text (tab size: {}): {:?}", @@ -586,7 +678,11 @@ mod tests { } for row in 0..=text.max_point().row { - assert_eq!(tabs_snapshot.line_len(row), text.line_len(row)); + assert_eq!( + tabs_snapshot.line_len(row), + text.line_len(row), + "line_len({row})" + ); } } } diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index f0d10ad423..311233850d 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1089,7 +1089,8 @@ mod tests { log::info!("FoldMap text: {:?}", fold_snapshot.text()); let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (tab_map, tabs_snapshot) = TabMap::new(suggestion_snapshot.clone(), tab_size); + let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size); + let tabs_snapshot = tab_map.set_max_expansion_column(32); log::info!("TabMap text: {:?}", tabs_snapshot.text()); let mut line_wrapper = LineWrapper::new(font_id, font_size, font_system); From 5c1f82ae3da9c15de1f74023bbae30a0c2115d33 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 28 Mar 2023 17:37:57 -0700 Subject: [PATCH 03/11] Don't use TabMap::expand_tabs in line_indent_for_buffer_row --- crates/editor/src/display_map.rs | 25 +++++++++++------------- crates/editor/src/display_map/tab_map.rs | 2 +- crates/editor/src/editor_tests.rs | 2 +- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index cdad986b49..4d83135205 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -634,24 +634,21 @@ impl DisplaySnapshot { .buffer_snapshot .buffer_line_for_row(buffer_row) .unwrap(); - let chars = buffer.chars_at(Point::new(range.start.row, 0)); + let mut indent_size = 0; let mut is_blank = false; - let indent_size = self.tab_snapshot.expand_tabs( - chars.take_while(|c| { - if *c == ' ' || *c == '\t' { - true - } else { - if *c == '\n' { - is_blank = true; - } - false + for c in buffer.chars_at(Point::new(range.start.row, 0)) { + if c == ' ' || c == '\t' { + indent_size += 1; + } else { + if c == '\n' { + is_blank = true; } - }), - buffer.line_len(buffer_row), // Never collapse - ); + break; + } + } - (indent_size as u32, is_blank) + (indent_size, is_blank) } pub fn line_len(&self, row: u32) -> u32 { diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 55b3425b24..50dc691a60 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -290,7 +290,7 @@ impl TabSnapshot { fold_point.to_buffer_point(&self.suggestion_snapshot.fold_snapshot) } - pub fn expand_tabs(&self, chars: impl Iterator, column: u32) -> u32 { + fn expand_tabs(&self, chars: impl Iterator, column: u32) -> u32 { let tab_size = self.tab_size.get(); let mut expanded_chars = 0; diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 4f61bde398..292c24f79a 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -630,7 +630,7 @@ fn test_cancel(cx: &mut gpui::MutableAppContext) { } #[gpui::test] -fn test_fold(cx: &mut gpui::MutableAppContext) { +fn test_fold_action(cx: &mut gpui::MutableAppContext) { cx.set_global(Settings::test(cx)); let buffer = MultiBuffer::build_simple( &" From a64296938d48cc387c3c022fc7afa22f3522307d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 29 Mar 2023 09:04:13 +0200 Subject: [PATCH 04/11] Delete unused code --- crates/language/src/language.rs | 6 ------ crates/pando/Cargo.toml | 21 --------------------- crates/pando/src/file_format.rs | 0 crates/pando/src/pando.rs | 15 --------------- 4 files changed, 42 deletions(-) delete mode 100644 crates/pando/Cargo.toml delete mode 100644 crates/pando/src/file_format.rs delete mode 100644 crates/pando/src/pando.rs diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 69053e9fc4..195a285d74 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -78,12 +78,6 @@ pub trait ToLspPosition { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct LanguageServerName(pub Arc); -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] -pub enum ServerExecutionKind { - Launch, - Node, -} - #[derive(Debug, Clone, Deserialize)] pub struct LanguageServerBinary { pub path: PathBuf, diff --git a/crates/pando/Cargo.toml b/crates/pando/Cargo.toml deleted file mode 100644 index 8521c4fd81..0000000000 --- a/crates/pando/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "pando" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -path = "src/pando.rs" - -[features] -test-support = [] - -[dependencies] -anyhow = "1.0.38" -client = { path = "../client" } -gpui = { path = "../gpui" } -settings = { path = "../settings" } -theme = { path = "../theme" } -workspace = { path = "../workspace" } -sqlez = { path = "../sqlez" } -sqlez_macros = { path = "../sqlez_macros" } \ No newline at end of file diff --git a/crates/pando/src/file_format.rs b/crates/pando/src/file_format.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/crates/pando/src/pando.rs b/crates/pando/src/pando.rs deleted file mode 100644 index e75f843720..0000000000 --- a/crates/pando/src/pando.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! ## Goals -//! - Opinionated Subset of Obsidian. Only the things that cant be done other ways in zed -//! - Checked in .zp file is an sqlite db containing graph metadata -//! - All nodes are file urls -//! - Markdown links auto add soft linked nodes to the db -//! - Links create positioning data regardless of if theres a file -//! - Lock links to make structure that doesn't rotate or spread -//! - Drag from file finder to pando item to add it in -//! - For linked files, zoom out to see closest linking pando file - -//! ## Plan -//! - [ ] Make item backed by .zp sqlite file with camera position by user account -//! - [ ] Render grid of dots and allow scrolling around the grid -//! - [ ] Add scale property to layer canvas and manipulate it with pinch zooming -//! - [ ] Allow dropping files onto .zp pane. Their relative path is recorded into the file along with From 813f722925515bf8899fcf8842b6a7e33c426d88 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 29 Mar 2023 14:52:50 +0200 Subject: [PATCH 05/11] Increment `input_column` correctly when inside the leading tab --- crates/editor/src/display_map/tab_map.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 50dc691a60..13263d642a 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -467,6 +467,7 @@ impl<'a> Iterator for TabChunks<'a> { if self.skip_leading_tab { self.chunk.text = &self.chunk.text[1..]; self.skip_leading_tab = false; + self.input_column += 1; } } else { return None; @@ -500,7 +501,7 @@ impl<'a> Iterator for TabChunks<'a> { self.input_column += 1; self.output_position = next_output_position; return Some(Chunk { - text: &SPACES[0..len as usize], + text: &SPACES[..len as usize], ..self.chunk }); } @@ -512,7 +513,9 @@ impl<'a> Iterator for TabChunks<'a> { } _ => { self.column += 1; - self.input_column += c.len_utf8() as u32; + if !self.skip_leading_tab { + self.input_column += c.len_utf8() as u32; + } self.output_position.column += c.len_utf8() as u32; } } @@ -659,11 +662,11 @@ mod tests { .collect::(); let expected_summary = TextSummary::from(expected_text.as_str()); assert_eq!( - expected_text, tabs_snapshot .chunks(start..end, false, None) .map(|c| c.text) .collect::(), + expected_text, "chunks({:?}..{:?})", start, end From 719d0f0abf59003fea7d6aaa045a53b5297291e3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 29 Mar 2023 14:53:28 +0200 Subject: [PATCH 06/11] Rename `skip_leading_tab` to `inside_leading_tab` --- crates/editor/src/display_map/tab_map.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 13263d642a..85f7acb2dd 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -227,7 +227,7 @@ impl TabSnapshot { text: &SPACES[0..(to_next_stop as usize)], ..Default::default() }, - skip_leading_tab: to_next_stop > 0, + inside_leading_tab: to_next_stop > 0, } } @@ -454,7 +454,7 @@ pub struct TabChunks<'a> { input_column: u32, max_output_position: Point, tab_size: NonZeroU32, - skip_leading_tab: bool, + inside_leading_tab: bool, } impl<'a> Iterator for TabChunks<'a> { @@ -464,9 +464,9 @@ impl<'a> Iterator for TabChunks<'a> { if self.chunk.text.is_empty() { if let Some(chunk) = self.suggestion_chunks.next() { self.chunk = chunk; - if self.skip_leading_tab { + if self.inside_leading_tab { self.chunk.text = &self.chunk.text[1..]; - self.skip_leading_tab = false; + self.inside_leading_tab = false; self.input_column += 1; } } else { @@ -513,7 +513,7 @@ impl<'a> Iterator for TabChunks<'a> { } _ => { self.column += 1; - if !self.skip_leading_tab { + if !self.inside_leading_tab { self.input_column += c.len_utf8() as u32; } self.output_position.column += c.len_utf8() as u32; From b86f8188d13cbd333ea5e44e19cc70504eba4daf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 29 Mar 2023 16:31:12 +0200 Subject: [PATCH 07/11] Expand edit to end of the line when old/new row exceeds max expansion --- crates/editor/src/display_map/tab_map.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 85f7acb2dd..7dd4d763e1 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -52,6 +52,28 @@ impl TabMap { if old_snapshot.tab_size == new_snapshot.tab_size { for suggestion_edit in &mut suggestion_edits { + let old_end = old_snapshot + .suggestion_snapshot + .to_point(suggestion_edit.old.end); + let old_end_row_len = old_snapshot.suggestion_snapshot.line_len(old_end.row()); + let old_end_row_exceeds_max_expansion = + old_end_row_len > old_snapshot.max_expansion_column; + let new_end = new_snapshot + .suggestion_snapshot + .to_point(suggestion_edit.new.end); + let new_end_row_len = new_snapshot.suggestion_snapshot.line_len(new_end.row()); + let new_end_row_exceeds_max_expansion = + new_end_row_len > new_snapshot.max_expansion_column; + if old_end_row_exceeds_max_expansion || new_end_row_exceeds_max_expansion { + suggestion_edit.old.end = old_snapshot + .suggestion_snapshot + .to_offset(SuggestionPoint::new(old_end.row(), old_end_row_len)); + suggestion_edit.new.end = new_snapshot + .suggestion_snapshot + .to_offset(SuggestionPoint::new(new_end.row(), new_end_row_len)); + continue; + } + let mut delta = 0; for chunk in old_snapshot.suggestion_snapshot.chunks( suggestion_edit.old.end..old_max_offset, From 6e2a9297ff34223c8984dfeb3babba79dc771338 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 29 Mar 2023 14:13:01 -0400 Subject: [PATCH 08/11] v0.81.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 02b27566e4..3a3e7c2dce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8439,7 +8439,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.80.0" +version = "0.81.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 4d7ce828d6..411cb301ed 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.80.0" +version = "0.81.0" publish = false [lib] From 49447128a9e0b2943890e5cf8cc5cac6ff19e709 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 29 Mar 2023 21:40:37 +0200 Subject: [PATCH 09/11] Make edits smaller when tab expansion changes on a line --- crates/editor/src/display_map/tab_map.rs | 66 ++++++++++++++---------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 7dd4d763e1..3b24579ae6 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -52,45 +52,59 @@ impl TabMap { if old_snapshot.tab_size == new_snapshot.tab_size { for suggestion_edit in &mut suggestion_edits { - let old_end = old_snapshot + let mut old_column = old_snapshot .suggestion_snapshot - .to_point(suggestion_edit.old.end); - let old_end_row_len = old_snapshot.suggestion_snapshot.line_len(old_end.row()); - let old_end_row_exceeds_max_expansion = - old_end_row_len > old_snapshot.max_expansion_column; - let new_end = new_snapshot + .to_point(suggestion_edit.old.end) + .column(); + let mut new_column = new_snapshot .suggestion_snapshot - .to_point(suggestion_edit.new.end); - let new_end_row_len = new_snapshot.suggestion_snapshot.line_len(new_end.row()); - let new_end_row_exceeds_max_expansion = - new_end_row_len > new_snapshot.max_expansion_column; - if old_end_row_exceeds_max_expansion || new_end_row_exceeds_max_expansion { - suggestion_edit.old.end = old_snapshot - .suggestion_snapshot - .to_offset(SuggestionPoint::new(old_end.row(), old_end_row_len)); - suggestion_edit.new.end = new_snapshot - .suggestion_snapshot - .to_offset(SuggestionPoint::new(new_end.row(), new_end_row_len)); - continue; - } + .to_point(suggestion_edit.new.end) + .column(); let mut delta = 0; - for chunk in old_snapshot.suggestion_snapshot.chunks( + let mut first_tab_ix = None; + let mut last_tab_ix_with_changed_expansion = None; + 'outer: for chunk in old_snapshot.suggestion_snapshot.chunks( suggestion_edit.old.end..old_max_offset, false, None, ) { let patterns: &[_] = &['\t', '\n']; - if let Some(ix) = chunk.text.find(patterns) { - if &chunk.text[ix..ix + 1] == "\t" { - suggestion_edit.old.end.0 += delta + ix + 1; - suggestion_edit.new.end.0 += delta + ix + 1; - } + for (ix, mat) in chunk.text.match_indices(patterns) { + match mat { + "\t" => { + if first_tab_ix.is_none() { + first_tab_ix = Some(delta + ix); + } - break; + let old_column = old_column + ix as u32; + let new_column = new_column + ix as u32; + let was_expanded = old_column < old_snapshot.max_expansion_column; + let is_expanded = new_column < new_snapshot.max_expansion_column; + if was_expanded != is_expanded { + last_tab_ix_with_changed_expansion = Some(delta + ix); + } else if !was_expanded && !is_expanded { + break 'outer; + } + } + "\n" => break 'outer, + _ => unreachable!(), + } } delta += chunk.text.len(); + old_column += chunk.text.len() as u32; + new_column += chunk.text.len() as u32; + if old_column >= old_snapshot.max_expansion_column + && new_column >= new_snapshot.max_expansion_column + { + break; + } + } + + if let Some(tab_ix) = last_tab_ix_with_changed_expansion.or(first_tab_ix) { + suggestion_edit.old.end.0 += tab_ix + 1; + suggestion_edit.new.end.0 += tab_ix + 1; } } From 737e2e1b3cb00cd76b126b361c4ebe0f9b0da7a2 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 29 Mar 2023 15:42:39 -0400 Subject: [PATCH 10/11] Open symbol outline when clicking on editor breadcrumbs --- Cargo.lock | 1 + crates/breadcrumbs/Cargo.toml | 1 + crates/breadcrumbs/src/breadcrumbs.rs | 66 +++++++++++++++++------ crates/editor/src/items.rs | 12 +++-- crates/terminal_view/src/terminal_view.rs | 2 +- crates/theme/src/theme.rs | 3 +- crates/workspace/src/pane.rs | 10 ++++ crates/workspace/src/toolbar.rs | 13 +++++ styles/src/styleTree/app.ts | 6 --- styles/src/styleTree/workspace.ts | 17 +++++- 10 files changed, 102 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02b27566e4..7bb3fcdd59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -785,6 +785,7 @@ dependencies = [ "gpui", "itertools", "language", + "outline", "project", "search", "settings", diff --git a/crates/breadcrumbs/Cargo.toml b/crates/breadcrumbs/Cargo.toml index 99476fdc0a..412a79a317 100644 --- a/crates/breadcrumbs/Cargo.toml +++ b/crates/breadcrumbs/Cargo.toml @@ -18,6 +18,7 @@ search = { path = "../search" } settings = { path = "../settings" } theme = { path = "../theme" } workspace = { path = "../workspace" } +outline = { path = "../outline" } itertools = "0.10" [dev-dependencies] diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index 47b7f88453..184dbe8468 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -1,5 +1,6 @@ use gpui::{ - elements::*, AppContext, Entity, RenderContext, Subscription, View, ViewContext, ViewHandle, + elements::*, AppContext, Entity, MouseButton, RenderContext, Subscription, View, ViewContext, + ViewHandle, }; use itertools::Itertools; use search::ProjectSearchView; @@ -14,6 +15,7 @@ pub enum Event { } pub struct Breadcrumbs { + pane_focused: bool, active_item: Option>, project_search: Option>, subscription: Option, @@ -22,6 +24,7 @@ pub struct Breadcrumbs { impl Breadcrumbs { pub fn new() -> Self { Self { + pane_focused: false, active_item: Default::default(), subscription: Default::default(), project_search: Default::default(), @@ -39,24 +42,53 @@ impl View for Breadcrumbs { } fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + let active_item = match &self.active_item { + Some(active_item) => active_item, + None => return Empty::new().boxed(), + }; + let not_editor = active_item.downcast::().is_none(); + let theme = cx.global::().theme.clone(); - if let Some(breadcrumbs) = self - .active_item - .as_ref() - .and_then(|item| item.breadcrumbs(&theme, cx)) - { - Flex::row() - .with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || { - Label::new(" 〉 ", theme.breadcrumbs.text.clone()).boxed() - })) - .contained() - .with_style(theme.breadcrumbs.container) + let style = &theme.workspace.breadcrumbs; + + let breadcrumbs = match active_item.breadcrumbs(&theme, cx) { + Some(breadcrumbs) => breadcrumbs, + None => return Empty::new().boxed(), + }; + + let crumbs = Flex::row() + .with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || { + Label::new(" 〉 ", style.default.text.clone()).boxed() + })) + .constrained() + .with_height(theme.workspace.breadcrumb_height) + .contained(); + + if not_editor || !self.pane_focused { + return crumbs + .with_style(style.default.container) .aligned() .left() - .boxed() - } else { - Empty::new().boxed() + .boxed(); } + + MouseEventHandler::::new(0, cx, |state, _| { + let style = style.style_for(state, false); + crumbs.with_style(style.container).boxed() + }) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(outline::Toggle); + }) + .with_tooltip::( + 0, + "Show symbol outline".to_owned(), + Some(Box::new(outline::Toggle)), + theme.tooltip.clone(), + cx, + ) + .aligned() + .left() + .boxed() } } @@ -103,4 +135,8 @@ impl ToolbarItemView for Breadcrumbs { current_location } } + + fn pane_focus_update(&mut self, pane_focused: bool, _: &mut gpui::MutableAppContext) { + self.pane_focused = pane_focused; + } } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 1d273a991e..a053fd8aca 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -747,11 +747,15 @@ impl Item for Editor { .map(|path| path.to_string_lossy().to_string()) .unwrap_or_else(|| "untitled".to_string()); - let mut breadcrumbs = vec![Label::new(filename, theme.breadcrumbs.text.clone()).boxed()]; + let filename_label = Label::new(filename, theme.workspace.breadcrumbs.default.text.clone()); + let mut breadcrumbs = vec![filename_label.boxed()]; breadcrumbs.extend(symbols.into_iter().map(|symbol| { - Text::new(symbol.text, theme.breadcrumbs.text.clone()) - .with_highlights(symbol.highlight_ranges) - .boxed() + Text::new( + symbol.text, + theme.workspace.breadcrumbs.default.text.clone(), + ) + .with_highlights(symbol.highlight_ranges) + .boxed() })); Some(breadcrumbs) } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index f1a627e29d..7b1a261652 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -612,7 +612,7 @@ impl Item for TerminalView { fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option> { Some(vec![Text::new( self.terminal().read(cx).breadcrumb_text.clone(), - theme.breadcrumbs.text.clone(), + theme.workspace.breadcrumbs.default.text.clone(), ) .boxed()]) } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index d64a1d2499..cb96fcf833 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -30,7 +30,6 @@ pub struct Theme { pub editor: Editor, pub search: Search, pub project_diagnostics: ProjectDiagnostics, - pub breadcrumbs: ContainedText, pub shared_screen: ContainerStyle, pub contact_notification: ContactNotification, pub update_notification: UpdateNotification, @@ -62,6 +61,8 @@ pub struct Workspace { pub sidebar: Sidebar, pub status_bar: StatusBar, pub toolbar: Toolbar, + pub breadcrumb_height: f32, + pub breadcrumbs: Interactive, pub disconnected_overlay: ContainedText, pub modal: ContainerStyle, pub notification: ContainerStyle, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index f4a5ad026a..66c0cc1a04 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1603,6 +1603,10 @@ impl View for Pane { } fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { + self.toolbar.update(cx, |toolbar, cx| { + toolbar.pane_focus_update(true, cx); + }); + if let Some(active_item) = self.active_item() { if cx.is_self_focused() { // Pane was focused directly. We need to either focus a view inside the active item, @@ -1626,6 +1630,12 @@ impl View for Pane { } } + fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + self.toolbar.update(cx, |toolbar, cx| { + toolbar.pane_focus_update(false, cx); + }); + } + fn keymap_context(&self, _: &AppContext) -> KeymapContext { let mut keymap = Self::default_keymap_context(); if self.docked.is_some() { diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index df10db91a0..55f1707766 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -20,6 +20,8 @@ pub trait ToolbarItemView: View { ) -> ToolbarItemLocation { current_location } + + fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut MutableAppContext) {} } trait ToolbarItemViewHandle { @@ -30,6 +32,7 @@ trait ToolbarItemViewHandle { active_pane_item: Option<&dyn ItemHandle>, cx: &mut MutableAppContext, ) -> ToolbarItemLocation; + fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut MutableAppContext); } #[derive(Copy, Clone, Debug, PartialEq)] @@ -260,6 +263,12 @@ impl Toolbar { } } + pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut MutableAppContext) { + for (toolbar_item, _) in self.items.iter_mut() { + toolbar_item.pane_focus_update(pane_focused, cx); + } + } + pub fn item_of_type(&self) -> Option> { self.items .iter() @@ -289,6 +298,10 @@ impl ToolbarItemViewHandle for ViewHandle { this.set_active_pane_item(active_pane_item, cx) }) } + + fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut MutableAppContext) { + self.update(cx, |this, cx| this.pane_focus_update(pane_focused, cx)); + } } impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle { diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index 423ce37d48..3582de4247 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -44,12 +44,6 @@ export default function app(colorScheme: ColorScheme): Object { contactList: contactList(colorScheme), search: search(colorScheme), sharedScreen: sharedScreen(colorScheme), - breadcrumbs: { - ...text(colorScheme.highest, "sans", "variant"), - padding: { - left: 6, - }, - }, updateNotification: updateNotification(colorScheme), simpleMessageNotification: simpleMessageNotification(colorScheme), tooltip: tooltip(colorScheme), diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 1de2fe9502..fe3faddcb9 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -276,9 +276,22 @@ export default function workspace(colorScheme: ColorScheme) { }, padding: { left: 8, right: 8, top: 4, bottom: 4 }, }, + breadcrumbHeight: 24, breadcrumbs: { - ...text(layer, "mono", "variant"), - padding: { left: 6 }, + ...text(colorScheme.highest, "sans", "variant"), + cornerRadius: 6, + padding: { + left: 6, + right: 6, + }, + hover: { + color: foreground(colorScheme.highest, "on", "hovered"), + background: background( + colorScheme.highest, + "on", + "hovered" + ), + }, }, disconnectedOverlay: { ...text(layer, "sans"), From 0c07a373a8db79167c2b563950726eb552864bc7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 29 Mar 2023 14:42:17 -0700 Subject: [PATCH 11/11] :art: --- crates/editor/src/display_map/tab_map.rs | 42 +++++++++++++----------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 3b24579ae6..f5776270a0 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -51,38 +51,41 @@ impl TabMap { let mut tab_edits = Vec::with_capacity(suggestion_edits.len()); if old_snapshot.tab_size == new_snapshot.tab_size { + // Expand each edit to include the next tab on the same line as the edit, + // and any subsequent tabs on that line that moved across the tab expansion + // boundary. for suggestion_edit in &mut suggestion_edits { - let mut old_column = old_snapshot + let old_end_column = old_snapshot .suggestion_snapshot .to_point(suggestion_edit.old.end) .column(); - let mut new_column = new_snapshot + let new_end_column = new_snapshot .suggestion_snapshot .to_point(suggestion_edit.new.end) .column(); - let mut delta = 0; - let mut first_tab_ix = None; - let mut last_tab_ix_with_changed_expansion = None; + let mut offset_from_edit = 0; + let mut first_tab_offset = None; + let mut last_tab_with_changed_expansion_offset = None; 'outer: for chunk in old_snapshot.suggestion_snapshot.chunks( suggestion_edit.old.end..old_max_offset, false, None, ) { - let patterns: &[_] = &['\t', '\n']; - for (ix, mat) in chunk.text.match_indices(patterns) { + for (ix, mat) in chunk.text.match_indices(&['\t', '\n']) { + let offset_from_edit = offset_from_edit + (ix as u32); match mat { "\t" => { - if first_tab_ix.is_none() { - first_tab_ix = Some(delta + ix); + if first_tab_offset.is_none() { + first_tab_offset = Some(offset_from_edit); } - let old_column = old_column + ix as u32; - let new_column = new_column + ix as u32; + let old_column = old_end_column + offset_from_edit; + let new_column = new_end_column + offset_from_edit; let was_expanded = old_column < old_snapshot.max_expansion_column; let is_expanded = new_column < new_snapshot.max_expansion_column; if was_expanded != is_expanded { - last_tab_ix_with_changed_expansion = Some(delta + ix); + last_tab_with_changed_expansion_offset = Some(offset_from_edit); } else if !was_expanded && !is_expanded { break 'outer; } @@ -92,22 +95,21 @@ impl TabMap { } } - delta += chunk.text.len(); - old_column += chunk.text.len() as u32; - new_column += chunk.text.len() as u32; - if old_column >= old_snapshot.max_expansion_column - && new_column >= new_snapshot.max_expansion_column + offset_from_edit += chunk.text.len() as u32; + if old_end_column + offset_from_edit >= old_snapshot.max_expansion_column + && new_end_column | offset_from_edit >= new_snapshot.max_expansion_column { break; } } - if let Some(tab_ix) = last_tab_ix_with_changed_expansion.or(first_tab_ix) { - suggestion_edit.old.end.0 += tab_ix + 1; - suggestion_edit.new.end.0 += tab_ix + 1; + if let Some(offset) = last_tab_with_changed_expansion_offset.or(first_tab_offset) { + suggestion_edit.old.end.0 += offset as usize + 1; + suggestion_edit.new.end.0 += offset as usize + 1; } } + // Combine any edits that overlap due to the expansion. let mut ix = 1; while ix < suggestion_edits.len() { let (prev_edits, next_edits) = suggestion_edits.split_at_mut(ix);