C-Menu 0.2.9
A User Interface Toolkit
Loading...
Searching...
No Matches
view_engine.c
Go to the documentation of this file.
1/** @file view_engine.c
2 @brief The working part of View
3 @author Bill Waller
4 Copyright (c) 2025
5 MIT License
6 billxwaller@gmail.com
7 @date 2026-02-09
8 */
9
10/**
11 @defgroup view_engine View Engine
12 @brief File mapping, user input, command processing, and display logic
13 */
14
15#include <common.h>
16#include <ctype.h>
17#include <errno.h>
18#include <fcntl.h>
19#include <iso646.h>
20#include <regex.h>
21#include <stdbool.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/mman.h>
26#include <sys/param.h>
27#include <sys/stat.h>
28#include <termios.h>
29#include <time.h>
30#include <unistd.h>
31
32/** @brief read the next characater from the virtual file
33 @ingroup view_engine
34 @details Line numbers are tracked when reading forward and stored in a line
35 table for quick access when moving backwards.
36 When reading forward, the End of Data (EOD) flag is set when the
37 position is equal to the file size, and cleared when the position or reading
38 direction changes.
39 Carriage-returns are ignored as they should be.
40 View uses the kernel's demand paged virtual address space to map files
41 directly into memory. This allows for efficient access to file contents
42 without the need for explicit buffering or read system calls, as the kernel
43 handles loading the necessary pages into memory on demand.
44 */
45#define get_next_char()
46 {
47 c = 0;
48 do {
49 if (view->file_pos == view->file_size) {
50 view->f_eod = true;
51 break;
52 } else
53 view->f_eod = false;
54 c = view->buf[view->file_pos++];
55 } while (c == 0x0d);
56 if (c == '\n')
57 increment_ln(view);
58 }
59/** @brief read the previous characater from the virtual file
60 @ingroup view_engine
61 There is no need to track line numbers when moving backwards as they
62 are stored in the line table and accessed as needed.
63 When reading in reverse, the Beginning of Data (BOD) flag is set when
64 the file position is zero, and cleared when the position or reading direction
65 changes.
66 Carriage-returns are ignored as they should be.
67 */
68#define get_prev_char()
69 {
70 c = 0;
71 do {
72 if (view->file_pos == 0) {
73 view->f_bod = true;
74 break;
75 } else
76 view->f_bod = false;
77 c = view->buf[--view->file_pos];
78 } while (c == 0x0d);
79 }
80
82FILE *dbgfp;
83int view_file(Init *);
84int view_cmd_processor(Init *);
85int get_cmd_char(View *, off_t *);
86int get_cmd_arg(View *, char *);
87void build_prompt(View *);
88void cat_file(View *);
89void lp(View *, char *);
90void go_to_mark(View *, int);
91void go_to_eof(View *);
92int go_to_line(View *, off_t);
93void go_to_percent(View *, int);
94void go_to_position(View *, off_t);
95bool search(View *, int, char *);
96void next_page(View *);
97void prev_page(View *);
98void scroll_down_n_lines(View *, int);
99void scroll_up_n_lines(View *, int);
100off_t get_next_line(View *, off_t);
101off_t get_prev_line(View *, off_t);
102off_t get_pos_next_line(View *, off_t);
103off_t get_pos_prev_line(View *, off_t);
104int fmt_line(View *);
105void display_line(View *);
106void view_display_page(View *);
107void parse_ansi_str(char *, attr_t *, int *);
108void view_display_help(Init *);
109int display_prompt(View *, char *);
110void remove_file(View *);
111int write_view_buffer(Init *, bool);
112bool enter_file_spec(Init *, char *);
113int a_toi(char *, bool *);
114void increment_ln(View *);
115void initialize_line_table(View *);
116void destroy_line_table(View *);
117int pad_refresh(View *);
118void sync_ln(View *);
120
121/** @brief Start view
122 @ingroup view_engine
123 @param init Pointer to the Init structure containing initialization
124 parameters and state for the view application. This structure is used to pass
125 necessary information and maintain state across different functions within
126 the view application.
127 @return Returns 0 on successful completion of the view application, or a
128 non-zero value if an error occurs during initialization or execution.
129 */
130int view_file(Init *init) {
131 View *view = init->view;
132 if (view->argc < 1) {
133 view->curr_argc = -1;
134 view->argc = 0;
135 view->next_file_spec_ptr = "-";
136 } else
137 view->next_file_spec_ptr = view->argv[0];
138 while (view->curr_argc < view->argc) {
139 if (view->next_file_spec_ptr == nullptr ||
140 *view->next_file_spec_ptr == '\0') {
141 break;
142 }
144 view->next_file_spec_ptr = nullptr;
146 if (view_init_input(init, view->cur_file_str) == 0) {
147 if (view->buf) {
148 view->f_eod = 0;
149 view->f_bod = 0;
150 view->maxcol = 0;
151 view->page_top_pos = 0;
152 view->page_top_ln = 0;
153 view->page_bot_ln = 0;
154 view->ln_max_pos = 0;
155 view->ln = 0;
156 view->page_bot_pos = 0;
157 view->file_pos = 0;
161 next_page(view);
164 munmap(view->buf, view->file_size);
165 }
166 } else {
167 view->curr_argc++;
168 if (view->curr_argc < view->argc) {
170 }
171 }
172 }
174 destroy_view(init);
175 return 0;
176}
177/** @brief Main Command Processing Loop for View
178 @ingroup view_engine
179 @param init Pointer to the Init structure containing initialization
180 parameters and state for the view application. This structure is used to pass
181 necessary information and maintain state across different functions within
182 the view application.
183*/
184int view_cmd_processor(Init *init) {
185 char tmp_str[MAXLEN];
186 int tfd;
187 int c;
188 int shift = 0;
189 int search_cmd = 0;
190 int prev_search_cmd = 0;
191 int rc, i;
192 ssize_t bytes_written;
193 char *e;
194 char shell_cmd_spec[MAXLEN];
195 off_t n_cmd = 0L;
196 off_t prev_file_pos;
197 int swidth;
198 int max_pmincol;
199 View *view = init->view;
200 view->cmd[0] = '\0';
201 while (1) {
202 if (view->f_redisplay_page) {
204 view->f_redisplay_page = false;
205 }
206 c = view->next_cmd_char;
207 view->next_cmd_char = 0;
208 if (!c) {
209 build_prompt(view);
211 c = get_cmd_char(view, &n_cmd);
212 if (c >= '0' && c <= '9') {
213 tmp_str[0] = (char)c;
214 tmp_str[1] = '\0';
215 c = get_cmd_arg(view, tmp_str);
216 }
217 }
218 switch (c) {
219 case Ctrl('R'): /**< Ctrl('R') or KEY_RESIZE - Handle terminal resize */
220 case 'x':
221 case KEY_RESIZE:
222 getmaxyx(stdscr, view->lines, view->cols);
223#ifdef DEBUG_RESIZE
224 ssnprintf(em0, MAXLEN - 1,
225 "%s:%d view->page_top_ln=%d, resized to lines: %d, cols: %d\n",
226 __FILE__, __LINE__, view->page_top_ln, view->lines, view->cols);
227 write_cmenu_log_nt(em0);
228#endif
229 if (view->f_full_screen)
231 else
233 view->f_redisplay_page = true;
234 continue;
235 case KEY_ALTHOME: /**< KEY_ALTHOME - horizontal scroll to the first
236 column */
237 view->pmincol = 0;
238 break;
239 case KEY_ALTEND: /**< KEY_ALTEND horizontal scroll to the last
240 * column
241 */
242 if (view->maxcol > view->cols)
243 view->pmincol = view->maxcol - view->cols;
244 else
245 view->pmincol = 0;
246 break;
247 case 'h': /**< 'h', Ctrl('H'), KEY_LEFT, KEY_BACKSPACE - Horizontal
248 scroll left by two thirds of the page width */
249 case Ctrl('H'):
250 case KEY_LEFT:
251 case KEY_BACKSPACE:
252 if (n_cmd > 0)
253 view->h_shift = n_cmd;
254 else if (n_cmd == 0) {
255 if (view->h_shift > 0)
256 n_cmd = view->h_shift;
257 else
258 n_cmd = 1;
259 }
260 shift = (int)n_cmd;
261 if (view->pmincol - shift > 0)
262 view->pmincol -= shift;
263 else
264 view->pmincol = 0;
265 break;
266 case 'l': /**< 'l', 'L', KEY_RIGHT - Horizontal scroll right by two
267 thirds of the page width */
268 case 'L':
269 case KEY_RIGHT:
270 if (n_cmd > 0)
271 view->h_shift = n_cmd;
272 else if (n_cmd == 0) {
273 if (view->h_shift > 0)
274 n_cmd = view->h_shift;
275 else
276 n_cmd = 1;
277 }
278 shift = (int)n_cmd;
279 swidth = view->smaxcol - view->smincol + 1;
280 if (view->f_ln)
281 max_pmincol = view->maxcol - swidth + view->ln_win_cols;
282 else
283 max_pmincol = view->maxcol - swidth;
284 if (view->pmincol + shift < max_pmincol)
285 view->pmincol += shift;
286 else
287 view->pmincol = max_pmincol;
288 break;
289 case 'k': /** 'k', 'K', KEY_UP, Ctrl('K') - Scroll up one line */
290 case 'K':
291 case KEY_UP:
292 case Ctrl('K'):
293 if (n_cmd <= 0)
294 n_cmd = 1;
295 scroll_up_n_lines(view, n_cmd);
296 break;
297 /** 'j', 'J', KEY_DOWN, KEY_ENTER, SPACE - scroll down one line */
298 case 'j':
299 case 'J':
300 case '\n':
301 case ' ':
302 case KEY_DOWN:
303 case KEY_ENTER:
304 if (n_cmd <= 0)
305 n_cmd = 1;
306 scroll_down_n_lines(view, n_cmd);
307 break;
308 /** 'b', 'B', Ctrl('B'), KEY_PPAGE - Previous Page */
309 case KEY_PPAGE:
310 case 'b':
311 case 'B':
312 case Ctrl('B'):
313 prev_page(view);
314 break;
315 /** 'f', 'F', Ctrl('F'), KEY_NPAGE Next Page */
316 case 'f':
317 case 'F':
318 case KEY_NPAGE:
319 case Ctrl('F'):
320 next_page(view);
321 break;
322 /** 'g', KEY_HOME - Go to the beginning of the document */
323 case 'g':
324 case KEY_HOME:
325 view->pmincol = 0;
326 go_to_line(view, 0);
327 break;
328 /** KEY_LL - Go to the end of the document */
329 case KEY_LL:
330 go_to_eof(view);
331 break;
332 /** '!', Execute Shell Command from within C-Menu View */
333 case '!':
334 if (view->f_displaying_help)
335 break;
336 if (get_cmd_arg(view, "!") == 0) {
337 if (!view->f_is_pipe) {
340 str_subc(shell_cmd_spec, view->cmd_arg, '%',
341 view->cur_file_str, MAXLEN - 1);
342 } else
343 strnz__cpy(shell_cmd_spec, view->cmd_arg, MAXLEN - 1);
344 full_screen_shell(shell_cmd_spec);
345 if (!view->f_is_pipe) {
347 return 0;
348 }
349 }
350 break;
351 /** '+', Set Startup Command */
352 case '+':
353 if (get_cmd_arg(view, "Startup Command:") == 0)
355 break;
356 /** '-', Change View Settings */
357 case '-':
358 display_prompt(view, "(i, n, s, t, or h)->");
359 c = get_cmd_char(view, &n_cmd);
360 c = S_TOLOWER(c);
361 switch (c) {
362 /** -i ignore_case in search */
363 /** -n line numbers */
364 /** -s squeeze multiple blank lines */
365 /** -t n set tab stop columns */
366 /** -h display help */
367 case 'i':
368 display_prompt(view, "Ignore Case in search (Y or N)->");
369 if ((c = get_cmd_char(view, &n_cmd)) == 'y' || c == 'Y')
370 view->f_ignore_case = true;
371 else if (c == 'n' || c == 'N')
372 view->f_ignore_case = false;
373 break;
374 /** -n line numbers */
375 case 'n':
376 if (view->f_ln)
377 view->f_ln = false;
378 else
379 view->f_ln = true;
381 view->ln = view->page_top_ln;
382 view->file_pos = view->ln_tbl[view->ln];
383 view->maxcol = 0;
384 view->cury = 0;
385 view->page_top_ln = view->ln;
386 mvwaddstr(view->pad, view->cmd_line, 0, " ");
387 pad_refresh(view);
389 break;
390 /** -s Squeeze Multiple Blank Lines */
391 case 's':
393 view, "view->f_squeeze Multiple Blank lines (Y or N)->");
394 if ((c = get_cmd_char(view, &n_cmd)) == 'y' || c == 'Y')
395 view->f_squeeze = true;
396 else if (c == 'n' || c == 'N')
397 view->f_squeeze = false;
398 break;
399 /** -t n Set Tab Stop Columns */
400 case 't':
401 sprintf(tmp_str,
402 "Tabstop Colums Currently %d:", view->tab_stop);
403 i = 0;
404 if (get_cmd_arg(view, tmp_str) == 0)
405 i = atoi(view->cmd_arg);
406 if (i >= 1 && i <= 12) {
407 view->tab_stop = i;
408 view->f_redisplay_page = true;
409 } else
410 Perror("Tab stops not changed");
411 break;
412 /** -h Display Help */
413 case 'h':
414 case KEY_F(1):
415 if (!view->f_displaying_help) {
417 /** Pedantic reassignment of view pointer as the
418 init->view pointer was was reassigned during
419 view_display_help */
420 view = init->view;
421 }
422 view->next_cmd_char = '-';
423 break;
424 default:
425 break;
426 }
427 break;
428 /** ':' - Set a Prompt String */
429 case ':':
430 view->next_cmd_char = get_cmd_arg(view, ":");
431 break;
432 /** 'n' - Repeat Previous Search */
433 case 'n':
434 if (view->f_search_complete) {
435 Perror("Search complete, no more matches");
436 break;
437 }
438 if (prev_search_cmd == 0) {
439 Perror("No previouis search");
440 break;
441 }
442 if (prev_search_cmd == '/') {
443 view->cury = 0;
445 } else {
446 view->cury = view->scroll_lines + 1;
448 }
449 rc = search(view, prev_search_cmd, prev_regex_pattern);
450 if (rc == false) {
451 Perror("No matches found");
452 break;
453 }
454 break;
455 /** '/' or '?' - Search Forward fromk top of page */
456 case '/':
457 view->f_search_complete = false;
458 strnz__cpy(tmp_str, " Forward", MAXLEN - 1);
459 search_cmd = c;
460 c = get_cmd_arg(view, tmp_str);
461 if (c == KEY_F(9) || c == '\033')
462 break;
463 if (c == KEY_ENTER) {
464 view->cury = 0;
465 view->f_first_iter = true;
468 rc = search(view, search_cmd, view->cmd_arg);
469 if (rc == false) {
470 Perror("No matches found");
471 break;
472 }
473 prev_search_cmd = search_cmd;
475 }
476 break;
477 /** '?' - Search Backward */
478 case '?':
479 view->f_search_complete = false;
480 strnz__cpy(tmp_str, " Backward", MAXLEN - 1);
481 search_cmd = c;
482 c = get_cmd_arg(view, tmp_str);
483 if (c == KEY_F(9) || c == '\033')
484 break;
485 if (c == '\n') {
486 view->cury = view->scroll_lines;
487 view->f_first_iter = true;
490 rc = search(view, search_cmd, view->cmd_arg);
491 if (rc == false) {
492 Perror("No matches found");
493 break;
494 }
495 prev_search_cmd = search_cmd;
497 }
498 break;
499 /** 'o' or 'O' - Open a File */
500 case 'o':
501 case 'O':
502 case 'e':
503 case 'E':
504 if (get_cmd_arg(view, "File name:") == 0) {
505 strtok(view->cmd_arg, " ");
506 view->next_file_spec_ptr = strdup(view->cmd_arg);
507 view->f_redisplay_page = true;
508 return 0;
509 }
510 break;
511 /** 'g' or 'G' - Go to the End of the Document */
512 case 'G':
513 case KEY_END:
514 if (n_cmd <= 0)
515 go_to_eof(view);
516 else
517 go_to_line(view, n_cmd);
518 break;
519 /** 'H' or KEY_F(1) - Display Help Information */
520 case 'H':
521 case KEY_F(1):
522 if (!view->f_displaying_help) {
524 view = init->view;
525 }
526 break;
527 /** 'm' - Set a Mark at the Current Position */
528 case 'm':
529 display_prompt(view, "Mark label (A-Z)->");
530 c = get_cmd_char(view, &n_cmd);
531 if (c == '@' || c == KEY_F(9) || c == '\033')
532 if (c >= 'A' && c <= 'Z')
533 c += ' ';
534 if (c < 'a' || c > 'z')
535 Perror("Not (a-z)");
536 else
537 view->mark_tbl[c - 'a'] = view->page_top_pos;
538 break;
539 /** 'M' - Go to a Mark */
540 case 'M':
541 display_prompt(view, "Goto mark (A-Z)->");
542 c = get_cmd_char(view, &n_cmd);
543 if (c == '@' || c == KEY_F(9) || c == '\033')
544 break;
545 if (c >= 'A' && c <= 'Z')
546 c += ' ';
547 if (c < 'a' || c > 'z')
548 Perror("Not (A-Z)");
549 else
550 go_to_mark(view, c);
551 break;
552 /** 'N' - Close Current File and Open Next File */
553 case 'N':
554 if (n_cmd <= 0)
555 n_cmd = 1;
556 if (view->curr_argc + n_cmd >= view->argc) {
557 Perror("no more files");
558 view->curr_argc = view->argc - 1;
559 } else {
560 view->curr_argc++;
561 if (view->curr_argc < view->argc)
563 return 0;
564 }
565 break;
566 /** 'p' or '%' - Go to a Percent of the File */
567 case 'p':
568 case '%':
569 if (n_cmd < 0)
570 go_to_line(view, 1);
571 if (n_cmd >= 100)
572 go_to_eof(view);
573 else
574 go_to_percent(view, n_cmd);
575 break;
576 /** Ctrl('Z') - Send File to Print Queue with Notation */
577 case Ctrl('Z'):
578 get_cmd_arg(view, "Enter Notation:");
579 strnz__cpy(tmp_str, "/tmp/view-XXXXXX", MAXLEN - 1);
580 tfd = mkstemp(tmp_str);
582 if (tfd == -1) {
583 Perror("Unable to create temporary file");
584 break;
585 }
586 strnz__cpy(shell_cmd_spec, "echo ", MAXLEN - 5);
587 strnz__cat(shell_cmd_spec, view->cmd_arg, MAXLEN - 5);
588 strnz__cat(shell_cmd_spec, view->tmp_file_name_ptr, MAXLEN - 5);
589 shell(shell_cmd_spec);
590 strnz__cpy(shell_cmd_spec, "cat ", MAXLEN - 5);
591 strnz__cat(shell_cmd_spec, view->cmd_arg, MAXLEN - 5);
592 strnz__cat(shell_cmd_spec, ">>", MAXLEN - 5);
593 strnz__cat(shell_cmd_spec, view->tmp_file_name_ptr, MAXLEN - 5);
594 shell(shell_cmd_spec);
595 lp(view, view->cur_file_str);
596 shell(shell_cmd_spec);
597 ssnprintf(shell_cmd_spec, (size_t)(MAXLEN - 5), "rm %s",
599 strnz__cpy(shell_cmd_spec, "rm ", MAXLEN - 5);
600 strnz__cat(shell_cmd_spec, view->tmp_file_name_ptr, MAXLEN - 5);
601 shell(shell_cmd_spec);
603 view->f_redisplay_page = true;
604 unlink(tmp_str);
605 break;
606 /** 'P' or KEY_CATAB or KEY_PRINT - Print Current File */
607 case Ctrl('P'):
608 case KEY_CATAB:
609 case KEY_PRINT:
610 lp(view, view->cur_file_str);
611 view->f_redisplay_page = true;
612 break;
613 /** 'P' or KEY_F(9) or ESC - Close Current File and Open Next */
614 case 'P':
615 if (n_cmd <= 0)
616 n_cmd = 1;
617 if (view->curr_argc - n_cmd < 0) {
618 Perror("No previous file");
619 view->curr_argc = 0;
620 } else {
621 view->curr_argc--;
622 if (view->curr_argc >= 0)
624 return 0;
625 }
626 break;
627 /** 'q' or 'Q' or KEY_F(9) or ESC - Quit the Application */
628 case 'q':
629 case 'Q':
630 case KEY_F(9):
631 case '\033':
632 mvwadd_wchnstr(view->cmdln_win, view->cmd_line, view->curx, &sp, 1);
633 view->curr_argc = view->argc;
634 view->next_file_spec_ptr = nullptr;
635 return 0;
636 /** 'v' - Open Current File in Editor */
637 case 'v':
638 if (init->editor[0] == 0) {
639 e = getenv("DEFAULTEDITOR");
640 if (e == nullptr || *e == '\0')
642 else
644 }
646 "View doesn't support editing current buffer directly",
647 MAXLEN - 1);
648 strnz__cpy(em1, "Would you like to write the buffer to a file?",
649 MAXLEN - 1);
650 strnz__cpy(em2, "Enter Y for yes or any other key to cancel.",
651 MAXLEN - 1);
652 rc = display_error(em0, em1, em2, nullptr);
653 if (rc != 'y' && rc != 'Y')
654 break;
655 if (!enter_file_spec(init, view->out_spec)) {
656 view->f_redisplay_page = true;
657 break;
658 }
659 prev_file_pos = view->page_top_pos;
660 bytes_written = write_view_buffer(init, view->f_strip_ansi);
661 if (bytes_written == 0) {
662 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__,
663 __LINE__ - 2);
664 strnz__cpy(em1, "0 bytes written", MAXLEN - 1);
665 strerror_r(errno, em1, MAXLEN - 1);
666 display_error(em0, em1, nullptr, nullptr);
667 break;
668 }
670 strnz__cpy(shell_cmd_spec, init->editor, MAXLEN - 5);
671 strnz__cat(shell_cmd_spec, " ", MAXLEN - 5);
672 strnz__cat(shell_cmd_spec, view->in_spec, MAXLEN - 5);
673 full_screen_shell(shell_cmd_spec);
675 prev_file_pos;
676
678 view->f_redisplay_page = true;
679 return 0;
680 /** 'w' - Write the current buffer to file */
681 case 'w':
682 if (!enter_file_spec(init, view->out_spec)) {
683 view->f_redisplay_page = true;
684 break;
685 }
686 // prev_file_pos = view->page_top_pos;
687 bytes_written = write_view_buffer(init, view->f_strip_ansi);
688 if (bytes_written == 0) {
689 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__,
690 __LINE__ - 2);
691 strnz__cpy(em1, "0 bytes written", MAXLEN - 1);
692 strerror_r(errno, em1, MAXLEN - 1);
693 display_error(em0, em1, nullptr, nullptr);
694 break;
695 }
696 display_prompt(view, tmp_str);
697 view->f_redisplay_page = true;
698 break;
699 case CT_VIEW:
700 break;
701 /** 'V' - Display Version Information */
702 case 'V':
703 ssnprintf(em0, MAXLEN - 1, "C-Menu Version: %s", CM_VERSION);
704 display_error(em0, em1, nullptr, nullptr);
705 break;
706 default:
707 break;
708 }
709 view->cmd_arg[0] = '\0';
710 }
711}
712/** @brief Get Command Character from User Input
713 @ingroup view_engine
714 @param view Pointer to the View structure containing the state and
715 parameters of the view application. This structure is used to access and
716 modify the state of the application as needed.
717 @param n Pointer to an off_t variable where the numeric argument entered by
718 the user will be stored. If the user enters a numeric argument, it will be
719 converted to an off_t value and stored in this variable for use by the
720 calling function.
721 @return Returns the command character entered by the user, or a special
722 value if a mouse event is detected. The function handles user input,
723 including editing keys, and updates the command argument buffer accordingly.
724 If the user enters a numeric argument, it is validated based on the context
725 of the command being executed.
726*/
727int get_cmd_char(View *view, off_t *n) {
728 int c = 0, i = 0;
729 char *d, *s;
730 int prevx;
731 bool once = false;
732 char cmd_str[33];
733 cmd_str[0] = '\0';
734 pad_refresh(view);
735 update_panels();
736 curs_set(1);
737 getyx(view->cmdln_win, view->cmd_line, view->curx);
738 wbkgrndset(view->cmdln_win, &CC_IND);
739 wmove(view->cmdln_win, view->cmd_line, view->curx);
740 mvwadd_wchnstr(view->cmdln_win, view->cmd_line, view->curx, &ran, 1);
741 wmove(view->cmdln_win, view->cmd_line, view->curx + 1);
742 update_panels();
743 doupdate();
744 while (1) {
745 if (i == 1 && !once) {
746 once = true;
747 view->curx = 0;
748 wmove(view->cmdln_win, view->cmd_line, view->curx);
749 wclrtoeol(view->cmdln_win);
750 mvwadd_wchnstr(view->cmdln_win, view->cmd_line, view->curx, &ran, 1);
751 mvwaddch(view->cmdln_win, view->cmd_line, view->curx + 1, (chtype)c);
752 }
753 getyx(view->cmdln_win, view->cmd_line, view->curx);
754 update_panels();
755 wmove(view->cmdln_win, view->cmd_line, view->curx);
756 doupdate();
757 c = vgetch(view->cmdln_win, 0);
758 switch (c) {
759 case KEY_MOUSE:
760 // if (getmouse(event) == OK) {
761 // if (event.bstate & BUTTON1_CLICKED) {
762 // c = KEY_MOUSE;
763 // break;
764 // }
765 // }
766 break;
767 case KEY_RESIZE:
768 case '\n':
769 case '\r':
770 break;
771 case 'q':
772 case KEY_F(9):
773 build_prompt(view);
775 c = KEY_F(9);
776 return c;
777 case '\b':
778 case KEY_BACKSPACE:
779 if (i > 0) {
780 s = &cmd_str[i];
781 d = &cmd_str[--i];
782 view->curx--;
783 prevx = view->curx;
784 while (*s != '\0') {
785 mvwaddch(view->cmdln_win, view->cmd_line, view->curx++, *s);
786 *d++ = *s++;
787 }
788 *d = '\0';
789 wmove(view->cmdln_win, view->cmd_line, view->curx);
790 wclrtoeol(view->cmdln_win);
791 view->curx = prevx;
792 wmove(view->cmdln_win, view->cmd_line, view->curx);
793 }
794 continue;
795 case KEY_DC:
796 if (i < 32 && cmd_str[i] != '\0') {
797 s = &cmd_str[i + 1];
798 d = &cmd_str[i];
799 while (*s != '\0') {
800 mvwaddch(view->cmdln_win, view->cmd_line, view->curx++, *s);
801 *d++ = *s++;
802 }
803 *d = '\0';
804 wmove(view->cmdln_win, view->cmd_line, view->curx);
805 wclrtoeol(view->cmdln_win);
806 wmove(view->cmdln_win, view->cmd_line, view->curx);
807 }
808 continue;
809 case KEY_SRIGHT:
810 if (i < 32 && cmd_str[i] != '\0') {
811 i++;
812 getyx(view->cmdln_win, view->cury, view->curx);
813 if (view->curx < view->cols - 1) {
814 view->curx++;
815 wmove(view->cmdln_win, view->cmd_line, view->curx);
816 }
817 }
818 continue;
819 case KEY_SLEFT:
820 if (i > 0) {
821 i--;
822 getyx(view->cmdln_win, view->cury, view->curx);
823 if (view->curx > 0) {
824 view->curx--;
825 wmove(view->cmdln_win, view->cmd_line, view->curx);
826 }
827 }
828 continue;
829 default:
830 if (c < '0' || c > '9' || i == 32)
831 break;
832 cmd_str[i++] = (char)c;
833 cmd_str[i] = '\0';
834 mvwaddch(view->cmdln_win, view->cmd_line, view->curx, (chtype)c);
835 view->curx++;
836 continue;
837 }
838 if (c < '0' || c > '9' || i == 32)
839 break;
840 }
841 if (cmd_str[0] == '\0') {
842 *n = 0;
843 view->cmd_arg[0] = '\0';
844 mvwadd_wchnstr(view->cmdln_win, view->cmd_line, view->curx + 1, &sp, 1);
845 wclrtoeol(view->cmdln_win);
846 return (c);
847 }
848 *n = atol(cmd_str);
849 view->cmd_arg[0] = '\0';
850 getyx(view->cmdln_win, view->cmd_line, view->curx);
851 mvwadd_wchnstr(view->cmdln_win, view->cmd_line, view->curx + 1, &sp, 1);
852 wclrtoeol(view->cmdln_win);
853 return (c);
854}
855/** @brief Get Command Argument from User Input
856 @ingroup view_engine
857 @param view Pointer to the View structure containing the state and
858 parameters of the view application. This structure is used to access and
859 modify the state of the application as needed.
860 @param prompt A string containing the prompt to be displayed to the user
861 when requesting input for the command argument. This prompt is shown on
862 the command line to guide the user in providing the necessary input for
863 the command being executed.
864 @return Returns the command character entered by the user, or a special
865 value if a mouse event is detected. The command argument entered by the
866 user is stored in the view->cmd_arg buffer for use by the calling
867 function. The function handles user input, including editing keys, and
868 updates the command argument buffer accordingly. If the user enters a
869 numeric argument, it is validated based on the context of the command
870 being executed.
871*/
872int get_cmd_arg(View *view, char *prompt) {
873 int c;
874 char prompt_s[MAXLEN];
875 int prompt_l = strnz__cpy(prompt_s, prompt, min(MAXLEN - 1, view->cols - 4));
876 if (view->cmd_arg[0] != '\0')
877 return 0;
878 wmove(view->cmdln_win, view->cmd_line, 0);
879 if (prompt_l > 1) {
880 wbkgrndset(view->cmdln_win, &CC_NT_REV);
881 waddstr(view->cmdln_win, prompt_s);
882 wbkgrndset(view->cmdln_win, &CC_NT);
883 }
884 view->curx = prompt_l;
885 wclrtoeol(view->cmdln_win);
886 pad_refresh(view);
887 update_panels();
888 curs_set(1);
889 mvwadd_wchnstr(view->cmdln_win, view->cmd_line, view->curx, &ran, 1);
890 wmove(view->cmdln_win, view->cmd_line, view->curx + 1);
891 doupdate();
892 int flin = view->cmd_line;
893 int fcol = prompt_l + 1;
894 int flen = view->cols - prompt_l;
895 c = cf_accept(view->cmdln_win, view->cmd_arg, flin, fcol, flen);
896 return c;
897}
898/** @brief Build Prompt String
899 * @ingroup view_engine
900 @param view Pointer to the View structure containing the state and
901 parameters of the view application. This structure is used to access and
902 modify the state of the application as needed.
903 @details
904 This function constructs a prompt string that provides information about
905 the current state of the view application. The prompt includes details such
906 as the file name, current column, file number, file position, and whether
907 the end of the document has been reached. The constructed prompt string is
908 stored in the view->prompt_str buffer for display to the user.
909 @note The prompt string is built based on the current state of the view
910 application, and segments that are not relevant will be omitted. Less
911 relevant segments will be omitted if the length of the prompt string
912 exceeds more than half the available space. The length of the prompt
913 string has a hard limit of view->cols - 4.
914 */
915void build_prompt(View *view) {
916 char tmp_str[MAXLEN];
917 int prompt_maxlen = min(MAXLEN - 1, view->cols - 4);
918 int prompt_l = 0;
919 // ----------------< File Name >----------------
920 if (view->f_is_pipe)
921 strnz__cpy(view->prompt_str, "stdin", prompt_maxlen);
922 else
923 strnz__cpy(view->prompt_str, view->file_name, prompt_maxlen);
924 // ----------------< Columns >----------------
925 if (view->pmincol > 0) {
926 sprintf(tmp_str, "Col %d of %d", view->pmincol, view->maxcol);
927 if (view->prompt_str[0] != '\0')
928 strnz__cat(view->prompt_str, "|", prompt_maxlen);
929 strnz__cat(view->prompt_str, tmp_str, prompt_maxlen);
930 }
931 // ----------------< File Number >----------------
932 if (view->argc > 1) {
933 prompt_l = (int)strlen(view->prompt_str);
934 if (prompt_l > (view->cols - 4) / 2)
935 return;
936 if (view->argc > 0) {
937 sprintf(tmp_str, "File %d of %d", view->curr_argc + 1, view->argc);
938 if (view->prompt_str[0] != '\0') {
939 strnz__cat(view->prompt_str, "|", prompt_maxlen);
940 strnz__cat(view->prompt_str, tmp_str, prompt_maxlen);
941 }
942 }
943 }
944 // ----------------< File Position >----------------
945 prompt_l = (int)strlen(view->prompt_str);
946 if (prompt_l > (view->cols - 4) / 2)
947 return;
949 if (view->page_top_pos == NULL_POSITION)
950 view->page_top_pos = view->file_size;
952 if (view->page_bot_pos == NULL_POSITION)
953 view->page_bot_pos = view->file_size;
954 sprintf(tmp_str, "Pos %zd-%zd", view->page_top_pos, view->page_bot_pos);
955 if (view->prompt_str[0] != '\0') {
956 strnz__cat(view->prompt_str, "|", prompt_maxlen);
957 strnz__cat(view->prompt_str, tmp_str, prompt_maxlen);
958 }
959 if (!view->f_is_pipe) {
960 if (view->file_size > 0) {
961 sprintf(tmp_str, " of %zd", view->file_size);
962 strnz__cat(view->prompt_str, tmp_str, prompt_maxlen);
963 }
964 }
965 // ----------------< (End) >----------------
966 prompt_l = (int)strlen(view->prompt_str);
967 if (prompt_l > (view->cols - 4) / 2)
968 return;
969 if (view->f_eod) {
970 if (view->prompt_str[0] != '\0')
971 strnz__cat(view->prompt_str, " ", prompt_maxlen);
972 strnz__cat(view->prompt_str, "(End)", prompt_maxlen);
973 if (view->curr_argc + 1 < view->argc) {
974 base_name(tmp_str, view->argv[view->curr_argc + 1]);
975 strnz__cpy(view->prompt_str, " Next File: ", prompt_maxlen);
976 strnz__cat(view->prompt_str, tmp_str, prompt_maxlen);
977 }
978 }
979}
980/** @brief Write buffer contents to files
981 @ingroup view_engine
982 @param init data structure
983 @param f_strip_ansi strip ANSI escape sequences
984 @details
985 */
986int write_view_buffer(Init *init, bool f_strip_ansi) {
987 ssize_t bytes_written = 0;
988 off_t pos;
989 View *view = init->view;
990 int rc;
991 size_t l;
992 char tmp_line_s[PAD_COLS];
993 if (!f_strip_ansi) {
994 strnz__cpy(em0, "Would you like to strip ansi escape sequences?",
995 MAXLEN - 1);
996 strnz__cpy(em1, "Enter Y for yes or any other key to cancel.",
997 MAXLEN - 1);
998 rc = answer_yn(nullptr, em0, em1, nullptr);
999 if (rc == 'y' || rc == 'Y')
1000 f_strip_ansi = true;
1001 else
1002 f_strip_ansi = false;
1003 }
1005 /** write the buffer */
1006 pos = 0;
1007 view->out_fd = open(view->out_spec, O_CREAT | O_TRUNC | O_WRONLY, 0644);
1008 if (view->out_fd == -1) {
1009 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__, __LINE__ - 2);
1010 strnz__cpy(em1, "fwrite ", MAXLEN - 1);
1012 strerror_r(errno, em2, MAXLEN - 1);
1014 return false;
1015 }
1016 bytes_written = 0;
1017 while (!view->f_eod) {
1018 pos = get_next_line(view, pos);
1019 if (f_strip_ansi)
1020 strip_ansi(tmp_line_s, view->line_in_s);
1021 else
1022 strnz__cpy(tmp_line_s, view->line_in_s, MAXLEN - 1);
1023 l = strnlf(tmp_line_s, PAD_COLS - 1);
1024 bytes_written += write(view->out_fd, tmp_line_s, l);
1025 }
1026 close(view->out_fd);
1028 return bytes_written;
1029}
1030/** @brief Concatenate File to Standard Output
1031 @ingroup view_engine
1032 */
1033void cat_file(View *view) {
1034 int c;
1035 while (1) {
1036 get_next_char();
1037 if (view->f_eod)
1038 break;
1039 putchar(c);
1040 }
1041}
1042/** @brief Send File to Print Queue
1043 @ingroup view_engine
1044 @param view Pointer to the View structure containing the state and
1045 @param PrintFile - file to print */
1046void lp(View *view, char *PrintFile) {
1047 char *print_cmd_ptr;
1048 char shell_cmd_spec[MAXLEN];
1049 print_cmd_ptr = getenv("PRINTCMD");
1050 if (print_cmd_ptr == nullptr || *print_cmd_ptr == '\0')
1051 print_cmd_ptr = PRINTCMD;
1052 ssnprintf(shell_cmd_spec, MAXLEN - 1, "%s %s", print_cmd_ptr, PrintFile);
1053 display_prompt(view, shell_cmd_spec);
1054 shell(shell_cmd_spec);
1055}
1056/** @defgroup view_navigation View Navigation
1057 @brief Navigation functions for the view application
1058 */
1059/** @brief Go to Mark
1060 @ingroup view_navigation
1061 @param view Pointer to the View structure containing the state and
1062 parameters of the view application. This structure is used to access and
1063 modify the state of the application as needed.
1064 @param c The character representing the mark to go to. This character is
1065 typically a lowercase letter (a-z) corresponding to a mark that has been
1066 set previously in the view application. The function will attempt to
1067 navigate to the position associated with this mark, allowing the user to
1068 quickly jump to specific locations in the file based on the marks they
1069 have set. If the mark is not set, an error message will be displayed to
1070 the user.
1071 */
1072void go_to_mark(View *view, int c) {
1073 if (c == '\'')
1074 view->file_pos = view->mark_tbl[(NMARKS - 1)];
1075 else
1076 view->file_pos = view->mark_tbl[c - 'a'];
1077 if (view->file_pos == NULL_POSITION)
1078 Perror("Mark not set");
1079 else
1081}
1082/** @brief Search for Regular Expression Pattern
1083 @ingroup view_navigation
1084 @param view Pointer to View Structure
1085 @param search_cmd Search Command Character ('/' or '?')
1086 @param regex_pattern Regular Expression Pattern to Search For
1087 @returns true if a match is found and displayed, false if the search
1088 completes without finding a match or if an error occurs
1089 @details The search performs extended regular expression matching, ignoring
1090 ANSI sequences and Unicode characters. Matches are highlighted on the
1091 screen, and the search continues until the page is full or the end of the
1092 file is reached. If the search wraps around the file, a message is
1093 displayed indicating that the search is complete.
1094 The search state is maintained in the view structure, allowing for
1095 repeat searches and tracking of the current search position. This
1096 function highlights all matches in the current ncurses pad, including
1097 those not displayed on the screen, and tracks the first and last match
1098 columns for prompt display.
1099 ANSI sequences and Unicode characters are stripped before
1100 matching, so matching corresponds to the visual display */
1101bool search(View *view, int search_cmd, char *regex_pattern) {
1102 char tmp_str[MAXLEN];
1103 int REG_FLAGS = 0;
1104 regmatch_t pmatch[1];
1105 regex_t compiled_regex;
1106 int reti;
1107 int line_offset;
1108 int line_len;
1109 int match_len;
1110 off_t prev_ln;
1111 bool f_page = false;
1112 if (*regex_pattern == '\0')
1113 return false;
1114 if (view->f_ignore_case)
1115 REG_FLAGS = REG_ICASE | REG_EXTENDED;
1116 else
1117 REG_FLAGS = REG_EXTENDED;
1118 reti = regcomp(&compiled_regex, regex_pattern, REG_FLAGS);
1119 if (reti) {
1120 Perror("Invalid pattern");
1121 return false;
1122 }
1123 bool rc = false;
1124 while (1) {
1125 /** initialize iteration */
1126 if (search_cmd == '/') {
1127 if (view->srch_curr_pos == view->file_size)
1128 view->srch_curr_pos = 0;
1129 } else {
1130 if (view->srch_curr_pos == 0)
1131 view->srch_curr_pos = view->file_size;
1132 }
1133 if (view->srch_curr_pos == view->srch_beg_pos) {
1134 if (view->f_first_iter == true) {
1135 view->f_first_iter = false;
1136 view->f_search_complete = false;
1137 if (search_cmd == '/')
1138 view->cury = 0;
1139 else
1140 view->cury = view->scroll_lines + 1;
1141 } else {
1142 view->f_search_complete = true;
1143 return rc;
1144 }
1145 }
1146 view->file_pos = view->srch_curr_pos;
1147 sync_ln(view);
1148 /** get line to scan */
1149 if (search_cmd == '/') {
1150 if (view->cury == view->scroll_lines)
1151 return rc;
1152 prev_ln = view->ln; /**< Note placement before get_next_line */
1154 view->page_bot_ln = view->ln;
1156 } else {
1157 if (view->cury == 0)
1158 return rc;
1160 prev_ln = view->ln; /**< Note placement after get_prev_line */
1161 view->page_top_ln = view->ln;
1163 }
1164 fmt_line(view);
1165 reti = regexec(&compiled_regex, view->stripped_line_out,
1166 compiled_regex.re_nsub + 1, pmatch, REG_FLAGS);
1167 if (reti == REG_NOMATCH) {
1168 if (f_page) {
1169 /** non-matching page filler */
1170 if (search_cmd == '?')
1171 view->cury -= 2;
1172 display_line(view);
1173 if ((search_cmd == '/' && view->cury == view->scroll_lines) ||
1174 (search_cmd == '?' && view->cury == 1)) {
1175 break;
1176 }
1177 }
1178 continue;
1179 }
1180 if (reti) {
1181 char err_str[MAXLEN];
1182 regerror(reti, &compiled_regex, err_str, sizeof(err_str));
1183 strnz__cpy(tmp_str, "Regex match failed: ", MAXLEN - 1);
1184 strnz__cat(tmp_str, err_str, MAXLEN - 1);
1185 Perror(tmp_str);
1186 regfree(&compiled_regex);
1187 return false;
1188 }
1189 rc = true;
1190 /** Display matching lines */
1191 if (!f_page) {
1192 if (search_cmd == '/') {
1193 view->page_top_ln = prev_ln;
1194 wmove(view->pad, view->cury, 0);
1195 } else {
1196 view->page_bot_ln = view->ln;
1197 wmove(view->pad, 0, 0);
1198 }
1199 wclrtobot(view->pad);
1200 f_page = true;
1201 }
1202 if (search_cmd == '?')
1203 view->cury -= 2;
1204 display_line(view);
1205 /** All matches on the current line are highlighted,
1206 including those not displayed on the screen.
1207 Track first and last match columns for prompt display. */
1208 view->first_match_x = -1;
1209 view->last_match_x = 0;
1210 line_len = strlen(view->stripped_line_out);
1211
1212 line_offset = 0;
1213 while (1) {
1214 view->curx = line_offset + pmatch[0].rm_so;
1215 match_len = pmatch[0].rm_eo - pmatch[0].rm_so;
1216 mvwchgat(view->pad, view->cury - 1, view->curx, match_len,
1217 WA_NORMAL, cp_nt_rev, nullptr);
1218 if (view->first_match_x == -1)
1219 view->first_match_x = pmatch[0].rm_so;
1220 view->last_match_x = line_offset + pmatch[0].rm_eo;
1221 line_offset += pmatch[0].rm_eo;
1222 if (line_offset >= line_len)
1223 break;
1224 view->line_out_p = view->stripped_line_out + line_offset;
1225 reti = regexec(&compiled_regex, view->line_out_p,
1226 compiled_regex.re_nsub + 1, pmatch, REG_FLAGS);
1227 if (reti == REG_NOMATCH)
1228 break;
1229 if (reti) {
1230 char msgbuf[100];
1231 regerror(reti, &compiled_regex, msgbuf, sizeof(msgbuf));
1232 sprintf(tmp_str, "Regex match failed: %s", msgbuf);
1233 Perror(tmp_str);
1234 regfree(&compiled_regex);
1235 return false;
1236 }
1237 if (search_cmd == '/') {
1238 if (view->cury == view->scroll_lines) {
1239 regfree(&compiled_regex);
1240 break;
1241 }
1242 } else if (view->cury == 1) {
1243 regfree(&compiled_regex);
1244 break;
1245 }
1246 }
1247 }
1248 view->file_pos = view->srch_curr_pos;
1249#ifdef DEBUG_SEARCH
1250 /** Statistics for debugging */
1251 ssnprintf(view->tmp_prompt_str, MAXLEN - 1,
1252 "%s|%c%s|Pos %zu-%zu|(%zd) %zu %zu", view->file_name, search_cmd,
1253 regex_pattern, view->page_top_pos, view->page_bot_pos,
1254 view->file_size, view->srch_beg_pos, view->srch_curr_pos);
1255#else
1256 if (view->last_match_x > view->maxcol)
1258 "%s|%c%s|Match Cols %d-%d of %d-%d|(%zd%%)", view->file_name,
1259 search_cmd, regex_pattern, view->first_match_x,
1261 (view->page_bot_pos * 100 / view->file_size));
1262 else
1264 "%s|%c%s|Pos %zu-%zu|(%zd%%)", view->file_name, search_cmd,
1265 regex_pattern, view->page_top_pos, view->page_bot_pos,
1266 (view->page_bot_pos * 100 / view->file_size));
1267#endif
1268 regfree(&compiled_regex);
1269 return rc;
1270}
1271
1272/** @defgroup view_display Manage View Display
1273 @brief Manage the View Display
1274 */
1275/** @brief Refresh Pad and Line Number Window
1276 @ingroup view_display
1277 @param view data structure
1278 @returns OK on success, ERR on failure
1279*/
1280int pad_refresh(View *view) {
1281 int rc;
1282 touchwin(view->pad);
1283 rc = pnoutrefresh(view->pad, view->pminrow, view->pmincol, view->sminrow,
1284 view->smincol, view->smaxrow, view->smaxcol);
1285 rc = prefresh(view->pad_view_win,
1286 view->pminrow,
1287 view->pmincol,
1288 view->sminrow,
1289 view->smincol,
1290 view->smaxrow,
1291 view->smaxcol);
1292 if (rc == ERR) {
1293 ssnprintf(em0, MAXLEN - 1, "%s:%d prefresh(view->pad_view_win, pminrow=%d, pmincol=%d, smaxrow=%d, smaxcol=%d) returned %d\n",
1294 __FILE__, __LINE__, view->pminrow, view->pmincol, view->smaxrow, view->smaxcol, rc);
1296 }
1297 return rc;
1298}
1299/*--------------------------------------------------------------
1300 Navigation
1301 *--------------------------------------------------------------- */
1302/** @brief display previous page
1303 @ingroup view_navigation
1304 @param view data structure
1305 @details Displays the previous page starting at (view->page_top_ln -
1306 view->scroll_lines).
1307 */
1308void prev_page(View *view) {
1309 if (view->page_top_pos == 0)
1310 return;
1311 view->cury = 0;
1312 view->ln = view->page_top_ln;
1313 if (view->ln - view->scroll_lines >= 0)
1314 view->ln -= view->scroll_lines;
1315 else
1316 view->ln = 0;
1317 next_page(view);
1318}
1319/** @brief Advance to Next Page
1320 @ingroup view_navigation
1321 @param view data structure
1322 @details Advances from view->page_bot_pos to view the next page of
1323 content. view->page_bot_pos must be set properly when calling this
1324 function. If the current bottom position of the page is at the end of the
1325 file, the function returns without making any changes. Otherwise, it
1326 resets the maximum column and current line position to the top of the
1327 page, updates the file position to the current bottom position of the
1328 page, and sets the top position and line number of the page accordingly.
1329 Finally, it calls the function to display the new page content.
1330*/
1331void next_page(View *view) {
1332 view->file_pos = view->ln_tbl[view->ln];
1333 if (view->file_pos == view->file_size)
1334 return;
1335 view->maxcol = 0;
1336 view->cury = 0;
1337 view->page_top_ln = view->ln;
1339}
1340/** @brief Display Current Page
1341 @ingroup view_display
1342 @param view data structure
1343 */
1344void view_display_page(View *view) {
1345 int i;
1346 view->cury = 0;
1347 wmove(view->pad, 0, 0);
1348 if (view->lnno_win)
1349 wmove(view->lnno_win, 0, 0);
1350 view->ln = view->page_top_ln;
1351 view->page_bot_ln = view->ln;
1352 view->page_top_pos = view->ln_tbl[view->ln];
1353 view->file_pos = view->page_top_pos;
1354 view->page_bot_pos = view->file_pos;
1355 for (i = 0; i < view->scroll_lines; i++) {
1357 if (view->f_eod)
1358 break;
1359 fmt_line(view);
1360 display_line(view);
1361 }
1362 if (view->cury < view->scroll_lines) {
1363 wmove(view->lnno_win, view->cury, 0);
1364 wclrtobot(view->lnno_win);
1365 wmove(view->pad, view->cury, 0);
1366 wclrtobot(view->pad);
1367 }
1368 view->page_bot_ln = view->ln;
1369}
1370/** @brief Scroll N Lines
1371 @ingroup view_navigation
1372 @param view data Structure
1373 @param n number of lines to scroll
1374 */
1375void scroll_down_n_lines(View *view, int n) {
1376 int i = 0;
1377 view->f_bod = false;
1378 if (view->page_bot_pos == view->file_size)
1379 return;
1380 /** Locate New Top of Page */
1381 if (view->page_top_ln + n < view->ln) {
1382 view->page_top_ln += n;
1383 view->page_top_pos = view->ln_tbl[view->page_top_ln];
1384 } else
1385 view->page_top_ln = view->ln;
1386 /** Scroll */
1387 if (n > view->scroll_lines) {
1388 if (view->f_ln) {
1389 wmove(view->lnno_win, 0, 0);
1390 wclrtobot(view->lnno_win);
1391 }
1392 wmove(view->pad, 0, 0);
1393 wclrtobot(view->pad);
1394 } else {
1395 if (view->f_ln)
1396 wscrl(view->lnno_win, n);
1397 wscrl(view->pad, n);
1398 // scrolled up
1399 if (n < view->scroll_lines)
1400 view->cury = view->scroll_lines - n;
1401 }
1402 /** Fill in Page Bottom */
1403 wmove(view->pad, view->cury, 0);
1404 for (i = 0; i < n; i++) {
1406 view->page_bot_ln = view->ln;
1407 if (view->f_eod)
1408 break;
1409 fmt_line(view);
1410 display_line(view);
1411 }
1412}
1413/** @brief Scroll Up N Lines
1414 @ingroup view_navigation
1415 @param view data Structure
1416 @param n number of lines to scroll
1417 */
1418void scroll_up_n_lines(View *view, int n) {
1419 int i;
1420 view->page_top_pos = view->ln_tbl[view->page_top_ln];
1421 view->f_eod = false;
1422 if (view->page_top_pos == 0)
1423 return;
1424 if (view->page_top_ln - n >= 0) {
1425 view->page_top_ln -= n;
1426 } else
1427 view->page_top_ln = 1;
1428 view->ln = view->page_top_ln;
1429 view->page_top_pos = view->ln_tbl[view->page_top_ln];
1430 view->file_pos = view->page_top_pos;
1431 if (view->f_ln)
1432 wscrl(view->lnno_win, -n);
1433 wscrl(view->pad, -n);
1434 view->cury = 0;
1435 wmove(view->pad, view->cury, 0);
1436 /** Fill in Page Top */
1437 for (i = 0; i < n; i++) {
1439 if (view->f_eod)
1440 break;
1441 fmt_line(view);
1442 display_line(view);
1443 }
1444 if (view->page_bot_ln - n >= view->scroll_lines)
1445 view->page_bot_ln -= n;
1446 else
1447 view->page_bot_ln = view->scroll_lines;
1448 view->ln = view->page_bot_ln;
1449 view->page_bot_pos = view->ln_tbl[view->page_bot_ln];
1450 view->file_pos = view->page_bot_pos;
1451 return;
1452}
1453/** @brief Get Next Line from View->buf
1454 @ingroup view_navigation
1455 @param view struct
1456 @param pos buffer offset
1457 @returns file position of next line
1458 @details gets view->line_in_s
1459 */
1460off_t get_next_line(View *view, off_t pos) {
1461 char c;
1462 char *line_in_p;
1463
1464 view->file_pos = pos;
1465 view->f_eod = false;
1466 get_next_char();
1467 if (view->f_eod)
1468 return view->file_pos;
1469 line_in_p = view->line_in_s;
1470 view->line_in_beg_p = view->line_in_s;
1472 while (1) {
1473 if (c == '\n')
1474 break;
1475 if (line_in_p >= view->line_in_end_p)
1476 break;
1477 *line_in_p++ = c;
1478 get_next_char();
1479 if (view->f_eod)
1480 return view->file_pos;
1481 }
1482 *line_in_p = '\0';
1483 if (view->f_squeeze) {
1484 while (1) {
1485 get_next_char();
1486 if (c != '\n')
1487 break;
1488 if (view->f_eod)
1489 break;
1490 }
1491 get_prev_char();
1492 if (view->f_eod)
1493 return view->file_pos;
1494 }
1495 return view->file_pos;
1496}
1497/** @brief Get Previous Line from View->buf
1498 @ingroup view_navigation
1499 @param view data structure
1500 @param pos buffer offset
1501 @returns file position of previous line
1502 */
1503off_t get_prev_line(View *view, off_t pos) {
1504 pos = get_pos_prev_line(view, pos);
1505 view->file_pos = pos;
1507 return pos;
1508}
1509/** @brief Get Position of Next Line
1510 @ingroup view_navigation
1511 @param view data structure
1512 @param pos buffer offset
1513 @returns file position of next line
1514 */
1515off_t get_pos_next_line(View *view, off_t pos) {
1516 char c;
1517 if (pos == view->file_size) {
1518 view->f_eod = true;
1519 return view->file_pos;
1520 } else
1521 view->f_eod = false;
1522 view->file_pos = pos;
1523 get_next_char();
1524 if (view->f_eod)
1525 return view->file_pos;
1526 if (view->f_squeeze) {
1527 while (1) {
1528 if (c != '\n')
1529 break;
1530 get_next_char();
1531 if (view->f_eod)
1532 return view->file_pos;
1533 }
1534 get_prev_char();
1535 }
1536 while (!view->f_eod) {
1537 if (c == '\n') {
1538 break;
1539 }
1540 get_next_char();
1541 }
1542 return view->file_pos;
1543}
1544/** @brief Get Position of Previous Line
1545 @ingroup view_navigation
1546 @param view data structure
1547 @param pos buffer offset
1548 @returns file position of previous line
1549 */
1550off_t get_pos_prev_line(View *view, off_t pos) {
1551 view->file_pos = pos;
1552 if (view->file_pos == 0) {
1553 view->f_bod = true;
1554 return view->file_pos;
1555 }
1556 while (view->ln_tbl[view->ln] >= view->file_pos) {
1557 if (view->ln == 0)
1558 break;
1559 view->ln--;
1560 }
1561 view->file_pos = view->ln_tbl[view->ln];
1562 return view->file_pos;
1563}
1564/** @brief Go to Specific File Position
1565 @ingroup view_navigation
1566 @param view data Structure
1567 @param go_to_pos
1568*/
1569void go_to_position(View *view, off_t go_to_pos) {
1570 view->file_pos = go_to_pos;
1571 view->page_top_pos = view->file_pos;
1572 view->page_bot_pos = view->file_pos;
1573 sync_ln(view);
1574 next_page(view);
1575}
1576/** @brief Go to End of File
1577 @ingroup view_navigation
1578 @param view data structure
1579 */
1580void go_to_eof(View *view) {
1581 view->file_pos = view->file_size;
1582 sync_ln(view);
1583 if (view->ln > view->scroll_lines)
1584 view->ln -= view->scroll_lines;
1585 else
1586 view->page_top_ln = 0;
1587 view->page_top_ln = view->ln;
1588 view->page_top_pos = view->ln_tbl[view->ln];
1590 view->file_pos = view->page_top_pos;
1591 view->cury = 0;
1592 next_page(view);
1593}
1594/** @brief Go to Percent of File
1595 @ingroup view_navigation
1596 @param view data structure
1597 @param percent of file
1598*/
1599void go_to_percent(View *view, int percent) {
1600 if (view->file_size < 0) {
1601 Perror("Cannot determine file length");
1602 return;
1603 }
1604 view->file_pos = (percent * view->file_size) / 100;
1605 sync_ln(view);
1606 if (view->ln > view->scroll_lines)
1607 view->page_top_ln = view->ln - view->scroll_lines;
1608 else
1609 view->page_top_ln = 0;
1610 view->page_top_pos = view->ln_tbl[view->page_top_ln];
1612 view->file_pos = view->page_top_pos;
1613 next_page(view);
1614}
1615/** @brief Go to Specific Line
1616 @ingroup view_navigation
1617 @param view data structure
1618 @param line_idx line number to go to (1-based index)
1619 @returns 0 on success, EOF if line index is out of bounds or end of data
1620 is reached
1621 */
1622int go_to_line(View *view, off_t line_idx) {
1623 if (line_idx <= 1) {
1624 go_to_position(view, 0);
1625 return EOF;
1626 }
1627 sync_ln(view);
1628 view->page_top_pos = view->file_pos;
1629 view->page_bot_pos = view->file_pos;
1630 view->file_pos = view->page_top_pos;
1631 next_page(view);
1632 return 0;
1633}
1634/** @brief Initialize Line Table
1635 @ingroup view_navigation
1636 @param view data structure
1637 @details The line table is initialized with a specified increment size
1638 (LINE_TBL_INCR). Memory is allocated for the line table, and the first
1639 entry is set to 0, indicating the file position of the first line. The
1640 line index (view->ln) is initialized to 0.
1641 */
1642void initialize_line_table(View *view) {
1644 view->ln_tbl = (off_t *)calloc(view->ln_tbl_size, sizeof(off_t));
1645 if (view->ln_tbl == nullptr) {
1646 Perror("Memory allocation failed");
1647 exit(EXIT_FAILURE);
1648 }
1649 view->ln_max_pos = 0;
1650 view->ln_tbl[0] = 0;
1651 view->ln = 0;
1652}
1653void destroy_line_table(View *view) {
1654 if (view->ln_tbl == nullptr)
1655 return;
1656 free(view->ln_tbl);
1657 view->ln_tbl = nullptr;
1658 view->ln_tbl_size = 0;
1659 view->ln_max_pos = 0;
1660 view->ln = 0;
1661}
1662/** @brief Increment Line Index and Update Line Table
1663 @ingroup view_navigation
1664 @param view data structure
1665 @details This function is called when a line feed character is
1666 encountered while reading the file. It increments the line index
1667 (view->ln) and checks if the current file position exceeds the maximum
1668 position recorded in the line table. If it does, it updates the line
1669 table with the new file position. If the line index exceeds the current
1670 size of the line table, the table is resized by allocating more memory.
1671 */
1672void increment_ln(View *view) {
1673 view->ln++;
1674 if (view->file_pos <= view->ln_max_pos)
1675 return;
1676 if (view->ln > view->ln_tbl_size - 1) {
1678 view->ln_tbl =
1679 (off_t *)realloc(view->ln_tbl, view->ln_tbl_size * sizeof(off_t));
1680 if (view->ln_tbl == nullptr) {
1681 Perror("Memory allocation failed");
1682 exit(EXIT_FAILURE);
1683 }
1684 }
1685 view->ln_tbl_cnt = view->ln;
1686 view->ln_max_pos = view->file_pos;
1687 view->ln_tbl[view->ln] = view->file_pos;
1688}
1689/** @brief Synchronize Line Table with Current File Position
1690 @ingroup view_navigation
1691 @param view data Structure
1692 @details The line table (view->ln_tbl) is an array that stores the file
1693 position of each line. The index (view->ln + 1) corresponds to the
1694 current line number. (the line number table is 0-based, while line
1695 numbering starts at 1).
1696 @details If the line or position requested is not in the line table, this
1697 function reads forward to sycn.
1698 If the line or positione requested is behind the current line
1699 table index, the line index will be decremented it matches the file
1700 position.
1701 */
1702void sync_ln(View *view) {
1703 int c = 0;
1704 off_t idx;
1705 off_t target_pos;
1706 if (view->ln_tbl[view->ln] == view->file_pos)
1707 return;
1708 target_pos = view->file_pos;
1709 view->file_pos = view->ln_tbl[view->ln_tbl_cnt];
1710 if (view->file_pos < target_pos) {
1711 view->ln = view->ln_tbl_cnt;
1712 while (view->ln_max_pos < target_pos) {
1713 get_next_char();
1714 if (view->f_eod)
1715 return;
1716 }
1717 } else if (view->ln_tbl[view->ln] > target_pos) {
1718 idx = view->ln - 1;
1719 while (view->ln_tbl[idx] > target_pos)
1720 idx--;
1721 view->ln = idx;
1722 view->file_pos = view->ln_tbl[view->ln];
1723 } else {
1724 view->ln = view->ln_tbl_cnt;
1725 view->file_pos = view->ln_tbl[view->ln];
1726 }
1727}
1728/*------------------------------------------------------------
1729 END NAVIGATION
1730 BEGIN DISPLAY
1731 ------------------------------------------------------------*/
1732/** @brief Display Line on Pad
1733 @ingroup view_display
1734 param View *view data structure
1735 @details This function displays a single line of text on the ncurses
1736 pad.
1737 If line numbering is enabled (view->f_ln), it is formatted and
1738 displayed at the beginning of the line with the specified attributes and
1739 color pair.
1740 @details Because get_next_char calls increment_ln upon encountering a
1741 line feed and increment_ln advances view->ln after updating the line
1742 table, the line number displayed is one greater than the index to the
1743 line table. That means the line counter begins with 1, while the table
1744 origin is 0.
1745 */
1746void display_line(View *view) {
1747 char ln_s[16];
1748
1749 if (view->cury < 0)
1750 view->cury = 0;
1751 if (view->cury > view->scroll_lines - 1)
1752 view->cury = view->scroll_lines - 1;
1753 if (view->f_ln) {
1754 ssnprintf(ln_s, 8, "%7jd", view->ln);
1755 wmove(view->lnno_win, view->cury, 0);
1756 wclrtoeol(view->lnno_win);
1757 mvwaddstr(view->lnno_win, view->cury, 0, ln_s);
1758 }
1759 wmove(view->pad, view->cury, 0);
1760 wclrtoeol(view->pad);
1761 wadd_wchstr(view->pad, view->cmplx_buf);
1762 view->cury++;
1763}
1764/** @brief Format Line for Display
1765 @ingroup view_display
1766 @param view pointer to View structure containing line input and output
1767 buffers
1768 @return length of formatted line in characters
1769 @details This function processes the input line from view->line_in_s,
1770 handling ANSI escape sequences for text attributes and colors, as well as
1771 multi-byte characters. It converts the input line into a formatted line
1772 suitable for display in the terminal, storing the result in
1773 view->cmplx_buf and view->stripped_line_out. The function returns the
1774 length of the formatted line in characters, which may be used for
1775 tracking the maximum column width of the displayed content.
1776 */
1777int fmt_line(View *view) {
1778 char ansi_tok[MAXLEN];
1779 int i = 0, j = 0;
1780 int len = 0;
1781 const char *s;
1782 attr_t attr = WA_NORMAL;
1783 int cpx = cp_nt;
1784 cchar_t cc = {0};
1785 wchar_t wstr[2] = {L'\0', L'\0'};
1786
1787 char *in_str = view->line_in_s;
1788 cchar_t *cmplx_buf = view->cmplx_buf;
1789
1791 mbstate_t mbstate;
1792 memset(&mbstate, 0, sizeof(mbstate));
1793 while (in_str[i] != '\0') {
1794 if (in_str[i] == '\033' && in_str[i + 1] == '[') {
1795 /** This section calls the decoder for ANSI escape sequences */
1796 len = strcspn(&in_str[i], "mK ") + 1;
1797 memcpy(ansi_tok, &in_str[i], len + 1);
1798 ansi_tok[len] = '\0';
1799 if (ansi_tok[0] == '\0') {
1800 if (i + 2 < MAXLEN)
1801 i += 2;
1802 continue;
1803 }
1804 if (len == 0 || in_str[i + len - 1] == ' ') {
1805 i += 2;
1806 continue;
1807 } else if (in_str[i + len - 1] == 'K') {
1808 i += len;
1809 continue;
1810 }
1811 parse_ansi_str(ansi_tok, &attr, &cpx);
1812 i += len;
1813 } else {
1814 /** This section converts the input string to wide characters,
1815 handling tabs and multi-byte characters, and stores the result
1816 in the complex buffer for display. ANSI escape sequences are
1817 ignored in this section since they are handled separately above.
1818 */
1819 if (in_str[i] == '\033') {
1820 i++;
1821 continue;
1822 }
1823 s = &in_str[i];
1824 if (*s == '\t') {
1825 do {
1826 wstr[0] = L' ';
1827 wstr[1] = L'\0';
1828 setcchar(&cc, wstr, attr, cpx, nullptr);
1829 view->stripped_line_out[j] = ' ';
1830 cmplx_buf[j++] = cc;
1831 } while ((j < PAD_COLS - 2) && (j % view->tab_stop != 0));
1832 i++;
1833 } else {
1834 wstr[1] = L'\0';
1835 len = mbrtowc(wstr, s, MB_CUR_MAX, &mbstate);
1836 if (len <= 0) {
1837 wstr[0] = L'?';
1838 wstr[1] = L'\0';
1839 len = 1;
1840 }
1841 if (setcchar(&cc, wstr, attr, cpx, nullptr) != ERR) {
1842 if (len > 0 && (j + len) < PAD_COLS - 1) {
1843 view->stripped_line_out[j] = *s;
1844 cmplx_buf[j++] = cc;
1845 }
1846 }
1847 i += len;
1848 }
1849 }
1850 }
1851 if (j > view->maxcol)
1852 view->maxcol = j;
1853 wstr[0] = '\0';
1854 wstr[1] = '\0';
1855 setcchar(&cc, wstr, WA_NORMAL, cpx, nullptr);
1856 cmplx_buf[j] = cc;
1857 view->stripped_line_out[j] = '\0';
1858 return j;
1859}
1860/** @brief Parse ANSI SGR Escape Sequence
1861 @ingroup view_display
1862 @param ansi_str is the ANSI escape sequence string to parse
1863 @param attr is a pointer to an attr_t variable where the parsed
1864 attributes will be stored
1865 @param cpx is a pointer to an int variable where the parsed color pair
1866 index will be stored
1867 @details This function parses an ANSI escape sequence and updates the color
1868 pair and color tables according to the attributes specified. Despite the
1869 depth of nested conditionals, with an understanding of the ANSI Select
1870 Graphics Rendition (SGR) scheme, which is defined in the ECMA-48 standard
1871 (ISO/IEC 6429), you will find that it is exceptionally simple and
1872 straightforward.
1873
1874 @verbatim
1875
1876 This function converts the following SGR specification types to the
1877 appropriate curses color pair index for use in the terminal display.
1878
1879 RGB:
1880
1881 foreground \033[38;2;r;g;bm
1882 background \033[48;2;r;g;bm
1883
1884 Where r, g, b are the red, green, and blue color components
1885 (0-255)
1886
1887 XTERM 256-color:
1888
1889 foreground \033[38;5;xm
1890 background \033[48;5;xm
1891
1892 Where x is the 256-color index (0-255)
1893
1894 uses xterm256_idx_to_rgb() to convert the 256-color index to
1895 RGB
1896
1897 8-color:
1898
1899 foreground \033[3cm
1900 background \033[4cm
1901
1902 Where c is the color code (0 for black, 1 for red, 2 for green, 3
1903 for yellow, 4 for blue, 5 for magenta, 6 for cyan, 7 for white).
1904
1905 Attributes:
1906
1907 \033[am
1908
1909 Where a is the attribute code (1 for bold, 2 for dim, 3 for italic,
1910 4 for underline, 5 for blink, 7 for reverse, 8 for invis). The function
1911 also supports resetting attributes and colors to default using \033[0m.
1912
1913 @sa xterm256_idx_to_rgb(), rgb_to_curses_clr(), extended_pair_content(),
1914 get_clr_pair()
1915
1916 @endverbatim
1917*/
1918void parse_ansi_str(char *ansi_str, attr_t *attr, int *cpx) {
1919 char *tok;
1920 char t0, t1;
1921 char tstr[3];
1922 int len, x_idx;
1923 int fg, bg;
1924 int fg_clr, bg_clr;
1925 char *ansi_p = ansi_str + 2;
1926 extended_pair_content(*cpx, &fg_clr, &bg_clr);
1927 fg = fg_clr;
1928 bg = bg_clr;
1929 RGB rgb;
1930 tok = strtok((char *)ansi_p, ";m");
1931 bool a_toi_error = false;
1932 while (1) {
1933 if (tok == nullptr || *tok == '\0')
1934 break;
1935 len = strlen(tok);
1936 if (len == 2) {
1937 t0 = tok[0];
1938 t1 = tok[1];
1939 if (t0 == '3' || t0 == '4') {
1940 if (t1 == '8') {
1941 tok = strtok(nullptr, ";m");
1942 if (tok != nullptr) {
1943 if (*tok == '5') {
1944 tok = strtok(nullptr, ";m");
1945 if (tok != nullptr) {
1946 x_idx = a_toi(tok, &a_toi_error);
1947 rgb = xterm256_idx_to_rgb(x_idx);
1948 }
1949 } else if (*tok == '2') {
1950 tok = strtok(nullptr, ";m");
1951 rgb.r = a_toi(tok, &a_toi_error);
1952 tok = strtok(nullptr, ";m");
1953 rgb.g = a_toi(tok, &a_toi_error);
1954 tok = strtok(nullptr, ";m");
1955 rgb.b = a_toi(tok, &a_toi_error);
1956 }
1957 }
1958 if (t0 == '3')
1959 fg_clr = rgb_to_curses_clr(&rgb);
1960 else if (t0 == '4')
1961 bg_clr = rgb_to_curses_clr(&rgb);
1962 } else if (t1 == '9') {
1963 if (t0 == '3')
1964 fg_clr = CLR_NT_FG;
1965 else if (t0 == '4')
1966 bg_clr = CLR_NT_BG;
1967 } else if (t1 >= '0' && t1 <= '7') {
1968 if (t0 == '3') {
1969 tstr[0] = t1;
1970 tstr[1] = '\0';
1971 x_idx = a_toi(tstr, &a_toi_error);
1972 rgb = xterm256_idx_to_rgb(x_idx);
1973 fg_clr = rgb_to_curses_clr(&rgb);
1974 } else if (t0 == '4') {
1975 tstr[0] = t1;
1976 tstr[1] = '\0';
1977 x_idx = a_toi(tstr, &a_toi_error);
1978 rgb = xterm256_idx_to_rgb(x_idx);
1979 bg_clr = rgb_to_curses_clr(&rgb);
1980 }
1981 }
1982 } else if (t0 == '0') {
1983 *tok = t1;
1984 len = 1;
1985 }
1986 }
1987 if (len == 1) {
1988 if (*tok == '0') {
1989 *attr = WA_NORMAL;
1990 fg_clr = CLR_NT_FG;
1991 bg_clr = CLR_NT_BG;
1992 } else {
1993 switch (a_toi(tok, &a_toi_error)) {
1994 case 1:
1995 *attr |= WA_BOLD;
1996 break;
1997 case 2:
1998 *attr |= WA_DIM;
1999 break;
2000 case 3:
2001 *attr |= WA_ITALIC;
2002 break;
2003 case 4:
2004 *attr |= WA_UNDERLINE;
2005 break;
2006 case 5:
2007 *attr |= WA_BLINK;
2008 break;
2009 case 7:
2010 *attr |= WA_REVERSE;
2011 break;
2012 case 8:
2013 *attr |= WA_INVIS;
2014 break;
2015 default:
2016 break;
2017 }
2018 }
2019 } else if (len == 0) {
2020 *attr = WA_NORMAL;
2021 fg_clr = CLR_NT_FG;
2022 bg_clr = CLR_NT_BG;
2023 }
2024 tok = strtok(nullptr, ";m");
2025 }
2026 if (!a_toi_error && (fg_clr != fg || bg_clr != bg)) {
2027 clr_pair_idx = get_clr_pair(fg_clr, bg_clr);
2028 *cpx = clr_pair_idx;
2029 }
2030 return;
2031}
2032/** @brief Display Command Line Prompt
2033 @ingroup view_display
2034 @param view is the current view data structure
2035 @param s is the prompt string */
2036int display_prompt(View *view, char *s) {
2037 char message_str[PAD_COLS + 1];
2038 int l;
2039 l = strnz__cpy(message_str, s, PAD_COLS);
2040 wmove(view->cmdln_win, view->cmd_line, 0);
2041 if (l != 0) {
2042 wclrtoeol(view->cmdln_win);
2043 // wbkgrndset(view->cmdln_win, &CC_NT_HL_REV);
2044 // mvwadd_wchnstr(view->cmdln_win, view->cmd_line, 0, &ran, 1);
2045 // mvwadd_wchnstr(view->cmdln_win, view->cmd_line, 0, &sp, 1);
2046 wbkgrndset(view->cmdln_win, &CC_NT_REV);
2047 mvwaddstr(view->cmdln_win, view->cmd_line, 0, " ");
2048 mvwaddstr(view->cmdln_win, view->cmd_line, 1, message_str);
2049 waddstr(view->cmdln_win, " ");
2050 wbkgrndset(view->cmdln_win, &CC_NT);
2051 getyx(view->cmdln_win, view->cmd_line, view->curx);
2052 wmove(view->cmdln_win, view->cmd_line, view->curx);
2053 }
2054 return (view->curx);
2055}
2056/** @brief Remove File
2057 @ingroup view_engine
2058 @param view is the current view data structure */
2059void remove_file(View *view) {
2060 char c;
2061 if (view->f_at_end_remove) {
2062 wmove(view->pad, view->cmd_line, 0);
2063 waddstr(view->pad, "Remove File (Y or N)->");
2064 wclrtoeol(view->pad);
2065 update_panels();
2066 doupdate();
2067 c = (char)vgetch(view->cmdln_win, -1);
2068 waddch(view->pad, (char)toupper(c));
2069 if (c == 'Y' || c == 'y')
2070 remove(view->cur_file_str);
2071 }
2072}
2073/** @brief Display View Help File
2074 @ingroup view_display
2075 @param init is the current initialization data structure.
2076 @details The current View context is set aside by assigning the view
2077 structure to "view_save" while the help file is displayed using a new,
2078 separate view structure.
2079 The help file is specified by the VIEW_HELP_FILE macro can be set
2080 to a default help file path or overridden by the user through an
2081 environment variable.
2082 After the help file is closed, the original view is restored and
2083 the page is redisplayed.
2084 It may be necessary to reassign view after calling this function
2085 because the init->view pointer is temporarily set to nullptr during the
2086 help file display, and the original view is restored afterward.
2087 The default screen size for help can be set in the code below. If
2088 set to 0, popup_view will determine reasonable maximal size based on the
2089 terminal dimensions.
2090 The help file may contain Unicode characters and ANSI escape
2091 sequences for formatting, which will be properly handled and displayed by
2092 popup_view. */
2093void view_display_help(Init *init) {
2094 char tmp_str[MAXLEN];
2095 int eargc = 0;
2096 char *eargv[MAXARGS];
2097 View *view = init->view;
2098 if (view->f_help_spec && view->help_spec[0] != '\0')
2099 strnz__cpy(tmp_str, view->help_spec, MAXLEN - 1);
2100 else {
2101 strnz__cpy(tmp_str, init->mapp_help, MAXLEN - 1);
2102 strnz__cat(tmp_str, "/", MAXLEN - 1);
2104 }
2105 eargv[eargc++] = strdup("view");
2106 eargv[eargc++] = strdup("-N");
2107 eargv[eargc++] = strdup("f");
2108 eargv[eargc++] = strdup(tmp_str);
2109 eargv[eargc] = nullptr;
2110 init->lines = 48;
2111 init->cols = 72;
2112 init->begy = 0;
2113 init->begx = 0;
2114 strnz__cpy(init->title, "View Help", MAXLEN - 1);
2115 popup_view(init, eargc, eargv, init->lines, init->cols, init->begy,
2116 init->begx);
2117 destroy_argv(eargc, eargv);
2118 init->view->f_redisplay_page = true;
2119}
2120/*------------------------------------------------------------
2121 END DISPLAY
2122 ------------------------------------------------------------*/
2123/** @brief use form to enter a file specification
2124 @ingroup view_engine
2125 @param init data structure
2126 @param file_spec - pointer to file specification
2127 the file_spec
2128 @returns true if successful
2129 @details the user must provide a character array large enough to
2130 hold file_spec without overflowing
2131 */
2132bool enter_file_spec(Init *init, char *file_spec) {
2133 char earg_str[MAXLEN];
2134 char *eargv[MAXARGS];
2135 int eargc;
2136 char tmp_dir[MAXLEN];
2137 char tmp_str[MAXLEN];
2138 char tmp_spec[MAXLEN];
2139 int rc = false;
2140 FILE *tmp_fp;
2141 View *view = init->view;
2142 strnz__cpy(tmp_dir, init->mapp_home, MAXLEN - 1);
2143 strnz__cat(tmp_dir, "/tmp", MAXLEN - 1);
2144 expand_tilde(tmp_dir, MAXLEN - 1);
2145 if (!mk_dir(tmp_dir)) {
2146 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__, __LINE__ - 2);
2147 strnz__cpy(em1, "Unable to ", MAXLEN - 1);
2148 strnz__cat(em1, "mkdir", MAXLEN - 1);
2149 strnz__cat(em1, tmp_dir, MAXLEN - 1);
2150 strerror_r(errno, em2, MAXLEN - 1);
2152 return false;
2153 }
2154 while (rc == false) {
2155 strnz__cpy(tmp_spec, tmp_dir, MAXLEN - 1);
2156 strnz__cat(tmp_spec, "/tmp_XXXXXX", MAXLEN - 1);
2157 view->in_fd = mkstemp(tmp_spec);
2158 if (view->in_fd == -1) {
2159 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__, __LINE__ - 2);
2160 strnz__cpy(em1, "unable to ", MAXLEN - 1);
2161 strnz__cat(em1, "mkstemp ", MAXLEN - 1);
2162 strnz__cat(em1, tmp_spec, MAXLEN - 1);
2163 strerror_r(errno, em2, MAXLEN - 1);
2164 display_error(em0, em1, nullptr, nullptr);
2165 return false;
2166 }
2167 /** call form to get file_name
2168 write the name to a temporary file */
2169
2170 strnz__cpy(earg_str, "form -d file_name.f -o ", MAXLEN - 1);
2171 strnz__cat(earg_str, tmp_spec, MAXLEN - 1);
2172 eargc = str_to_args(eargv, earg_str, MAX_ARGS);
2173 rc = popup_form(init, eargc, eargv, view->begy + view->lines - 7, 4);
2174 destroy_argv(eargc, eargv);
2176 if (rc == FA_CANCEL || rc == 'q' || rc == 'Q' || rc == KEY_F(9))
2177 return false;
2178 close(view->in_fd);
2179 tmp_fp = fopen(tmp_spec, "r");
2180 if (tmp_fp == nullptr) {
2181 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__, __LINE__ - 2);
2182 strnz__cpy(em1, "unable to ", MAXLEN - 1);
2183 strnz__cat(em1, "fopen ", MAXLEN - 1);
2184 strnz__cat(em1, tmp_spec, MAXLEN - 1);
2185 strerror_r(errno, em2, MAXLEN - 1);
2187 return false;
2188 }
2189 fgets(tmp_str, MAXLEN - 1, tmp_fp);
2190 strnz(tmp_str, MAXLEN - 1);
2191 fclose(tmp_fp);
2192 unlink(tmp_spec);
2193 if (!verify_spec_arg(file_spec, tmp_str, "~/menuapp/tmp", ".",
2194 S_WCOK | S_QUIET)) {
2195 ssnprintf(em0, MAXLEN - 1, "Unable to open %s for writing",
2196 tmp_str);
2197 strnz__cpy(em1, "Try again? y (yes) or n (no) ", MAXLEN - 1);
2198 rc = display_error(em0, em1, nullptr, nullptr);
2199 if (rc == 'y' || rc == 'Y')
2200 continue;
2201
2202 } else
2203 return true;
2204 }
2205 return true;
2206}
2207
2208/** @brief Initialize View Stack
2209 @ingroup view_engine
2210 @param s pointer to ViewStack structure
2211 @param initial_capacity initial capacity of the stack
2212 @returns true if successful, false if memory allocation fails
2213 @details This function initializes a ViewStack structure by allocating
2214 memory for the items array with the specified initial capacity. It sets
2215 the capacity and top index accordingly. If memory allocation fails, it
2216 returns false.
2217 */
2218bool view_stack_init(ViewStack *s, size_t initial_capacity) {
2219 s->items = malloc(initial_capacity * sizeof(View));
2220 if (!s->items)
2221 return false;
2222 s->capacity = initial_capacity;
2223 s->top = 0;
2224 return true;
2225}
2226/** @brief Push Item onto View Stack
2227 @ingroup view_engine
2228 @param s pointer to ViewStack structure
2229 @param item View item to push onto the stack
2230 @returns true if successful, false if memory allocation fails during
2231 resizing
2232 @details This function pushes a View item onto the stack. If the stack is
2233 full, it reallocates memory to double the capacity. If memory allocation
2234 fails during resizing, it returns false.
2235 */
2236bool view_stack_push(ViewStack *s, View item) {
2237 if (s->top >= s->capacity) {
2238 size_t new_capacity = s->capacity * 2;
2239 View *new_items = realloc(s->items, new_capacity * sizeof(View));
2240 if (!new_items)
2241 return false; // Out of memory
2242 s->items = new_items;
2243 s->capacity = new_capacity;
2244 }
2245 s->items[s->top++] = item; // Structure copy
2246 return true;
2247}
2248/** @brief Pop Item from View Stack
2249 @ingroup view_engine
2250 @param s pointer to ViewStack structure
2251 @param out_item pointer to View structure where the popped item will be
2252 stored
2253 @returns true if successful, false if the stack is empty (underflow)
2254 @details This function pops a View item from the stack and stores it in
2255 the provided out_item pointer. If the stack is empty, it returns false.
2256 */
2257bool view_stack_pop(ViewStack *s, View *out_item) {
2258 if (s->top == 0)
2259 return false; // Stack underflow
2260 *out_item = s->items[--s->top];
2261 return true;
2262}
2263/** @brief Peek at Top Item of View Stack
2264 @ingroup view_engine
2265 @param s pointer to ViewStack structure
2266 @param out_item pointer to View structure where the top item will be
2267 stored
2268 @returns true if successful, false if the stack is empty
2269 @details This function retrieves the top item of the stack without
2270 removing it. If the stack is empty, it returns false.
2271 */
2272bool view_stack_peek(const ViewStack *s, View *out_item) {
2273 if (s->top == 0)
2274 return false;
2275 *out_item = s->items[s->top - 1];
2276 return true;
2277}
2278/** @brief Free View Stack
2279 @ingroup view_engine
2280 @param s pointer to ViewStack structure
2281 @details This function frees the memory allocated for the items array in
2282 the ViewStack structure and resets the capacity and top index to zero.
2283 */
2284void view_stack_free(ViewStack *s) {
2285 free(s->items);
2286 s->items = NULL;
2287 s->capacity = 0;
2288 s->top = 0;
2289}
@ FA_CANCEL
Definition form.h:33
#define PRINTCMD
Definition common.h:47
int popup_form(Init *, int, char **, int, int)
instantiate a form popup window
Definition popups.c:112
#define DEFAULTEDITOR
Definition common.h:37
void destroy_view_win(Init *)
Definition init_view.c:318
int popup_view(Init *, int, char **, int, int, int, int)
instantiate a view popup window
Definition popups.c:144
#define VIEW_HELP_FILE
Definition common.h:35
void destroy_line_table(View *)
size_t rtrim(char *)
Trims trailing spaces from string s in place.
Definition futil.c:338
int cp_nt
Definition dwin.c:183
#define KEY_ALTEND
Definition cm.h:560
#define KEY_ALTHOME
Definition cm.h:557
#define MAX_ARGS
Definition cm.h:46
char em1[MAXLEN]
Definition dwin.c:176
#define MAXARGS
Definition cm.h:48
cchar_t CC_NT
Definition dwin.c:204
int clr_pair_idx
Definition dwin.c:196
cchar_t ran
Definition cm.h:631
cchar_t CC_IND
Definition dwin.c:200
#define S_TOLOWER(c)
Definition cm.h:142
cchar_t sp
Definition cm.h:631
@ CLR_NT_FG
Definition cm.h:188
@ CLR_NT_BG
Definition cm.h:189
#define min(x, y)
min macro evaluates two expressions, returning least result
Definition cm.h:89
char em0[MAXLEN]
Definition dwin.c:175
#define S_QUIET
Definition cm.h:286
#define S_WCOK
Definition cm.h:285
cchar_t CC_NT_REV
Definition dwin.c:205
int cp_nt_rev
Definition dwin.c:184
#define Ctrl(c)
Definition cm.h:52
char em2[MAXLEN]
Definition dwin.c:177
#define CM_VERSION
Definition version.h:7
@ CT_VIEW
Definition menu.h:47
#define LINE_TBL_INCR
Definition view.h:39
#define NMARKS
Definition view.h:26
char err_msg[MAXLEN]
void go_to_position(View *, long)
#define PAD_COLS
Definition view.h:32
#define NULL_POSITION
Definition view.h:29
#define MAXLEN
Definition curskeys.c:15
char prev_regex_pattern[MAXLEN]
Definition view_engine.c:81
FILE * dbgfp
Definition view_engine.c:82
int cf_accept(WINDOW *win, char *accept_s, int flin, int fcol, int flen)
Accepts input for a field in a C-Menu window.
Definition cm_fields.c:51
int border_title(WINDOW *, char *)
Draw a box with a title around the specified window.
Definition dwin.c:1327
void restore_wins()
Restore all windows after a screen resize.
Definition dwin.c:1225
void view_full_screen_resize(Init *)
Resize the full screen view and its components.
Definition init_view.c:140
void view_boxwin_resize(Init *)
Resize the current window and its box.
Definition init_view.c:346
int vgetch(WINDOW *, int)
Wrapper for wgetch that handles signals and mouse events, and accepts a single character answer.
Definition dwin.c:2258
RGB xterm256_idx_to_rgb(int)
Convert XTerm 256 color index to RGB.
Definition dwin.c:483
int rgb_to_curses_clr(RGB *)
Get color index for RGB color.
Definition dwin.c:432
int get_clr_pair(int, int)
Get color pair index for foreground and background colors.
Definition dwin.c:401
int answer_yn(char *, char *, char *, char *)
Accept a single letter answer.
Definition dwin.c:1406
int Perror(char *)
Display a simple error message window or print to stderr.
Definition dwin.c:1526
int display_error(char *, char *, char *, char *)
Display an error message window or print to stderr.
Definition dwin.c:1467
int shell(char *)
Execute a shell command.
Definition exec.c:78
int full_screen_shell(char *)
Execute a shell command in full screen mode.
Definition exec.c:62
size_t strnz__cpy(char *, const char *, size_t)
safer alternative to strncpy
Definition futil.c:544
int destroy_argv(int argc, char **argv)
Deallocates memory allocated for argument strings in argv.
Definition futil.c:494
size_t strnz(char *, size_t)
terminates string at New Line, Carriage Return, or max_len
Definition futil.c:615
size_t ssnprintf(char *, size_t, const char *,...)
ssnprintf was designed to be a safer alternative to snprintf.
Definition futil.c:420
bool expand_tilde(char *, int)
Replaces "~/" in string with the user's home directory.
Definition futil.c:970
size_t strip_ansi(char *, char *)
Strips ANSI SGR escape sequences (ending in 'm') from string s to d.
Definition futil.c:829
bool mk_dir(char *dir)
If directory doesn't exist, make it.
Definition futil.c:1320
size_t strnz__cat(char *, const char *, size_t)
safer alternative to strncat
Definition futil.c:573
bool str_subc(char *, char *, char, char *, int)
Replaces "ReplaceChr" in "s" with "Withstr" in "d" won't copy more than "l" bytes to "d" Replaces all...
Definition futil.c:690
size_t strnlf(char *, size_t)
terminates string with line feed
Definition futil.c:633
int a_toi(char *, bool *)
a safer alternative to atoi() for converting ASCII strings to integers.
Definition futil.c:769
bool base_name(char *, char *)
Returns the base name of a file specification.
Definition futil.c:1123
int str_to_args(char **, char *, int)
Converts a string into an array of argument strings.
Definition futil.c:440
int view_init_input(Init *, char *)
Initialize the input for the C-Menu View.
Definition init_view.c:447
View * destroy_view(Init *init)
Destroy View structure.
Definition mem.c:356
bool verify_spec_arg(char *, char *, char *, char *, int)
Verify file specification argument.
Definition mem.c:380
int view_file(Init *)
Start view.
bool view_stack_pop(ViewStack *, View *)
Pop Item from View Stack.
bool enter_file_spec(Init *, char *)
use form to enter a file specification
int write_view_buffer(Init *, bool)
Write buffer contents to files.
void lp(View *, char *)
Send File to Print Queue.
#define get_prev_char()
read the previous characater from the virtual fileThere is no need to track line numbers when moving ...
Definition view_engine.c:68
bool view_stack_peek(const ViewStack *, View *)
Peek at Top Item of View Stack.
void cat_file(View *)
Concatenate File to Standard Output.
void remove_file(View *)
Remove File.
void view_stack_free(ViewStack *)
Free View Stack.
int get_cmd_arg(View *, char *)
Get Command Argument from User Input.
int view_cmd_processor(Init *)
Main Command Processing Loop for View.
bool view_stack_init(ViewStack *, size_t)
Initialize View Stack.
int get_cmd_char(View *, off_t *)
Get Command Character from User Input.
void build_prompt(View *)
Build Prompt String.
bool view_stack_push(ViewStack *, View)
Push Item onto View Stack.
#define get_next_char()
read the next characater from the virtual file
Definition view_engine.c:45
void sync_ln(View *)
Synchronize Line Table with Current File Position.
off_t get_next_line(View *, off_t)
Get Next Line from View->buf.
void go_to_eof(View *)
Go to End of File.
void prev_page(View *)
display previous page
void initialize_line_table(View *)
Initialize Line Table.
bool search(View *, int, char *)
Search for Regular Expression Pattern.
void scroll_down_n_lines(View *, int)
Scroll N Lines.
off_t get_pos_prev_line(View *, off_t)
Get Position of Previous Line.
void scroll_up_n_lines(View *, int)
Scroll Up N Lines.
off_t get_prev_line(View *, off_t)
Get Previous Line from View->buf.
int go_to_line(View *, off_t)
Go to Specific Line.
void next_page(View *)
Advance to Next Page.
void go_to_mark(View *, int)
Go to Mark.
void increment_ln(View *)
Increment Line Index and Update Line Table.
void go_to_percent(View *, int)
Go to Percent of File.
off_t get_pos_next_line(View *, off_t)
Get Position of Next Line.
void display_line(View *)
Display Line on Padparam View *view data structure.
int pad_refresh(View *)
Refresh Pad and Line Number Window.
int display_prompt(View *, char *)
Display Command Line Prompt.
int fmt_line(View *)
Format Line for Display.
void parse_ansi_str(char *, attr_t *, int *)
Parse ANSI SGR Escape Sequence.
void view_display_help(Init *)
Display View Help File.
void view_display_page(View *)
Display Current Page.
char title[MAXLEN]
Definition common.h:129
char mapp_help[MAXLEN]
Definition common.h:148
int begx
Definition common.h:118
int cols
Definition common.h:116
char mapp_home[MAXLEN]
Definition common.h:146
int begy
Definition common.h:117
View * view
Definition common.h:188
int lines
Definition common.h:115
char editor[MAXLEN]
Definition common.h:171
int r
Definition cm.h:382
int b
Definition cm.h:382
int g
Definition cm.h:382
char in_spec[MAXLEN]
Definition view.h:136
int cury
Definition view.h:117
int argc
Definition view.h:63
char ** argv
Definition view.h:64
int smaxrow
Definition view.h:128
bool f_search_complete
Definition view.h:103
int next_cmd_char
Definition view.h:93
char * buf
Definition view.h:161
char * line_out_p
Definition view.h:114
int curx
Definition view.h:118
char * line_in_end_p
Definition view.h:116
int in_fd
Definition view.h:154
off_t srch_curr_pos
Definition view.h:150
WINDOW * pad
Definition view.h:82
off_t page_top_pos
Definition view.h:148
off_t file_pos
Definition view.h:146
off_t page_top_ln
Definition view.h:178
char prompt_str[MAXLEN]
Definition view.h:61
int smincol
Definition view.h:126
off_t prev_file_pos
Definition view.h:147
bool f_bod
Definition view.h:95
bool f_first_iter
Definition view.h:102
char line_in_s[PAD_COLS]
Definition view.h:110
char help_spec[MAXLEN]
Definition view.h:138
bool f_redisplay_page
Definition view.h:100
char * file_spec_ptr
Definition view.h:142
char * next_file_spec_ptr
Definition view.h:143
bool f_is_pipe
Definition view.h:98
bool f_ignore_case
Definition view.h:66
bool f_help_spec
Definition view.h:141
char cmd[MAXLEN]
Definition view.h:59
off_t ln
Definition view.h:172
int first_match_x
Definition view.h:132
WINDOW * lnno_win
Definition view.h:76
WINDOW * cmdln_win
Definition view.h:78
int smaxcol
Definition view.h:130
off_t * ln_tbl
Definition view.h:174
int cols
Definition view.h:54
bool f_full_screen
Definition view.h:104
int ln_win_cols
Definition view.h:170
bool f_ln
Definition view.h:171
bool f_strip_ansi
Definition view.h:69
bool f_eod
Definition view.h:96
off_t mark_tbl[NMARKS]
Definition view.h:152
int lines
Definition view.h:53
int last_match_x
Definition view.h:134
int pminrow
Definition view.h:122
off_t ln_max_pos
Definition view.h:177
char stripped_line_out[PAD_COLS]
Definition view.h:112
off_t srch_beg_pos
Definition view.h:151
off_t ln_tbl_cnt
Definition view.h:176
bool f_at_end_remove
Definition view.h:67
char * tmp_file_name_ptr
Definition view.h:144
int pmincol
Definition view.h:123
off_t file_size
Definition view.h:145
bool f_squeeze
Definition view.h:68
char tmp_prompt_str[MAXLEN]
Definition view.h:86
char cur_file_str[MAXLEN]
Definition view.h:109
char line_out_s[PAD_COLS]
Definition view.h:111
int sminrow
Definition view.h:124
cchar_t cmplx_buf[PAD_COLS]
Definition view.h:113
int cmd_line
Definition view.h:120
char out_spec[MAXLEN]
Definition view.h:137
int tab_stop
Definition view.h:91
off_t page_bot_ln
Definition view.h:179
char title[MAXLEN]
Definition view.h:62
off_t page_bot_pos
Definition view.h:149
bool f_displaying_help
Definition view.h:101
int begy
Definition view.h:55
int h_shift
Definition view.h:92
int maxcol
Definition view.h:121
int curr_argc
Definition view.h:88
int out_fd
Definition view.h:155
int scroll_lines
Definition view.h:119
char file_name[MAXLEN]
Definition view.h:99
int begx
Definition view.h:56
WINDOW * pad_view_win
Definition view.h:83
off_t ln_tbl_size
Definition view.h:175
char * line_in_beg_p
Definition view.h:115
WINDOW * box_win
Definition view.h:72
char cmd_arg[MAXLEN]
Definition view.h:90
size_t capacity
Definition view.h:185
size_t top
Definition view.h:186
View * items
Definition view.h:184