Coverage Report

Created: 2026-05-30 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/grid-reader.c
Line
Count
Source
1
/* $OpenBSD$ */
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_reader_line_length(gr);
59
0
    if (px != 0)
60
0
      px--;
61
0
  }
62
63
0
  if (wrap && gr->cx >= px && gr->cy < gr->gd->hsize + gr->gd->sy - 1) {
64
0
    grid_reader_cursor_start_of_line(gr, 0);
65
0
    grid_reader_cursor_down(gr);
66
0
  } else if (gr->cx < px) {
67
0
    gr->cx++;
68
0
    while (gr->cx < px) {
69
0
      grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
70
0
      if (~gc.flags & GRID_FLAG_PADDING)
71
0
        break;
72
0
      gr->cx++;
73
0
    }
74
0
  }
75
0
}
76
77
/* Move cursor back one position. */
78
void
79
grid_reader_cursor_left(struct grid_reader *gr, int wrap)
80
0
{
81
0
  struct grid_cell  gc;
82
83
0
  while (gr->cx > 0) {
84
0
    grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
85
0
    if (~gc.flags & GRID_FLAG_PADDING)
86
0
      break;
87
0
    gr->cx--;
88
0
  }
89
0
  if (gr->cx == 0 && gr->cy > 0 &&
90
0
      (wrap ||
91
0
       grid_get_line(gr->gd, gr->cy - 1)->flags & GRID_LINE_WRAPPED)) {
92
0
    grid_reader_cursor_up(gr);
93
0
    grid_reader_cursor_end_of_line(gr, 0, 0);
94
0
  } else if (gr->cx > 0)
95
0
    gr->cx--;
96
0
}
97
98
/* Move cursor down one line. */
99
void
100
grid_reader_cursor_down(struct grid_reader *gr)
101
0
{
102
0
  struct grid_cell  gc;
103
104
0
  if (gr->cy < gr->gd->hsize + gr->gd->sy - 1)
105
0
    gr->cy++;
106
0
  while (gr->cx > 0) {
107
0
    grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
108
0
    if (~gc.flags & GRID_FLAG_PADDING)
109
0
      break;
110
0
    gr->cx--;
111
0
  }
112
0
}
113
114
/* Move cursor up one line. */
115
void
116
grid_reader_cursor_up(struct grid_reader *gr)
117
0
{
118
0
  struct grid_cell  gc;
119
120
0
  if (gr->cy > 0)
121
0
    gr->cy--;
122
0
  while (gr->cx > 0) {
123
0
    grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
124
0
    if (~gc.flags & GRID_FLAG_PADDING)
125
0
      break;
126
0
    gr->cx--;
127
0
  }
128
0
}
129
130
/* Move cursor to the start of the line. */
131
void
132
grid_reader_cursor_start_of_line(struct grid_reader *gr, int wrap)
133
0
{
134
0
  if (wrap) {
135
0
    while (gr->cy > 0 &&
136
0
        grid_get_line(gr->gd, gr->cy - 1)->flags &
137
0
            GRID_LINE_WRAPPED)
138
0
      gr->cy--;
139
0
  }
140
0
  gr->cx = 0;
141
0
}
142
143
/* Move cursor to the end of the line. */
144
void
145
grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all)
146
0
{
147
0
  u_int yy;
148
149
0
  if (wrap) {
150
0
    yy = gr->gd->hsize + gr->gd->sy - 1;
151
0
    while (gr->cy < yy && grid_get_line(gr->gd, gr->cy)->flags &
152
0
        GRID_LINE_WRAPPED)
153
0
      gr->cy++;
154
0
  }
155
0
  if (all)
156
0
    gr->cx = gr->gd->sx;
157
0
  else
158
0
    gr->cx = grid_reader_line_length(gr);
159
0
}
160
161
/* Handle line wrapping while moving the cursor. */
162
static int
163
grid_reader_handle_wrap(struct grid_reader *gr, u_int *xx, u_int *yy)
164
0
{
165
  /*
166
   * Make sure the cursor lies within the grid reader's bounding area,
167
   * wrapping to the next line as necessary. Return zero if the cursor
168
   * would wrap past the bottom of the grid.
169
   */
170
0
  while (gr->cx > *xx) {
171
0
    if (gr->cy == *yy)
172
0
      return (0);
173
0
    grid_reader_cursor_start_of_line(gr, 0);
174
0
    grid_reader_cursor_down(gr);
175
176
0
    if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
177
0
      *xx = gr->gd->sx - 1;
178
0
    else
179
0
      *xx = grid_reader_line_length(gr);
180
0
  }
181
0
  return (1);
182
0
}
183
184
/* Check if character under cursor is in set. */
185
int
186
grid_reader_in_set(struct grid_reader *gr, const char *set)
187
0
{
188
0
  return (grid_in_set(gr->gd, gr->cx, gr->cy, set));
189
0
}
190
191
/* Move cursor to the start of the next word. */
192
void
193
grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators)
194
0
{
195
0
  u_int xx, yy, width;
196
197
  /* Do not break up wrapped words. */
198
0
  if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
199
0
    xx = gr->gd->sx - 1;
200
0
  else
201
0
    xx = grid_reader_line_length(gr);
202
0
  yy = gr->gd->hsize + gr->gd->sy - 1;
203
204
  /*
205
   * When navigating via spaces (for example with next-space) separators
206
   * should be empty.
207
   *
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.
213
   */
214
0
  if (!grid_reader_handle_wrap(gr, &xx, &yy))
215
0
    return;
216
0
  if (!grid_reader_in_set(gr, WHITESPACE)) {
217
0
    if (grid_reader_in_set(gr, separators)) {
218
0
      do
219
0
        gr->cx++;
220
0
      while (grid_reader_handle_wrap(gr, &xx, &yy) &&
221
0
          grid_reader_in_set(gr, separators) &&
222
0
          !grid_reader_in_set(gr, WHITESPACE));
223
0
    } else {
224
0
      do
225
0
        gr->cx++;
226
0
      while (grid_reader_handle_wrap(gr, &xx, &yy) &&
227
0
          !(grid_reader_in_set(gr, separators) ||
228
0
          grid_reader_in_set(gr, WHITESPACE)));
229
0
    }
230
0
  }
231
0
  while (grid_reader_handle_wrap(gr, &xx, &yy) &&
232
0
      (width = grid_reader_in_set(gr, WHITESPACE)))
233
0
    gr->cx += width;
234
0
}
235
236
/* Move cursor to the end of the next word. */
237
void
238
grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators)
239
0
{
240
0
  u_int xx, yy;
241
242
  /* Do not break up wrapped words. */
243
0
  if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
244
0
    xx = gr->gd->sx - 1;
245
0
  else
246
0
    xx = grid_reader_line_length(gr);
247
0
  yy = gr->gd->hsize + gr->gd->sy - 1;
248
249
  /*
250
   * When navigating via spaces (for example with next-space), separators
251
   * should be empty in both modes.
252
   *
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.
258
   */
259
260
0
  while (grid_reader_handle_wrap(gr, &xx, &yy)) {
261
0
    if (grid_reader_in_set(gr, WHITESPACE))
262
0
      gr->cx++;
263
0
    else if (grid_reader_in_set(gr, separators)) {
264
0
      do
265
0
        gr->cx++;
266
0
      while (grid_reader_handle_wrap(gr, &xx, &yy) &&
267
0
          grid_reader_in_set(gr, separators) &&
268
0
          !grid_reader_in_set(gr, WHITESPACE));
269
0
      return;
270
0
    } else {
271
0
      do
272
0
        gr->cx++;
273
0
      while (grid_reader_handle_wrap(gr, &xx, &yy) &&
274
0
          !(grid_reader_in_set(gr, WHITESPACE) ||
275
0
          grid_reader_in_set(gr, separators)));
276
0
      return;
277
0
    }
278
0
  }
279
0
}
280
281
/* Move to the previous place where a word begins. */
282
void
283
grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators,
284
    int already, int stop_at_eol)
285
0
{
286
0
  int oldx, oldy, at_eol, word_is_letters;
287
288
  /* Move back to the previous word character. */
289
0
  if (already || grid_reader_in_set(gr, WHITESPACE)) {
290
0
    for (;;) {
291
0
      if (gr->cx > 0) {
292
0
        gr->cx--;
293
0
        if (!grid_reader_in_set(gr, WHITESPACE)) {
294
0
          word_is_letters =
295
0
              !grid_reader_in_set(gr, separators);
296
0
          break;
297
0
        }
298
0
      } else {
299
0
        if (gr->cy == 0)
300
0
          return;
301
0
        grid_reader_cursor_up(gr);
302
0
        grid_reader_cursor_end_of_line(gr, 0, 0);
303
304
        /* Stop if separator at EOL. */
305
0
        if (stop_at_eol && gr->cx > 0) {
306
0
          oldx = gr->cx;
307
0
          gr->cx--;
308
0
          at_eol = grid_reader_in_set(gr,
309
0
              WHITESPACE);
310
0
          gr->cx = oldx;
311
0
          if (at_eol) {
312
0
            word_is_letters = 0;
313
0
            break;
314
0
          }
315
0
        }
316
0
      }
317
0
    }
318
0
  } else
319
0
    word_is_letters = !grid_reader_in_set(gr, separators);
320
321
  /* Move back to the beginning of this word. */
322
0
  do {
323
0
    oldx = gr->cx;
324
0
    oldy = gr->cy;
325
0
    if (gr->cx == 0) {
326
0
      if (gr->cy == 0 ||
327
0
          (~grid_get_line(gr->gd, gr->cy - 1)->flags &
328
0
          GRID_LINE_WRAPPED))
329
0
        break;
330
0
      grid_reader_cursor_up(gr);
331
0
      grid_reader_cursor_end_of_line(gr, 0, 1);
332
0
    }
333
0
    if (gr->cx > 0)
334
0
      gr->cx--;
335
0
  } while (!grid_reader_in_set(gr, WHITESPACE) &&
336
0
      word_is_letters != grid_reader_in_set(gr, separators));
337
0
  gr->cx = oldx;
338
0
  gr->cy = oldy;
339
0
}
340
341
/* Compare grid cell to UTF-8 data. Return 1 if equal, 0 if not. */
342
static int
343
grid_reader_cell_equals_data(const struct grid_cell *gc,
344
    const struct utf8_data *ud)
345
0
{
346
0
  if (gc->flags & GRID_FLAG_PADDING)
347
0
    return (0);
348
0
  if (gc->flags & GRID_FLAG_TAB && ud->size == 1 && *ud->data == '\t')
349
0
    return (1);
350
0
  if (gc->data.size != ud->size)
351
0
    return (0);
352
0
  return (memcmp(gc->data.data, ud->data, gc->data.size) == 0);
353
0
}
354
355
/* Jump forward to character. */
356
int
357
grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc)
358
0
{
359
0
  struct grid_cell  gc;
360
0
  u_int     px, py, xx, yy;
361
362
0
  px = gr->cx;
363
0
  yy = gr->gd->hsize + gr->gd->sy - 1;
364
365
0
  for (py = gr->cy; py <= yy; py++) {
366
0
    xx = grid_line_length(gr->gd, py);
367
0
    while (px < xx) {
368
0
      grid_get_cell(gr->gd, px, py, &gc);
369
0
      if (grid_reader_cell_equals_data(&gc, jc)) {
370
0
        gr->cx = px;
371
0
        gr->cy = py;
372
0
        return (1);
373
0
      }
374
0
      px++;
375
0
    }
376
377
0
    if (py == yy ||
378
0
        !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED))
379
0
      return (0);
380
0
    px = 0;
381
0
  }
382
0
  return (0);
383
0
}
384
385
/* Jump back to character. */
386
int
387
grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc)
388
0
{
389
0
  struct grid_cell  gc;
390
0
  u_int     px, py, xx;
391
392
0
  xx = gr->cx + 1;
393
394
0
  for (py = gr->cy + 1; py > 0; py--) {
395
0
    for (px = xx; px > 0; px--) {
396
0
      grid_get_cell(gr->gd, px - 1, py - 1, &gc);
397
0
      if (grid_reader_cell_equals_data(&gc, jc)) {
398
0
        gr->cx = px - 1;
399
0
        gr->cy = py - 1;
400
0
        return (1);
401
0
      }
402
0
    }
403
404
0
    if (py == 1 ||
405
0
        !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED))
406
0
      return (0);
407
0
    xx = grid_line_length(gr->gd, py - 2);
408
0
  }
409
0
  return (0);
410
0
}
411
412
/* Jump back to the first non-blank character of the line. */
413
void
414
grid_reader_cursor_back_to_indentation(struct grid_reader *gr)
415
0
{
416
0
  struct grid_cell  gc;
417
0
  u_int     px, py, xx, yy, oldx, oldy;
418
419
0
  yy = gr->gd->hsize + gr->gd->sy - 1;
420
0
  oldx = gr->cx;
421
0
  oldy = gr->cy;
422
0
  grid_reader_cursor_start_of_line(gr, 1);
423
424
0
  for (py = gr->cy; py <= yy; py++) {
425
0
    xx = grid_line_length(gr->gd, py);
426
0
    for (px = 0; px < xx; px++) {
427
0
      grid_get_cell(gr->gd, px, py, &gc);
428
0
      if ((gc.data.size != 1 || *gc.data.data != ' ') &&
429
0
          ~gc.flags & GRID_FLAG_TAB &&
430
0
          ~gc.flags & GRID_FLAG_PADDING) {
431
0
        gr->cx = px;
432
0
        gr->cy = py;
433
0
        return;
434
0
      }
435
0
    }
436
0
    if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)
437
0
      break;
438
0
  }
439
0
  gr->cx = oldx;
440
0
  gr->cy = oldy;
441
0
}