commit
87ba68e3ea
27 changed files with 1229 additions and 277 deletions
|
@ -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::{
|
||||
|
@ -51,7 +52,7 @@ use std::{
|
|||
any::TypeId,
|
||||
borrow::Cow,
|
||||
cmp::{self, Ordering, Reverse},
|
||||
iter, mem,
|
||||
mem,
|
||||
ops::{Deref, DerefMut, Range, RangeInclusive},
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
|
@ -1932,9 +1933,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;
|
||||
|
||||
|
@ -1967,9 +1967,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);
|
||||
}
|
||||
|
@ -3070,14 +3070,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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3128,21 +3135,27 @@ impl Editor {
|
|||
for selection in &mut selections {
|
||||
let language_name =
|
||||
buffer.language_at(selection.start, cx).map(|l| l.name());
|
||||
let tab_size = cx.global::<Settings>().tab_size(language_name.as_deref());
|
||||
let char_column = buffer
|
||||
.read(cx)
|
||||
.text_for_range(Point::new(selection.start.row, 0)..selection.start)
|
||||
.flat_map(str::chars)
|
||||
.count();
|
||||
let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
|
||||
let settings = cx.global::<Settings>();
|
||||
let tab_size = if settings.hard_tabs(language_name.as_deref()) {
|
||||
IndentSize::tab()
|
||||
} else {
|
||||
let tab_size = settings.tab_size(language_name.as_deref());
|
||||
let char_column = buffer
|
||||
.read(cx)
|
||||
.text_for_range(Point::new(selection.start.row, 0)..selection.start)
|
||||
.flat_map(str::chars)
|
||||
.count();
|
||||
let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
|
||||
IndentSize::spaces(chars_to_next_tab_stop)
|
||||
};
|
||||
buffer.edit(
|
||||
[(
|
||||
selection.start..selection.start,
|
||||
" ".repeat(chars_to_next_tab_stop as usize),
|
||||
tab_size.chars().collect::<String>(),
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
selection.start.column += chars_to_next_tab_stop;
|
||||
selection.start.column += tab_size.len;
|
||||
selection.end = selection.start;
|
||||
}
|
||||
});
|
||||
|
@ -3163,7 +3176,14 @@ impl Editor {
|
|||
let snapshot = buffer.snapshot(cx);
|
||||
for selection in &mut selections {
|
||||
let language_name = buffer.language_at(selection.start, cx).map(|l| l.name());
|
||||
let tab_size = cx.global::<Settings>().tab_size(language_name.as_deref());
|
||||
let settings = &cx.global::<Settings>();
|
||||
let tab_size = settings.tab_size(language_name.as_deref());
|
||||
let indent_kind = if settings.hard_tabs(language_name.as_deref()) {
|
||||
IndentKind::Tab
|
||||
} else {
|
||||
IndentKind::Space
|
||||
};
|
||||
|
||||
let mut start_row = selection.start.row;
|
||||
let mut end_row = selection.end.row + 1;
|
||||
|
||||
|
@ -3187,26 +3207,35 @@ 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, indent_kind) {
|
||||
(IndentKind::Space, IndentKind::Space) => {
|
||||
let columns_to_next_tab_stop =
|
||||
tab_size - (current_indent.len % tab_size);
|
||||
IndentSize::spaces(columns_to_next_tab_stop)
|
||||
}
|
||||
(IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
|
||||
(_, 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));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -3239,12 +3268,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);
|
||||
}
|
||||
|
@ -4558,7 +4594,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()
|
||||
|
@ -7712,6 +7748,88 @@ mod tests {
|
|||
four"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<Settings, _, _>(|settings, _| {
|
||||
settings.hard_tabs = true;
|
||||
});
|
||||
});
|
||||
|
||||
// select two ranges on one line
|
||||
cx.set_state(indoc! {"
|
||||
[one} [two}
|
||||
three
|
||||
four"});
|
||||
cx.update_editor(|e, cx| e.tab(&Tab, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
\t[one} [two}
|
||||
three
|
||||
four"});
|
||||
cx.update_editor(|e, cx| e.tab(&Tab, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
\t\t[one} [two}
|
||||
three
|
||||
four"});
|
||||
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
\t[one} [two}
|
||||
three
|
||||
four"});
|
||||
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
[one} [two}
|
||||
three
|
||||
four"});
|
||||
|
||||
// select across a line ending
|
||||
cx.set_state(indoc! {"
|
||||
one two
|
||||
t[hree
|
||||
}four"});
|
||||
cx.update_editor(|e, cx| e.tab(&Tab, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
one two
|
||||
\tt[hree
|
||||
}four"});
|
||||
cx.update_editor(|e, cx| e.tab(&Tab, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
one two
|
||||
\t\tt[hree
|
||||
}four"});
|
||||
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
one two
|
||||
\tt[hree
|
||||
}four"});
|
||||
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
one two
|
||||
t[hree
|
||||
}four"});
|
||||
|
||||
// Ensure that indenting/outdenting works when the cursor is at column 0.
|
||||
cx.set_state(indoc! {"
|
||||
one two
|
||||
|three
|
||||
four"});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
one two
|
||||
|three
|
||||
four"});
|
||||
cx.update_editor(|e, cx| e.tab(&Tab, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
one two
|
||||
\t|three
|
||||
four"});
|
||||
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
one two
|
||||
|three
|
||||
four"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_indent_outdent_with_excerpts(cx: &mut gpui::MutableAppContext) {
|
||||
cx.set_global(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -341,9 +341,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);
|
||||
|
@ -462,9 +467,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 {
|
||||
|
@ -1838,14 +1849,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -109,9 +109,10 @@ impl<'a> EditorTestContext<'a> {
|
|||
self.editor.update(self.cx, update)
|
||||
}
|
||||
|
||||
pub fn editor_text(&mut self) -> String {
|
||||
self.editor
|
||||
.update(self.cx, |editor, cx| editor.snapshot(cx).text())
|
||||
pub fn buffer_text(&mut self) -> String {
|
||||
self.editor.read_with(self.cx, |editor, cx| {
|
||||
editor.buffer.read(cx).snapshot(cx).text()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
|
||||
|
@ -171,10 +172,10 @@ impl<'a> EditorTestContext<'a> {
|
|||
&text,
|
||||
vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
|
||||
);
|
||||
let editor_text = self.editor_text();
|
||||
let buffer_text = self.buffer_text();
|
||||
assert_eq!(
|
||||
editor_text, unmarked_text,
|
||||
"Unmarked text doesn't match editor text"
|
||||
buffer_text, unmarked_text,
|
||||
"Unmarked text doesn't match buffer text"
|
||||
);
|
||||
|
||||
let expected_empty_selections = selection_ranges.remove(&'|'.into()).unwrap_or_default();
|
||||
|
@ -254,7 +255,7 @@ impl<'a> EditorTestContext<'a> {
|
|||
let actual_selections =
|
||||
self.insert_markers(&empty_selections, &reverse_selections, &forward_selections);
|
||||
|
||||
let unmarked_text = self.editor_text();
|
||||
let unmarked_text = self.buffer_text();
|
||||
let all_eq: Result<(), SetEqError<String>> =
|
||||
set_eq!(expected_empty_selections, empty_selections)
|
||||
.map_err(|err| {
|
||||
|
@ -322,7 +323,7 @@ impl<'a> EditorTestContext<'a> {
|
|||
reverse_selections: &Vec<Range<usize>>,
|
||||
forward_selections: &Vec<Range<usize>>,
|
||||
) -> String {
|
||||
let mut editor_text_with_selections = self.editor_text();
|
||||
let mut editor_text_with_selections = self.buffer_text();
|
||||
let mut selection_marks = BTreeMap::new();
|
||||
for range in empty_selections {
|
||||
selection_marks.insert(&range.start, '|');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue