diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 9c8d9d6f9c..8f1355a5c7 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -56,6 +56,7 @@ use std::ops::Add; use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; use sum_tree::{Bias, TreeMap}; use tab_map::{TabMap, TabSnapshot}; +use text::LineIndent; use ui::WindowContext; use wrap_map::{WrapMap, WrapSnapshot}; @@ -843,7 +844,7 @@ impl DisplaySnapshot { result } - pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> (u32, bool) { + pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent { let (buffer, range) = self .buffer_snapshot .buffer_line_for_row(buffer_row) @@ -866,17 +867,16 @@ impl DisplaySnapshot { return false; } - let (indent_size, is_blank) = self.line_indent_for_buffer_row(buffer_row); - if is_blank { + let line_indent = self.line_indent_for_buffer_row(buffer_row); + if line_indent.is_line_blank() { return false; } for next_row in (buffer_row.0 + 1)..=max_row.0 { - let (next_indent_size, next_line_is_blank) = - self.line_indent_for_buffer_row(MultiBufferRow(next_row)); - if next_indent_size > indent_size { + let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row)); + if next_line_indent.raw_len() > line_indent.raw_len() { return true; - } else if !next_line_is_blank { + } else if !next_line_indent.is_line_blank() { break; } } @@ -900,13 +900,15 @@ impl DisplaySnapshot { } else if self.starts_indent(MultiBufferRow(start.row)) && !self.is_line_folded(MultiBufferRow(start.row)) { - let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row); + let start_line_indent = self.line_indent_for_buffer_row(buffer_row); let max_point = self.buffer_snapshot.max_point(); let mut end = None; for row in (buffer_row.0 + 1)..=max_point.row { - let (indent, is_blank) = self.line_indent_for_buffer_row(MultiBufferRow(row)); - if !is_blank && indent <= start_indent { + let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row)); + if !line_indent.is_line_blank() + && line_indent.raw_len() <= start_line_indent.raw_len() + { let prev_row = row - 1; end = Some(Point::new( prev_row, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index c055112768..42824fd81b 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -11523,7 +11523,7 @@ fn assert_indent_guides( } #[gpui::test] -async fn test_indent_guides_single_line(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" fn main() { @@ -11543,7 +11543,7 @@ async fn test_indent_guides_single_line(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_indent_guides_simple_block(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" fn main() { @@ -11564,7 +11564,7 @@ async fn test_indent_guides_simple_block(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_indent_guides_nested(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" fn main() { @@ -11593,7 +11593,7 @@ async fn test_indent_guides_nested(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_indent_guides_tab(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" fn main() { @@ -11618,7 +11618,7 @@ async fn test_indent_guides_tab(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_indent_guides_continues_on_empty_line(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" fn main() { @@ -11640,7 +11640,7 @@ async fn test_indent_guides_continues_on_empty_line(cx: &mut gpui::TestAppContex } #[gpui::test] -async fn test_indent_guides_complex(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" fn main() { @@ -11672,7 +11672,7 @@ async fn test_indent_guides_complex(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_indent_guides_starts_off_screen(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" fn main() { @@ -11704,7 +11704,7 @@ async fn test_indent_guides_starts_off_screen(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_indent_guides_ends_off_screen(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" fn main() { @@ -11736,7 +11736,7 @@ async fn test_indent_guides_ends_off_screen(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_indent_guides_without_brackets(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" block1 @@ -11764,7 +11764,7 @@ async fn test_indent_guides_without_brackets(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_indent_guides_ends_before_empty_line(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" block1 @@ -11790,7 +11790,7 @@ async fn test_indent_guides_ends_before_empty_line(cx: &mut gpui::TestAppContext } #[gpui::test] -async fn test_indent_guides_continuing_off_screen(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" block1 @@ -11813,7 +11813,34 @@ async fn test_indent_guides_continuing_off_screen(cx: &mut gpui::TestAppContext) } #[gpui::test] -async fn test_active_indent_guides_single_line(cx: &mut gpui::TestAppContext) { +async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) { + let (buffer_id, mut cx) = setup_indent_guides_editor( + &" + def a: + \tb = 3 + \tif True: + \t\tc = 4 + \t\td = 5 + \tprint(b) + " + .unindent(), + cx, + ) + .await; + + assert_indent_guides( + 0..6, + vec![ + IndentGuide::new(buffer_id, 1, 6, 0, 4), + IndentGuide::new(buffer_id, 3, 4, 1, 4), + ], + None, + &mut cx, + ); +} + +#[gpui::test] +async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" fn main() { @@ -11839,7 +11866,7 @@ async fn test_active_indent_guides_single_line(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_active_indent_guides_respect_indented_range(cx: &mut gpui::TestAppContext) { +async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" fn main() { @@ -11902,7 +11929,7 @@ async fn test_active_indent_guides_respect_indented_range(cx: &mut gpui::TestApp } #[gpui::test] -async fn test_active_indent_guides_empty_line(cx: &mut gpui::TestAppContext) { +async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" fn main() { @@ -11930,7 +11957,7 @@ async fn test_active_indent_guides_empty_line(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_active_indent_guides_non_matching_indent(cx: &mut gpui::TestAppContext) { +async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) { let (buffer_id, mut cx) = setup_indent_guides_editor( &" def m: diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fb3bdad865..71eb7f7e25 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1417,9 +1417,9 @@ impl EditorElement { .into_iter() .enumerate() .filter_map(|(i, indent_guide)| { - let indent_size = self.column_pixels(indent_guide.indent_size as usize, cx); - let total_width = indent_size * px(indent_guide.depth as f32); - + let single_indent_width = + self.column_pixels(indent_guide.tab_size as usize, cx); + let total_width = single_indent_width * indent_guide.depth as f32; let start_x = content_origin.x + total_width - scroll_pixel_position.x; if start_x >= text_origin.x { let (offset_y, length) = Self::calculate_indent_guide_bounds( @@ -1433,7 +1433,7 @@ impl EditorElement { Some(IndentGuideLayout { origin: point(start_x, start_y), length, - indent_size, + single_indent_width, depth: indent_guide.depth, active: active_indent_guide_indices.contains(&i), }) @@ -1467,6 +1467,11 @@ impl EditorElement { let mut offset_y = row_range.start.0 as f32 * line_height; let mut length = (cons_line.0.saturating_sub(row_range.start.0)) as f32 * line_height; + // If we are at the end of the buffer, ensure that the indent guide extends to the end of the line. + if row_range.end == cons_line { + length += line_height; + } + // If there is a block (e.g. diagnostic) in between the start of the indent guide and the line above, // we want to extend the indent guide to the start of the block. let mut block_height = 0; @@ -2637,7 +2642,7 @@ impl EditorElement { } if let Some(color) = background_color { - let width = indent_guide.indent_size - px(line_indicator_width); + let width = indent_guide.single_indent_width - px(line_indicator_width); cx.paint_quad(fill( Bounds { origin: point( @@ -5114,7 +5119,7 @@ fn layout_line( pub struct IndentGuideLayout { origin: gpui::Point, length: Pixels, - indent_size: Pixels, + single_indent_width: Pixels, depth: u32, active: bool, } diff --git a/crates/editor/src/indent_guides.rs b/crates/editor/src/indent_guides.rs index 377fd25a25..3e212b047c 100644 --- a/crates/editor/src/indent_guides.rs +++ b/crates/editor/src/indent_guides.rs @@ -4,7 +4,7 @@ use collections::HashSet; use gpui::{AppContext, Task}; use language::BufferRow; use multi_buffer::{MultiBufferIndentGuide, MultiBufferRow}; -use text::{BufferId, Point}; +use text::{BufferId, LineIndent, Point}; use ui::ViewContext; use util::ResultExt; @@ -13,7 +13,7 @@ use crate::{DisplaySnapshot, Editor}; struct ActiveIndentedRange { buffer_id: BufferId, row_range: Range, - indent: u32, + indent: LineIndent, } #[derive(Default)] @@ -112,7 +112,8 @@ impl Editor { .enumerate() .filter(|(_, indent_guide)| { indent_guide.buffer_id == active_indent_range.buffer_id - && indent_guide.indent_width() == active_indent_range.indent + && indent_guide.indent_level() + == active_indent_range.indent.len(indent_guide.tab_size) }); let mut matches = HashSet::default(); @@ -189,19 +190,19 @@ fn should_recalculate_indented_range( return true; } - let (old_indent, old_is_blank) = snapshot.line_indent_for_row(prev_row.0); - let (new_indent, new_is_blank) = snapshot.line_indent_for_row(new_row.0); + let old_line_indent = snapshot.line_indent_for_row(prev_row.0); + let new_line_indent = snapshot.line_indent_for_row(new_row.0); - if old_is_blank - || new_is_blank - || old_indent != new_indent + if old_line_indent.is_line_empty() + || new_line_indent.is_line_empty() + || old_line_indent != new_line_indent || snapshot.max_point().row == new_row.0 { return true; } - let (next_line_indent, next_line_is_blank) = snapshot.line_indent_for_row(new_row.0 + 1); - next_line_is_blank || next_line_indent != old_indent + let next_line_indent = snapshot.line_indent_for_row(new_row.0 + 1); + next_line_indent.is_line_empty() || next_line_indent != old_line_indent } else { true } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index bde5a8442a..9703302e76 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -541,7 +541,7 @@ pub struct IndentGuide { pub start_row: BufferRow, pub end_row: BufferRow, pub depth: u32, - pub indent_size: u32, + pub tab_size: u32, } impl IndentGuide { @@ -550,19 +550,19 @@ impl IndentGuide { start_row: BufferRow, end_row: BufferRow, depth: u32, - indent_size: u32, + tab_size: u32, ) -> Self { Self { buffer_id, start_row, end_row, depth, - indent_size, + tab_size, } } - pub fn indent_width(&self) -> u32 { - self.indent_size * self.depth + pub fn indent_level(&self) -> u32 { + self.depth * self.tab_size } } @@ -3118,7 +3118,7 @@ impl BufferSnapshot { range: Range, cx: &AppContext, ) -> Vec { - fn indent_size_for_row(this: &BufferSnapshot, row: BufferRow, cx: &AppContext) -> u32 { + fn tab_size_for_row(this: &BufferSnapshot, row: BufferRow, cx: &AppContext) -> u32 { let language = this.language_at(Point::new(row, 0)); language_settings(language, None, cx).tab_size.get() as u32 } @@ -3133,19 +3133,19 @@ impl BufferSnapshot { let mut indent_stack = SmallVec::<[IndentGuide; 8]>::new(); // TODO: This should be calculated for every row but it is pretty expensive - let indent_size = indent_size_for_row(self, start_row, cx); + let tab_size = tab_size_for_row(self, start_row, cx); - while let Some((first_row, mut line_indent, empty)) = row_indents.next() { + while let Some((first_row, mut line_indent)) = row_indents.next() { let current_depth = indent_stack.len() as u32; // When encountering empty, continue until found useful line indent // then add to the indent stack with the depth found let mut found_indent = false; let mut last_row = first_row; - if empty { + if line_indent.is_line_empty() { let mut trailing_row = end_row; while !found_indent { - let (target_row, new_line_indent, empty) = + let (target_row, new_line_indent) = if let Some(display_row) = row_indents.next() { display_row } else { @@ -3160,11 +3160,11 @@ impl BufferSnapshot { { break; } - let (new_line_indent, empty) = self.line_indent_for_row(trailing_row); - (trailing_row, new_line_indent, empty) + let new_line_indent = self.line_indent_for_row(trailing_row); + (trailing_row, new_line_indent) }; - if empty { + if new_line_indent.is_line_empty() { continue; } last_row = target_row.min(end_row); @@ -3177,7 +3177,8 @@ impl BufferSnapshot { } let depth = if found_indent { - line_indent / indent_size + ((line_indent % indent_size) > 0) as u32 + line_indent.len(tab_size) / tab_size + + ((line_indent.len(tab_size) % tab_size) > 0) as u32 } else { current_depth }; @@ -3203,7 +3204,7 @@ impl BufferSnapshot { start_row: first_row, end_row: last_row, depth: next_depth, - indent_size, + tab_size, }); } } @@ -3221,20 +3222,22 @@ impl BufferSnapshot { pub async fn enclosing_indent( &self, mut buffer_row: BufferRow, - ) -> Option<(Range, u32)> { + ) -> Option<(Range, LineIndent)> { let max_row = self.max_point().row; if buffer_row >= max_row { return None; } - let (mut target_indent_size, is_blank) = self.line_indent_for_row(buffer_row); + let mut target_indent = self.line_indent_for_row(buffer_row); // If the current row is at the start of an indented block, we want to return this // block as the enclosing indent. - if !is_blank && buffer_row < max_row { - let (next_line_indent, is_blank) = self.line_indent_for_row(buffer_row + 1); - if !is_blank && target_indent_size < next_line_indent { - target_indent_size = next_line_indent; + if !target_indent.is_line_empty() && buffer_row < max_row { + let next_line_indent = self.line_indent_for_row(buffer_row + 1); + if !next_line_indent.is_line_empty() + && target_indent.raw_len() < next_line_indent.raw_len() + { + target_indent = next_line_indent; buffer_row += 1; } } @@ -3246,12 +3249,12 @@ impl BufferSnapshot { let mut accessed_row_counter = 0; // If there is a blank line at the current row, search for the next non indented lines - if is_blank { + if target_indent.is_line_empty() { let start = buffer_row.saturating_sub(SEARCH_WHITESPACE_ROW_LIMIT); let end = (max_row + 1).min(buffer_row + SEARCH_WHITESPACE_ROW_LIMIT); let mut non_empty_line_above = None; - for (row, indent_size, is_blank) in self + for (row, indent) in self .text .reversed_line_indents_in_row_range(start..buffer_row) { @@ -3260,30 +3263,28 @@ impl BufferSnapshot { accessed_row_counter = 0; yield_now().await; } - if !is_blank { - non_empty_line_above = Some((row, indent_size)); + if !indent.is_line_empty() { + non_empty_line_above = Some((row, indent)); break; } } let mut non_empty_line_below = None; - for (row, indent_size, is_blank) in - self.text.line_indents_in_row_range((buffer_row + 1)..end) - { + for (row, indent) in self.text.line_indents_in_row_range((buffer_row + 1)..end) { accessed_row_counter += 1; if accessed_row_counter == YIELD_INTERVAL { accessed_row_counter = 0; yield_now().await; } - if !is_blank { - non_empty_line_below = Some((row, indent_size)); + if !indent.is_line_empty() { + non_empty_line_below = Some((row, indent)); break; } } - let (row, indent_size) = match (non_empty_line_above, non_empty_line_below) { + let (row, indent) = match (non_empty_line_above, non_empty_line_below) { (Some((above_row, above_indent)), Some((below_row, below_indent))) => { - if above_indent >= below_indent { + if above_indent.raw_len() >= below_indent.raw_len() { (above_row, above_indent) } else { (below_row, below_indent) @@ -3294,7 +3295,7 @@ impl BufferSnapshot { _ => return None, }; - target_indent_size = indent_size; + target_indent = indent; buffer_row = row; } @@ -3302,7 +3303,7 @@ impl BufferSnapshot { let end = (max_row + 1).min(buffer_row + SEARCH_ROW_LIMIT); let mut start_indent = None; - for (row, indent_size, is_blank) in self + for (row, indent) in self .text .reversed_line_indents_in_row_range(start..buffer_row) { @@ -3311,36 +3312,38 @@ impl BufferSnapshot { accessed_row_counter = 0; yield_now().await; } - if !is_blank && indent_size < target_indent_size { - start_indent = Some((row, indent_size)); + if !indent.is_line_empty() && indent.raw_len() < target_indent.raw_len() { + start_indent = Some((row, indent)); break; } } let (start_row, start_indent_size) = start_indent?; let mut end_indent = (end, None); - for (row, indent_size, is_blank) in - self.text.line_indents_in_row_range((buffer_row + 1)..end) - { + for (row, indent) in self.text.line_indents_in_row_range((buffer_row + 1)..end) { accessed_row_counter += 1; if accessed_row_counter == YIELD_INTERVAL { accessed_row_counter = 0; yield_now().await; } - if !is_blank && indent_size < target_indent_size { - end_indent = (row.saturating_sub(1), Some(indent_size)); + if !indent.is_line_empty() && indent.raw_len() < target_indent.raw_len() { + end_indent = (row.saturating_sub(1), Some(indent)); break; } } let (end_row, end_indent_size) = end_indent; - let indent_size = if let Some(end_indent_size) = end_indent_size { - start_indent_size.max(end_indent_size) + let indent = if let Some(end_indent_size) = end_indent_size { + if start_indent_size.raw_len() > end_indent_size.raw_len() { + start_indent_size + } else { + end_indent_size + } } else { start_indent_size }; - Some((start_row..end_row, indent_size)) + Some((start_row..end_row, indent)) } /// Returns selections for remote peers intersecting the given range. diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 38b3f7c6bc..fdde10ab74 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -19,7 +19,7 @@ use std::{ time::{Duration, Instant}, }; use text::network::Network; -use text::{BufferId, LineEnding}; +use text::{BufferId, LineEnding, LineIndent}; use text::{Point, ToPoint}; use unindent::Unindent as _; use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter}; @@ -2060,7 +2060,7 @@ async fn test_find_matching_indent(cx: &mut TestAppContext) { text: impl Into, buffer_row: u32, cx: &mut TestAppContext, - ) -> Option<(Range, u32)> { + ) -> Option<(Range, LineIndent)> { let buffer = cx.new_model(|cx| Buffer::local(text, cx)); let snapshot = cx.read(|cx| buffer.read(cx).snapshot()); snapshot.enclosing_indent(buffer_row).await @@ -2079,7 +2079,14 @@ async fn test_find_matching_indent(cx: &mut TestAppContext) { cx, ) .await, - Some((1..2, 4)) + Some(( + 1..2, + LineIndent { + tabs: 0, + spaces: 4, + line_blank: false, + } + )) ); assert_eq!( @@ -2095,7 +2102,14 @@ async fn test_find_matching_indent(cx: &mut TestAppContext) { cx, ) .await, - Some((1..2, 4)) + Some(( + 1..2, + LineIndent { + tabs: 0, + spaces: 4, + line_blank: false, + } + )) ); assert_eq!( @@ -2113,7 +2127,14 @@ async fn test_find_matching_indent(cx: &mut TestAppContext) { cx, ) .await, - Some((1..4, 4)) + Some(( + 1..4, + LineIndent { + tabs: 0, + spaces: 4, + line_blank: false, + } + )) ); } diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 141c57a10c..79fdb39b0d 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -516,6 +516,85 @@ pub struct UndoOperation { pub counts: HashMap, } +/// Stores information about the indentation of a line (tabs and spaces). +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct LineIndent { + pub tabs: u32, + pub spaces: u32, + pub line_blank: bool, +} + +impl LineIndent { + /// Constructs a new `LineIndent` which only contains spaces. + pub fn spaces(spaces: u32) -> Self { + Self { + tabs: 0, + spaces, + line_blank: true, + } + } + + /// Constructs a new `LineIndent` which only contains tabs. + pub fn tabs(tabs: u32) -> Self { + Self { + tabs, + spaces: 0, + line_blank: true, + } + } + + /// Indicates whether the line is empty. + pub fn is_line_empty(&self) -> bool { + self.tabs == 0 && self.spaces == 0 && self.line_blank + } + + /// Indicates whether the line is blank (contains only whitespace). + pub fn is_line_blank(&self) -> bool { + self.line_blank + } + + /// Returns the number of indentation characters (tabs or spaces). + pub fn raw_len(&self) -> u32 { + self.tabs + self.spaces + } + + /// Returns the number of indentation characters (tabs or spaces), taking tab size into account. + pub fn len(&self, tab_size: u32) -> u32 { + self.tabs * tab_size + self.spaces + } +} + +impl From<&str> for LineIndent { + fn from(value: &str) -> Self { + Self::from_iter(value.chars()) + } +} + +impl FromIterator for LineIndent { + fn from_iter>(chars: T) -> Self { + let mut tabs = 0; + let mut spaces = 0; + let mut line_blank = true; + for c in chars { + if c == '\t' { + tabs += 1; + } else if c == ' ' { + spaces += 1; + } else { + if c != '\n' { + line_blank = false; + } + break; + } + } + Self { + tabs, + spaces, + line_blank, + } + } +} + impl Buffer { pub fn new(replica_id: u16, remote_id: BufferId, mut base_text: String) -> Buffer { let line_ending = LineEnding::detect(&base_text); @@ -1868,7 +1947,7 @@ impl BufferSnapshot { pub fn line_indents_in_row_range( &self, row_range: Range, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { let start = Point::new(row_range.start, 0).to_offset(self); let end = Point::new(row_range.end, 0).to_offset(self); @@ -1876,20 +1955,9 @@ impl BufferSnapshot { let mut row = row_range.start; std::iter::from_fn(move || { if let Some(line) = lines.next() { - let mut indent_size = 0; - let mut is_blank = true; - - for c in line.chars() { - is_blank = false; - if c == ' ' || c == '\t' { - indent_size += 1; - } else { - break; - } - } - + let indent = LineIndent::from(line); row += 1; - Some((row - 1, indent_size, is_blank)) + Some((row - 1, indent)) } else { None } @@ -1899,7 +1967,7 @@ impl BufferSnapshot { pub fn reversed_line_indents_in_row_range( &self, row_range: Range, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { let start = Point::new(row_range.start, 0).to_offset(self); let end = Point::new(row_range.end, 0) .to_offset(self) @@ -1909,41 +1977,17 @@ impl BufferSnapshot { let mut row = row_range.end; std::iter::from_fn(move || { if let Some(line) = lines.next() { - let mut indent_size = 0; - let mut is_blank = true; - - for c in line.chars() { - is_blank = false; - if c == ' ' || c == '\t' { - indent_size += 1; - } else { - break; - } - } - + let indent = LineIndent::from(line); row = row.saturating_sub(1); - Some((row, indent_size, is_blank)) + Some((row, indent)) } else { None } }) } - pub fn line_indent_for_row(&self, row: u32) -> (u32, bool) { - let mut indent_size = 0; - let mut is_blank = false; - for c in self.chars_at(Point::new(row, 0)) { - if c == ' ' || c == '\t' { - indent_size += 1; - } else { - if c == '\n' { - is_blank = true; - } - break; - } - } - - (indent_size, is_blank) + pub fn line_indent_for_row(&self, row: u32) -> LineIndent { + LineIndent::from_iter(self.chars_at(Point::new(row, 0))) } pub fn is_line_blank(&self, row: u32) -> bool {