Closes #ISSUE

Release Notes:

- vim: Added gq/gw for rewrapping lines
This commit is contained in:
Conrad Irwin 2024-09-20 13:02:39 -06:00 committed by GitHub
parent 7dac5594cd
commit 45388805ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 177 additions and 3 deletions

View file

@ -124,7 +124,6 @@
"g i": "vim::InsertAtPrevious",
"g ,": "vim::ChangeListNewer",
"g ;": "vim::ChangeListOlder",
"g q": "editor::Rewrap",
"shift-h": "vim::WindowTop",
"shift-m": "vim::WindowMiddle",
"shift-l": "vim::WindowBottom",
@ -240,6 +239,8 @@
"g shift-u": ["vim::PushOperator", "Uppercase"],
"g ~": ["vim::PushOperator", "OppositeCase"],
"\"": ["vim::PushOperator", "Register"],
"g q": ["vim::PushOperator", "Rewrap"],
"g w": ["vim::PushOperator", "Rewrap"],
"q": "vim::ToggleRecord",
"shift-q": "vim::ReplayLastRecording",
"@": ["vim::PushOperator", "ReplayRegister"],
@ -301,6 +302,7 @@
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"g c": "vim::ToggleComments",
"g q": "vim::Rewrap",
"\"": ["vim::PushOperator", "Register"],
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
@ -428,6 +430,15 @@
"~": "vim::CurrentLine"
}
},
{
"context": "vim_operator == gq",
"bindings": {
"g q": "vim::CurrentLine",
"q": "vim::CurrentLine",
"g w": "vim::CurrentLine",
"w": "vim::CurrentLine"
}
},
{
"context": "vim_operator == y",
"bindings": {

View file

@ -6705,6 +6705,10 @@ impl Editor {
}
pub fn rewrap(&mut self, _: &Rewrap, cx: &mut ViewContext<Self>) {
self.rewrap_impl(true, cx)
}
pub fn rewrap_impl(&mut self, only_text: bool, cx: &mut ViewContext<Self>) {
let buffer = self.buffer.read(cx).snapshot(cx);
let selections = self.selections.all::<Point>(cx);
let mut selections = selections.iter().peekable();
@ -6725,7 +6729,7 @@ impl Editor {
continue;
}
let mut should_rewrap = false;
let mut should_rewrap = !only_text;
if let Some(language_scope) = buffer.language_scope_at(selection.head()) {
match language_scope.language_name().0.as_ref() {

View file

@ -168,6 +168,7 @@ impl Vim {
Some(Operator::Yank) => self.yank_motion(motion, times, cx),
Some(Operator::AddSurrounds { target: None }) => {}
Some(Operator::Indent) => self.indent_motion(motion, times, IndentDirection::In, cx),
Some(Operator::Rewrap) => self.rewrap_motion(motion, times, cx),
Some(Operator::Outdent) => self.indent_motion(motion, times, IndentDirection::Out, cx),
Some(Operator::Lowercase) => {
self.change_case_motion(motion, times, CaseTarget::Lowercase, cx)
@ -199,6 +200,7 @@ impl Vim {
Some(Operator::Outdent) => {
self.indent_object(object, around, IndentDirection::Out, cx)
}
Some(Operator::Rewrap) => self.rewrap_object(object, around, cx),
Some(Operator::Lowercase) => {
self.change_case_object(object, around, CaseTarget::Lowercase, cx)
}
@ -478,8 +480,9 @@ impl Vim {
}
#[cfg(test)]
mod test {
use gpui::{KeyBinding, TestAppContext};
use gpui::{KeyBinding, TestAppContext, UpdateGlobal};
use indoc::indoc;
use language::language_settings::AllLanguageSettings;
use settings::SettingsStore;
use crate::{
@ -1386,4 +1389,29 @@ mod test {
cx.simulate_shared_keystrokes("2 0 r - ").await;
cx.shared_state().await.assert_eq("ˇhello world\n");
}
#[gpui::test]
async fn test_gq(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_neovim_option("textwidth=5").await;
cx.update(|cx| {
SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.defaults.preferred_line_length = Some(5);
});
})
});
cx.set_shared_state("ˇth th th th th th\n").await;
cx.simulate_shared_keystrokes("g q q").await;
cx.shared_state().await.assert_eq("th th\nth th\nˇth th\n");
cx.set_shared_state("ˇth th th th th th\nth th th th th th\n")
.await;
cx.simulate_shared_keystrokes("v j g q").await;
cx.shared_state()
.await
.assert_eq("th th\nth th\nth th\nth th\nth th\nˇth th\n");
}
}

114
crates/vim/src/rewrap.rs Normal file
View file

@ -0,0 +1,114 @@
use crate::{motion::Motion, object::Object, state::Mode, Vim};
use collections::HashMap;
use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Bias, Editor};
use gpui::actions;
use language::SelectionGoal;
use ui::ViewContext;
actions!(vim, [Rewrap]);
pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
Vim::action(editor, cx, |vim, _: &Rewrap, cx| {
vim.record_current_action(cx);
vim.take_count(cx);
vim.store_visual_marks(cx);
vim.update_editor(cx, |vim, editor, cx| {
editor.transact(cx, |editor, cx| {
let mut positions = vim.save_selection_starts(editor, cx);
editor.rewrap_impl(false, cx);
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|map, selection| {
if let Some(anchor) = positions.remove(&selection.id) {
let mut point = anchor.to_display_point(map);
*point.column_mut() = 0;
selection.collapse_to(point, SelectionGoal::None);
}
});
});
});
});
if vim.mode.is_visual() {
vim.switch_mode(Mode::Normal, true, cx)
}
});
}
impl Vim {
pub(crate) fn rewrap_motion(
&mut self,
motion: Motion,
times: Option<usize>,
cx: &mut ViewContext<Self>,
) {
self.stop_recording(cx);
self.update_editor(cx, |_, editor, cx| {
let text_layout_details = editor.text_layout_details(cx);
editor.transact(cx, |editor, cx| {
let mut selection_starts: HashMap<_, _> = Default::default();
editor.change_selections(None, cx, |s| {
s.move_with(|map, selection| {
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
selection_starts.insert(selection.id, anchor);
motion.expand_selection(map, selection, times, false, &text_layout_details);
});
});
editor.rewrap_impl(false, cx);
editor.change_selections(None, cx, |s| {
s.move_with(|map, selection| {
let anchor = selection_starts.remove(&selection.id).unwrap();
let mut point = anchor.to_display_point(map);
*point.column_mut() = 0;
selection.collapse_to(point, SelectionGoal::None);
});
});
});
});
}
pub(crate) fn rewrap_object(
&mut self,
object: Object,
around: bool,
cx: &mut ViewContext<Self>,
) {
self.stop_recording(cx);
self.update_editor(cx, |_, editor, cx| {
editor.transact(cx, |editor, cx| {
let mut original_positions: HashMap<_, _> = Default::default();
editor.change_selections(None, cx, |s| {
s.move_with(|map, selection| {
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
original_positions.insert(selection.id, anchor);
object.expand_selection(map, selection, around);
});
});
editor.rewrap_impl(false, cx);
editor.change_selections(None, cx, |s| {
s.move_with(|map, selection| {
let anchor = original_positions.remove(&selection.id).unwrap();
let mut point = anchor.to_display_point(map);
*point.column_mut() = 0;
selection.collapse_to(point, SelectionGoal::None);
});
});
});
});
}
}
#[cfg(test)]
mod test {
use crate::test::NeovimBackedTestContext;
#[gpui::test]
async fn test_indent_gv(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_neovim_option("shiftwidth=4").await;
cx.set_shared_state("ˇhello\nworld\n").await;
cx.simulate_shared_keystrokes("v j > g v").await;
cx.shared_state()
.await
.assert_eq("« hello\n ˇ» world\n");
}
}

View file

@ -72,6 +72,7 @@ pub enum Operator {
Jump { line: bool },
Indent,
Outdent,
Rewrap,
Lowercase,
Uppercase,
OppositeCase,
@ -454,6 +455,7 @@ impl Operator {
Operator::Jump { line: true } => "'",
Operator::Jump { line: false } => "`",
Operator::Indent => ">",
Operator::Rewrap => "gq",
Operator::Outdent => "<",
Operator::Uppercase => "gU",
Operator::Lowercase => "gu",
@ -482,6 +484,7 @@ impl Operator {
Operator::Change
| Operator::Delete
| Operator::Yank
| Operator::Rewrap
| Operator::Indent
| Operator::Outdent
| Operator::Lowercase

View file

@ -13,6 +13,7 @@ mod motion;
mod normal;
mod object;
mod replace;
mod rewrap;
mod state;
mod surrounds;
mod visual;
@ -291,6 +292,7 @@ impl Vim {
command::register(editor, cx);
replace::register(editor, cx);
indent::register(editor, cx);
rewrap::register(editor, cx);
object::register(editor, cx);
visual::register(editor, cx);
change_list::register(editor, cx);

View file

@ -0,0 +1,12 @@
{"SetOption":{"value":"textwidth=5"}}
{"Put":{"state":"ˇth th th th th th\n"}}
{"Key":"g"}
{"Key":"q"}
{"Key":"q"}
{"Get":{"state":"th th\nth th\nˇth th\n","mode":"Normal"}}
{"Put":{"state":"ˇth th th th th th\nth th th th th th\n"}}
{"Key":"v"}
{"Key":"j"}
{"Key":"g"}
{"Key":"q"}
{"Get":{"state":"th th\nth th\nth th\nth th\nth th\nˇth th\n","mode":"Normal"}}