summaryrefslogtreecommitdiff
path: root/tests/resize_property_test.c
blob: 360e6400b595a199fb5d3193f1c77eec89439887 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include "term/term.h"
#include "term_api.h"

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static uint64_t state;

static unsigned random_below(unsigned limit)
{
    state ^= state << 13;
    state ^= state >> 7;
    state ^= state << 17;
    return (unsigned)(state % limit);
}

/* Select from the oldest retained row to the end of the live screen.
 * Selection text joins wrapped rows and trims blank cells on other rows. */
static char *logical_text(struct term *t)
{
    struct term_vector2i size = term_size(t);
    term_scroll_to(t, term_scrollback(t).total);
    term_select_start(t, (struct term_vector2i){ .x = 0, .y = 0 }, false, TERM_SELECT_CELL);
    term_scroll_to(t, 0);
    term_select_extend(t, (struct term_vector2i){ .x = size.x - 1, .y = size.y - 1 }, true);
    char *text = tt_selection_text(t);
    term_select_clear(t);
    if (!text) return NULL;
    size_t out = 0;
    for (size_t i = 0; text[i]; i++) {
        if (text[i] == '\n') {
            while (out && text[out - 1] == ' ') out--;
        }
        text[out++] = text[i];
    }
    while (out && (text[out - 1] == '\n' || text[out - 1] == ' ')) out--;
    text[out] = 0;
    return text;
}

static uint32_t cell_at(struct term *t, struct term_snapshot *s, int x, int y)
{
    if (!term_snapshot_update(s, t)) return 0;
    return term_snapshot_row(s, y)[x].text;
}

/* A height change that pulls rows back from history moves the saved
 * cursor with its row. DECRC then prints next to the saved text. */
static int saved_cursor_follows_history(struct term_snapshot *s)
{
    static const struct { int cols, rows, grow_cols, grow_rows; } cases[] = {
        { 8, 3, 8, 5 },     /* same width: rows come back from history */
        { 8, 3, 10, 5 },    /* new width: reflow */
    };
    for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
        struct term_options opt = {
            .size = { .x = cases[i].cols, .y = cases[i].rows }, .scrollback = 10,
        };
        struct term *t = term_new(&opt);
        if (!t) return 1;
        const char *text = "l1\r\nl2\r\nl3\r\nl4\r\nab\0337\033[1;1H";
        term_feed(t, text, strlen(text), 0);
        struct term_vector2i size = { .x = cases[i].grow_cols, .y = cases[i].grow_rows };
        if (!term_resize(t, size)) return 1;
        term_feed(t, "\0338X", 3, 0);
        struct term_cursor c = term_cursor(t);
        int y = c.pos.y;
        if (cell_at(t, s, 0, y) != 'a' || cell_at(t, s, 1, y) != 'b' ||
            cell_at(t, s, 2, y) != 'X') {
            fprintf(stderr, "case %zu: DECRC after a resize lost the saved row\n", i);
            return 1;
        }
        term_free(t);
    }
    return 0;
}

/* Narrowing a full screen with the cursor at the top wraps more rows than
 * the ring holds. The cursor row must survive and stay on screen. */
static int narrow_resize_keeps_cursor_row(struct term_snapshot *s)
{
    struct term_options opt = { .size = { .x = 40, .y = 12 }, .scrollback = 5 };
    struct term *t = term_new(&opt);
    if (!t) return 1;
    for (int y = 0; y < 12; y++) {
        char line[64];
        memset(line, 'a' + y, 40);
        line[40] = 0;
        term_feed(t, line, 40, 0);
    }
    term_feed(t, "\033[1;1H", 6, 0);
    if (!term_resize(t, (struct term_vector2i){ .x = 1, .y = 12 })) return 1;
    struct term_cursor c = term_cursor(t);
    if (c.pos.x != 0 || c.pos.y != 0 || cell_at(t, s, 0, 0) != 'a') {
        fprintf(stderr, "narrow resize: cursor %d,%d, cell %x\n", c.pos.x, c.pos.y,
                cell_at(t, s, 0, 0));
        return 1;
    }
    term_feed(t, "Z", 1, 0);
    if (cell_at(t, s, 0, 0) != 'Z') return 1;
    term_free(t);
    return 0;
}

int main(int argc, char **argv)
{
    struct term_snapshot *snap = term_snapshot_new();
    if (!snap || saved_cursor_follows_history(snap) || narrow_resize_keeps_cursor_row(snap))
        return 1;
    term_snapshot_free(snap);

    int seeds = argc > 1 ? atoi(argv[1]) : 100;
    static const char *word[] = {
        "alpha", " x", "中", "日", "e\xcc\x81", "🦊", "👍", " beta", "123", "a",
    };
    for (int seed = 1; seed <= seeds; seed++) {
        state = 0x9e3779b97f4a7c15ull * (uint64_t)seed;
        struct term_options opt = {
            .size = { .x = 40, .y = 12 },
            .scrollback = 4096,
        };
        struct term *t = term_new(&opt);
        if (!t) return 1;
        for (int line = 0; line < 16; line++) {
            int words = 1 + (int)random_below(4);
            for (int i = 0; i < words; i++) {
                const char *s = word[random_below(sizeof(word) / sizeof(word[0]))];
                term_feed(t, s, strlen(s), 0);
            }
            if (line != 15) term_feed(t, "\r\n", 2, 0);
        }
        char *before = logical_text(t);
        if (!before) return 1;
        for (int step = 0; step < 20; step++) {
            struct term_vector2i size = {
                .x = 1 + (int)random_below(200),
                .y = 1 + (int)random_below(60),
            };
            if (!term_resize(t, size)) return 1;
            char *after = logical_text(t);
            if (!after) return 1;
            if (strcmp(before, after)) {
                fprintf(stderr, "seed %d step %d resize %dx%d: logical text changed\n"
                        "before: <%s>\nafter:  <%s>\n",
                        seed, step, size.x, size.y, before, after);
                return 1;
            }
            free(after);
        }
        free(before);
        term_free(t);
    }
    return 0;
}