Skip to content

Commit 4538880

Browse files
authored
vim: gq (#18156)
Closes #ISSUE Release Notes: - vim: Added gq/gw for rewrapping lines
1 parent 7dac559 commit 4538880

File tree

7 files changed

+177
-3
lines changed

7 files changed

+177
-3
lines changed

assets/keymaps/vim.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@
124124
"g i": "vim::InsertAtPrevious",
125125
"g ,": "vim::ChangeListNewer",
126126
"g ;": "vim::ChangeListOlder",
127-
"g q": "editor::Rewrap",
128127
"shift-h": "vim::WindowTop",
129128
"shift-m": "vim::WindowMiddle",
130129
"shift-l": "vim::WindowBottom",
@@ -240,6 +239,8 @@
240239
"g shift-u": ["vim::PushOperator", "Uppercase"],
241240
"g ~": ["vim::PushOperator", "OppositeCase"],
242241
"\"": ["vim::PushOperator", "Register"],
242+
"g q": ["vim::PushOperator", "Rewrap"],
243+
"g w": ["vim::PushOperator", "Rewrap"],
243244
"q": "vim::ToggleRecord",
244245
"shift-q": "vim::ReplayLastRecording",
245246
"@": ["vim::PushOperator", "ReplayRegister"],
@@ -301,6 +302,7 @@
301302
"i": ["vim::PushOperator", { "Object": { "around": false } }],
302303
"a": ["vim::PushOperator", { "Object": { "around": true } }],
303304
"g c": "vim::ToggleComments",
305+
"g q": "vim::Rewrap",
304306
"\"": ["vim::PushOperator", "Register"],
305307
// tree-sitter related commands
306308
"[ x": "editor::SelectLargerSyntaxNode",
@@ -428,6 +430,15 @@
428430
"~": "vim::CurrentLine"
429431
}
430432
},
433+
{
434+
"context": "vim_operator == gq",
435+
"bindings": {
436+
"g q": "vim::CurrentLine",
437+
"q": "vim::CurrentLine",
438+
"g w": "vim::CurrentLine",
439+
"w": "vim::CurrentLine"
440+
}
441+
},
431442
{
432443
"context": "vim_operator == y",
433444
"bindings": {

crates/editor/src/editor.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6705,6 +6705,10 @@ impl Editor {
67056705
}
67066706

67076707
pub fn rewrap(&mut self, _: &Rewrap, cx: &mut ViewContext<Self>) {
6708+
self.rewrap_impl(true, cx)
6709+
}
6710+
6711+
pub fn rewrap_impl(&mut self, only_text: bool, cx: &mut ViewContext<Self>) {
67086712
let buffer = self.buffer.read(cx).snapshot(cx);
67096713
let selections = self.selections.all::<Point>(cx);
67106714
let mut selections = selections.iter().peekable();
@@ -6725,7 +6729,7 @@ impl Editor {
67256729
continue;
67266730
}
67276731

6728-
let mut should_rewrap = false;
6732+
let mut should_rewrap = !only_text;
67296733

67306734
if let Some(language_scope) = buffer.language_scope_at(selection.head()) {
67316735
match language_scope.language_name().0.as_ref() {

crates/vim/src/normal.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ impl Vim {
168168
Some(Operator::Yank) => self.yank_motion(motion, times, cx),
169169
Some(Operator::AddSurrounds { target: None }) => {}
170170
Some(Operator::Indent) => self.indent_motion(motion, times, IndentDirection::In, cx),
171+
Some(Operator::Rewrap) => self.rewrap_motion(motion, times, cx),
171172
Some(Operator::Outdent) => self.indent_motion(motion, times, IndentDirection::Out, cx),
172173
Some(Operator::Lowercase) => {
173174
self.change_case_motion(motion, times, CaseTarget::Lowercase, cx)
@@ -199,6 +200,7 @@ impl Vim {
199200
Some(Operator::Outdent) => {
200201
self.indent_object(object, around, IndentDirection::Out, cx)
201202
}
203+
Some(Operator::Rewrap) => self.rewrap_object(object, around, cx),
202204
Some(Operator::Lowercase) => {
203205
self.change_case_object(object, around, CaseTarget::Lowercase, cx)
204206
}
@@ -478,8 +480,9 @@ impl Vim {
478480
}
479481
#[cfg(test)]
480482
mod test {
481-
use gpui::{KeyBinding, TestAppContext};
483+
use gpui::{KeyBinding, TestAppContext, UpdateGlobal};
482484
use indoc::indoc;
485+
use language::language_settings::AllLanguageSettings;
483486
use settings::SettingsStore;
484487

485488
use crate::{
@@ -1386,4 +1389,29 @@ mod test {
13861389
cx.simulate_shared_keystrokes("2 0 r - ").await;
13871390
cx.shared_state().await.assert_eq("ˇhello world\n");
13881391
}
1392+
1393+
#[gpui::test]
1394+
async fn test_gq(cx: &mut gpui::TestAppContext) {
1395+
let mut cx = NeovimBackedTestContext::new(cx).await;
1396+
cx.set_neovim_option("textwidth=5").await;
1397+
1398+
cx.update(|cx| {
1399+
SettingsStore::update_global(cx, |settings, cx| {
1400+
settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1401+
settings.defaults.preferred_line_length = Some(5);
1402+
});
1403+
})
1404+
});
1405+
1406+
cx.set_shared_state("ˇth th th th th th\n").await;
1407+
cx.simulate_shared_keystrokes("g q q").await;
1408+
cx.shared_state().await.assert_eq("th th\nth th\nˇth th\n");
1409+
1410+
cx.set_shared_state("ˇth th th th th th\nth th th th th th\n")
1411+
.await;
1412+
cx.simulate_shared_keystrokes("v j g q").await;
1413+
cx.shared_state()
1414+
.await
1415+
.assert_eq("th th\nth th\nth th\nth th\nth th\nˇth th\n");
1416+
}
13891417
}

crates/vim/src/rewrap.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use crate::{motion::Motion, object::Object, state::Mode, Vim};
2+
use collections::HashMap;
3+
use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Bias, Editor};
4+
use gpui::actions;
5+
use language::SelectionGoal;
6+
use ui::ViewContext;
7+
8+
actions!(vim, [Rewrap]);
9+
10+
pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
11+
Vim::action(editor, cx, |vim, _: &Rewrap, cx| {
12+
vim.record_current_action(cx);
13+
vim.take_count(cx);
14+
vim.store_visual_marks(cx);
15+
vim.update_editor(cx, |vim, editor, cx| {
16+
editor.transact(cx, |editor, cx| {
17+
let mut positions = vim.save_selection_starts(editor, cx);
18+
editor.rewrap_impl(false, cx);
19+
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
20+
s.move_with(|map, selection| {
21+
if let Some(anchor) = positions.remove(&selection.id) {
22+
let mut point = anchor.to_display_point(map);
23+
*point.column_mut() = 0;
24+
selection.collapse_to(point, SelectionGoal::None);
25+
}
26+
});
27+
});
28+
});
29+
});
30+
if vim.mode.is_visual() {
31+
vim.switch_mode(Mode::Normal, true, cx)
32+
}
33+
});
34+
}
35+
36+
impl Vim {
37+
pub(crate) fn rewrap_motion(
38+
&mut self,
39+
motion: Motion,
40+
times: Option<usize>,
41+
cx: &mut ViewContext<Self>,
42+
) {
43+
self.stop_recording(cx);
44+
self.update_editor(cx, |_, editor, cx| {
45+
let text_layout_details = editor.text_layout_details(cx);
46+
editor.transact(cx, |editor, cx| {
47+
let mut selection_starts: HashMap<_, _> = Default::default();
48+
editor.change_selections(None, cx, |s| {
49+
s.move_with(|map, selection| {
50+
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
51+
selection_starts.insert(selection.id, anchor);
52+
motion.expand_selection(map, selection, times, false, &text_layout_details);
53+
});
54+
});
55+
editor.rewrap_impl(false, cx);
56+
editor.change_selections(None, cx, |s| {
57+
s.move_with(|map, selection| {
58+
let anchor = selection_starts.remove(&selection.id).unwrap();
59+
let mut point = anchor.to_display_point(map);
60+
*point.column_mut() = 0;
61+
selection.collapse_to(point, SelectionGoal::None);
62+
});
63+
});
64+
});
65+
});
66+
}
67+
68+
pub(crate) fn rewrap_object(
69+
&mut self,
70+
object: Object,
71+
around: bool,
72+
cx: &mut ViewContext<Self>,
73+
) {
74+
self.stop_recording(cx);
75+
self.update_editor(cx, |_, editor, cx| {
76+
editor.transact(cx, |editor, cx| {
77+
let mut original_positions: HashMap<_, _> = Default::default();
78+
editor.change_selections(None, cx, |s| {
79+
s.move_with(|map, selection| {
80+
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
81+
original_positions.insert(selection.id, anchor);
82+
object.expand_selection(map, selection, around);
83+
});
84+
});
85+
editor.rewrap_impl(false, cx);
86+
editor.change_selections(None, cx, |s| {
87+
s.move_with(|map, selection| {
88+
let anchor = original_positions.remove(&selection.id).unwrap();
89+
let mut point = anchor.to_display_point(map);
90+
*point.column_mut() = 0;
91+
selection.collapse_to(point, SelectionGoal::None);
92+
});
93+
});
94+
});
95+
});
96+
}
97+
}
98+
99+
#[cfg(test)]
100+
mod test {
101+
use crate::test::NeovimBackedTestContext;
102+
103+
#[gpui::test]
104+
async fn test_indent_gv(cx: &mut gpui::TestAppContext) {
105+
let mut cx = NeovimBackedTestContext::new(cx).await;
106+
cx.set_neovim_option("shiftwidth=4").await;
107+
108+
cx.set_shared_state("ˇhello\nworld\n").await;
109+
cx.simulate_shared_keystrokes("v j > g v").await;
110+
cx.shared_state()
111+
.await
112+
.assert_eq("« hello\n ˇ» world\n");
113+
}
114+
}

crates/vim/src/state.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pub enum Operator {
7272
Jump { line: bool },
7373
Indent,
7474
Outdent,
75+
Rewrap,
7576
Lowercase,
7677
Uppercase,
7778
OppositeCase,
@@ -454,6 +455,7 @@ impl Operator {
454455
Operator::Jump { line: true } => "'",
455456
Operator::Jump { line: false } => "`",
456457
Operator::Indent => ">",
458+
Operator::Rewrap => "gq",
457459
Operator::Outdent => "<",
458460
Operator::Uppercase => "gU",
459461
Operator::Lowercase => "gu",
@@ -482,6 +484,7 @@ impl Operator {
482484
Operator::Change
483485
| Operator::Delete
484486
| Operator::Yank
487+
| Operator::Rewrap
485488
| Operator::Indent
486489
| Operator::Outdent
487490
| Operator::Lowercase

crates/vim/src/vim.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod motion;
1313
mod normal;
1414
mod object;
1515
mod replace;
16+
mod rewrap;
1617
mod state;
1718
mod surrounds;
1819
mod visual;
@@ -291,6 +292,7 @@ impl Vim {
291292
command::register(editor, cx);
292293
replace::register(editor, cx);
293294
indent::register(editor, cx);
295+
rewrap::register(editor, cx);
294296
object::register(editor, cx);
295297
visual::register(editor, cx);
296298
change_list::register(editor, cx);

crates/vim/test_data/test_gq.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{"SetOption":{"value":"textwidth=5"}}
2+
{"Put":{"state":"ˇth th th th th th\n"}}
3+
{"Key":"g"}
4+
{"Key":"q"}
5+
{"Key":"q"}
6+
{"Get":{"state":"th th\nth th\nˇth th\n","mode":"Normal"}}
7+
{"Put":{"state":"ˇth th th th th th\nth th th th th th\n"}}
8+
{"Key":"v"}
9+
{"Key":"j"}
10+
{"Key":"g"}
11+
{"Key":"q"}
12+
{"Get":{"state":"th th\nth th\nth th\nth th\nth th\nˇth th\n","mode":"Normal"}}

0 commit comments

Comments
 (0)