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