Add AutoIndent action and '=' vim operator (#21427)
Release Notes: - vim: Added the `=` operator, for auto-indent Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
parent
f3140f54d8
commit
7c994cd4a5
13 changed files with 481 additions and 186 deletions
|
@ -55,10 +55,10 @@
|
||||||
"n": "vim::MoveToNextMatch",
|
"n": "vim::MoveToNextMatch",
|
||||||
"shift-n": "vim::MoveToPrevMatch",
|
"shift-n": "vim::MoveToPrevMatch",
|
||||||
"%": "vim::Matching",
|
"%": "vim::Matching",
|
||||||
"] }": ["vim::UnmatchedForward", { "char": "}" } ],
|
"] }": ["vim::UnmatchedForward", { "char": "}" }],
|
||||||
"[ {": ["vim::UnmatchedBackward", { "char": "{" } ],
|
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
|
||||||
"] )": ["vim::UnmatchedForward", { "char": ")" } ],
|
"] )": ["vim::UnmatchedForward", { "char": ")" }],
|
||||||
"[ (": ["vim::UnmatchedBackward", { "char": "(" } ],
|
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
|
||||||
"f": ["vim::PushOperator", { "FindForward": { "before": false } }],
|
"f": ["vim::PushOperator", { "FindForward": { "before": false } }],
|
||||||
"t": ["vim::PushOperator", { "FindForward": { "before": true } }],
|
"t": ["vim::PushOperator", { "FindForward": { "before": true } }],
|
||||||
"shift-f": ["vim::PushOperator", { "FindBackward": { "after": false } }],
|
"shift-f": ["vim::PushOperator", { "FindBackward": { "after": false } }],
|
||||||
|
@ -209,6 +209,7 @@
|
||||||
"shift-s": "vim::SubstituteLine",
|
"shift-s": "vim::SubstituteLine",
|
||||||
">": ["vim::PushOperator", "Indent"],
|
">": ["vim::PushOperator", "Indent"],
|
||||||
"<": ["vim::PushOperator", "Outdent"],
|
"<": ["vim::PushOperator", "Outdent"],
|
||||||
|
"=": ["vim::PushOperator", "AutoIndent"],
|
||||||
"g u": ["vim::PushOperator", "Lowercase"],
|
"g u": ["vim::PushOperator", "Lowercase"],
|
||||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
||||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
"g ~": ["vim::PushOperator", "OppositeCase"],
|
||||||
|
@ -275,6 +276,7 @@
|
||||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||||
">": "vim::Indent",
|
">": "vim::Indent",
|
||||||
"<": "vim::Outdent",
|
"<": "vim::Outdent",
|
||||||
|
"=": "vim::AutoIndent",
|
||||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||||
"g c": "vim::ToggleComments",
|
"g c": "vim::ToggleComments",
|
||||||
|
|
|
@ -303,6 +303,7 @@ gpui::actions!(
|
||||||
OpenPermalinkToLine,
|
OpenPermalinkToLine,
|
||||||
OpenUrl,
|
OpenUrl,
|
||||||
Outdent,
|
Outdent,
|
||||||
|
AutoIndent,
|
||||||
PageDown,
|
PageDown,
|
||||||
PageUp,
|
PageUp,
|
||||||
Paste,
|
Paste,
|
||||||
|
|
|
@ -6297,6 +6297,25 @@ impl Editor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn autoindent(&mut self, _: &AutoIndent, cx: &mut ViewContext<Self>) {
|
||||||
|
if self.read_only(cx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let selections = self
|
||||||
|
.selections
|
||||||
|
.all::<usize>(cx)
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.range());
|
||||||
|
|
||||||
|
self.transact(cx, |this, cx| {
|
||||||
|
this.buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.autoindent_ranges(selections, cx);
|
||||||
|
});
|
||||||
|
let selections = this.selections.all::<usize>(cx);
|
||||||
|
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
|
pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let selections = self.selections.all::<Point>(cx);
|
let selections = self.selections.all::<Point>(cx);
|
||||||
|
|
|
@ -34,6 +34,7 @@ use serde_json::{self, json};
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::atomic::{self, AtomicBool};
|
use std::sync::atomic::{self, AtomicBool};
|
||||||
use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
|
use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
|
||||||
|
use test::editor_lsp_test_context::rust_lang;
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
use util::{
|
use util::{
|
||||||
assert_set_eq,
|
assert_set_eq,
|
||||||
|
@ -5458,7 +5459,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
async fn test_autoindent(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
let language = Arc::new(
|
let language = Arc::new(
|
||||||
|
@ -5520,6 +5521,89 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
|
||||||
|
cx.set_state(indoc! {"
|
||||||
|
impl A {
|
||||||
|
|
||||||
|
fn b() {}
|
||||||
|
|
||||||
|
«fn c() {
|
||||||
|
|
||||||
|
}ˇ»
|
||||||
|
}
|
||||||
|
"});
|
||||||
|
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
editor.autoindent(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.assert_editor_state(indoc! {"
|
||||||
|
impl A {
|
||||||
|
|
||||||
|
fn b() {}
|
||||||
|
|
||||||
|
«fn c() {
|
||||||
|
|
||||||
|
}ˇ»
|
||||||
|
}
|
||||||
|
"});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut cx = EditorTestContext::new_multibuffer(
|
||||||
|
cx,
|
||||||
|
[indoc! { "
|
||||||
|
impl A {
|
||||||
|
«
|
||||||
|
// a
|
||||||
|
fn b(){}
|
||||||
|
»
|
||||||
|
«
|
||||||
|
}
|
||||||
|
fn c(){}
|
||||||
|
»
|
||||||
|
"}],
|
||||||
|
);
|
||||||
|
|
||||||
|
let buffer = cx.update_editor(|editor, cx| {
|
||||||
|
let buffer = editor.buffer().update(cx, |buffer, _| {
|
||||||
|
buffer.all_buffers().iter().next().unwrap().clone()
|
||||||
|
});
|
||||||
|
buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
|
||||||
|
buffer
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
editor.select_all(&Default::default(), cx);
|
||||||
|
editor.autoindent(&Default::default(), cx)
|
||||||
|
});
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
pretty_assertions::assert_eq!(
|
||||||
|
buffer.read(cx).text(),
|
||||||
|
indoc! { "
|
||||||
|
impl A {
|
||||||
|
|
||||||
|
// a
|
||||||
|
fn b(){}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
fn c(){}
|
||||||
|
|
||||||
|
" }
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
|
async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
@ -13933,20 +14017,6 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
|
||||||
update_test_language_settings(cx, f);
|
update_test_language_settings(cx, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rust_lang() -> Arc<Language> {
|
|
||||||
Arc::new(Language::new(
|
|
||||||
LanguageConfig {
|
|
||||||
name: "Rust".into(),
|
|
||||||
matcher: LanguageMatcher {
|
|
||||||
path_suffixes: vec!["rs".to_string()],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_hunk_revert(
|
fn assert_hunk_revert(
|
||||||
not_reverted_text_with_selections: &str,
|
not_reverted_text_with_selections: &str,
|
||||||
|
|
|
@ -189,6 +189,7 @@ impl EditorElement {
|
||||||
register_action(view, cx, Editor::tab_prev);
|
register_action(view, cx, Editor::tab_prev);
|
||||||
register_action(view, cx, Editor::indent);
|
register_action(view, cx, Editor::indent);
|
||||||
register_action(view, cx, Editor::outdent);
|
register_action(view, cx, Editor::outdent);
|
||||||
|
register_action(view, cx, Editor::autoindent);
|
||||||
register_action(view, cx, Editor::delete_line);
|
register_action(view, cx, Editor::delete_line);
|
||||||
register_action(view, cx, Editor::join_lines);
|
register_action(view, cx, Editor::join_lines);
|
||||||
register_action(view, cx, Editor::sort_lines_case_sensitive);
|
register_action(view, cx, Editor::sort_lines_case_sensitive);
|
||||||
|
|
|
@ -1258,6 +1258,7 @@ pub mod tests {
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
scroll::{scroll_amount::ScrollAmount, Autoscroll},
|
scroll::{scroll_amount::ScrollAmount, Autoscroll},
|
||||||
|
test::editor_lsp_test_context::rust_lang,
|
||||||
ExcerptRange,
|
ExcerptRange,
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
@ -2274,7 +2275,7 @@ pub mod tests {
|
||||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(crate::editor_tests::rust_lang());
|
language_registry.add(rust_lang());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
|
@ -2570,7 +2571,7 @@ pub mod tests {
|
||||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
let language = crate::editor_tests::rust_lang();
|
let language = rust_lang();
|
||||||
language_registry.add(language);
|
language_registry.add(language);
|
||||||
let mut fake_servers = language_registry.register_fake_lsp(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
|
@ -2922,7 +2923,7 @@ pub mod tests {
|
||||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(crate::editor_tests::rust_lang());
|
language_registry.add(rust_lang());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
|
@ -3153,7 +3154,7 @@ pub mod tests {
|
||||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(crate::editor_tests::rust_lang());
|
language_registry.add(rust_lang());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
|
@ -3396,7 +3397,7 @@ pub mod tests {
|
||||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(crate::editor_tests::rust_lang());
|
language_registry.add(rust_lang());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
|
|
|
@ -31,6 +31,47 @@ pub struct EditorLspTestContext {
|
||||||
pub buffer_lsp_url: lsp::Url,
|
pub buffer_lsp_url: lsp::Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn rust_lang() -> Arc<Language> {
|
||||||
|
let language = Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
name: "Rust".into(),
|
||||||
|
matcher: LanguageMatcher {
|
||||||
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
|
)
|
||||||
|
.with_queries(LanguageQueries {
|
||||||
|
indents: Some(Cow::from(indoc! {r#"
|
||||||
|
[
|
||||||
|
((where_clause) _ @end)
|
||||||
|
(field_expression)
|
||||||
|
(call_expression)
|
||||||
|
(assignment_expression)
|
||||||
|
(let_declaration)
|
||||||
|
(let_chain)
|
||||||
|
(await_expression)
|
||||||
|
] @indent
|
||||||
|
|
||||||
|
(_ "[" "]" @end) @indent
|
||||||
|
(_ "<" ">" @end) @indent
|
||||||
|
(_ "{" "}" @end) @indent
|
||||||
|
(_ "(" ")" @end) @indent"#})),
|
||||||
|
brackets: Some(Cow::from(indoc! {r#"
|
||||||
|
("(" @open ")" @close)
|
||||||
|
("[" @open "]" @close)
|
||||||
|
("{" @open "}" @close)
|
||||||
|
("<" @open ">" @close)
|
||||||
|
("\"" @open "\"" @close)
|
||||||
|
(closure_parameters "|" @open "|" @close)"#})),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.expect("Could not parse queries");
|
||||||
|
Arc::new(language)
|
||||||
|
}
|
||||||
impl EditorLspTestContext {
|
impl EditorLspTestContext {
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
language: Language,
|
language: Language,
|
||||||
|
@ -119,46 +160,7 @@ impl EditorLspTestContext {
|
||||||
capabilities: lsp::ServerCapabilities,
|
capabilities: lsp::ServerCapabilities,
|
||||||
cx: &mut gpui::TestAppContext,
|
cx: &mut gpui::TestAppContext,
|
||||||
) -> EditorLspTestContext {
|
) -> EditorLspTestContext {
|
||||||
let language = Language::new(
|
Self::new(Arc::into_inner(rust_lang()).unwrap(), capabilities, cx).await
|
||||||
LanguageConfig {
|
|
||||||
name: "Rust".into(),
|
|
||||||
matcher: LanguageMatcher {
|
|
||||||
path_suffixes: vec!["rs".to_string()],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
|
||||||
)
|
|
||||||
.with_queries(LanguageQueries {
|
|
||||||
indents: Some(Cow::from(indoc! {r#"
|
|
||||||
[
|
|
||||||
((where_clause) _ @end)
|
|
||||||
(field_expression)
|
|
||||||
(call_expression)
|
|
||||||
(assignment_expression)
|
|
||||||
(let_declaration)
|
|
||||||
(let_chain)
|
|
||||||
(await_expression)
|
|
||||||
] @indent
|
|
||||||
|
|
||||||
(_ "[" "]" @end) @indent
|
|
||||||
(_ "<" ">" @end) @indent
|
|
||||||
(_ "{" "}" @end) @indent
|
|
||||||
(_ "(" ")" @end) @indent"#})),
|
|
||||||
brackets: Some(Cow::from(indoc! {r#"
|
|
||||||
("(" @open ")" @close)
|
|
||||||
("[" @open "]" @close)
|
|
||||||
("{" @open "}" @close)
|
|
||||||
("<" @open ">" @close)
|
|
||||||
("\"" @open "\"" @close)
|
|
||||||
(closure_parameters "|" @open "|" @close)"#})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.expect("Could not parse queries");
|
|
||||||
|
|
||||||
Self::new(language, capabilities, cx).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new_typescript(
|
pub async fn new_typescript(
|
||||||
|
|
|
@ -467,6 +467,7 @@ struct AutoindentRequest {
|
||||||
before_edit: BufferSnapshot,
|
before_edit: BufferSnapshot,
|
||||||
entries: Vec<AutoindentRequestEntry>,
|
entries: Vec<AutoindentRequestEntry>,
|
||||||
is_block_mode: bool,
|
is_block_mode: bool,
|
||||||
|
ignore_empty_lines: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -1381,7 +1382,7 @@ impl Buffer {
|
||||||
|
|
||||||
let autoindent_requests = self.autoindent_requests.clone();
|
let autoindent_requests = self.autoindent_requests.clone();
|
||||||
Some(async move {
|
Some(async move {
|
||||||
let mut indent_sizes = BTreeMap::new();
|
let mut indent_sizes = BTreeMap::<u32, (IndentSize, bool)>::new();
|
||||||
for request in autoindent_requests {
|
for request in autoindent_requests {
|
||||||
// Resolve each edited range to its row in the current buffer and in the
|
// Resolve each edited range to its row in the current buffer and in the
|
||||||
// buffer before this batch of edits.
|
// buffer before this batch of edits.
|
||||||
|
@ -1475,10 +1476,12 @@ impl Buffer {
|
||||||
let suggested_indent = indent_sizes
|
let suggested_indent = indent_sizes
|
||||||
.get(&suggestion.basis_row)
|
.get(&suggestion.basis_row)
|
||||||
.copied()
|
.copied()
|
||||||
|
.map(|e| e.0)
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
snapshot.indent_size_for_line(suggestion.basis_row)
|
snapshot.indent_size_for_line(suggestion.basis_row)
|
||||||
})
|
})
|
||||||
.with_delta(suggestion.delta, language_indent_size);
|
.with_delta(suggestion.delta, language_indent_size);
|
||||||
|
|
||||||
if old_suggestions.get(&new_row).map_or(
|
if old_suggestions.get(&new_row).map_or(
|
||||||
true,
|
true,
|
||||||
|(old_indentation, was_within_error)| {
|
|(old_indentation, was_within_error)| {
|
||||||
|
@ -1486,7 +1489,10 @@ impl Buffer {
|
||||||
&& (!suggestion.within_error || *was_within_error)
|
&& (!suggestion.within_error || *was_within_error)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
indent_sizes.insert(new_row, suggested_indent);
|
indent_sizes.insert(
|
||||||
|
new_row,
|
||||||
|
(suggested_indent, request.ignore_empty_lines),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1494,10 +1500,12 @@ impl Buffer {
|
||||||
if let (true, Some(original_indent_column)) =
|
if let (true, Some(original_indent_column)) =
|
||||||
(request.is_block_mode, original_indent_column)
|
(request.is_block_mode, original_indent_column)
|
||||||
{
|
{
|
||||||
let new_indent = indent_sizes
|
let new_indent =
|
||||||
.get(&row_range.start)
|
if let Some((indent, _)) = indent_sizes.get(&row_range.start) {
|
||||||
.copied()
|
*indent
|
||||||
.unwrap_or_else(|| snapshot.indent_size_for_line(row_range.start));
|
} else {
|
||||||
|
snapshot.indent_size_for_line(row_range.start)
|
||||||
|
};
|
||||||
let delta = new_indent.len as i64 - original_indent_column as i64;
|
let delta = new_indent.len as i64 - original_indent_column as i64;
|
||||||
if delta != 0 {
|
if delta != 0 {
|
||||||
for row in row_range.skip(1) {
|
for row in row_range.skip(1) {
|
||||||
|
@ -1512,7 +1520,7 @@ impl Buffer {
|
||||||
Ordering::Equal => {}
|
Ordering::Equal => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size
|
(size, request.ignore_empty_lines)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1523,6 +1531,15 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
indent_sizes
|
indent_sizes
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(row, (indent, ignore_empty_lines))| {
|
||||||
|
if ignore_empty_lines && snapshot.line_len(row) == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((row, indent))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2067,6 +2084,7 @@ impl Buffer {
|
||||||
before_edit,
|
before_edit,
|
||||||
entries,
|
entries,
|
||||||
is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
|
is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
|
||||||
|
ignore_empty_lines: false,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2094,6 +2112,30 @@ impl Buffer {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn autoindent_ranges<I, T>(&mut self, ranges: I, cx: &mut ModelContext<Self>)
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Range<T>>,
|
||||||
|
T: ToOffset + Copy,
|
||||||
|
{
|
||||||
|
let before_edit = self.snapshot();
|
||||||
|
let entries = ranges
|
||||||
|
.into_iter()
|
||||||
|
.map(|range| AutoindentRequestEntry {
|
||||||
|
range: before_edit.anchor_before(range.start)..before_edit.anchor_after(range.end),
|
||||||
|
first_line_is_new: true,
|
||||||
|
indent_size: before_edit.language_indent_size_at(range.start, cx),
|
||||||
|
original_indent_column: None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
self.autoindent_requests.push(Arc::new(AutoindentRequest {
|
||||||
|
before_edit,
|
||||||
|
entries,
|
||||||
|
is_block_mode: false,
|
||||||
|
ignore_empty_lines: true,
|
||||||
|
}));
|
||||||
|
self.request_autoindent(cx);
|
||||||
|
}
|
||||||
|
|
||||||
// Inserts newlines at the given position to create an empty line, returning the start of the new line.
|
// Inserts newlines at the given position to create an empty line, returning the start of the new line.
|
||||||
// You can also request the insertion of empty lines above and below the line starting at the returned point.
|
// You can also request the insertion of empty lines above and below the line starting at the returned point.
|
||||||
pub fn insert_empty_line(
|
pub fn insert_empty_line(
|
||||||
|
|
|
@ -325,6 +325,13 @@ struct ExcerptBytes<'a> {
|
||||||
reversed: bool,
|
reversed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BufferEdit {
|
||||||
|
range: Range<usize>,
|
||||||
|
new_text: Arc<str>,
|
||||||
|
is_insertion: bool,
|
||||||
|
original_indent_column: u32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum ExpandExcerptDirection {
|
pub enum ExpandExcerptDirection {
|
||||||
Up,
|
Up,
|
||||||
|
@ -525,57 +532,146 @@ impl MultiBuffer {
|
||||||
pub fn edit<I, S, T>(
|
pub fn edit<I, S, T>(
|
||||||
&self,
|
&self,
|
||||||
edits: I,
|
edits: I,
|
||||||
mut autoindent_mode: Option<AutoindentMode>,
|
autoindent_mode: Option<AutoindentMode>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) where
|
) where
|
||||||
I: IntoIterator<Item = (Range<S>, T)>,
|
I: IntoIterator<Item = (Range<S>, T)>,
|
||||||
S: ToOffset,
|
S: ToOffset,
|
||||||
T: Into<Arc<str>>,
|
T: Into<Arc<str>>,
|
||||||
{
|
{
|
||||||
if self.read_only() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if self.buffers.borrow().is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let snapshot = self.read(cx);
|
let snapshot = self.read(cx);
|
||||||
let edits = edits.into_iter().map(|(range, new_text)| {
|
let edits = edits
|
||||||
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
|
.into_iter()
|
||||||
if range.start > range.end {
|
.map(|(range, new_text)| {
|
||||||
mem::swap(&mut range.start, &mut range.end);
|
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
|
||||||
|
if range.start > range.end {
|
||||||
|
mem::swap(&mut range.start, &mut range.end);
|
||||||
|
}
|
||||||
|
(range, new_text.into())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
return edit_internal(self, snapshot, edits, autoindent_mode, cx);
|
||||||
|
|
||||||
|
// Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
|
||||||
|
fn edit_internal(
|
||||||
|
this: &MultiBuffer,
|
||||||
|
snapshot: Ref<MultiBufferSnapshot>,
|
||||||
|
edits: Vec<(Range<usize>, Arc<str>)>,
|
||||||
|
mut autoindent_mode: Option<AutoindentMode>,
|
||||||
|
cx: &mut ModelContext<MultiBuffer>,
|
||||||
|
) {
|
||||||
|
if this.read_only() || this.buffers.borrow().is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(buffer) = this.as_singleton() {
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.edit(edits, autoindent_mode, cx);
|
||||||
|
});
|
||||||
|
cx.emit(Event::ExcerptsEdited {
|
||||||
|
ids: this.excerpt_ids(),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let original_indent_columns = match &mut autoindent_mode {
|
||||||
|
Some(AutoindentMode::Block {
|
||||||
|
original_indent_columns,
|
||||||
|
}) => mem::take(original_indent_columns),
|
||||||
|
_ => Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (buffer_edits, edited_excerpt_ids) =
|
||||||
|
this.convert_edits_to_buffer_edits(edits, &snapshot, &original_indent_columns);
|
||||||
|
drop(snapshot);
|
||||||
|
|
||||||
|
for (buffer_id, mut edits) in buffer_edits {
|
||||||
|
edits.sort_unstable_by_key(|edit| edit.range.start);
|
||||||
|
this.buffers.borrow()[&buffer_id]
|
||||||
|
.buffer
|
||||||
|
.update(cx, |buffer, cx| {
|
||||||
|
let mut edits = edits.into_iter().peekable();
|
||||||
|
let mut insertions = Vec::new();
|
||||||
|
let mut original_indent_columns = Vec::new();
|
||||||
|
let mut deletions = Vec::new();
|
||||||
|
let empty_str: Arc<str> = Arc::default();
|
||||||
|
while let Some(BufferEdit {
|
||||||
|
mut range,
|
||||||
|
new_text,
|
||||||
|
mut is_insertion,
|
||||||
|
original_indent_column,
|
||||||
|
}) = edits.next()
|
||||||
|
{
|
||||||
|
while let Some(BufferEdit {
|
||||||
|
range: next_range,
|
||||||
|
is_insertion: next_is_insertion,
|
||||||
|
..
|
||||||
|
}) = edits.peek()
|
||||||
|
{
|
||||||
|
if range.end >= next_range.start {
|
||||||
|
range.end = cmp::max(next_range.end, range.end);
|
||||||
|
is_insertion |= *next_is_insertion;
|
||||||
|
edits.next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_insertion {
|
||||||
|
original_indent_columns.push(original_indent_column);
|
||||||
|
insertions.push((
|
||||||
|
buffer.anchor_before(range.start)
|
||||||
|
..buffer.anchor_before(range.end),
|
||||||
|
new_text.clone(),
|
||||||
|
));
|
||||||
|
} else if !range.is_empty() {
|
||||||
|
deletions.push((
|
||||||
|
buffer.anchor_before(range.start)
|
||||||
|
..buffer.anchor_before(range.end),
|
||||||
|
empty_str.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let deletion_autoindent_mode =
|
||||||
|
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
|
||||||
|
Some(AutoindentMode::Block {
|
||||||
|
original_indent_columns: Default::default(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
autoindent_mode.clone()
|
||||||
|
};
|
||||||
|
let insertion_autoindent_mode =
|
||||||
|
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
|
||||||
|
Some(AutoindentMode::Block {
|
||||||
|
original_indent_columns,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
autoindent_mode.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
buffer.edit(deletions, deletion_autoindent_mode, cx);
|
||||||
|
buffer.edit(insertions, insertion_autoindent_mode, cx);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
(range, new_text)
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(buffer) = self.as_singleton() {
|
|
||||||
buffer.update(cx, |buffer, cx| {
|
|
||||||
buffer.edit(edits, autoindent_mode, cx);
|
|
||||||
});
|
|
||||||
cx.emit(Event::ExcerptsEdited {
|
cx.emit(Event::ExcerptsEdited {
|
||||||
ids: self.excerpt_ids(),
|
ids: edited_excerpt_ids,
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let original_indent_columns = match &mut autoindent_mode {
|
fn convert_edits_to_buffer_edits(
|
||||||
Some(AutoindentMode::Block {
|
&self,
|
||||||
original_indent_columns,
|
edits: Vec<(Range<usize>, Arc<str>)>,
|
||||||
}) => mem::take(original_indent_columns),
|
snapshot: &MultiBufferSnapshot,
|
||||||
_ => Default::default(),
|
original_indent_columns: &[u32],
|
||||||
};
|
) -> (HashMap<BufferId, Vec<BufferEdit>>, Vec<ExcerptId>) {
|
||||||
|
|
||||||
struct BufferEdit {
|
|
||||||
range: Range<usize>,
|
|
||||||
new_text: Arc<str>,
|
|
||||||
is_insertion: bool,
|
|
||||||
original_indent_column: u32,
|
|
||||||
}
|
|
||||||
let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
|
let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
|
||||||
let mut edited_excerpt_ids = Vec::new();
|
let mut edited_excerpt_ids = Vec::new();
|
||||||
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
|
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
|
||||||
for (ix, (range, new_text)) in edits.enumerate() {
|
for (ix, (range, new_text)) in edits.into_iter().enumerate() {
|
||||||
let new_text: Arc<str> = new_text.into();
|
|
||||||
let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0);
|
let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0);
|
||||||
cursor.seek(&range.start, Bias::Right, &());
|
cursor.seek(&range.start, Bias::Right, &());
|
||||||
if cursor.item().is_none() && range.start == *cursor.start() {
|
if cursor.item().is_none() && range.start == *cursor.start() {
|
||||||
|
@ -667,84 +763,71 @@ impl MultiBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(buffer_edits, edited_excerpt_ids)
|
||||||
|
}
|
||||||
|
|
||||||
drop(cursor);
|
pub fn autoindent_ranges<I, S>(&self, ranges: I, cx: &mut ModelContext<Self>)
|
||||||
drop(snapshot);
|
where
|
||||||
// Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
|
I: IntoIterator<Item = Range<S>>,
|
||||||
fn tail(
|
S: ToOffset,
|
||||||
|
{
|
||||||
|
let snapshot = self.read(cx);
|
||||||
|
let empty = Arc::<str>::from("");
|
||||||
|
let edits = ranges
|
||||||
|
.into_iter()
|
||||||
|
.map(|range| {
|
||||||
|
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
|
||||||
|
if range.start > range.end {
|
||||||
|
mem::swap(&mut range.start, &mut range.end);
|
||||||
|
}
|
||||||
|
(range, empty.clone())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
return autoindent_ranges_internal(self, snapshot, edits, cx);
|
||||||
|
|
||||||
|
fn autoindent_ranges_internal(
|
||||||
this: &MultiBuffer,
|
this: &MultiBuffer,
|
||||||
buffer_edits: HashMap<BufferId, Vec<BufferEdit>>,
|
snapshot: Ref<MultiBufferSnapshot>,
|
||||||
autoindent_mode: Option<AutoindentMode>,
|
edits: Vec<(Range<usize>, Arc<str>)>,
|
||||||
edited_excerpt_ids: Vec<ExcerptId>,
|
|
||||||
cx: &mut ModelContext<MultiBuffer>,
|
cx: &mut ModelContext<MultiBuffer>,
|
||||||
) {
|
) {
|
||||||
|
if this.read_only() || this.buffers.borrow().is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(buffer) = this.as_singleton() {
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.autoindent_ranges(edits.into_iter().map(|e| e.0), cx);
|
||||||
|
});
|
||||||
|
cx.emit(Event::ExcerptsEdited {
|
||||||
|
ids: this.excerpt_ids(),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (buffer_edits, edited_excerpt_ids) =
|
||||||
|
this.convert_edits_to_buffer_edits(edits, &snapshot, &[]);
|
||||||
|
drop(snapshot);
|
||||||
|
|
||||||
for (buffer_id, mut edits) in buffer_edits {
|
for (buffer_id, mut edits) in buffer_edits {
|
||||||
edits.sort_unstable_by_key(|edit| edit.range.start);
|
edits.sort_unstable_by_key(|edit| edit.range.start);
|
||||||
|
|
||||||
|
let mut ranges: Vec<Range<usize>> = Vec::new();
|
||||||
|
for edit in edits {
|
||||||
|
if let Some(last_range) = ranges.last_mut() {
|
||||||
|
if edit.range.start <= last_range.end {
|
||||||
|
last_range.end = last_range.end.max(edit.range.end);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ranges.push(edit.range);
|
||||||
|
}
|
||||||
|
|
||||||
this.buffers.borrow()[&buffer_id]
|
this.buffers.borrow()[&buffer_id]
|
||||||
.buffer
|
.buffer
|
||||||
.update(cx, |buffer, cx| {
|
.update(cx, |buffer, cx| {
|
||||||
let mut edits = edits.into_iter().peekable();
|
buffer.autoindent_ranges(ranges, cx);
|
||||||
let mut insertions = Vec::new();
|
|
||||||
let mut original_indent_columns = Vec::new();
|
|
||||||
let mut deletions = Vec::new();
|
|
||||||
let empty_str: Arc<str> = Arc::default();
|
|
||||||
while let Some(BufferEdit {
|
|
||||||
mut range,
|
|
||||||
new_text,
|
|
||||||
mut is_insertion,
|
|
||||||
original_indent_column,
|
|
||||||
}) = edits.next()
|
|
||||||
{
|
|
||||||
while let Some(BufferEdit {
|
|
||||||
range: next_range,
|
|
||||||
is_insertion: next_is_insertion,
|
|
||||||
..
|
|
||||||
}) = edits.peek()
|
|
||||||
{
|
|
||||||
if range.end >= next_range.start {
|
|
||||||
range.end = cmp::max(next_range.end, range.end);
|
|
||||||
is_insertion |= *next_is_insertion;
|
|
||||||
edits.next();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_insertion {
|
|
||||||
original_indent_columns.push(original_indent_column);
|
|
||||||
insertions.push((
|
|
||||||
buffer.anchor_before(range.start)
|
|
||||||
..buffer.anchor_before(range.end),
|
|
||||||
new_text.clone(),
|
|
||||||
));
|
|
||||||
} else if !range.is_empty() {
|
|
||||||
deletions.push((
|
|
||||||
buffer.anchor_before(range.start)
|
|
||||||
..buffer.anchor_before(range.end),
|
|
||||||
empty_str.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let deletion_autoindent_mode =
|
|
||||||
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
|
|
||||||
Some(AutoindentMode::Block {
|
|
||||||
original_indent_columns: Default::default(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
autoindent_mode.clone()
|
|
||||||
};
|
|
||||||
let insertion_autoindent_mode =
|
|
||||||
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
|
|
||||||
Some(AutoindentMode::Block {
|
|
||||||
original_indent_columns,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
autoindent_mode.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
buffer.edit(deletions, deletion_autoindent_mode, cx);
|
|
||||||
buffer.edit(insertions, insertion_autoindent_mode, cx);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -752,7 +835,6 @@ impl MultiBuffer {
|
||||||
ids: edited_excerpt_ids,
|
ids: edited_excerpt_ids,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
tail(self, buffer_edits, autoindent_mode, edited_excerpt_ids, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserts newlines at the given position to create an empty line, returning the start of the new line.
|
// Inserts newlines at the given position to create an empty line, returning the start of the new line.
|
||||||
|
|
|
@ -9,9 +9,10 @@ use ui::ViewContext;
|
||||||
pub(crate) enum IndentDirection {
|
pub(crate) enum IndentDirection {
|
||||||
In,
|
In,
|
||||||
Out,
|
Out,
|
||||||
|
Auto,
|
||||||
}
|
}
|
||||||
|
|
||||||
actions!(vim, [Indent, Outdent,]);
|
actions!(vim, [Indent, Outdent, AutoIndent]);
|
||||||
|
|
||||||
pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||||
Vim::action(editor, cx, |vim, _: &Indent, cx| {
|
Vim::action(editor, cx, |vim, _: &Indent, cx| {
|
||||||
|
@ -49,6 +50,24 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||||
vim.switch_mode(Mode::Normal, true, cx)
|
vim.switch_mode(Mode::Normal, true, cx)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Vim::action(editor, cx, |vim, _: &AutoIndent, cx| {
|
||||||
|
vim.record_current_action(cx);
|
||||||
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
|
vim.store_visual_marks(cx);
|
||||||
|
vim.update_editor(cx, |vim, editor, cx| {
|
||||||
|
editor.transact(cx, |editor, cx| {
|
||||||
|
let original_positions = vim.save_selection_starts(editor, cx);
|
||||||
|
for _ in 0..count {
|
||||||
|
editor.autoindent(&Default::default(), cx);
|
||||||
|
}
|
||||||
|
vim.restore_selection_cursors(editor, cx, original_positions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if vim.mode.is_visual() {
|
||||||
|
vim.switch_mode(Mode::Normal, true, cx)
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vim {
|
impl Vim {
|
||||||
|
@ -71,10 +90,10 @@ impl Vim {
|
||||||
motion.expand_selection(map, selection, times, false, &text_layout_details);
|
motion.expand_selection(map, selection, times, false, &text_layout_details);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if dir == IndentDirection::In {
|
match dir {
|
||||||
editor.indent(&Default::default(), cx);
|
IndentDirection::In => editor.indent(&Default::default(), cx),
|
||||||
} else {
|
IndentDirection::Out => editor.outdent(&Default::default(), cx),
|
||||||
editor.outdent(&Default::default(), cx);
|
IndentDirection::Auto => editor.autoindent(&Default::default(), cx),
|
||||||
}
|
}
|
||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
|
@ -104,10 +123,10 @@ impl Vim {
|
||||||
object.expand_selection(map, selection, around);
|
object.expand_selection(map, selection, around);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if dir == IndentDirection::In {
|
match dir {
|
||||||
editor.indent(&Default::default(), cx);
|
IndentDirection::In => editor.indent(&Default::default(), cx),
|
||||||
} else {
|
IndentDirection::Out => editor.outdent(&Default::default(), cx),
|
||||||
editor.outdent(&Default::default(), cx);
|
IndentDirection::Auto => editor.autoindent(&Default::default(), cx),
|
||||||
}
|
}
|
||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
|
@ -122,7 +141,11 @@ impl Vim {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::test::NeovimBackedTestContext;
|
use crate::{
|
||||||
|
state::Mode,
|
||||||
|
test::{NeovimBackedTestContext, VimTestContext},
|
||||||
|
};
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_indent_gv(cx: &mut gpui::TestAppContext) {
|
async fn test_indent_gv(cx: &mut gpui::TestAppContext) {
|
||||||
|
@ -135,4 +158,46 @@ mod test {
|
||||||
.await
|
.await
|
||||||
.assert_eq("« hello\n ˇ» world\n");
|
.assert_eq("« hello\n ˇ» world\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_autoindent_op(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = VimTestContext::new(cx, true).await;
|
||||||
|
|
||||||
|
cx.set_state(
|
||||||
|
indoc!(
|
||||||
|
"
|
||||||
|
fn a() {
|
||||||
|
b();
|
||||||
|
c();
|
||||||
|
|
||||||
|
d();
|
||||||
|
ˇe();
|
||||||
|
f();
|
||||||
|
|
||||||
|
g();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
),
|
||||||
|
Mode::Normal,
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.simulate_keystrokes("= a p");
|
||||||
|
cx.assert_state(
|
||||||
|
indoc!(
|
||||||
|
"
|
||||||
|
fn a() {
|
||||||
|
b();
|
||||||
|
c();
|
||||||
|
|
||||||
|
d();
|
||||||
|
ˇe();
|
||||||
|
f();
|
||||||
|
|
||||||
|
g();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
),
|
||||||
|
Mode::Normal,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,6 +170,9 @@ impl Vim {
|
||||||
Some(Operator::Indent) => self.indent_motion(motion, times, IndentDirection::In, cx),
|
Some(Operator::Indent) => self.indent_motion(motion, times, IndentDirection::In, cx),
|
||||||
Some(Operator::Rewrap) => self.rewrap_motion(motion, times, cx),
|
Some(Operator::Rewrap) => self.rewrap_motion(motion, times, cx),
|
||||||
Some(Operator::Outdent) => self.indent_motion(motion, times, IndentDirection::Out, cx),
|
Some(Operator::Outdent) => self.indent_motion(motion, times, IndentDirection::Out, cx),
|
||||||
|
Some(Operator::AutoIndent) => {
|
||||||
|
self.indent_motion(motion, times, IndentDirection::Auto, cx)
|
||||||
|
}
|
||||||
Some(Operator::Lowercase) => {
|
Some(Operator::Lowercase) => {
|
||||||
self.change_case_motion(motion, times, CaseTarget::Lowercase, cx)
|
self.change_case_motion(motion, times, CaseTarget::Lowercase, cx)
|
||||||
}
|
}
|
||||||
|
@ -202,6 +205,9 @@ impl Vim {
|
||||||
Some(Operator::Outdent) => {
|
Some(Operator::Outdent) => {
|
||||||
self.indent_object(object, around, IndentDirection::Out, cx)
|
self.indent_object(object, around, IndentDirection::Out, cx)
|
||||||
}
|
}
|
||||||
|
Some(Operator::AutoIndent) => {
|
||||||
|
self.indent_object(object, around, IndentDirection::Auto, cx)
|
||||||
|
}
|
||||||
Some(Operator::Rewrap) => self.rewrap_object(object, around, cx),
|
Some(Operator::Rewrap) => self.rewrap_object(object, around, cx),
|
||||||
Some(Operator::Lowercase) => {
|
Some(Operator::Lowercase) => {
|
||||||
self.change_case_object(object, around, CaseTarget::Lowercase, cx)
|
self.change_case_object(object, around, CaseTarget::Lowercase, cx)
|
||||||
|
|
|
@ -72,6 +72,7 @@ pub enum Operator {
|
||||||
Jump { line: bool },
|
Jump { line: bool },
|
||||||
Indent,
|
Indent,
|
||||||
Outdent,
|
Outdent,
|
||||||
|
AutoIndent,
|
||||||
Rewrap,
|
Rewrap,
|
||||||
Lowercase,
|
Lowercase,
|
||||||
Uppercase,
|
Uppercase,
|
||||||
|
@ -465,6 +466,7 @@ impl Operator {
|
||||||
Operator::Jump { line: true } => "'",
|
Operator::Jump { line: true } => "'",
|
||||||
Operator::Jump { line: false } => "`",
|
Operator::Jump { line: false } => "`",
|
||||||
Operator::Indent => ">",
|
Operator::Indent => ">",
|
||||||
|
Operator::AutoIndent => "eq",
|
||||||
Operator::Rewrap => "gq",
|
Operator::Rewrap => "gq",
|
||||||
Operator::Outdent => "<",
|
Operator::Outdent => "<",
|
||||||
Operator::Uppercase => "gU",
|
Operator::Uppercase => "gU",
|
||||||
|
@ -510,6 +512,7 @@ impl Operator {
|
||||||
| Operator::Rewrap
|
| Operator::Rewrap
|
||||||
| Operator::Indent
|
| Operator::Indent
|
||||||
| Operator::Outdent
|
| Operator::Outdent
|
||||||
|
| Operator::AutoIndent
|
||||||
| Operator::Lowercase
|
| Operator::Lowercase
|
||||||
| Operator::Uppercase
|
| Operator::Uppercase
|
||||||
| Operator::Object { .. }
|
| Operator::Object { .. }
|
||||||
|
|
|
@ -470,6 +470,7 @@ impl Vim {
|
||||||
| Operator::Replace
|
| Operator::Replace
|
||||||
| Operator::Indent
|
| Operator::Indent
|
||||||
| Operator::Outdent
|
| Operator::Outdent
|
||||||
|
| Operator::AutoIndent
|
||||||
| Operator::Lowercase
|
| Operator::Lowercase
|
||||||
| Operator::Uppercase
|
| Operator::Uppercase
|
||||||
| Operator::OppositeCase
|
| Operator::OppositeCase
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue