Coverage Report

Created: 2025-11-15 06:27

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