mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-11 12:46:07 +00:00
Unconditionally preserve indentation when inserting newlines
Co-Authored-By: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
parent
c60bc00c9e
commit
b4680144c5
1 changed files with 106 additions and 2 deletions
|
@ -21,7 +21,7 @@ use smol::Timer;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
mem,
|
iter, mem,
|
||||||
ops::{Range, RangeInclusive},
|
ops::{Range, RangeInclusive},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -38,6 +38,7 @@ action!(Cancel);
|
||||||
action!(Backspace);
|
action!(Backspace);
|
||||||
action!(Delete);
|
action!(Delete);
|
||||||
action!(Input, String);
|
action!(Input, String);
|
||||||
|
action!(Newline);
|
||||||
action!(Tab);
|
action!(Tab);
|
||||||
action!(DeleteLine);
|
action!(DeleteLine);
|
||||||
action!(DeleteToPreviousWordBoundary);
|
action!(DeleteToPreviousWordBoundary);
|
||||||
|
@ -96,7 +97,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
Binding::new("ctrl-h", Backspace, Some("Editor")),
|
Binding::new("ctrl-h", Backspace, Some("Editor")),
|
||||||
Binding::new("delete", Delete, Some("Editor")),
|
Binding::new("delete", Delete, Some("Editor")),
|
||||||
Binding::new("ctrl-d", Delete, Some("Editor")),
|
Binding::new("ctrl-d", Delete, Some("Editor")),
|
||||||
Binding::new("enter", Input("\n".into()), Some("Editor && mode == full")),
|
Binding::new("enter", Newline, Some("Editor && mode == full")),
|
||||||
Binding::new(
|
Binding::new(
|
||||||
"alt-enter",
|
"alt-enter",
|
||||||
Input("\n".into()),
|
Input("\n".into()),
|
||||||
|
@ -194,6 +195,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(Editor::select);
|
cx.add_action(Editor::select);
|
||||||
cx.add_action(Editor::cancel);
|
cx.add_action(Editor::cancel);
|
||||||
cx.add_action(Editor::handle_input);
|
cx.add_action(Editor::handle_input);
|
||||||
|
cx.add_action(Editor::newline);
|
||||||
cx.add_action(Editor::backspace);
|
cx.add_action(Editor::backspace);
|
||||||
cx.add_action(Editor::delete);
|
cx.add_action(Editor::delete);
|
||||||
cx.add_action(Editor::tab);
|
cx.add_action(Editor::tab);
|
||||||
|
@ -752,6 +754,84 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
|
||||||
|
self.start_transaction(cx);
|
||||||
|
let mut old_selections = SmallVec::<[_; 32]>::new();
|
||||||
|
{
|
||||||
|
let selections = self.selections(cx);
|
||||||
|
let buffer = self.buffer.read(cx);
|
||||||
|
for selection in selections.iter() {
|
||||||
|
let start_point = selection.start.to_point(buffer);
|
||||||
|
let indent = buffer
|
||||||
|
.indent_column_for_line(start_point.row)
|
||||||
|
.min(start_point.column);
|
||||||
|
let start = selection.start.to_offset(buffer);
|
||||||
|
let end = selection.end.to_offset(buffer);
|
||||||
|
old_selections.push((selection.id, start..end, indent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_selections = Vec::with_capacity(old_selections.len());
|
||||||
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
|
let mut delta = 0_isize;
|
||||||
|
let mut pending_edit: Option<PendingEdit> = None;
|
||||||
|
for (_, range, indent) in &old_selections {
|
||||||
|
if pending_edit
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |pending| pending.indent != *indent)
|
||||||
|
{
|
||||||
|
let pending = pending_edit.take().unwrap();
|
||||||
|
let mut new_text = String::with_capacity(1 + pending.indent as usize);
|
||||||
|
new_text.push('\n');
|
||||||
|
new_text.extend(iter::repeat(' ').take(pending.indent as usize));
|
||||||
|
buffer.edit_with_autoindent(pending.ranges, new_text, cx);
|
||||||
|
delta += pending.delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = (range.start as isize + delta) as usize;
|
||||||
|
let end = (range.end as isize + delta) as usize;
|
||||||
|
let text_len = *indent as usize + 1;
|
||||||
|
|
||||||
|
let pending = pending_edit.get_or_insert_with(Default::default);
|
||||||
|
pending.delta += text_len as isize - (end - start) as isize;
|
||||||
|
pending.indent = *indent;
|
||||||
|
pending.ranges.push(start..end);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pending = pending_edit.unwrap();
|
||||||
|
let mut new_text = String::with_capacity(1 + pending.indent as usize);
|
||||||
|
new_text.push('\n');
|
||||||
|
new_text.extend(iter::repeat(' ').take(pending.indent as usize));
|
||||||
|
buffer.edit_with_autoindent(pending.ranges, new_text, cx);
|
||||||
|
|
||||||
|
let mut delta = 0_isize;
|
||||||
|
new_selections.extend(old_selections.into_iter().map(|(id, range, indent)| {
|
||||||
|
let start = (range.start as isize + delta) as usize;
|
||||||
|
let end = (range.end as isize + delta) as usize;
|
||||||
|
let text_len = indent as usize + 1;
|
||||||
|
let anchor = buffer.anchor_before(start + text_len);
|
||||||
|
delta += text_len as isize - (end - start) as isize;
|
||||||
|
Selection {
|
||||||
|
id,
|
||||||
|
start: anchor.clone(),
|
||||||
|
end: anchor,
|
||||||
|
reversed: false,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
self.update_selections(new_selections, true, cx);
|
||||||
|
self.end_transaction(cx);
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct PendingEdit {
|
||||||
|
indent: u32,
|
||||||
|
delta: isize,
|
||||||
|
ranges: SmallVec<[Range<usize>; 32]>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
||||||
self.start_transaction(cx);
|
self.start_transaction(cx);
|
||||||
let mut old_selections = SmallVec::<[_; 32]>::new();
|
let mut old_selections = SmallVec::<[_; 32]>::new();
|
||||||
|
@ -3554,6 +3634,30 @@ mod tests {
|
||||||
assert_eq!(buffer.read(cx).text(), "e t te our");
|
assert_eq!(buffer.read(cx).text(), "e t te our");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_newline(cx: &mut gpui::MutableAppContext) {
|
||||||
|
let buffer = cx.add_model(|cx| Buffer::new(0, "aaaa\n bbbb\n", cx));
|
||||||
|
let settings = EditorSettings::test(&cx);
|
||||||
|
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||||
|
build_editor(buffer.clone(), settings, cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
view.update(cx, |view, cx| {
|
||||||
|
view.select_display_ranges(
|
||||||
|
&[
|
||||||
|
DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
|
||||||
|
DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
|
||||||
|
DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
|
||||||
|
],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
view.newline(&Newline, cx);
|
||||||
|
assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_backspace(cx: &mut gpui::MutableAppContext) {
|
fn test_backspace(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| {
|
let buffer = cx.add_model(|cx| {
|
||||||
|
|
Loading…
Reference in a new issue