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.
This commit is contained in:
Max Brunsfeld
2022-06-08 18:30:10 -07:00
parent 129fc515ef
commit f62fd3cddd
8 changed files with 324 additions and 200 deletions
+46 -25
View File
@@ -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::<Settings>().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::<Settings>().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::<String>(),
)],
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()
+1 -1
View File
@@ -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;
+23 -10
View File
@@ -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::<Settings>().tab_size(language_name.as_deref());
if autoindent {
let language_name = buffer.language().map(|language| language.name());
let settings = cx.global::<Settings>();
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::<Settings>().tab_size(language_name.as_deref());
if autoindent {
let settings = cx.global::<Settings>();
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)
}
}
+205 -142
View File
@@ -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<Anchor>,
inserted: Option<Vec<Range<Anchor>>>,
indent_size: u32,
indent_size: IndentSize,
}
#[derive(Debug)]
@@ -723,18 +735,18 @@ impl Buffer {
}
fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
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<impl Future<Output = BTreeMap<u32, u32>>> {
fn compute_autoindents(&self) -> Option<impl Future<Output = BTreeMap<u32, IndentSize>>> {
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::<BTreeMap<u32, u32>>();
let mut old_suggestions = HashMap::<u32, u32>::default();
let mut old_suggestions = HashMap::<u32, IndentSize>::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<u32, u32>,
indent_sizes: BTreeMap<u32, IndentSize>,
cx: &mut ModelContext<Self>,
) {
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<Self>) {
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<Self>,
) {
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::<String>(),
)],
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<I, S, T>(
&mut self,
edits_iter: I,
indent_size: u32,
indent_size: IndentSize,
cx: &mut ModelContext<Self>,
) -> Option<clock::Local>
where
@@ -1098,7 +1110,7 @@ impl Buffer {
pub fn edit_internal<I, S, T>(
&mut self,
edits_iter: I,
autoindent_size: Option<u32>,
autoindent_size: Option<IndentSize>,
cx: &mut ModelContext<Self>,
) -> Option<clock::Local>
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<u32>,
) -> Option<impl Iterator<Item = IndentSuggestion> + '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<Point>, &'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<Point> = None;
let mut end: Option<Point> = 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()));
}
}
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 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<Point>>::new();
for mat in query_cursor.matches(
&grammar.indents_query,
self.tree.as_ref()?.root_node(),
TextProvider(self.as_rope()),
) {
let mut start: Option<Point> = None;
let mut end: Option<Point> = 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()));
}
}
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 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;
}
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 let Some((start, end)) = start.zip(end) {
if start.row == end.row {
continue;
}
let suggestion = if outdent_to_row == prev_row {
IndentSuggestion {
basis_row: prev_row,
indent: false,
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);
}
} 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
}
}
}
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 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);
}
}
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
}))
}
fn prev_non_blank_row(&self, mut row: u32) -> Option<u32> {
@@ -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<Item = char> {
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 {
+30 -10
View File
@@ -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
});
+11
View File
@@ -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<u32>,
pub hard_tabs: Option<bool>,
pub soft_wrap: Option<SoftWrap>,
pub preferred_line_length: Option<u32>,
pub format_on_save: Option<bool>,
@@ -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,
-12
View File
@@ -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<O>) -> D
where
D: TextDimension,
+8
View File
@@ -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 {