Coverage Report

Created: 2026-08-13 06:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/grid-reader.c
Line
Count
Source
1
/* $OpenBSD: grid-reader.c,v 1.11 2026/07/29 17:42:56 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2020 Anindya Mukherjee <anindya49@hotmail.com>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include "tmux.h"
20
#include <string.h>
21
22
/* Initialise virtual cursor. */
23
void
24
grid_reader_start(struct grid_reader *gr, struct grid *gd, u_int cx, u_int cy)
25
0
{
26
0
  gr->gd = gd;
27
0
  gr->cx = cx;
28
0
  gr->cy = cy;
29
0
}
30
31
/* Get cursor position from reader. */
32
void
33
grid_reader_get_cursor(struct grid_reader *gr, u_int *cx, u_int *cy)
34
0
{
35
0
  *cx = gr->cx;
36
0
  *cy = gr->cy;
37
0
}
38
39
/* Get length of line containing the cursor. */
40
u_int
41
grid_reader_line_length(struct grid_reader *gr)
42
0
{
43
0
  return (grid_line_length(gr->gd, gr->cy));
44
0
}
45
46
/* Move cursor forward one position. */
47
void
48
grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all, int onemore)
49
0
{
50
0
  u_int     px;
51
0
  struct grid_cell  gc;
52
53
0
  if (all)
54
0
    px = gr->gd->sx;
55
0
  else if (onemore)
56
0
    px = grid_reader_line_length(gr);
57
0
  else
58
0
    px = grid_line_limit(gr->gd, gr->cy);
59
60
0
  if (wrap && gr->cx >= px && gr->cy < gr->gd->hsize + gr->gd->sy - 1) {
61
0
    grid_reader_cursor_start_of_line(gr, 0);
62
0
    grid_reader_cursor_down(gr);
63
0
  } else if (gr->cx < px) {
64
0
    gr->cx++;
65
0
    while (gr->cx < px) {
66
0
      grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
67
0
      if (~gc.flags & GRID_FLAG_PADDING)
68
0
        break;
69
0
      gr->cx++;
70
0
    }
71
0
  }
72
0
}
73
74
/* Move cursor back one position. */
75
void
76
grid_reader_cursor_left(struct grid_reader *gr, int wrap)
77
0
{
78
0
  struct grid_cell  gc;
79
80
0
  while (gr->cx > 0) {
81
0
    grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
82
0
    if (~gc.flags & GRID_FLAG_PADDING)
83
0
      break;
84
0
    gr->cx--;
85
0
  }
86
0
  if (gr->cx == 0 && gr->cy > 0 &&
87
0
      (wrap ||
88
0
       grid_get_line(gr->gd, gr->cy - 1)->flags & GRID_LINE_WRAPPED)) {
89
0
    grid_reader_cursor_up(gr);
90
0
    grid_reader_cursor_end_of_line(gr, 0, 0);
91
0
  } else if (gr->cx > 0)
92
0
    gr->cx--;
93
0
}
94
95
/* Move cursor down one line. */
96
void
97
grid_reader_cursor_down(struct grid_reader *gr)
98
0
{
99
0
  struct grid_cell  gc;
100
101
0
  if (gr->cy < gr->gd->hsize + gr->gd->sy - 1)
102
0
    gr->cy++;
103
0
  while (gr->cx > 0) {
104
0
    grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
105
0
    if (~gc.flags & GRID_FLAG_PADDING)
106
0
      break;
107
0
    gr->cx--;
108
0
  }
109
0
}
110
111
/* Move cursor up one line. */
112
void
113
grid_reader_cursor_up(struct grid_reader *gr)
114
0
{
115
0
  struct grid_cell  gc;
116
117
0
  if (gr->cy > 0)
118
0
    gr->cy--;
119
0
  while (gr->cx > 0) {
120
0
    grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
121
0
    if (~gc.flags & GRID_FLAG_PADDING)
122
0
      break;
123
0
    gr->cx--;
124
0
  }
125
0
}
126
127
/* Move cursor to the start of the line. */
128
void
129
grid_reader_cursor_start_of_line(struct grid_reader *gr, int wrap)
130
0
{
131
0
  if (wrap) {
132
0
    while (gr->cy > 0 &&
133
0
        grid_get_line(gr->gd, gr->cy - 1)->flags &
134
0
            GRID_LINE_WRAPPED)
135
0
      gr->cy--;
136
0
  }
137
0
  gr->cx = 0;
138
0
}
139
140
/* Move cursor to the end of the line. */
141
void
142
grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all)
143
0
{
144
0
  u_int yy;
145
146
0
  if (wrap) {
147
0
    yy = gr->gd->hsize + gr->gd->sy - 1;
148
0
    while (gr->cy < yy && grid_get_line(gr->gd, gr->cy)->flags &
149
0
        GRID_LINE_WRAPPED)
150
0
      gr->cy++;
151
0
  }
152
0
  if (all)
153
0
    gr->cx = gr->gd->sx;
154
0
  else
155
0
    gr->cx = grid_reader_line_length(gr);
156
0
}
157
158
/* Handle line wrapping while moving the cursor. */
159
static int
160
grid_reader_handle_wrap(struct grid_reader *gr, u_int *xx, u_int *yy)
161
0
{
162
  /*
163
   * Make sure the cursor lies within the grid reader's bounding area,
164
   * wrapping to the next line as necessary. Return zero if the cursor
165
   * would wrap past the bottom of the grid.
166
   */
167
0
  while (gr->cx > *xx) {
168
0
    if (gr->cy == *yy)
169
0
      return (0);
170
0
    grid_reader_cursor_start_of_line(gr, 0);
171
0
    grid_reader_cursor_down(gr);
172
173
0
    if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
174
0
      *xx = gr->gd->sx - 1;
175
0
    else
176
0
      *xx = grid_reader_line_length(gr);
177
0
  }
178
0
  return (1);
179
0
}
180
181
/* Check if character under cursor is in set. */
182
int
183
grid_reader_in_set(struct grid_reader *gr, const char *set)
184
0
{
185
0
  return (grid_in_set(gr->gd, gr->cx, gr->cy, set));
186
0
}
187
188
/* Move cursor to the start of the next word. */
189
void
190
grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators)
191
0
{
192
0
  u_int xx, yy, width;
193
194
  /* Do not break up wrapped words. */
195
0
  if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
196
0
    xx = gr->gd->sx - 1;
197
0
  else
198
0
    xx = grid_reader_line_length(gr);
199
0
  yy = gr->gd->hsize + gr->gd->sy - 1;
200
201
  /*
202
   * When navigating via spaces (for example with next-space) separators
203
   * should be empty.
204
   *
205
   * If we started on a separator that is not whitespace, skip over
206
   * subsequent separators that are not whitespace. Otherwise, if we
207
   * started on a non-whitespace character, skip over subsequent
208
   * characters that are neither whitespace nor separators. Then, skip
209
   * over whitespace (if any) until the next non-whitespace character.
210
   */
211
0
  if (!grid_reader_handle_wrap(gr, &xx, &yy))
212
0
    return;
213
0
  if (!grid_reader_in_set(gr, WHITESPACE)) {
214
0
    if (grid_reader_in_set(gr, separators)) {
215
0
      do
216
0
        gr->cx++;
217
0
      while (grid_reader_handle_wrap(gr, &xx, &yy) &&
218
0
          grid_reader_in_set(gr, separators) &&
219
0
          !grid_reader_in_set(gr, WHITESPACE));
220
0
    } else {
221
0
      do
222
0
        gr->cx++;
223
0
      while (grid_reader_handle_wrap(gr, &xx, &yy) &&
224
0
          !(grid_reader_in_set(gr, separators) ||
225
0
          grid_reader_in_set(gr, WHITESPACE)));
226
0
    }
227
0
  }
228
0
  while (grid_reader_handle_wrap(gr, &xx, &yy) &&
229
0
      (width = grid_reader_in_set(gr, WHITESPACE)))
230
0
    gr->cx += width;
231
0
}
232
233
/* Move cursor to the end of the next word. */
234
void
235
grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators)
236
0
{
237
0
  u_int xx, yy;
238
239
  /* Do not break up wrapped words. */
240
0
  if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
241
0
    xx = gr->gd->sx - 1;
242
0
  else
243
0
    xx = grid_reader_line_length(gr);
244
0
  yy = gr->gd->hsize + gr->gd->sy - 1;
245
246
  /*
247
   * When navigating via spaces (for example with next-space), separators
248
   * should be empty in both modes.
249
   *
250
   * If we started on a whitespace, move until reaching the first
251
   * non-whitespace character. If that character is a separator, treat
252
   * subsequent separators as a word, and continue moving until the first
253
   * non-separator. Otherwise, continue moving until the first separator
254
   * or whitespace.
255
   */
256
257
0
  while (grid_reader_handle_wrap(gr, &xx, &yy)) {
258
0
    if (grid_reader_in_set(gr, WHITESPACE))
259
0
      gr->cx++;
260
0
    else if (grid_reader_in_set(gr, separators)) {
261
0
      do
262
0
        gr->cx++;
263
0
      while (grid_reader_handle_wrap(gr, &xx, &yy) &&
264
0
          grid_reader_in_set(gr, separators) &&
265
0
          !grid_reader_in_set(gr, WHITESPACE));
266
0
      return;
267
0
    } else {
268
0
      do
269
0
        gr->cx++;
270
0
      while (grid_reader_handle_wrap(gr, &xx, &yy) &&
271
0
          !(grid_reader_in_set(gr, WHITESPACE) ||
272
0
          grid_reader_in_set(gr, separators)));
273
0
      return;
274
0
    }
275
0
  }
276
0
}
277
278
/* Move to the previous place where a word begins. */
279
void
280
grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators,
281
    int already, int stop_at_eol)
282
0
{
283
0
  int oldx, oldy, at_eol, word_is_letters;
284
285
  /* Move back to the previous word character. */
286
0
  if (already || grid_reader_in_set(gr, WHITESPACE)) {
287
0
    for (;;) {
288
0
      if (gr->cx > 0) {
289
0
        gr->cx--;
290
0
        if (!grid_reader_in_set(gr, WHITESPACE)) {
291
0
          word_is_letters =
292
0
              !grid_reader_in_set(gr, separators);
293
0
          break;
294
0
        }
295
0
      } else {
296
0
        if (gr->cy == 0)
297
0
          return;
298
0
        grid_reader_cursor_up(gr);
299
0
        grid_reader_cursor_end_of_line(gr, 0, 0);
300
301
        /* Stop if separator at EOL. */
302
0
        if (stop_at_eol && gr->cx > 0) {
303
0
          oldx = gr->cx;
304
0
          gr->cx--;
305
0
          at_eol = grid_reader_in_set(gr,
306
0
              WHITESPACE);
307
0
          gr->cx = oldx;
308
0
          if (at_eol) {
309
0
            word_is_letters = 0;
310
0
            break;
311
0
          }
312
0
        }
313
0
      }
314
0
    }
315
0
  } else
316
0
    word_is_letters = !grid_reader_in_set(gr, separators);
317
318
  /* Move back to the beginning of this word. */
319
0
  do {
320
0
    oldx = gr->cx;
321
0
    oldy = gr->cy;
322
0
    if (gr->cx == 0) {
323
0
      if (gr->cy == 0 ||
324
0
          (~grid_get_line(gr->gd, gr->cy - 1)->flags &
325
0
          GRID_LINE_WRAPPED))
326
0
        break;
327
0
      grid_reader_cursor_up(gr);
328
0
      grid_reader_cursor_end_of_line(gr, 0, 1);
329
0
    }
330
0
    if (gr->cx > 0)
331
0
      gr->cx--;
332
0
  } while (!grid_reader_in_set(gr, WHITESPACE) &&
333
0
      word_is_letters != grid_reader_in_set(gr, separators));
334
0
  gr->cx = oldx;
335
0
  gr->cy = oldy;
336
0
}
337
338
/* Compare grid cell to UTF-8 data. Return 1 if equal, 0 if not. */
339
static int
340
grid_reader_cell_equals_data(const struct grid_cell *gc,
341
    const struct utf8_data *ud)
342
0
{
343
0
  if (gc->flags & GRID_FLAG_PADDING)
344
0
    return (0);
345
0
  if (gc->flags & GRID_FLAG_TAB && ud->size == 1 && *ud->data == '\t')
346
0
    return (1);
347
0
  if (gc->data.size != ud->size)
348
0
    return (0);
349
0
  return (memcmp(gc->data.data, ud->data, gc->data.size) == 0);
350
0
}
351
352
/* Jump forward to character. */
353
int
354
grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc)
355
0
{
356
0
  struct grid_cell  gc;
357
0
  u_int     px, py, xx, yy;
358
359
0
  px = gr->cx;
360
0
  yy = gr->gd->hsize + gr->gd->sy - 1;
361
362
0
  for (py = gr->cy; py <= yy; py++) {
363
0
    xx = grid_line_length(gr->gd, py);
364
0
    while (px < xx) {
365
0
      grid_get_cell(gr->gd, px, py, &gc);
366
0
      if (grid_reader_cell_equals_data(&gc, jc)) {
367
0
        gr->cx = px;
368
0
        gr->cy = py;
369
0
        return (1);
370
0
      }
371
0
      px++;
372
0
    }
373
374
0
    if (py == yy ||
375
0
        !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED))
376
0
      return (0);
377
0
    px = 0;
378
0
  }
379
0
  return (0);
380
0
}
381
382
/* Jump back to character. */
383
int
384
grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc)
385
0
{
386
0
  struct grid_cell  gc;
387
0
  u_int     px, py, xx;
388
389
0
  xx = gr->cx + 1;
390
391
0
  for (py = gr->cy + 1; py > 0; py--) {
392
0
    for (px = xx; px > 0; px--) {
393
0
      grid_get_cell(gr->gd, px - 1, py - 1, &gc);
394
0
      if (grid_reader_cell_equals_data(&gc, jc)) {
395
0
        gr->cx = px - 1;
396
0
        gr->cy = py - 1;
397
0
        return (1);
398
0
      }
399
0
    }
400
401
0
    if (py == 1 ||
402
0
        !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED))
403
0
      return (0);
404
0
    xx = grid_line_length(gr->gd, py - 2);
405
0
  }
406
0
  return (0);
407
0
}
408
409
/* Jump back to the first non-blank character of the line. */
410
void
411
grid_reader_cursor_back_to_indentation(struct grid_reader *gr)
412
0
{
413
0
  struct grid_cell  gc;
414
0
  u_int     px, py, xx, yy, oldx, oldy;
415
416
0
  yy = gr->gd->hsize + gr->gd->sy - 1;
417
0
  oldx = gr->cx;
418
0
  oldy = gr->cy;
419
0
  grid_reader_cursor_start_of_line(gr, 1);
420
421
0
  for (py = gr->cy; py <= yy; py++) {
422
0
    xx = grid_line_length(gr->gd, py);
423
0
    for (px = 0; px < xx; px++) {
424
0
      grid_get_cell(gr->gd, px, py, &gc);
425
0
      if ((gc.data.size != 1 || *gc.data.data != ' ') &&
426
0
          ~gc.flags & GRID_FLAG_TAB &&
427
0
          ~gc.flags & GRID_FLAG_PADDING) {
428
0
        gr->cx = px;
429
0
        gr->cy = py;
430
0
        return;
431
0
      }
432
0
    }
433
0
    if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)
434
0
      break;
435
0
  }
436
0
  gr->cx = oldx;
437
0
  gr->cy = oldy;
438
0
}