From f62fd3cddd408808059ead4739810db6f9e9651d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 8 Jun 2022 18:08:07 -0700 Subject: [PATCH] Add support for hard tabs * Add a `hard_tabs` setting that causes indentation to be performed using a tab instead of multiple spaces. * Change Buffer's indentation-related APIs to return an `IndentSize` struct with a length and a kind, instead of just a single u32. * Use hard tabs by default in Go. --- crates/editor/src/editor.rs | 71 ++++--- crates/editor/src/movement.rs | 2 +- crates/editor/src/multi_buffer.rs | 33 ++- crates/language/src/buffer.rs | 337 ++++++++++++++++++------------ crates/language/src/tests.rs | 40 +++- crates/settings/src/settings.rs | 11 + crates/text/src/text.rs | 12 -- crates/zed/src/main.rs | 8 + 8 files changed, 319 insertions(+), 195 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 20bc53db377ce25c029cd6cd128a947c89cc5c49..14cd5dfe4ce9c17f39207ff7024cbf92aa2a0c6b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -32,7 +32,8 @@ use gpui::{ pub use language::{char_kind, CharKind}; use language::{ BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticSeverity, - Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId, + IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, + TransactionId, }; use multi_buffer::MultiBufferChunks; pub use multi_buffer::{ @@ -50,7 +51,7 @@ use std::{ any::TypeId, borrow::Cow, cmp::{self, Ordering, Reverse}, - iter, mem, + mem, ops::{Deref, DerefMut, Range, RangeInclusive}, sync::Arc, time::{Duration, Instant}, @@ -1923,9 +1924,8 @@ impl Editor { .iter() .map(|selection| { let start_point = selection.start.to_point(&buffer); - let indent = buffer - .indent_column_for_line(start_point.row) - .min(start_point.column); + let mut indent = buffer.indent_size_for_line(start_point.row); + indent.len = cmp::min(indent.len, start_point.column); let start = selection.start; let end = selection.end; @@ -1958,9 +1958,9 @@ impl Editor { }); } - let mut new_text = String::with_capacity(1 + indent as usize); + let mut new_text = String::with_capacity(1 + indent.len as usize); new_text.push('\n'); - new_text.extend(iter::repeat(' ').take(indent as usize)); + new_text.extend(indent.chars()); if insert_extra_newline { new_text = new_text.repeat(2); } @@ -3061,14 +3061,21 @@ impl Editor { .buffer_snapshot .buffer_line_for_row(old_head.row) { - let indent_column = - buffer.indent_column_for_line(line_buffer_range.start.row); + let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row); let language_name = buffer.language().map(|language| language.name()); - let indent = cx.global::().tab_size(language_name.as_deref()); - if old_head.column <= indent_column && old_head.column > 0 { + let indent_len = match indent_size.kind { + IndentKind::Space => { + cx.global::().tab_size(language_name.as_deref()) + } + IndentKind::Tab => 1, + }; + if old_head.column <= indent_size.len && old_head.column > 0 { new_head = cmp::min( new_head, - Point::new(old_head.row, ((old_head.column - 1) / indent) * indent), + Point::new( + old_head.row, + ((old_head.column - 1) / indent_len) * indent_len, + ), ); } } @@ -3178,26 +3185,33 @@ impl Editor { } for row in start_row..end_row { - let indent_column = snapshot.indent_column_for_line(row); - let columns_to_next_tab_stop = tab_size - (indent_column % tab_size); + let current_indent = snapshot.indent_size_for_line(row); + let indent_delta = match current_indent.kind { + IndentKind::Space => { + let columns_to_next_tab_stop = + tab_size - (current_indent.len % tab_size); + IndentSize::spaces(columns_to_next_tab_stop) + } + IndentKind::Tab => IndentSize::tab(), + }; let row_start = Point::new(row, 0); buffer.edit( [( row_start..row_start, - " ".repeat(columns_to_next_tab_stop as usize), + indent_delta.chars().collect::(), )], cx, ); // Update this selection's endpoints to reflect the indentation. if row == selection.start.row { - selection.start.column += columns_to_next_tab_stop as u32; + selection.start.column += indent_delta.len; } if row == selection.end.row { - selection.end.column += columns_to_next_tab_stop as u32; + selection.end.column += indent_delta.len as u32; } - last_indent = Some((row, columns_to_next_tab_stop as u32)); + last_indent = Some((row, indent_delta.len)); } } }); @@ -3230,12 +3244,19 @@ impl Editor { } for row in rows { - let column = snapshot.indent_column_for_line(row); - if column > 0 { - let mut deletion_len = column % tab_size; - if deletion_len == 0 { - deletion_len = tab_size; - } + let indent_size = snapshot.indent_size_for_line(row); + if indent_size.len > 0 { + let deletion_len = match indent_size.kind { + IndentKind::Space => { + let columns_to_prev_tab_stop = indent_size.len % tab_size; + if columns_to_prev_tab_stop == 0 { + tab_size + } else { + columns_to_prev_tab_stop + } + } + IndentKind::Tab => 1, + }; deletion_ranges.push(Point::new(row, 0)..Point::new(row, deletion_len)); last_outdent = Some(row); } @@ -4549,7 +4570,7 @@ impl Editor { continue; } - let start = Point::new(row, snapshot.indent_column_for_line(row)); + let start = Point::new(row, snapshot.indent_size_for_line(row).len); let mut line_bytes = snapshot .bytes_in_range(start..snapshot.max_point()) .flatten() diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 1f4486739bd41a0764ec5ed02093e181d097d739..7e9682ebe7030a1f98b38cebeb23a84237e4de74 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -106,7 +106,7 @@ pub fn line_beginning( let soft_line_start = map.clip_point(DisplayPoint::new(display_point.row(), 0), Bias::Right); let indent_start = Point::new( point.row, - map.buffer_snapshot.indent_column_for_line(point.row), + map.buffer_snapshot.indent_size_for_line(point.row).len, ) .to_display_point(map); let line_start = map.prev_line_boundary(point).1; diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 32e2021fd2b2fc52d9b87a8e021fe28f95dab1a8..4523e21da1405c89a3b3b15a5c471564700f8bc6 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -8,8 +8,8 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ char_kind, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, DiagnosticEntry, Event, File, - Language, OffsetRangeExt, Outline, OutlineItem, Selection, ToOffset as _, ToPoint as _, - ToPointUtf16 as _, TransactionId, + IndentSize, Language, OffsetRangeExt, Outline, OutlineItem, Selection, ToOffset as _, + ToPoint as _, ToPointUtf16 as _, TransactionId, }; use settings::Settings; use smallvec::SmallVec; @@ -322,9 +322,14 @@ impl MultiBuffer { if let Some(buffer) = self.as_singleton() { return buffer.update(cx, |buffer, cx| { - let language_name = buffer.language().map(|language| language.name()); - let indent_size = cx.global::().tab_size(language_name.as_deref()); if autoindent { + let language_name = buffer.language().map(|language| language.name()); + let settings = cx.global::(); + let indent_size = if settings.hard_tabs(language_name.as_deref()) { + IndentSize::tab() + } else { + IndentSize::spaces(settings.tab_size(language_name.as_deref())) + }; buffer.edit_with_autoindent(edits, indent_size, cx); } else { buffer.edit(edits, cx); @@ -426,9 +431,15 @@ impl MultiBuffer { } } let language_name = buffer.language().map(|l| l.name()); - let indent_size = cx.global::().tab_size(language_name.as_deref()); if autoindent { + let settings = cx.global::(); + let indent_size = if settings.hard_tabs(language_name.as_deref()) { + IndentSize::tab() + } else { + IndentSize::spaces(settings.tab_size(language_name.as_deref())) + }; + buffer.edit_with_autoindent(deletions, indent_size, cx); buffer.edit_with_autoindent(insertions, indent_size, cx); } else { @@ -1787,14 +1798,16 @@ impl MultiBufferSnapshot { } } - pub fn indent_column_for_line(&self, row: u32) -> u32 { + pub fn indent_size_for_line(&self, row: u32) -> IndentSize { if let Some((buffer, range)) = self.buffer_line_for_row(row) { - buffer - .indent_column_for_line(range.start.row) + let mut size = buffer.indent_size_for_line(range.start.row); + size.len = size + .len .min(range.end.column) - .saturating_sub(range.start.column) + .saturating_sub(range.start.column); + size } else { - 0 + IndentSize::spaces(0) } } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 38dbaa29fde63e8a9b42e0cdc6b0a39e9ce06d2d..a1b6bb2127d88c5942a6d4be8ba004d70940748c 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -22,7 +22,7 @@ use std::{ collections::{BTreeMap, HashMap}, ffi::OsString, future::Future, - iter::{Iterator, Peekable}, + iter::{self, Iterator, Peekable}, mem, ops::{Deref, DerefMut, Range}, path::{Path, PathBuf}, @@ -82,6 +82,18 @@ pub struct BufferSnapshot { parse_count: usize, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct IndentSize { + pub len: u32, + pub kind: IndentKind, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum IndentKind { + Space, + Tab, +} + #[derive(Clone, Debug)] struct SelectionSet { line_mode: bool, @@ -215,7 +227,7 @@ struct AutoindentRequest { before_edit: BufferSnapshot, edited: Vec, inserted: Option>>, - indent_size: u32, + indent_size: IndentSize, } #[derive(Debug)] @@ -723,18 +735,18 @@ impl Buffer { } fn request_autoindent(&mut self, cx: &mut ModelContext) { - if let Some(indent_columns) = self.compute_autoindents() { - let indent_columns = cx.background().spawn(indent_columns); + if let Some(indent_sizes) = self.compute_autoindents() { + let indent_sizes = cx.background().spawn(indent_sizes); match cx .background() - .block_with_timeout(Duration::from_micros(500), indent_columns) + .block_with_timeout(Duration::from_micros(500), indent_sizes) { - Ok(indent_columns) => self.apply_autoindents(indent_columns, cx), - Err(indent_columns) => { + Ok(indent_sizes) => self.apply_autoindents(indent_sizes, cx), + Err(indent_sizes) => { self.pending_autoindent = Some(cx.spawn(|this, mut cx| async move { - let indent_columns = indent_columns.await; + let indent_sizes = indent_sizes.await; this.update(&mut cx, |this, cx| { - this.apply_autoindents(indent_columns, cx); + this.apply_autoindents(indent_sizes, cx); }); })); } @@ -742,7 +754,7 @@ impl Buffer { } } - fn compute_autoindents(&self) -> Option>> { + fn compute_autoindents(&self) -> Option>> { let max_rows_between_yields = 100; let snapshot = self.snapshot(); if snapshot.language.is_none() @@ -754,7 +766,7 @@ impl Buffer { let autoindent_requests = self.autoindent_requests.clone(); Some(async move { - let mut indent_columns = BTreeMap::new(); + let mut indent_sizes = BTreeMap::new(); for request in autoindent_requests { let old_to_new_rows = request .edited @@ -768,7 +780,7 @@ impl Buffer { ) .collect::>(); - let mut old_suggestions = HashMap::::default(); + let mut old_suggestions = HashMap::::default(); let old_edited_ranges = contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields); for old_edited_range in old_edited_ranges { @@ -778,23 +790,19 @@ impl Buffer { .into_iter() .flatten(); for (old_row, suggestion) in old_edited_range.zip(suggestions) { - let indentation_basis = old_to_new_rows + let mut suggested_indent = old_to_new_rows .get(&suggestion.basis_row) .and_then(|from_row| old_suggestions.get(from_row).copied()) .unwrap_or_else(|| { request .before_edit - .indent_column_for_line(suggestion.basis_row) + .indent_size_for_line(suggestion.basis_row) }); - let delta = if suggestion.indent { - request.indent_size - } else { - 0 - }; - old_suggestions.insert( - *old_to_new_rows.get(&old_row).unwrap(), - indentation_basis + delta, - ); + if suggestion.indent { + suggested_indent += request.indent_size; + } + old_suggestions + .insert(*old_to_new_rows.get(&old_row).unwrap(), suggested_indent); } yield_now().await; } @@ -809,23 +817,18 @@ impl Buffer { .into_iter() .flatten(); for (new_row, suggestion) in new_edited_row_range.zip(suggestions) { - let delta = if suggestion.indent { - request.indent_size - } else { - 0 - }; - let new_indentation = indent_columns + let mut suggested_indent = indent_sizes .get(&suggestion.basis_row) .copied() - .unwrap_or_else(|| { - snapshot.indent_column_for_line(suggestion.basis_row) - }) - + delta; + .unwrap_or_else(|| snapshot.indent_size_for_line(suggestion.basis_row)); + if suggestion.indent { + suggested_indent += request.indent_size; + } if old_suggestions .get(&new_row) - .map_or(true, |old_indentation| new_indentation != *old_indentation) + .map_or(true, |old_indentation| suggested_indent != *old_indentation) { - indent_columns.insert(new_row, new_indentation); + indent_sizes.insert(new_row, suggested_indent); } } yield_now().await; @@ -845,56 +848,65 @@ impl Buffer { .into_iter() .flatten(); for (row, suggestion) in inserted_row_range.zip(suggestions) { - let delta = if suggestion.indent { - request.indent_size - } else { - 0 - }; - let new_indentation = indent_columns + let mut suggested_indent = indent_sizes .get(&suggestion.basis_row) .copied() .unwrap_or_else(|| { - snapshot.indent_column_for_line(suggestion.basis_row) - }) - + delta; - indent_columns.insert(row, new_indentation); + snapshot.indent_size_for_line(suggestion.basis_row) + }); + if suggestion.indent { + suggested_indent += request.indent_size; + } + indent_sizes.insert(row, suggested_indent); } yield_now().await; } } } - indent_columns + + indent_sizes }) } fn apply_autoindents( &mut self, - indent_columns: BTreeMap, + indent_sizes: BTreeMap, cx: &mut ModelContext, ) { self.autoindent_requests.clear(); self.start_transaction(); - for (row, indent_column) in &indent_columns { - self.set_indent_column_for_line(*row, *indent_column, cx); + for (row, indent_size) in &indent_sizes { + self.set_indent_size_for_line(*row, *indent_size, cx); } self.end_transaction(cx); } - fn set_indent_column_for_line(&mut self, row: u32, column: u32, cx: &mut ModelContext) { - let current_column = self.indent_column_for_line(row); - if column > current_column { + fn set_indent_size_for_line( + &mut self, + row: u32, + size: IndentSize, + cx: &mut ModelContext, + ) { + let current_size = indent_size_for_line(&self, row); + if size.kind != current_size.kind && current_size.len > 0 { + return; + } + + if size.len > current_size.len { let offset = Point::new(row, 0).to_offset(&*self); self.edit( [( offset..offset, - " ".repeat((column - current_column) as usize), + iter::repeat(size.char()) + .take((size.len - current_size.len) as usize) + .collect::(), )], cx, ); - } else if column < current_column { + } else if size.len < current_size.len { self.edit( [( - Point::new(row, 0)..Point::new(row, current_column - column), + Point::new(row, 0)..Point::new(row, current_size.len - size.len), "", )], cx, @@ -1084,7 +1096,7 @@ impl Buffer { pub fn edit_with_autoindent( &mut self, edits_iter: I, - indent_size: u32, + indent_size: IndentSize, cx: &mut ModelContext, ) -> Option where @@ -1098,7 +1110,7 @@ impl Buffer { pub fn edit_internal( &mut self, edits_iter: I, - autoindent_size: Option, + autoindent_size: Option, cx: &mut ModelContext, ) -> Option where @@ -1500,103 +1512,101 @@ impl Deref for Buffer { } impl BufferSnapshot { + pub fn indent_size_for_line(&self, row: u32) -> IndentSize { + indent_size_for_line(&self, row) + } + fn suggest_autoindents<'a>( &'a self, row_range: Range, ) -> Option + 'a> { + // Get the "indentation ranges" that intersect this row range. + let grammar = self.grammar()?; + let prev_non_blank_row = self.prev_non_blank_row(row_range.start); let mut query_cursor = QueryCursorHandle::new(); - if let Some((grammar, tree)) = self.grammar().zip(self.tree.as_ref()) { - let prev_non_blank_row = self.prev_non_blank_row(row_range.start); - - // Get the "indentation ranges" that intersect this row range. - let indent_capture_ix = grammar.indents_query.capture_index_for_name("indent"); - let end_capture_ix = grammar.indents_query.capture_index_for_name("end"); - query_cursor.set_point_range( - Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0).to_ts_point() - ..Point::new(row_range.end, 0).to_ts_point(), - ); - let mut indentation_ranges = Vec::<(Range, &'static str)>::new(); - for mat in query_cursor.matches( - &grammar.indents_query, - tree.root_node(), - TextProvider(self.as_rope()), - ) { - let mut node_kind = ""; - let mut start: Option = None; - let mut end: Option = None; - for capture in mat.captures { - if Some(capture.index) == indent_capture_ix { - node_kind = capture.node.kind(); - start.get_or_insert(Point::from_ts_point(capture.node.start_position())); - end.get_or_insert(Point::from_ts_point(capture.node.end_position())); - } else if Some(capture.index) == end_capture_ix { - end = Some(Point::from_ts_point(capture.node.start_position().into())); - } + let indent_capture_ix = grammar.indents_query.capture_index_for_name("indent"); + let end_capture_ix = grammar.indents_query.capture_index_for_name("end"); + query_cursor.set_point_range( + Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0).to_ts_point() + ..Point::new(row_range.end, 0).to_ts_point(), + ); + let mut indentation_ranges = Vec::>::new(); + for mat in query_cursor.matches( + &grammar.indents_query, + self.tree.as_ref()?.root_node(), + TextProvider(self.as_rope()), + ) { + let mut start: Option = None; + let mut end: Option = None; + for capture in mat.captures { + if Some(capture.index) == indent_capture_ix { + start.get_or_insert(Point::from_ts_point(capture.node.start_position())); + end.get_or_insert(Point::from_ts_point(capture.node.end_position())); + } else if Some(capture.index) == end_capture_ix { + end = Some(Point::from_ts_point(capture.node.start_position().into())); } + } - if let Some((start, end)) = start.zip(end) { - if start.row == end.row { - continue; - } + if let Some((start, end)) = start.zip(end) { + if start.row == end.row { + continue; + } - let range = start..end; - match indentation_ranges.binary_search_by_key(&range.start, |r| r.0.start) { - Err(ix) => indentation_ranges.insert(ix, (range, node_kind)), - Ok(ix) => { - let prev_range = &mut indentation_ranges[ix]; - prev_range.0.end = prev_range.0.end.max(range.end); - } + let range = start..end; + match indentation_ranges.binary_search_by_key(&range.start, |r| r.start) { + Err(ix) => indentation_ranges.insert(ix, range), + Ok(ix) => { + let prev_range = &mut indentation_ranges[ix]; + prev_range.end = prev_range.end.max(range.end); } } } + } - let mut prev_row = prev_non_blank_row.unwrap_or(0); - Some(row_range.map(move |row| { - let row_start = Point::new(row, self.indent_column_for_line(row)); + let mut prev_row = prev_non_blank_row.unwrap_or(0); + Some(row_range.map(move |row| { + let row_start = Point::new(row, self.indent_size_for_line(row).len); - let mut indent_from_prev_row = false; - let mut outdent_to_row = u32::MAX; - for (range, _node_kind) in &indentation_ranges { - if range.start.row >= row { - break; - } + let mut indent_from_prev_row = false; + let mut outdent_to_row = u32::MAX; + for range in &indentation_ranges { + if range.start.row >= row { + break; + } - if range.start.row == prev_row && range.end > row_start { - indent_from_prev_row = true; - } - if range.end.row >= prev_row && range.end <= row_start { - outdent_to_row = outdent_to_row.min(range.start.row); - } + if range.start.row == prev_row && range.end > row_start { + indent_from_prev_row = true; + } + if range.end.row >= prev_row && range.end <= row_start { + outdent_to_row = outdent_to_row.min(range.start.row); } + } - let suggestion = if outdent_to_row == prev_row { - IndentSuggestion { - basis_row: prev_row, - indent: false, - } - } else if indent_from_prev_row { - IndentSuggestion { - basis_row: prev_row, - indent: true, - } - } else if outdent_to_row < prev_row { - IndentSuggestion { - basis_row: outdent_to_row, - indent: false, - } - } else { - IndentSuggestion { - basis_row: prev_row, - indent: false, - } - }; + let suggestion = if outdent_to_row == prev_row { + IndentSuggestion { + basis_row: prev_row, + indent: false, + } + } else if indent_from_prev_row { + IndentSuggestion { + basis_row: prev_row, + indent: true, + } + } else if outdent_to_row < prev_row { + IndentSuggestion { + basis_row: outdent_to_row, + indent: false, + } + } else { + IndentSuggestion { + basis_row: prev_row, + indent: false, + } + }; - prev_row = row; - suggestion - })) - } else { - None - } + prev_row = row; + suggestion + })) } fn prev_non_blank_row(&self, mut row: u32) -> Option { @@ -1989,6 +1999,22 @@ impl BufferSnapshot { } } +pub fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize { + let mut result = IndentSize::spaces(0); + for c in text.chars_at(Point::new(row, 0)) { + match (c, &result.kind) { + (' ', IndentKind::Space) | ('\t', IndentKind::Tab) => result.len += 1, + ('\t', IndentKind::Space) => { + if result.len == 0 { + result = IndentSize::tab(); + } + } + _ => break, + } + } + result +} + impl Clone for BufferSnapshot { fn clone(&self) -> Self { Self { @@ -2311,6 +2337,43 @@ impl Default for Diagnostic { } } +impl IndentSize { + pub fn spaces(len: u32) -> Self { + Self { + len, + kind: IndentKind::Space, + } + } + + pub fn tab() -> Self { + Self { + len: 1, + kind: IndentKind::Tab, + } + } + + pub fn chars(&self) -> impl Iterator { + iter::repeat(self.char()).take(self.len as usize) + } + + pub fn char(&self) -> char { + match self.kind { + IndentKind::Space => ' ', + IndentKind::Tab => '\t', + } + } +} + +impl std::ops::AddAssign for IndentSize { + fn add_assign(&mut self, other: IndentSize) { + if self.len == 0 { + *self = other; + } else if self.kind == other.kind { + self.len += other.len; + } + } +} + impl Completion { pub fn sort_key(&self) -> (usize, &str) { let kind_key = match self.lsp_completion.kind { diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 3bc9f4b9dcfcaea660688f03f658f2579f3b342f..7cd830d581738573bfb65657509dcb8e8c179dee 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -576,13 +576,21 @@ fn test_edit_with_autoindent(cx: &mut MutableAppContext) { let text = "fn a() {}"; let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); - buffer.edit_with_autoindent([(8..8, "\n\n")], 4, cx); + buffer.edit_with_autoindent([(8..8, "\n\n")], IndentSize::spaces(4), cx); assert_eq!(buffer.text(), "fn a() {\n \n}"); - buffer.edit_with_autoindent([(Point::new(1, 4)..Point::new(1, 4), "b()\n")], 4, cx); + buffer.edit_with_autoindent( + [(Point::new(1, 4)..Point::new(1, 4), "b()\n")], + IndentSize::spaces(4), + cx, + ); assert_eq!(buffer.text(), "fn a() {\n b()\n \n}"); - buffer.edit_with_autoindent([(Point::new(2, 4)..Point::new(2, 4), ".c")], 4, cx); + buffer.edit_with_autoindent( + [(Point::new(2, 4)..Point::new(2, 4), ".c")], + IndentSize::spaces(4), + cx, + ); assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}"); buffer @@ -609,7 +617,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta (empty(Point::new(1, 1)), "()"), (empty(Point::new(2, 1)), "()"), ], - 4, + IndentSize::spaces(4), cx, ); assert_eq!( @@ -630,7 +638,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta (empty(Point::new(1, 1)), "\n.f\n.g"), (empty(Point::new(2, 1)), "\n.f\n.g"), ], - 4, + IndentSize::spaces(4), cx, ); assert_eq!( @@ -653,13 +661,21 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta cx.add_model(|cx| { let text = "fn a() {\n {\n b()?\n }\n\n Ok(())\n}"; let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); - buffer.edit_with_autoindent([(Point::new(3, 4)..Point::new(3, 5), "")], 4, cx); + buffer.edit_with_autoindent( + [(Point::new(3, 4)..Point::new(3, 5), "")], + IndentSize::spaces(4), + cx, + ); assert_eq!( buffer.text(), "fn a() {\n {\n b()?\n \n\n Ok(())\n}" ); - buffer.edit_with_autoindent([(Point::new(3, 0)..Point::new(3, 12), "")], 4, cx); + buffer.edit_with_autoindent( + [(Point::new(3, 0)..Point::new(3, 12), "")], + IndentSize::spaces(4), + cx, + ); assert_eq!( buffer.text(), "fn a() {\n {\n b()?\n\n\n Ok(())\n}" @@ -678,7 +694,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); - buffer.edit_with_autoindent([(5..5, "\nb")], 4, cx); + buffer.edit_with_autoindent([(5..5, "\nb")], IndentSize::spaces(4), cx); assert_eq!( buffer.text(), " @@ -690,7 +706,11 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte // The indentation suggestion changed because `@end` node (a close paren) // is now at the beginning of the line. - buffer.edit_with_autoindent([(Point::new(1, 4)..Point::new(1, 5), "")], 4, cx); + buffer.edit_with_autoindent( + [(Point::new(1, 4)..Point::new(1, 5), "")], + IndentSize::spaces(4), + cx, + ); assert_eq!( buffer.text(), " @@ -709,7 +729,7 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut MutableAppContext) { cx.add_model(|cx| { let text = "a\nb"; let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); - buffer.edit_with_autoindent([(0..1, "\n"), (2..3, "\n")], 4, cx); + buffer.edit_with_autoindent([(0..1, "\n"), (2..3, "\n")], IndentSize::spaces(4), cx); assert_eq!(buffer.text(), "\n\n\n"); buffer }); diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 0efedbfd9b8ae92ce4a91c5b3fb3cf59991a2580..08c153740a1b9524f0c456af7e6b7ae86f9e3410 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -25,6 +25,7 @@ pub struct Settings { pub default_buffer_font_size: f32, pub vim_mode: bool, pub tab_size: u32, + pub hard_tabs: bool, pub soft_wrap: SoftWrap, pub preferred_line_length: u32, pub format_on_save: bool, @@ -35,6 +36,7 @@ pub struct Settings { #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] pub struct LanguageOverride { pub tab_size: Option, + pub hard_tabs: Option, pub soft_wrap: Option, pub preferred_line_length: Option, pub format_on_save: Option, @@ -80,6 +82,7 @@ impl Settings { default_buffer_font_size: 15., vim_mode: false, tab_size: 4, + hard_tabs: false, soft_wrap: SoftWrap::None, preferred_line_length: 80, language_overrides: Default::default(), @@ -106,6 +109,13 @@ impl Settings { .unwrap_or(self.tab_size) } + pub fn hard_tabs(&self, language: Option<&str>) -> bool { + language + .and_then(|language| self.language_overrides.get(language)) + .and_then(|settings| settings.hard_tabs) + .unwrap_or(self.hard_tabs) + } + pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap { language .and_then(|language| self.language_overrides.get(language)) @@ -135,6 +145,7 @@ impl Settings { default_buffer_font_size: 14., vim_mode: false, tab_size: 4, + hard_tabs: false, soft_wrap: SoftWrap::None, preferred_line_length: 80, format_on_save: true, diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 8d37ad0c8b0c7dca7c12ef03740d763fe0554823..2c8fc13313c49025faf35aafd7ef14fec1c1558a 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1642,18 +1642,6 @@ impl BufferSnapshot { .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none()) } - pub fn indent_column_for_line(&self, row: u32) -> u32 { - let mut result = 0; - for c in self.chars_at(Point::new(row, 0)) { - if c == ' ' { - result += 1; - } else { - break; - } - } - result - } - pub fn text_summary_for_range<'a, D, O: ToOffset>(&'a self, range: Range) -> D where D: TextDimension, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8706f1327a4587af2f283d7d2b2c4c35da8b150c..276c08cc28f91b51b02f1dd4a6fc4038ed1bf65b 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -94,6 +94,14 @@ fn main() { ..Default::default() }, ) + .with_overrides( + "Go", + settings::LanguageOverride { + tab_size: Some(4), + hard_tabs: Some(true), + ..Default::default() + }, + ) .with_overrides( "Markdown", settings::LanguageOverride {