Skip to content

Commit 022d021

Browse files
committed
More accurate vi(1) word navigation in copy mode and on the status line. This
changes the meaning of the word-separators option - setting it to the empty string is equivalent to the previous behavior. From Will Noble in GitHub issue 2693.
1 parent f03b27c commit 022d021

File tree

11 files changed

+565
-177
lines changed

11 files changed

+565
-177
lines changed

CHANGES

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
CHANGES FROM 3.2 TO 3.3
22

3-
XXX
3+
* More accurate vi(1) word navigation in copy mode and on the status line. This
4+
changes the meaning of the word-separators option - setting it to the empty
5+
string is equivalent to the previous behavior.
46

57
CHANGES FROM 3.1c TO 3.2
68

grid-reader.c

Lines changed: 98 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,29 @@ grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all)
153153
gr->cx = grid_reader_line_length(gr);
154154
}
155155

156+
/* Handle line wrapping while moving the cursor. */
157+
static int
158+
grid_reader_handle_wrap(struct grid_reader *gr, u_int *xx, u_int *yy)
159+
{
160+
/*
161+
* Make sure the cursor lies within the grid reader's bounding area,
162+
* wrapping to the next line as necessary. Return zero if the cursor
163+
* would wrap past the bottom of the grid.
164+
*/
165+
while (gr->cx > *xx) {
166+
if (gr->cy == *yy)
167+
return (0);
168+
grid_reader_cursor_start_of_line(gr, 0);
169+
grid_reader_cursor_down(gr);
170+
171+
if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
172+
*xx = gr->gd->sx - 1;
173+
else
174+
*xx = grid_reader_line_length(gr);
175+
}
176+
return (1);
177+
}
178+
156179
/* Check if character under cursor is in set. */
157180
int
158181
grid_reader_in_set(struct grid_reader *gr, const char *set)
@@ -170,7 +193,6 @@ void
170193
grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators)
171194
{
172195
u_int xx, yy;
173-
int expected = 0;
174196

175197
/* Do not break up wrapped words. */
176198
if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
@@ -180,41 +202,42 @@ grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators)
180202
yy = gr->gd->hsize + gr->gd->sy - 1;
181203

182204
/*
183-
* If we started inside a word, skip over word characters. Then skip
184-
* over separators till the next word.
205+
* When navigating via spaces (for example with next-space) separators
206+
* should be empty.
185207
*
186-
* expected is initially set to 0 for the former and then 1 for the
187-
* latter. It is finally set to 0 when the beginning of the next word is
188-
* found.
208+
* If we started on a separator that is not whitespace, skip over
209+
* subsequent separators that are not whitespace. Otherwise, if we
210+
* started on a non-whitespace character, skip over subsequent
211+
* characters that are neither whitespace nor separators. Then, skip
212+
* over whitespace (if any) until the next non-whitespace character.
189213
*/
190-
do {
191-
while (gr->cx > xx ||
192-
grid_reader_in_set(gr, separators) == expected) {
193-
/* Move down if we are past the end of the line. */
194-
if (gr->cx > xx) {
195-
if (gr->cy == yy)
196-
return;
197-
grid_reader_cursor_start_of_line(gr, 0);
198-
grid_reader_cursor_down(gr);
199-
200-
if (grid_get_line(gr->gd, gr->cy)->flags &
201-
GRID_LINE_WRAPPED)
202-
xx = gr->gd->sx - 1;
203-
else
204-
xx = grid_reader_line_length(gr);
205-
} else
214+
if (!grid_reader_handle_wrap(gr, &xx, &yy))
215+
return;
216+
if (!grid_reader_in_set(gr, WHITESPACE)) {
217+
if (grid_reader_in_set(gr, separators)) {
218+
do
206219
gr->cx++;
220+
while (grid_reader_handle_wrap(gr, &xx, &yy) &&
221+
grid_reader_in_set(gr, separators) &&
222+
!grid_reader_in_set(gr, WHITESPACE));
223+
} else {
224+
do
225+
gr->cx++;
226+
while (grid_reader_handle_wrap(gr, &xx, &yy) &&
227+
!(grid_reader_in_set(gr, separators) ||
228+
grid_reader_in_set(gr, WHITESPACE)));
207229
}
208-
expected = !expected;
209-
} while (expected == 1);
230+
}
231+
while (grid_reader_handle_wrap(gr, &xx, &yy) &&
232+
grid_reader_in_set(gr, WHITESPACE))
233+
gr->cx++;
210234
}
211235

212236
/* Move cursor to the end of the next word. */
213237
void
214238
grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators)
215239
{
216240
u_int xx, yy;
217-
int expected = 1;
218241

219242
/* Do not break up wrapped words. */
220243
if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
@@ -224,83 +247,93 @@ grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators)
224247
yy = gr->gd->hsize + gr->gd->sy - 1;
225248

226249
/*
227-
* If we started on a separator, skip over separators. Then skip over
228-
* word characters till the next separator.
250+
* When navigating via spaces (for example with next-space), separators
251+
* should be empty in both modes.
229252
*
230-
* expected is initially set to 1 for the former and then 1 for the
231-
* latter. It is finally set to 1 when the end of the next word is
232-
* found.
253+
* If we started on a whitespace, move until reaching the first
254+
* non-whitespace character. If that character is a separator, treat
255+
* subsequent separators as a word, and continue moving until the first
256+
* non-separator. Otherwise, continue moving until the first separator
257+
* or whitespace.
233258
*/
234-
do {
235-
while (gr->cx > xx ||
236-
grid_reader_in_set(gr, separators) == expected) {
237-
/* Move down if we are past the end of the line. */
238-
if (gr->cx > xx) {
239-
if (gr->cy == yy)
240-
return;
241-
grid_reader_cursor_start_of_line(gr, 0);
242-
grid_reader_cursor_down(gr);
243-
244-
if (grid_get_line(gr->gd, gr->cy)->flags &
245-
GRID_LINE_WRAPPED)
246-
xx = gr->gd->sx - 1;
247-
else
248-
xx = grid_reader_line_length(gr);
249-
} else
259+
260+
while (grid_reader_handle_wrap(gr, &xx, &yy)) {
261+
if (grid_reader_in_set(gr, WHITESPACE))
262+
gr->cx++;
263+
else if (grid_reader_in_set(gr, separators)) {
264+
do
265+
gr->cx++;
266+
while (grid_reader_handle_wrap(gr, &xx, &yy) &&
267+
grid_reader_in_set(gr, separators) &&
268+
!grid_reader_in_set(gr, WHITESPACE));
269+
return;
270+
} else {
271+
do
250272
gr->cx++;
273+
while (grid_reader_handle_wrap(gr, &xx, &yy) &&
274+
!(grid_reader_in_set(gr, WHITESPACE) ||
275+
grid_reader_in_set(gr, separators)));
276+
return;
251277
}
252-
expected = !expected;
253-
} while (expected == 0);
278+
}
254279
}
255280

256281
/* Move to the previous place where a word begins. */
257282
void
258283
grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators,
259-
int already)
284+
int already, int stop_at_eol)
260285
{
261-
int oldx, oldy, r;
286+
int oldx, oldy, at_eol, word_is_letters;
262287

263288
/* Move back to the previous word character. */
264-
if (already || grid_reader_in_set(gr, separators)) {
289+
if (already || grid_reader_in_set(gr, WHITESPACE)) {
265290
for (;;) {
266291
if (gr->cx > 0) {
267292
gr->cx--;
268-
if (!grid_reader_in_set(gr, separators))
293+
if (!grid_reader_in_set(gr, WHITESPACE)) {
294+
word_is_letters =
295+
!grid_reader_in_set(gr, separators);
269296
break;
297+
}
270298
} else {
271299
if (gr->cy == 0)
272300
return;
273301
grid_reader_cursor_up(gr);
274302
grid_reader_cursor_end_of_line(gr, 0, 0);
275303

276304
/* Stop if separator at EOL. */
277-
if (gr->cx > 0) {
305+
if (stop_at_eol && gr->cx > 0) {
278306
oldx = gr->cx;
279307
gr->cx--;
280-
r = grid_reader_in_set(gr, separators);
308+
at_eol = grid_reader_in_set(gr,
309+
WHITESPACE);
281310
gr->cx = oldx;
282-
if (r)
311+
if (at_eol) {
312+
word_is_letters = 0;
283313
break;
314+
}
284315
}
285316
}
286317
}
287-
}
318+
} else
319+
word_is_letters = !grid_reader_in_set(gr, separators);
288320

289321
/* Move back to the beginning of this word. */
290322
do {
291323
oldx = gr->cx;
292324
oldy = gr->cy;
293325
if (gr->cx == 0) {
294326
if (gr->cy == 0 ||
295-
~grid_get_line(gr->gd, gr->cy - 1)->flags &
296-
GRID_LINE_WRAPPED)
327+
(~grid_get_line(gr->gd, gr->cy - 1)->flags &
328+
GRID_LINE_WRAPPED))
297329
break;
298330
grid_reader_cursor_up(gr);
299331
grid_reader_cursor_end_of_line(gr, 0, 1);
300332
}
301333
if (gr->cx > 0)
302334
gr->cx--;
303-
} while (!grid_reader_in_set(gr, separators));
335+
} while (!grid_reader_in_set(gr, WHITESPACE) &&
336+
word_is_letters != grid_reader_in_set(gr, separators));
304337
gr->cx = oldx;
305338
gr->cy = oldy;
306339
}
@@ -324,17 +357,17 @@ grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc)
324357
memcmp(gc.data.data, jc->data, gc.data.size) == 0) {
325358
gr->cx = px;
326359
gr->cy = py;
327-
return 1;
360+
return (1);
328361
}
329362
px++;
330363
}
331364

332365
if (py == yy ||
333366
!(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED))
334-
return 0;
367+
return (0);
335368
px = 0;
336369
}
337-
return 0;
370+
return (0);
338371
}
339372

340373
/* Jump back to character. */
@@ -354,16 +387,16 @@ grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc)
354387
memcmp(gc.data.data, jc->data, gc.data.size) == 0) {
355388
gr->cx = px - 1;
356389
gr->cy = py - 1;
357-
return 1;
390+
return (1);
358391
}
359392
}
360393

361394
if (py == 1 ||
362395
!(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED))
363-
return 0;
396+
return (0);
364397
xx = grid_line_length(gr->gd, py - 2);
365398
}
366-
return 0;
399+
return (0);
367400
}
368401

369402
/* Jump back to the first non-blank character of the line. */

options-table.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,11 @@ const struct options_table_entry options_table[] = {
754754
{ .name = "word-separators",
755755
.type = OPTIONS_TABLE_STRING,
756756
.scope = OPTIONS_TABLE_SESSION,
757-
.default_str = " ",
757+
/*
758+
* The set of non-alphanumeric printable ASCII characters minus the
759+
* underscore.
760+
*/
761+
.default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~",
758762
.text = "Characters considered to separate words."
759763
},
760764

0 commit comments

Comments
 (0)