1 /* sources.c:
2  * ----------
3  *
4  * Source file management routines for the GUI.  Provides the ability to
5  * add files to the list, load files, and display within a curses window.
6  * Files are buffered in memory when they are displayed, and held in
7  * memory for the duration of execution.  If memory consumption becomes a
8  * problem, this can be optimized to unload files which have not been
9  * displayed recently, or only load portions of large files at once. (May
10  * affect syntax highlighting.)
11  *
12  */
13 
14 #if HAVE_CONFIG_H
15 #include "config.h"
16 #endif /* HAVE_CONFIG_H */
17 
18 /* System Includes */
19 #if HAVE_STDIO_H
20 #include <stdio.h>
21 #endif /* HAVE_STDIO_H */
22 
23 #if HAVE_STDLIB_H
24 #include <stdlib.h>
25 #endif /* HAVE_STDLIB_H */
26 
27 #if HAVE_STRING_H
28 #include <string.h>
29 #endif /* HAVE_STRING_H */
30 
31 #ifdef HAVE_SYS_TIME_H
32 #include <sys/time.h>
33 #endif
34 
35 #if HAVE_SYS_STAT_H
36 #include <sys/stat.h>
37 #endif
38 
39 #if HAVE_UNISTD_H
40 #include <unistd.h>
41 #endif /* HAVE_UNISTD_H */
42 
43 #if HAVE_CTYPE_H
44 #include <ctype.h>
45 #endif
46 
47 #ifdef HAVE_STDINT_H
48 #include <stdint.h>
49 #endif
50 
51 /* Local Includes */
52 #include "sys_util.h"
53 #include "stretchy.h"
54 #include "sys_win.h"
55 #include "cgdb.h"
56 #include "highlight.h"
57 #include "tokenizer.h"
58 #include "sources.h"
59 #include "logo.h"
60 #include "fs_util.h"
61 #include "cgdbrc.h"
62 #include "highlight_groups.h"
63 #include "interface.h"
64 #include "tgdb.h"
65 
66 int sources_syntax_on = 1;
67 
68 // This speeds up loading sqlite.c from 2:48 down to ~2 seconds.
69 // sqlite3 is 6,596,401 bytes, 188,185 lines.
70 
71 /* --------------- */
72 /* Local Functions */
73 /* --------------- */
74 
75 /* source_get_node:  Returns a pointer to the node that matches the given path.
76  * ---------
77  *   path:  Full path to source file
78  *
79  * Return Value:  Pointer to the matching node, or NULL if not found.
80  */
source_get_node(struct sviewer * sview,const char * path)81 struct list_node *source_get_node(struct sviewer *sview, const char *path)
82 {
83     if (sview && path && path[0]) {
84         struct list_node *cur;
85 
86         for (cur = sview->list_head; cur != NULL; cur = cur->next) {
87             if (cur->path && (strcmp(path, cur->path) == 0))
88                 return cur;
89         }
90     }
91 
92     return NULL;
93 }
94 
95 /**
96  * Get's the timestamp of a particular file.
97  *
98  * \param path
99  * The path to the file to get the timestamp of
100  *
101  * \param timestamp
102  * The timestamp of the file, or 0 on error.
103  *
104  * \return
105  * 0 on success, -1 on error.
106  */
get_timestamp(const char * path,time_t * timestamp)107 static int get_timestamp(const char *path, time_t * timestamp)
108 {
109     int val;
110     struct stat s;
111 
112     /* Special buffer not backed by file */
113     if (path[0] == '*') {
114         *timestamp = 0;
115         return 0;
116     }
117 
118     val = path ? stat(path, &s) : -1;
119     *timestamp = val ? 0 : s.st_mtime;
120     return val;
121 }
122 
init_file_buffer(struct buffer * buf)123 static void init_file_buffer(struct buffer *buf)
124 {
125     buf->lines = NULL;
126     buf->addrs = NULL;
127     buf->max_width = 0;
128     buf->file_data = NULL;
129     buf->tabstop = cgdbrc_get_int(CGDBRC_TABSTOP);
130     buf->language = TOKENIZER_LANGUAGE_UNKNOWN;
131 }
132 
release_file_buffer(struct buffer * buf)133 static void release_file_buffer(struct buffer *buf)
134 {
135     if (buf) {
136         int i;
137 
138         for (i = 0; i < sbcount(buf->lines); i++) {
139             sbfree(buf->lines[i].attrs);
140             buf->lines[i].attrs = NULL;
141 
142             sbfree(buf->lines[i].line);
143             buf->lines[i].line = NULL;
144         }
145 
146         /* Free entire file buffer */
147         sbfree(buf->file_data);
148         buf->file_data = NULL;
149 
150         sbfree(buf->lines);
151         buf->lines = NULL;
152 
153         sbfree(buf->addrs);
154         buf->addrs = NULL;
155 
156         buf->max_width = 0;
157         buf->language = TOKENIZER_LANGUAGE_UNKNOWN;
158     }
159 }
160 
161 /**
162  * Remove's the memory related to a file.
163  *
164  * \param node
165  * The node who's file buffer data needs to be freed.
166  *
167  * \return
168  * 0 on success, or -1 on error.
169  */
release_file_memory(struct list_node * node)170 static int release_file_memory(struct list_node *node)
171 {
172     if (!node)
173         return -1;
174 
175     /* Release file buffers */
176     release_file_buffer(&node->file_buf);
177 
178     return 0;
179 }
180 
detab_buffer(char * buffer,int tabstop)181 static char *detab_buffer(char *buffer, int tabstop)
182 {
183     int i;
184     int dst = 0;
185     char *newbuf = NULL;
186     int size = sbcount(buffer);
187 
188     char *tab = strchr(buffer, '\t');
189     if (!tab)
190         return buffer;
191 
192     for (i = 0; i < size; i++) {
193         if (buffer[i] == '\t') {
194             int spaces = tabstop - dst % tabstop;
195 
196             while(spaces--) {
197                 sbpush(newbuf, ' ');
198                 dst++;
199             }
200         } else {
201             sbpush(newbuf, buffer[i]);
202             dst++;
203         }
204 
205         if (buffer[i] == '\n' || buffer[i] == '\r')
206             dst = 0;
207     }
208 
209     sbfree(buffer);
210     return newbuf;
211 }
212 
213 /**
214  * Load file and fill tlines line pointers.
215  *
216  * \param buf
217  * struct buffer pointer
218  *
219  * \param filename
220  * name of file to load
221  *
222  * \return
223  * 0 on sucess, -1 on error
224  */
load_file_buf(struct buffer * buf,const char * filename)225 static int load_file_buf(struct buffer *buf, const char *filename)
226 {
227     FILE *file;
228     long file_size;
229     int ret = -1;
230 
231     /* Special buffer not backed by file */
232     if (filename[0] == '*')
233         return 0;
234 
235     file = fopen(filename, "rb");
236     if (!file)
237         return -1;
238 
239     file_size = get_file_size(file);
240     if (file_size > 0) {
241         size_t bytes_read;
242 
243         /* Set the stretchy buffer size to our file size plus one for nil */
244         sbsetcount(buf->file_data, file_size + 1);
245 
246         /* Read in the entire file */
247         bytes_read = fread(buf->file_data, 1, file_size, file);
248 
249         /* If we had a partial read, bail */
250         if (bytes_read != file_size) {
251             sbfree(buf->file_data);
252             buf->file_data = NULL;
253 
254             fclose(file);
255             return -1;
256         }
257 
258         /* Zero terminate buffer */
259         buf->file_data[bytes_read] = 0;
260 
261         /* Convert tabs to spaces */
262         buf->tabstop = cgdbrc_get_int(CGDBRC_TABSTOP);
263         buf->file_data = detab_buffer(buf->file_data, buf->tabstop);
264 
265         {
266             char *line_start = buf->file_data;
267             char *line_feed = strchr(line_start, '\n');
268 
269             while (line_feed)
270             {
271                 size_t line_len;
272                 char *line_end = line_feed;
273 
274                 /* Trim trailing cr-lfs */
275                 while (line_end >= line_start && (*line_end == '\n' || *line_end == '\r'))
276                     line_end--;
277 
278                 /* Update max length string found */
279                 line_len = line_end - line_start + 1;
280                 if (line_len > buf->max_width)
281                     buf->max_width = line_len;
282 
283                 struct source_line sline;
284                 sline.line = NULL;
285                 sbsetcount(sline.line, line_len + 1);
286                 strncpy(sline.line, line_start, line_len);
287                 sline.len = line_len;
288                 sline.attrs = NULL;
289 
290                 /* Add this line to lines array */
291                 sbpush(buf->lines, sline);
292 
293                 line_start = line_feed + 1;
294                 line_feed = strchr(line_start, '\n');
295             }
296 
297             if (*line_start) {
298                 struct source_line sline;
299                 int len = strlen(line_start);
300                 sline.line = NULL;
301                 sbsetcount(sline.line, len + 1);
302                 strncpy(sline.line, line_start, len);
303                 sline.len = len;
304                 sline.attrs = NULL;
305 
306                 sbpush(buf->lines, sline);
307             }
308 
309             ret = 0;
310         }
311     }
312 
313     fclose(file);
314     return ret;
315 }
316 
317 /* load_file:  Loads the file in the list_node into its memory buffer.
318  * ----------
319  *
320  *   node:  The list node to work on
321  *
322  * Return Value:  Zero on success, non-zero on error.
323  */
load_file(struct list_node * node)324 static int load_file(struct list_node *node)
325 {
326     /* No node pointer? */
327     if (!node)
328         return -1;
329 
330     /* File already loaded - success! */
331     if (node->file_buf.lines)
332         return 0;
333 
334     /* Stat the file to get the timestamp */
335     if (get_timestamp(node->path, &(node->last_modification)) == -1)
336         return -1;
337 
338     node->language = tokenizer_get_default_file_type(strrchr(node->path, '.'));
339 
340     /* Add the highlighted lines */
341     return source_highlight(node);
342 }
343 
344 /* --------- */
345 /* Functions */
346 /* --------- */
347 
348 /* Descriptive comments found in header file: sources.h */
349 
350 /* Returns HLG_LAST on error */
hlg_from_tokenizer_type(enum tokenizer_type type,const char * tok_data)351 static enum hl_group_kind hlg_from_tokenizer_type(enum tokenizer_type type, const char *tok_data)
352 {
353     switch(type) {
354         case TOKENIZER_KEYWORD: return HLG_KEYWORD;
355         case TOKENIZER_TYPE: return HLG_TYPE;
356         case TOKENIZER_LITERAL: return HLG_LITERAL;
357         case TOKENIZER_NUMBER: return HLG_TEXT;
358         case TOKENIZER_COMMENT: return HLG_COMMENT;
359         case TOKENIZER_DIRECTIVE: return HLG_DIRECTIVE;
360         case TOKENIZER_TEXT: return HLG_TEXT;
361         case TOKENIZER_NEWLINE: return HLG_LAST;
362         case TOKENIZER_ERROR: return HLG_TEXT;
363         case TOKENIZER_SEARCH: return HLG_SEARCH;
364         case TOKENIZER_STATUS_BAR: return HLG_STATUS_BAR;
365         case TOKENIZER_EXECUTING_LINE_ARROW: return HLG_EXECUTING_LINE_ARROW;
366         case TOKENIZER_SELECTED_LINE_ARROW: return HLG_SELECTED_LINE_ARROW;
367         case TOKENIZER_EXECUTING_LINE_HIGHLIGHT: return HLG_EXECUTING_LINE_HIGHLIGHT;
368         case TOKENIZER_SELECTED_LINE_HIGHLIGHT: return HLG_SELECTED_LINE_HIGHLIGHT;
369         case TOKENIZER_EXECUTING_LINE_BLOCK: return HLG_EXECUTING_LINE_BLOCK;
370         case TOKENIZER_SELECTED_LINE_BLOCK: return HLG_SELECTED_LINE_BLOCK;
371         case TOKENIZER_ENABLED_BREAKPOINT: return HLG_ENABLED_BREAKPOINT;
372         case TOKENIZER_DISABLED_BREAKPOINT: return HLG_DISABLED_BREAKPOINT;
373         case TOKENIZER_SELECTED_LINE_NUMBER: return HLG_SELECTED_LINE_NUMBER;
374         case TOKENIZER_SCROLL_MODE_STATUS: return HLG_SCROLL_MODE_STATUS;
375         case TOKENIZER_LOGO: return HLG_LOGO;
376         case TOKENIZER_COLOR: return hl_get_color_group(tok_data);
377     }
378 
379     return HLG_TEXT;
380 }
381 
highlight_node(struct list_node * node)382 static int highlight_node(struct list_node *node)
383 {
384     int i;
385     int ret;
386     int line = 0;
387     int length = 0;
388     int lasttype = -1;
389     struct token_data tok_data;
390     struct tokenizer *t = tokenizer_init();
391     struct buffer *buf = &node->file_buf;
392 
393     for (i = 0; i < sbcount(buf->lines); i++) {
394         sbfree(buf->lines[i].attrs);
395         buf->lines[i].attrs = NULL;
396     }
397 
398     if (!buf->file_data) {
399         for (line = 0; line < sbcount(buf->lines); line++) {
400             struct source_line *sline = &buf->lines[line];
401 
402             tokenizer_set_buffer(t, sline->line, buf->language);
403 
404             length = 0;
405             lasttype = -1;
406             while ((ret = tokenizer_get_token(t, &tok_data)) > 0) {
407                 if (tok_data.e == TOKENIZER_NEWLINE)
408                     break;
409 
410                 enum hl_group_kind hlg = hlg_from_tokenizer_type(tok_data.e, tok_data.data);
411 
412                 /* Add attribute if highlight group has changed */
413                 if (lasttype != hlg) {
414                     sbpush(buf->lines[line].attrs, hl_line_attr(length, hlg));
415 
416                     lasttype = hlg;
417                 }
418 
419                 /* Add the text and bump our length */
420                 length += strlen(tok_data.data);
421             }
422         }
423 
424     } else {
425         if (tokenizer_set_buffer(t, buf->file_data, buf->language) == -1) {
426             if_print_message("%s:%d tokenizer_set_buffer error", __FILE__, __LINE__);
427             return -1;
428         }
429 
430         while ((ret = tokenizer_get_token(t, &tok_data)) > 0) {
431             if (tok_data.e == TOKENIZER_NEWLINE) {
432                 if (length > buf->max_width)
433                     buf->max_width = length;
434 
435                 length = 0;
436                 lasttype = -1;
437                 line++;
438             } else {
439                 enum hl_group_kind hlg = hlg_from_tokenizer_type(tok_data.e, tok_data.data);
440 
441                 if (hlg == HLG_LAST) {
442                     clog_error(CLOG_CGDB, "Bad hlg_type for '%s', e==%d\n", tok_data.data, tok_data.e);
443                     hlg = HLG_TEXT;
444                 }
445 
446                 /* Add attribute if highlight group has changed */
447                 if (lasttype != hlg) {
448                     sbpush(buf->lines[line].attrs, hl_line_attr(length, hlg));
449 
450                     lasttype = hlg;
451                 }
452 
453                 /* Add the text and bump our length */
454                 length += strlen(tok_data.data);
455             }
456         }
457     }
458 
459     tokenizer_destroy(t);
460     return 0;
461 }
462 
source_highlight(struct list_node * node)463 int source_highlight(struct list_node *node)
464 {
465     int do_color = sources_syntax_on &&
466                    (node->language != TOKENIZER_LANGUAGE_UNKNOWN) &&
467                    swin_has_colors();
468 
469     /* Load the entire file */
470     if (!sbcount(node->file_buf.lines))
471         load_file_buf(&node->file_buf, node->path);
472 
473     /* If we're doing color and we haven't already loaded this file
474      * with this language, then load and highlight it.
475      */
476     if (do_color && (node->file_buf.language != node->language)) {
477         node->file_buf.language = node->language;
478         highlight_node(node);
479     }
480 
481     /* Allocate the breakpoints array */
482     if (!node->lflags) {
483         int count = sbcount(node->file_buf.lines);
484         sbsetcount(node->lflags, count);
485 
486         memset(node->lflags, 0, sbcount(node->lflags));
487     }
488 
489     if (node->file_buf.lines)
490         return 0;
491 
492     return -1;
493 }
494 
source_new(SWINDOW * win)495 struct sviewer *source_new(SWINDOW *win)
496 {
497     struct sviewer *rv;
498 
499     /* Allocate a new structure */
500     rv = (struct sviewer *)cgdb_malloc(sizeof (struct sviewer));
501 
502     /* Initialize the structure */
503     rv->win = win;
504     rv->cur = NULL;
505     rv->list_head = NULL;
506 
507     /* Initialize global marks */
508     memset(rv->global_marks, 0, sizeof(rv->global_marks));
509     rv->jump_back_mark.node = NULL;
510     rv->jump_back_mark.line = -1;
511 
512     rv->addr_frame = 0;
513 
514     rv->hlregex = NULL;
515     rv->last_hlregex = NULL;
516 
517     return rv;
518 }
519 
source_add(struct sviewer * sview,const char * path)520 struct list_node *source_add(struct sviewer *sview, const char *path)
521 {
522     struct list_node *new_node;
523 
524     new_node = source_get_node(sview, path);
525     if (new_node)
526         return new_node;
527 
528     new_node = (struct list_node *)cgdb_malloc(sizeof (struct list_node));
529     new_node->path = strdup(path);
530 
531     init_file_buffer(&new_node->file_buf);
532 
533     new_node->lflags = NULL;
534     new_node->sel_line = 0;
535     new_node->sel_col = 0;
536     new_node->sel_rline = 0;
537     new_node->exe_line = -1;
538     new_node->last_modification = 0;    /* No timestamp yet */
539     new_node->language = TOKENIZER_LANGUAGE_UNKNOWN;
540     new_node->addr_start = 0;
541     new_node->addr_end = 0;
542 
543     /* Initialize all local marks to -1 */
544     memset(new_node->local_marks, 0xff, sizeof(new_node->local_marks));
545 
546     if (sview->list_head == NULL) {
547         /* List is empty, this is the first node */
548         new_node->next = NULL;
549         sview->list_head = new_node;
550     } else {
551         /* Insert at the front of the list (easy) */
552         new_node->next = sview->list_head;
553         sview->list_head = new_node;
554     }
555 
556     return new_node;
557 }
558 
source_add_disasm_line(struct list_node * node,const char * line)559 void source_add_disasm_line(struct list_node *node, const char *line)
560 {
561     uint64_t addr = 0;
562     struct source_line sline;
563     char *colon = 0, colon_char = 0;
564 
565     sline.line = NULL;
566     sbsetcount(sline.line, strlen(line) + 1);
567     strcpy(sline.line, line);
568     sline.line = detab_buffer(sline.line, node->file_buf.tabstop);
569 
570     sline.attrs = NULL;
571     sline.len = sbcount(sline.line);
572 
573     colon = strchr((char*)line, ':');
574     if (colon) {
575         colon_char = *colon;
576         *colon = 0;
577     }
578 
579     cgdb_hexstr_to_u64(line, &addr);
580 
581     if (colon) {
582         *colon = colon_char;
583     }
584 
585     sbpush(node->file_buf.addrs, addr);
586 
587     struct line_flags lf = { 0, 0 };
588     sbpush(node->file_buf.lines, sline);
589     sbpush(node->lflags, lf);
590 }
591 
source_del(struct sviewer * sview,const char * path)592 int source_del(struct sviewer *sview, const char *path)
593 {
594     int i;
595     struct list_node *cur;
596     struct list_node *prev = NULL;
597 
598     /* Find the target node */
599     for (cur = sview->list_head; cur != NULL; cur = cur->next) {
600         if (strcmp(path, cur->path) == 0)
601             break;
602         prev = cur;
603     }
604 
605     if (cur == NULL)
606         return 1;               /* Node not found */
607 
608     /* Release file buffers */
609     release_file_buffer(&cur->file_buf);
610 
611     /* Release file name */
612     free(cur->path);
613     cur->path = NULL;
614 
615     sbfree(cur->lflags);
616     cur->lflags = NULL;
617 
618     /* Remove link from list */
619     if (cur == sview->list_head)
620         sview->list_head = sview->list_head->next;
621     else
622         prev->next = cur->next;
623 
624     /* Free the node */
625     free(cur);
626 
627     /* Free any global marks pointing to this bugger */
628     for (i = 0; i < sizeof(sview->global_marks) / sizeof(sview->global_marks[0]); i++) {
629         if (sview->global_marks[i].node == cur)
630             sview->global_marks[i].node = NULL;
631     }
632 
633     return 0;
634 }
635 
source_length(struct sviewer * sview,const char * path)636 int source_length(struct sviewer *sview, const char *path)
637 {
638     struct list_node *cur = source_get_node(sview, path);
639 
640     /* Load the file if it's not already */
641     if (load_file(cur))
642         return -1;
643 
644     return sbcount(cur->file_buf.lines);
645 }
646 
source_current_file(struct sviewer * sview)647 char *source_current_file(struct sviewer *sview)
648 {
649     return (sview && sview->cur) ? sview->cur->path : NULL;
650 }
651 
652 /* source_get_mark_char:  Return mark char for line.
653  * --------------------
654  *
655  *   sview:  The source viewer object
656  *   node:
657  *   line: line to check for mark
658  *   return: -1 on error, 0 if no char exists on line, otherwise char
659  */
source_get_mark_char(struct sviewer * sview,struct list_node * node,int line)660 static int source_get_mark_char(struct sviewer *sview,
661     struct list_node *node, int line)
662 {
663     if (!node || (line < 0) || (line >= sbcount(node->lflags)))
664         return -1;
665 
666     if (node->lflags[line].has_mark) {
667         int i;
668 
669         for (i = 0; i < MARK_COUNT; i++) {
670             if (sview->global_marks[i].line == line)
671                 return 'A' + i;
672         }
673 
674         for (i = 0; i < MARK_COUNT; i++) {
675             if (node->local_marks[i] == line)
676                 return 'a' + i;
677         }
678     }
679 
680     return 0;
681 }
682 
source_set_mark(struct sviewer * sview,int key)683 int source_set_mark(struct sviewer *sview, int key)
684 {
685     int ret = 0;
686     int old_line;
687     struct list_node *old_node;
688     int sel_line = sview->cur->sel_line;
689 
690     if (key >= 'a' && key <= 'z') {
691         /* Local buffer mark */
692         old_line = sview->cur->local_marks[key - 'a'];
693         old_node = sview->cur;
694         sview->cur->local_marks[key - 'a'] = sel_line;
695         ret = 1;
696     } else if (key >= 'A' && key <= 'Z') {
697         /* Global buffer mark */
698         old_line = sview->global_marks[key - 'A'].line;
699         old_node = sview->global_marks[key - 'A'].node;
700         sview->global_marks[key - 'A'].line = sel_line;
701         sview->global_marks[key - 'A'].node = sview->cur;
702         ret = 1;
703     }
704 
705     if (ret) {
706         /* Just added a mark to the selected line, flag it */
707         sview->cur->lflags[sel_line].has_mark = 1;
708 
709         /* Check if the old line still has a mark */
710         if (source_get_mark_char(sview, old_node, old_line) == 0)
711             old_node->lflags[old_line].has_mark = 0;
712     }
713 
714     return ret;
715 }
716 
source_goto_mark(struct sviewer * sview,int key)717 int source_goto_mark(struct sviewer *sview, int key)
718 {
719     int line;
720     struct list_node *node = NULL;
721 
722     if (key >= 'a' && key <= 'z') {
723         /* Local buffer mark */
724         line = sview->cur->local_marks[key - 'a'];
725         node = (line >= 0) ? sview->cur : NULL;
726     } else if (key >= 'A' && key <= 'Z') {
727         /* Global buffer mark */
728         line = sview->global_marks[key - 'A'].line;
729         node = sview->global_marks[key - 'A'].node;
730     } else if (key == '\'' ) {
731         /* Jump back to where we jumped from */
732         line = sview->jump_back_mark.line;
733         node = sview->jump_back_mark.node;
734     } else if (key == '.') {
735         /* Jump to currently executing line if it's set */
736         line = sview->cur->exe_line;
737         node = (line >= 0) ? sview->cur : NULL;
738     }
739 
740     if (node) {
741         sview->jump_back_mark.line = sview->cur->sel_line;
742         sview->jump_back_mark.node = sview->cur;
743 
744         sview->cur = node;
745         source_set_sel_line(sview, line + 1);
746         return 1;
747     }
748 
749     return 0;
750 }
751 
get_line_leading_ws_count(const char * otext,int length)752 static int get_line_leading_ws_count(const char *otext, int length)
753 {
754     int i;
755     int column_offset = 0; /* Text to skip due to arrow */
756 
757     for (i = 0; i < length - 1; i++) {
758         /* Bail if we hit a non whitespace character */
759         if (!isspace(otext[i]))
760             break;
761 
762         column_offset++;
763     }
764 
765     return column_offset;
766 }
767 
768 /**
769  * Display the source.
770  *
771  * A line in the source viewer looks like,
772  *   # │ marker text
773  * where,
774  *   # is the line number to display or ~ if no line number
775  *   │ is the divider between the line number or it is a mark
776  *   marker is shortarrow, longarrow, highlight, block, etc
777  *   text is the source code to display
778  *
779  * The syntax highlighting works as follows,
780  *
781  * The #
782  * - If breakpoint is set, use Breakpoint
783  * - If breakpoint is disabled, use DisabledBreakpoint
784  * - If selected line, use SelectedLineNr
785  * - If executing line, use ExecutingLineNr
786  * - Otherwise, no highlighting group
787  *
788  * The │
789  * - When source window is in focus, the character is bolded, otherwise normal
790  * - If the user has a mark set, the mark will be displayed instead of any
791  *   other character.
792  * - Edge case: When the marker is long or short arrow, CGDB prints ├
793  *   instead of │ the ├ is colored based on highlighting group for
794  *   the selected or executing arrow.
795  *
796  * The marker
797  * - The marker is the shortarrow, longarrow, highlight or block
798  * - The color is based off the corresponding highlighting group
799  *
800  * The text
801  * - The syntax highlighting source code to display
802  * - Will be colored with SelectedLineHighlight or ExecutingLineHighlight
803  *   if the line is the selected or executing line and the display is set
804  *   to highlight.
805  */
source_display(struct sviewer * sview,int focus,enum win_refresh dorefresh)806 int source_display(struct sviewer *sview, int focus, enum win_refresh dorefresh)
807 {
808     int i;
809     int lwidth;
810     int line;
811     int count;
812 
813     enum LineDisplayStyle exe_display_style, sel_display_style;
814     int sellineno, exelineno;
815     int enabled_bp, disabled_bp;
816     int exe_line_display_is_arrow, sel_line_display_is_arrow;
817     int exe_arrow_attr, sel_arrow_attr;
818     int exe_block_attr, sel_block_attr;
819     char fmt[16];
820     int width, height;
821     int focus_attr = focus ? SWIN_A_BOLD : 0;
822     int showmarks = cgdbrc_get_int(CGDBRC_SHOWMARKS);
823     int hlsearch = cgdbrc_get_int(CGDBRC_HLSEARCH);
824     int mark_attr;
825 
826     struct hl_line_attr *sel_highlight_attrs = 0;
827     struct hl_line_attr *exe_highlight_attrs = 0;
828 
829     /* Check that a file is loaded */
830     if (!sview->cur || !sview->cur->file_buf.lines) {
831         logo_display(sview->win);
832 
833         if (dorefresh == WIN_REFRESH)
834             swin_wrefresh(sview->win);
835         else
836             swin_wnoutrefresh(sview->win);
837 
838         return 0;
839     }
840 
841     sellineno = hl_groups_get_attr(
842         hl_groups_instance, HLG_SELECTED_LINE_NUMBER);
843     exelineno = hl_groups_get_attr(
844         hl_groups_instance, HLG_EXECUTING_LINE_NUMBER);
845     enabled_bp = hl_groups_get_attr(
846         hl_groups_instance, HLG_ENABLED_BREAKPOINT);
847     disabled_bp = hl_groups_get_attr(
848         hl_groups_instance, HLG_DISABLED_BREAKPOINT);
849 
850     exe_display_style = cgdbrc_get_displaystyle(CGDBRC_EXECUTING_LINE_DISPLAY);
851     exe_arrow_attr = hl_groups_get_attr(
852         hl_groups_instance, HLG_EXECUTING_LINE_ARROW);
853     exe_block_attr = hl_groups_get_attr(
854         hl_groups_instance, HLG_EXECUTING_LINE_BLOCK);
855 
856     sel_display_style = cgdbrc_get_displaystyle(CGDBRC_SELECTED_LINE_DISPLAY);
857     sel_arrow_attr = hl_groups_get_attr(
858         hl_groups_instance, HLG_SELECTED_LINE_ARROW);
859     sel_block_attr = hl_groups_get_attr(
860         hl_groups_instance, HLG_SELECTED_LINE_BLOCK);
861 
862     exe_line_display_is_arrow =
863         exe_display_style == LINE_DISPLAY_SHORT_ARROW ||
864         exe_display_style == LINE_DISPLAY_LONG_ARROW;
865     sel_line_display_is_arrow =
866         sel_display_style == LINE_DISPLAY_SHORT_ARROW ||
867         sel_display_style == LINE_DISPLAY_LONG_ARROW;
868 
869     mark_attr = hl_groups_get_attr(hl_groups_instance, HLG_MARK);
870 
871     sbpush(sel_highlight_attrs, hl_line_attr(0, HLG_SELECTED_LINE_HIGHLIGHT));
872     sbpush(exe_highlight_attrs, hl_line_attr(0, HLG_EXECUTING_LINE_HIGHLIGHT));
873 
874     /* Make sure cursor is visible */
875     swin_curs_set(!!focus);
876 
877     /* Initialize variables */
878     height = swin_getmaxy(sview->win);
879     width = swin_getmaxx(sview->win);
880 
881     /* Set starting line number (center source file if it's small enough) */
882     count = sbcount(sview->cur->file_buf.lines);
883     if (count < height) {
884         line = (count - height) / 2;
885     } else {
886         line = sview->cur->sel_line - height / 2;
887         if (line > count - height)
888             line = count - height;
889         else if (line < 0)
890             line = 0;
891     }
892 
893     /* Print 'height' lines of the file, starting at 'line' */
894     lwidth = log10_uint(count) + 1;
895     snprintf(fmt, sizeof(fmt), "%%%dd", lwidth);
896 
897     for (i = 0; i < height; i++, line++) {
898 
899         int column_offset = 0;
900         /* Is this the current selected line? */
901         int is_sel_line = (line >= 0 && sview->cur->sel_line == line);
902         /* Is this the current executing line */
903         int is_exe_line = (line >= 0 && sview->cur->exe_line == line);
904         struct source_line *sline = (line < 0 || line >= count)?
905             NULL:&sview->cur->file_buf.lines[line];
906         struct hl_line_attr *printline_attrs = (sline)?sline->attrs:0;
907 
908         swin_wmove(sview->win, i, 0);
909 
910         /* Print the line number */
911         if (line < 0 || line >= count) {
912             for (int j = 1; j < lwidth; j++)
913                 swin_waddch(sview->win, ' ');
914             swin_waddch(sview->win, '~');
915         } else {
916             int line_attr = 0;
917             int bp_val = sview->cur->lflags[line].breakpt;
918             if (bp_val == 1) {
919                 line_attr = enabled_bp;
920             } else if (bp_val == 2) {
921                 line_attr = disabled_bp;
922             } else if (bp_val == 0 && is_exe_line) {
923                 line_attr = exelineno;
924             } else if (bp_val == 0 && is_sel_line) {
925                 line_attr = sellineno;
926             }
927 
928             swin_wattron(sview->win, line_attr);
929             swin_wprintw(sview->win, fmt, line + 1);
930             swin_wattroff(sview->win, line_attr);
931         }
932 
933         if (!swin_has_colors()) {
934             /* TODO:
935             swin_wprintw(sview->win, "%.*s\n",
936                     sview->cur->file_buf.lines[line].line,
937                     sview->cur->file_buf.lines[line].len);
938             */
939             continue;
940         }
941 
942         /* Print the vertical bar or mark */
943         {
944             SWIN_CHTYPE vert_bar_char;
945             int vert_bar_attr;
946             int mc;
947 
948             if (showmarks &&
949                 ((mc = source_get_mark_char(sview, sview->cur, line)) > 0)) {
950                 vert_bar_char = mc;
951                 vert_bar_attr = mark_attr;
952             } else if (is_exe_line && exe_line_display_is_arrow) {
953                 vert_bar_attr = exe_arrow_attr;
954                 vert_bar_char = SWIN_SYM_LTEE;
955             } else if (is_sel_line && sel_line_display_is_arrow) {
956                 vert_bar_attr = sel_arrow_attr;
957                 vert_bar_char = SWIN_SYM_LTEE;
958             } else {
959                 vert_bar_attr = focus_attr;
960                 vert_bar_char = SWIN_SYM_VLINE;
961             }
962 
963             swin_wattron(sview->win, vert_bar_attr);
964             swin_waddch(sview->win, vert_bar_char);
965             swin_wattroff(sview->win, vert_bar_attr);
966         }
967 
968         /* Print the marker */
969         if (is_exe_line || is_sel_line) {
970             enum LineDisplayStyle display_style;
971             int arrow_attr, block_attr;
972             struct hl_line_attr *highlight_attr;
973 
974             if (is_exe_line) {
975                 display_style = exe_display_style;
976                 arrow_attr = exe_arrow_attr;
977                 block_attr = exe_block_attr;
978                 highlight_attr = exe_highlight_attrs;
979             } else {
980                 display_style = sel_display_style;
981                 arrow_attr = sel_arrow_attr;
982                 block_attr = sel_block_attr;
983                 highlight_attr = sel_highlight_attrs;
984             }
985 
986             switch (display_style) {
987                 case LINE_DISPLAY_SHORT_ARROW:
988                     swin_wattron(sview->win, arrow_attr);
989                     swin_waddch(sview->win, '>');
990                     swin_wattroff(sview->win, arrow_attr);
991                     break;
992                 case LINE_DISPLAY_LONG_ARROW:
993                     swin_wattron(sview->win, arrow_attr);
994                     column_offset = get_line_leading_ws_count(
995                         sline->line, sline->len);
996                     column_offset -= (sview->cur->sel_col + 1);
997                     if (column_offset < 0)
998                         column_offset = 0;
999 
1000                     /* Now actually draw the arrow */
1001                     for (int j = 0; j < column_offset; j++)
1002                         swin_waddch(sview->win, SWIN_SYM_HLINE);
1003 
1004                     swin_waddch(sview->win, '>');
1005                     swin_wattroff(sview->win, arrow_attr);
1006 
1007                     break;
1008                 case LINE_DISPLAY_HIGHLIGHT:
1009                     swin_waddch(sview->win, ' ');
1010                     printline_attrs = highlight_attr;
1011                     break;
1012                 case LINE_DISPLAY_BLOCK:
1013                     column_offset = get_line_leading_ws_count(
1014                         sline->line, sline->len);
1015                     column_offset -= (sview->cur->sel_col + 1);
1016                     if (column_offset < 0)
1017                         column_offset = 0;
1018 
1019                     /* Now actually draw the space to the block */
1020                     for (int j = 0; j < column_offset; j++)
1021                         swin_waddch(sview->win, ' ');
1022 
1023                     /* Draw the block */
1024                     swin_wattron(sview->win, block_attr);
1025                     swin_waddch(sview->win, ' ');
1026                     swin_wattroff(sview->win, block_attr);
1027                     break;
1028             }
1029         } else {
1030             swin_waddch(sview->win, ' ');
1031         }
1032 
1033 
1034         /* Print the text */
1035         if (line < 0 || line >= count) {
1036             for (int j = 2 + lwidth; j < width; j++)
1037                 swin_waddch(sview->win, ' ');
1038         } else {
1039             int x, y;
1040             y = swin_getcury(sview->win);
1041             x = swin_getcurx(sview->win);
1042 
1043             hl_printline(sview->win, sline->line, sline->len,
1044                 printline_attrs, -1, -1, sview->cur->sel_col + column_offset,
1045                 width - lwidth - 2);
1046 
1047             if (hlsearch && sview->last_hlregex) {
1048                 struct hl_line_attr *attrs = hl_regex_highlight(
1049                         &sview->last_hlregex, sline->line, HLG_SEARCH);
1050                 if (sbcount(attrs)) {
1051                     hl_printline_highlight(sview->win, sline->line, sline->len,
1052                         attrs, x, y, sview->cur->sel_col + column_offset,
1053                         width - lwidth - 2);
1054                     sbfree(attrs);
1055                 }
1056             }
1057 
1058             if (is_sel_line && sview->hlregex) {
1059                 struct hl_line_attr *attrs = hl_regex_highlight(
1060                         &sview->hlregex, sline->line, HLG_INCSEARCH);
1061                 if (sbcount(attrs)) {
1062                     hl_printline_highlight(sview->win, sline->line, sline->len,
1063                         attrs, x, y, sview->cur->sel_col + column_offset,
1064                         width - lwidth - 2);
1065                     sbfree(attrs);
1066                 }
1067             }
1068         }
1069     }
1070 
1071     switch(dorefresh) {
1072         case WIN_NO_REFRESH:
1073             swin_wnoutrefresh(sview->win);
1074             break;
1075         case WIN_REFRESH:
1076             swin_wrefresh(sview->win);
1077             break;
1078     }
1079 
1080     sbfree(sel_highlight_attrs);
1081     sbfree(exe_highlight_attrs);
1082 
1083     return 0;
1084 }
1085 
source_move(struct sviewer * sview,SWINDOW * win)1086 void source_move(struct sviewer *sview, SWINDOW *win)
1087 {
1088     swin_delwin(sview->win);
1089     sview->win = win;
1090 }
1091 
clamp_line(struct sviewer * sview,int line)1092 static int clamp_line(struct sviewer *sview, int line)
1093 {
1094     if (line < 0)
1095         line = 0;
1096     if (line >= sbcount(sview->cur->file_buf.lines))
1097         line = sbcount(sview->cur->file_buf.lines) - 1;
1098 
1099     return line;
1100 }
1101 
source_vscroll(struct sviewer * sview,int offset)1102 void source_vscroll(struct sviewer *sview, int offset)
1103 {
1104     if (sview->cur) {
1105         sview->cur->sel_line = clamp_line(sview, sview->cur->sel_line + offset);
1106         sview->cur->sel_rline = sview->cur->sel_line;
1107     }
1108 }
1109 
source_hscroll(struct sviewer * sview,int offset)1110 void source_hscroll(struct sviewer *sview, int offset)
1111 {
1112     int lwidth;
1113     int max_width;
1114     int width, height;
1115 
1116     if (sview->cur) {
1117         height = swin_getmaxy(sview->win);
1118         width = swin_getmaxx(sview->win);
1119 
1120         lwidth = log10_uint(sbcount(sview->cur->file_buf.lines)) + 1;
1121         max_width = sview->cur->file_buf.max_width - width + lwidth + 6;
1122 
1123         sview->cur->sel_col += offset;
1124         if (sview->cur->sel_col > max_width)
1125             sview->cur->sel_col = max_width;
1126         if (sview->cur->sel_col < 0)
1127             sview->cur->sel_col = 0;
1128     }
1129 }
1130 
source_set_sel_line(struct sviewer * sview,int line)1131 void source_set_sel_line(struct sviewer *sview, int line)
1132 {
1133     if (sview->cur) {
1134         if (line == -1) {
1135             sview->cur->sel_line = sbcount(sview->cur->file_buf.lines) - 1;
1136         } else {
1137             /* Set line (note correction for 0-based line counting) */
1138             sview->cur->sel_line = clamp_line(sview, line - 1);
1139         }
1140 
1141         sview->cur->sel_rline = sview->cur->sel_line;
1142     }
1143 }
1144 
source_get_asmnode(struct sviewer * sview,uint64_t addr,int * line)1145 static struct list_node *source_get_asmnode(struct sviewer *sview,
1146     uint64_t addr, int *line)
1147 {
1148     struct list_node *node = NULL;
1149 
1150     if (addr)
1151     {
1152         /* Search for a node which contains this address */
1153         for (node = sview->list_head; node; node = node->next)
1154         {
1155             if (addr >= node->addr_start && addr <= node->addr_end)
1156                 break;
1157         }
1158     }
1159 
1160     if (node && line)
1161     {
1162         int i;
1163 
1164         for (i = 0; i < sbcount(node->file_buf.addrs); i++)
1165         {
1166             if (node->file_buf.addrs[i] == addr)
1167             {
1168                 *line = i;
1169                 break;
1170             }
1171         }
1172     }
1173 
1174     return node;
1175 }
1176 
source_set_exec_line(struct sviewer * sview,const char * path,int sel_line,int exe_line)1177 int source_set_exec_line(struct sviewer *sview, const char *path, int sel_line, int exe_line)
1178 {
1179     if (path) {
1180         /* If they passed us a path, try to locate that node */
1181         sview->cur = source_get_node(sview, path);
1182 
1183         /* Not found.... */
1184         if (!sview->cur) {
1185             /* Check that the file exists */
1186             if (!fs_verify_file_exists(path))
1187                 return 5;
1188 
1189             /* Add a new node for this file */
1190             sview->cur = source_add(sview, path);
1191         }
1192     }
1193 
1194     /* Buffer the file if it's not already */
1195     if (load_file(sview->cur))
1196         return 4;
1197 
1198     /* Update line, if set */
1199     if (sel_line > 0)
1200         sview->cur->sel_line = clamp_line(sview, sel_line - 1);
1201 
1202     /* Set executing line if passed a valid value */
1203     if (exe_line == -1) {
1204         sview->cur->exe_line = -1;
1205     } else if (exe_line > 0) {
1206         sview->cur->exe_line = clamp_line(sview, exe_line - 1);
1207     }
1208 
1209     return 0;
1210 }
1211 
source_set_exec_addr(struct sviewer * sview,uint64_t addr)1212 int source_set_exec_addr(struct sviewer *sview, uint64_t addr)
1213 {
1214     int line = -1;
1215 
1216     if (!addr)
1217         addr = sview->addr_frame;
1218 
1219     /* Search for a node which contains this address */
1220     sview->cur = source_get_asmnode(sview, addr, &line);
1221     if (!sview->cur)
1222         return -1;
1223 
1224     sview->cur->sel_line = clamp_line(sview, line);
1225     sview->cur->exe_line = clamp_line(sview, line);
1226     return 0;
1227 }
1228 
source_free(struct sviewer * sview)1229 void source_free(struct sviewer *sview)
1230 {
1231     /* Free all file buffers */
1232     while (sview->list_head)
1233         source_del(sview, sview->list_head->path);
1234 
1235     hl_regex_free(&sview->hlregex);
1236     sview->hlregex = NULL;
1237     hl_regex_free(&sview->last_hlregex);
1238     sview->last_hlregex = NULL;
1239 
1240     swin_delwin(sview->win);
1241     sview->win = NULL;
1242 
1243     free(sview);
1244 }
1245 
source_search_regex_init(struct sviewer * sview)1246 void source_search_regex_init(struct sviewer *sview)
1247 {
1248     if (!sview || !sview->cur)
1249         return;
1250 
1251     /* Start searching at the beginning of the selected line */
1252     sview->cur->sel_rline = sview->cur->sel_line;
1253 }
1254 
wrap_line(struct list_node * node,int line)1255 static int wrap_line(struct list_node *node, int line)
1256 {
1257     int count = sbcount(node->file_buf.lines);
1258 
1259     if (line < 0)
1260         line = count - 1;
1261     else if (line >= count)
1262         line = 0;
1263 
1264     return line;
1265 }
1266 
source_search_regex(struct sviewer * sview,const char * regex,int opt,int direction,int icase)1267 int source_search_regex(struct sviewer *sview,
1268         const char *regex, int opt, int direction, int icase)
1269 {
1270     struct list_node *node = sview ? sview->cur : NULL;
1271 
1272     if (!node)
1273         return -1;
1274 
1275     if (regex && *regex) {
1276         int line;
1277         int line_end;
1278         int line_inc = direction ? +1 : -1;
1279         int line_start = node->sel_rline;
1280 
1281         line = wrap_line(node, line_start + line_inc);
1282 
1283         if (cgdbrc_get_int(CGDBRC_WRAPSCAN))
1284         {
1285             // Wrapping is on so stop at the line we started on.
1286             line_end = line_start;
1287         }
1288         else
1289         {
1290             // No wrapping. Stop at line 0 if searching down and last line
1291             // if searching up.
1292             line_end = direction ? 0 : sbcount(node->file_buf.lines) - 1;
1293         }
1294 
1295         for(;;) {
1296             int ret;
1297             int start, end;
1298             char *line_str = node->file_buf.lines[line].line;
1299 
1300             ret = hl_regex_search(&sview->hlregex, line_str, regex, icase, &start, &end);
1301             if (ret > 0) {
1302                 /* Got a match */
1303                 node->sel_line = line;
1304 
1305                 /* Finalized match - move to this location */
1306                 if (opt == 2) {
1307                     node->sel_rline = line;
1308 
1309                     hl_regex_free(&sview->last_hlregex);
1310                     sview->last_hlregex = sview->hlregex;
1311                     sview->hlregex = 0;
1312                 }
1313                 return 1;
1314             }
1315 
1316             line = wrap_line(node, line + line_inc);
1317             if (line == line_end)
1318                 break;
1319         }
1320     }
1321 
1322     /* Nothing found - go back to original line */
1323     node->sel_line = node->sel_rline;
1324     return 0;
1325 }
1326 
source_clear_breaks(struct sviewer * sview)1327 static void source_clear_breaks(struct sviewer *sview)
1328 {
1329     struct list_node *node;
1330 
1331     for (node = sview->list_head; node != NULL; node = node->next)
1332     {
1333         int i;
1334         for (i = 0; i < sbcount(node->lflags); i++)
1335             node->lflags[i].breakpt = 0;
1336     }
1337 }
1338 
source_set_breakpoints(struct sviewer * sview,struct tgdb_breakpoint * breakpoints)1339 void source_set_breakpoints(struct sviewer *sview,
1340         struct tgdb_breakpoint *breakpoints)
1341 {
1342     int i;
1343     struct list_node *node;
1344 
1345     source_clear_breaks(sview);
1346 
1347     // Loop over each breakpoint and let the source view and the
1348     // disassembly view know about them. This way if you set a breakpoint
1349     // in one mode, then switch modes, the other mode will know about
1350     // it as well.
1351     for (i = 0; i < sbcount(breakpoints); i++) {
1352         if (breakpoints[i].path) {
1353             node = source_get_node(sview, breakpoints[i].path);
1354             if (!load_file(node)) {
1355                 int line = breakpoints[i].line;
1356                 int enabled = breakpoints[i].enabled;
1357                 if (line > 0 && line <= sbcount(node->lflags)) {
1358                     node->lflags[line - 1].breakpt = enabled ? 1 : 2;
1359                 }
1360             }
1361         }
1362         if (breakpoints[i].addr) {
1363             int line = 0;
1364             node = source_get_asmnode(sview, breakpoints[i].addr, &line);
1365             if (node) {
1366                 node->lflags[line].breakpt = breakpoints[i].enabled ? 1 : 2;
1367             }
1368         }
1369     }
1370 }
1371 
source_reload(struct sviewer * sview,const char * path,int force)1372 int source_reload(struct sviewer *sview, const char *path, int force)
1373 {
1374     time_t timestamp;
1375     struct list_node *cur;
1376     struct list_node *prev = NULL;
1377     int auto_source_reload = cgdbrc_get_int(CGDBRC_AUTOSOURCERELOAD);
1378 
1379     if (!path)
1380         return -1;
1381 
1382     if (get_timestamp(path, &timestamp) == -1)
1383         return -1;
1384 
1385     /* Find the target node */
1386     for (cur = sview->list_head; cur != NULL; cur = cur->next) {
1387         if (strcmp(path, cur->path) == 0)
1388             break;
1389         prev = cur;
1390     }
1391 
1392     if (cur == NULL)
1393         return 1;               /* Node not found */
1394 
1395     /* If the file timestamp or tab size changed, reload the file */
1396     int dirty = cur->last_modification < timestamp;
1397     dirty |= cgdbrc_get_int(CGDBRC_TABSTOP) != cur->file_buf.tabstop;
1398 
1399     if ((auto_source_reload || force) && dirty) {
1400 
1401         if (release_file_memory(cur) == -1)
1402             return -1;
1403 
1404         if (load_file(cur))
1405             return -1;
1406     }
1407 
1408     return 0;
1409 }
1410