hyx

Minimalist but powerful terminal hex editor
git clone https://git.sinitax.com/yx7/hyx
Log | Files | Refs | sfeed.txt

view.c (10440B)


      1
      2#include "common.h"
      3#include "blob.h"
      4#include "view.h"
      5#include "input.h"
      6#include "ansi.h"
      7
      8#include <stdlib.h>
      9#include <stdio.h>
     10#include <string.h>
     11#include <ctype.h>
     12
     13static void fprint(FILE *fp, char const *s) { fprintf(fp, "%s", s); }
     14static void print(char const *s) { fprint(stdout, s); }
     15
     16static size_t view_end(struct view const *view)
     17{
     18    return view->start + view->rows * view->cols;
     19}
     20
     21void view_init(struct view *view, struct blob *blob, struct input *input)
     22{
     23    memset(view, 0, sizeof(*view));
     24    view->blob = blob;
     25    view->input = input;
     26    view->pos_digits = 4; /* rather arbitrary */
     27    view->color = true;
     28    if (tcgetattr(fileno(stdin), &view->term))
     29        pdie("tcgetattr");
     30    view->initialized = true;
     31}
     32
     33void view_text(struct view *view, bool leave_alternate)
     34{
     35    if (!view->initialized) return;
     36
     37    if (leave_alternate)
     38        print(leave_alternate_screen);
     39    cursor_column(0);
     40    print(clear_line);
     41    print(show_cursor);
     42    fflush(stdout);
     43
     44    if (tcsetattr(fileno(stdin), TCSANOW, &view->term)) {
     45        /* can't pdie() because that risks infinite recursion */
     46        perror("tcsetattr");
     47        exit(EXIT_FAILURE);
     48    }
     49}
     50
     51void view_visual(struct view *view)
     52{
     53    assert(view->initialized);
     54
     55    struct termios term = view->term;
     56    term.c_lflag &= ~ICANON & ~ECHO;
     57    if (tcsetattr(fileno(stdin), TCSANOW, &term))
     58        pdie("tcsetattr");
     59
     60    print(enter_alternate_screen);
     61    print(hide_cursor);
     62    fflush(stdout);
     63}
     64
     65void view_set_cols(struct view *view, bool relative, int cols)
     66{
     67    if (relative) {
     68        if (cols >= 0 || (unsigned) -cols <= view->cols)
     69            cols += view->cols;
     70        else
     71            cols = view->cols;
     72    }
     73
     74    if (!cols) {
     75        if (view->cols_fixed) {
     76            view->cols_fixed = false;
     77            view_recompute(view, true);
     78        }
     79        return;
     80    }
     81
     82    view->cols_fixed = true;
     83
     84    if ((unsigned) cols != view->cols) {
     85        view->cols = cols;
     86        view_dirty_from(view, 0);
     87    }
     88}
     89
     90void view_recompute(struct view *view, bool winch)
     91{
     92    struct winsize winsz;
     93    unsigned old_rows = view->rows, old_cols = view->cols;
     94    unsigned digs = (bit_length(max(2, blob_length(view->blob)) - 1) + 3) / 4;
     95
     96    if (digs > view->pos_digits) {
     97        view->pos_digits = digs;
     98        view_dirty_from(view, 0);
     99    }
    100    else if (!winch)
    101        return;
    102
    103    if (-1 == ioctl(fileno(stdout), TIOCGWINSZ, &winsz))
    104        pdie("ioctl");
    105
    106    view->rows = winsz.ws_row;
    107    if (!view->cols_fixed) {
    108        view->cols = (winsz.ws_col - (view->pos_digits + strlen(": ") + strlen("||"))) / strlen("xx c");
    109
    110        if (view->cols > CONFIG_ROUND_COLS)
    111            view->cols -= view->cols % CONFIG_ROUND_COLS;
    112    }
    113
    114    if (!view->rows || !view->cols)
    115        die("window too small.");
    116
    117    if (view->rows == old_rows && view->cols == old_cols)
    118        return;
    119
    120    /* update dirtiness array */
    121    if ((view->dirty = realloc_strict(view->dirty, view->rows * sizeof(*view->dirty))))
    122        memset(view->dirty, 0, view->rows * sizeof(*view->dirty));
    123    view_dirty_from(view, 0);
    124
    125    view_adjust(view);
    126
    127    print(clear_screen);
    128}
    129
    130void view_free(struct view *view)
    131{
    132    free(view->dirty);
    133}
    134
    135void view_message(struct view *view, char const *msg, char const *color)
    136{
    137    cursor_line(view->rows - 1);
    138    print(clear_line);
    139    if (view->color && color) print(color);
    140    printf("%*c  %s", view->pos_digits, ' ', msg);
    141    if (view->color && color) print(color_normal);
    142    fflush(stdout);
    143    view->dirty[view->rows - 1] = 2; /* redraw at the next keypress */
    144}
    145
    146void view_error(struct view *view, char const *msg)
    147{
    148    view_message(view, msg, color_red);
    149}
    150
    151/* FIXME hex and ascii mode look very similar */
    152static void render_line(struct view *view, size_t off, size_t last)
    153{
    154    byte b;
    155    char digits[0x10], *asciiptr;
    156    size_t asciilen;
    157    FILE *asciifp;
    158    struct input *I = view->input;
    159
    160    size_t sel_start = min(I->cur, I->sel), sel_end = max(I->cur, I->sel);
    161    char const *last_color = NULL, *next_color;
    162
    163    if (!(asciifp = open_memstream(&asciiptr, &asciilen)))
    164        pdie("open_memstream");
    165#define BOTH(EX) for (FILE *fp; ; ) { fp = stdout; EX; fp = asciifp; EX; break; }
    166
    167    if (off <= I->cur && I->cur < off + view->cols) {
    168        /* cursor in current line */
    169        if (view->color) print(color_yellow);
    170        printf("%0*zx%c ", view->pos_digits, I->cur, I->input_mode.insert ? '+' : '>');
    171        if (view->color) print(color_normal);
    172    }
    173    else {
    174        printf("%0*zx: ", view->pos_digits, off);
    175    }
    176
    177    if (I->mode == SELECT && off > sel_start && off <= sel_end)
    178        print(underline_on);
    179
    180    for (size_t j = 0, len = blob_length(view->blob); j < view->cols; ++j) {
    181
    182        if (off + j < len) {
    183            sprintf(digits, "%02hhx", b = blob_at(view->blob, off + j));
    184        }
    185        else {
    186            b = 0;
    187            strcpy(digits, "  ");
    188        }
    189
    190        if (I->mode == SELECT && off + j == sel_start)
    191            print(underline_on);
    192
    193        if (off + j >= last) {
    194            for (size_t p = j; p < view->cols; ++p)
    195                printf("   ");
    196            break;
    197        }
    198
    199        if (off + j == I->cur) {
    200            next_color = I->cur >= blob_length(view->blob) ? color_red : color_yellow;
    201            BOTH(
    202                if (view->color && next_color != last_color) fprint(fp, next_color);
    203                fprint(fp, inverse_video_on);
    204            );
    205
    206            if (!I->input_mode.ascii) {
    207                print(bold_on);
    208                if (I->mode == INPUT && !I->low_nibble) print(underline_on);
    209                putchar(digits[0]);
    210                if (I->mode == INPUT) print(I->low_nibble ? underline_on : underline_off);
    211                putchar(digits[1]);
    212                if (I->mode == INPUT && I->low_nibble) print(underline_off);
    213                print(bold_off);
    214            }
    215            else
    216                printf("%s", digits);
    217
    218            if (I->mode == INPUT && I->input_mode.ascii)
    219                fprintf(asciifp, "%s%s%c%s%s", bold_on, underline_on, isprint(b) ? b : '.', underline_off, bold_off);
    220            else
    221                fputc(isprint(b) ? b : '.', asciifp);
    222
    223            BOTH(
    224                fprint(fp, inverse_video_off);
    225            );
    226        }
    227        else {
    228            next_color = isalnum(b) ? color_cyan
    229                       : isprint(b) ? color_blue
    230                       : !b ? color_red
    231                       : color_normal;
    232            BOTH(
    233                if (view->color && next_color != last_color)
    234                    fprint(fp, next_color);
    235            );
    236            fputc(isprint(b) ? b : '.', asciifp);
    237            printf("%s", digits);
    238        }
    239        last_color = next_color;
    240
    241        if (I->mode == SELECT && (off + j == sel_end || j == view->cols - 1))
    242            print(underline_off);
    243
    244        putchar(' ');
    245    }
    246    if (view->color) print(color_normal);
    247
    248#undef BOTH
    249    if (fclose(asciifp))
    250        pdie("fclose");
    251    putchar('|');
    252    printf("%s", asciiptr);
    253    free(asciiptr);
    254    if (view->color) print(color_normal);
    255    putchar('|');
    256}
    257
    258void view_update(struct view *view)
    259{
    260    size_t last = max(blob_length(view->blob), view->input->cur + 1);
    261
    262    if (view->scroll) {
    263        printf("\x1b[%ld%c", labs(view->scroll), view->scroll > 0 ? 'S' : 'T');
    264        view->scroll = 0;
    265    }
    266
    267    for (size_t i = view->start, l = 0; i < view_end(view); i += view->cols, ++l) {
    268        /* dirtiness counter enables displaying messages until keypressed; */
    269        if (!view->dirty[l] || --view->dirty[l])
    270            continue;
    271        cursor_line(l);
    272        print(clear_line);
    273        if (i < last)
    274            render_line(view, i, last);
    275    }
    276
    277    fflush(stdout);
    278}
    279
    280void view_dirty_at(struct view *view, size_t pos)
    281{
    282    view_dirty_fromto(view, pos, pos + 1);
    283}
    284
    285void view_dirty_from(struct view *view, size_t from)
    286{
    287    view_dirty_fromto(view, from, view_end(view));
    288}
    289
    290void view_dirty_fromto(struct view *view, size_t from, size_t to)
    291{
    292    size_t lfrom, lto;
    293    from = max(view->start, from);
    294    to = min(view_end(view), to);
    295    if (from < to) {
    296        lfrom = (from - view->start) / view->cols;
    297        lto = (to - view->start + view->cols - 1) / view->cols;
    298        for (size_t i = lfrom; i < lto; ++i)
    299            view->dirty[i] = max(view->dirty[i], 1);
    300    }
    301}
    302
    303static size_t satadd(size_t x, size_t y, size_t b)
    304    { assert(b >= 1); return min(b - 1, x + y); }
    305static size_t satsub(size_t x, size_t y, size_t a)
    306    { return x < y || x - y < a ? a : x - y; }
    307
    308void view_adjust(struct view *view)
    309{
    310    size_t old_start = view->start;
    311
    312    assert(view->input->cur <= blob_length(view->blob));
    313
    314    if (view->input->cur >= view_end(view))
    315        view->start = satadd(view->start,
    316                (view->input->cur + 1 - view_end(view) + view->cols - 1) / view->cols * view->cols,
    317                blob_length(view->blob));
    318
    319    if (view->input->cur < view->start)
    320        view->start = satsub(view->start,
    321                (view->start - view->input->cur + view->cols - 1) / view->cols * view->cols,
    322                0);
    323
    324    assert(view->input->cur >= view->start);
    325    assert(view->input->cur < view_end(view));
    326
    327    /* scrolling */
    328    if (view->start != old_start) {
    329        if (!(((ssize_t) view->start - (ssize_t) old_start) % (ssize_t) view->cols)) {
    330            view->scroll = ((ssize_t) view->start - (ssize_t) old_start) / (ssize_t) view->cols;
    331            if (view->scroll > (signed) view->rows || -view->scroll > (signed) view->rows) {
    332                view->scroll = 0;
    333                view_dirty_from(view, 0);
    334                return;
    335            }
    336            if (view->scroll > 0) {
    337                memmove(
    338                        view->dirty,
    339                        view->dirty + view->scroll,
    340                        (view->rows - view->scroll) * sizeof(*view->dirty)
    341                );
    342                for (size_t i = view->rows - view->scroll; i < view->rows; ++i)
    343                    view->dirty[i] = 1;
    344            }
    345            else {
    346                memmove(
    347                        view->dirty + (-view->scroll),
    348                        view->dirty,
    349                        (view->rows - (-view->scroll)) * sizeof(*view->dirty)
    350                );
    351                for (size_t i = 0; i < (size_t) -view->scroll; ++i)
    352                    view->dirty[i] = 1;
    353            }
    354        }
    355        else
    356            view_dirty_from(view, 0);
    357    }
    358
    359}
    360