├── .gitignore ├── TODO ├── INSTALL ├── hash.h ├── lespipe ├── Makefile ├── configure ├── stage.c ├── rx.h ├── hash.c ├── tabs.c ├── movement.c ├── les.1 ├── les.h ├── README.md ├── LICENSE ├── search.c ├── linewrap.c ├── recentfiles.c ├── readfile.c ├── prompt.c ├── charinfo.c ├── main.c ├── page.c └── rx.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | les 3 | defines.h 4 | defines.mk 5 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Paste into the search prompt doesn't work 2 | Remove ansi escapes from buffer, so search can work when the search insersects with ansi escapes 3 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | To install les, run: 2 | 3 | make 4 | sudo make install 5 | 6 | To install into a different directory than /usr/local, supply the 7 | prefix option to the configure script: 8 | 9 | ./configure --prefix=/some/where/else 10 | make 11 | sudo make install 12 | 13 | -------------------------------------------------------------------------------- /hash.h: -------------------------------------------------------------------------------- 1 | #ifndef __HASH_H__ 2 | #define __HASH_H__ 3 | 4 | #include "rx.h" 5 | 6 | hash_t *hash_init (hash_func_t *hash_func, equal_func_t *equal_func); 7 | unsigned int hash_direct_hash (void *key); 8 | int hash_direct_equal (void *key1, void *key2); 9 | unsigned int hash_str_hash (void *key); 10 | int hash_str_equal (void *key1, void *key2); 11 | int hash_index (hash_t *h, void *key, unsigned int hash); 12 | void hash_clear (hash_t *h); 13 | void hash_free (hash_t *h); 14 | void hash_resize (hash_t *h, int new_allocated); 15 | void hash_insert (hash_t *h, void *key, void *value); 16 | void *hash_lookup (hash_t *h, void *key); 17 | 18 | #endif 19 | 20 | -------------------------------------------------------------------------------- /lespipe: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -d "$1" ]; then 4 | echo directory 5 | if [ $(uname) = "Darwin" ]; then 6 | CLICOLOR_FORCE=1 ls -G -l -h "$1" 7 | else 8 | ls -lh --color "$1" 9 | fi 10 | exit 11 | fi 12 | 13 | case $(echo "$1" | tr '[:upper:]' '[:lower:]') in 14 | *.tar.gz|*.tgz|*.tar.bz2|*.tar.tb2|*.tbz|*.tbz2) 15 | echo tar 16 | du -h "$1" | cut -f1 | sed 's/ *//' 17 | tar -tf "$1" 18 | ;; 19 | 20 | *.gz) 21 | echo gzip 22 | gzcat "$1" 23 | ;; 24 | 25 | *.bz2) 26 | echo bzip2 27 | bzcat "$1" 28 | ;; 29 | 30 | *.zip) 31 | echo zip 32 | unzip -l "$1" 33 | ;; 34 | 35 | *.a) 36 | echo ar 37 | cd $(dirname "$1") 38 | nm -j -U "$(basename "$1")" 39 | ;; 40 | 41 | esac 42 | 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = 3 | LDLIBS = -lncurses 4 | include defines.mk 5 | 6 | all: les 7 | 8 | %: 9 | $(CC) $(LDFLAGS) $(TARGET_ARCH) $(filter %.o %.a %.so, $^) $(LDLIBS) -o $@ 10 | 11 | %.o: 12 | $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $(filter %.c, $^) -o $@ 13 | 14 | les: main.o charinfo.o prompt.o linewrap.o movement.o stage.o page.o tabs.o readfile.o recentfiles.o search.o rx.o hash.o 15 | main.o: main.c les.h 16 | charinfo.o: charinfo.c les.h 17 | prompt.o: prompt.c les.h 18 | linewrap.o: linewrap.c les.h 19 | movement.o: movement.c les.h 20 | stage.o: stage.c les.h 21 | page.o: page.c les.h 22 | tabs.o: tabs.c les.h 23 | readfile.o: readfile.c les.h 24 | recentfiles.o: recentfiles.c les.h 25 | search.o: search.c les.h 26 | rx.o: rx.c rx.h 27 | hash.o: hash.c hash.h 28 | 29 | defines.mk: 30 | ./configure 31 | 32 | install: les 33 | mkdir -p $(PREFIX)/share/les 34 | install -c lespipe $(PREFIX)/share/les 35 | install -c les $(PREFIX)/bin 36 | mkdir -p $(PREFIX)/share/man/man1 37 | install -c les.1 $(PREFIX)/share/man/man1 38 | 39 | uninstall: 40 | rm -rf $(PREFIX)/share/les 41 | rm $(PREFIX)/bin/les 42 | 43 | clean: 44 | rm -rf les *.o 45 | 46 | clean2: 47 | rm -rf les *.o defines.h defines.mk 48 | 49 | 50 | .PHONY: all install uninstall clean clean2 51 | 52 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | args=() 4 | help= 5 | prefix="/usr/local" 6 | 7 | while [ $# -gt 0 ]; do 8 | case $1 in 9 | -h|--help) 10 | help="yes" 11 | ;; 12 | 13 | --prefix*) 14 | if [[ "$1" == *=* ]]; then 15 | prefix=${1#*=} 16 | else 17 | shift 18 | prefix=$1 19 | fi 20 | ;; 21 | 22 | --) 23 | shift 24 | args+=($@) 25 | break 26 | ;; 27 | 28 | --*) 29 | echo "Unrecognized option $1" 30 | exit 1 31 | ;; 32 | 33 | *) 34 | args+=($1) 35 | esac 36 | shift 37 | done 38 | 39 | if [ ${#args[@]} -gt 0 ]; then 40 | echo "Too many arguments" 41 | exit 1 42 | fi 43 | 44 | if [ "$help" = "yes" ]; then 45 | cat < defines.h 59 | 60 | echo "Writing defines.mk" 61 | echo "PREFIX = $prefix" > defines.mk 62 | if [ $(uname) = "Darwin" ]; then 63 | echo "LDLIBS += -liconv" >> defines.mk 64 | fi 65 | 66 | -------------------------------------------------------------------------------- /stage.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | char *stg; 7 | size_t stg_len = 0; 8 | size_t stg_size = 0; 9 | 10 | void stage_init () { 11 | stg_len = 0; 12 | stg_size = 1024; 13 | stg = malloc(stg_size); 14 | } 15 | 16 | void stage_cat (const char *str) { 17 | int i; 18 | for (i = 0; str[i]; i++) { 19 | if (stg_len >= stg_size) { 20 | stg_size *= 2; 21 | stg = realloc(stg, stg_size); 22 | } 23 | stg_len++; 24 | stg[stg_len - 1] = str[i]; 25 | } 26 | } 27 | 28 | void stage_ncat (const char *str, size_t n) { 29 | while (stg_len + n >= stg_size) { 30 | stg_size *= 2; 31 | stg = realloc(stg, stg_size); 32 | } 33 | int i; 34 | for (i = 0; i < n; i++) { 35 | stg_len++; 36 | stg[stg_len - 1] = str[i]; 37 | } 38 | } 39 | 40 | void stage_vprintf (const char *fmt, va_list args) { 41 | va_list args2; 42 | va_copy(args2, args); 43 | int retval = vsnprintf(stg + stg_len, stg_size - stg_len, fmt, args); 44 | if (stg_len + retval >= stg_size) { 45 | while (stg_len + retval >= stg_size) { 46 | stg_size *= 2; 47 | stg = realloc(stg, stg_size); 48 | } 49 | retval = vsnprintf(stg + stg_len, stg_size - stg_len, fmt, args2); 50 | } 51 | stg_len += retval; 52 | va_end(args2); 53 | } 54 | 55 | void stage_printf (const char *fmt, ...) { 56 | va_list args; 57 | va_start(args, fmt); 58 | stage_vprintf(fmt, args); 59 | va_end(args); 60 | } 61 | 62 | void stage_write () { 63 | write(1, stg, stg_len); 64 | stg_len = 0; 65 | } 66 | 67 | -------------------------------------------------------------------------------- /rx.h: -------------------------------------------------------------------------------- 1 | #ifndef __RX_H__ 2 | #define __RX_H__ 3 | 4 | enum { 5 | EMPTY, 6 | TAKE, 7 | BRANCH, 8 | CAPTURE_START, 9 | CAPTURE_END, 10 | MATCH_END, 11 | ASSERTION, 12 | CHAR_CLASS, 13 | CHAR_SET, 14 | GROUP_START, 15 | GROUP_END, 16 | }; 17 | 18 | enum { 19 | ASSERT_SOS, // start of string 20 | ASSERT_SOL, // start of line 21 | ASSERT_EOS, // end of string 22 | ASSERT_EOL, // end of line 23 | ASSERT_SOP, // start of position 24 | ASSERT_SOW, // start of word 25 | ASSERT_EOW, // end of word 26 | }; 27 | 28 | enum { 29 | CS_ANY, 30 | CS_NOTNL, 31 | CS_DIGIT, 32 | CS_NOTDIGIT, 33 | CS_WORD, 34 | CS_NOTWORD, 35 | CS_SPACE, 36 | CS_NOTSPACE, 37 | }; 38 | 39 | typedef unsigned int (hash_func_t) (void *key); 40 | typedef int (equal_func_t) (void *key1, void *key2); 41 | 42 | typedef struct { 43 | int allocated; 44 | int count; 45 | int *defined; 46 | unsigned int *hashes; 47 | void **keys; 48 | void **values; 49 | hash_func_t *hash_func; 50 | equal_func_t *equal_func; 51 | } hash_t; 52 | 53 | typedef struct node_t node_t; 54 | 55 | typedef struct { 56 | int min; 57 | int max; 58 | int greedy; 59 | } quantifier_t; 60 | 61 | typedef struct { 62 | char negated; 63 | int values_count; 64 | char *values; 65 | int ranges_count; 66 | char *ranges; 67 | int char_sets_count; 68 | char *char_sets; 69 | int str_size; 70 | char *str; 71 | } char_class_t; 72 | 73 | struct node_t { 74 | char type; 75 | node_t *next; 76 | union { 77 | int value; 78 | node_t *next2; 79 | char_class_t *ccval; 80 | }; 81 | }; 82 | 83 | typedef struct { 84 | node_t *start; 85 | int regexp_size; 86 | char *regexp; 87 | node_t **nodes; 88 | int nodes_count; 89 | int nodes_allocated; 90 | int cap_count; 91 | int cap_allocated; 92 | node_t **cap_start; 93 | node_t **or_end; 94 | int error; 95 | char *errorstr; 96 | int ignorecase; 97 | int char_classes_count; 98 | int char_classes_allocated; 99 | char_class_t **char_classes; 100 | int dfs_stack_count; 101 | int dfs_stack_allocated; 102 | node_t **dfs_stack; 103 | hash_t *dfs_map; 104 | } rx_t; 105 | 106 | typedef struct { 107 | node_t *node; 108 | int pos; 109 | } path_t; 110 | 111 | // The matcher maintains a list of positions that are important for backtracking 112 | // and for remembering captures. 113 | typedef struct { 114 | int path_count; 115 | int path_allocated; 116 | path_t *path; 117 | int cap_count; 118 | int cap_allocated; 119 | int *cap_start; 120 | int *cap_end; 121 | char *cap_defined; 122 | char **cap_str; 123 | int *cap_size; 124 | int success; 125 | int value; 126 | } matcher_t; 127 | 128 | rx_t *rx_alloc (); 129 | matcher_t *rx_matcher_alloc (); 130 | int rx_init (rx_t *rx, int regexp_size, char *regexp); 131 | int rx_init_start (rx_t *rx, int regexp_size, char *regexp, node_t *start, int value); 132 | node_t *rx_node_create (rx_t *rx); 133 | void rx_print (rx_t *rx); 134 | void rx_match_print (matcher_t *m); 135 | int rx_match (rx_t *rx, matcher_t *m, int str_size, char *str, int start_pos); 136 | int rx_hex_to_int (char *str, int size, unsigned int *dest); 137 | int rx_int_to_utf8 (unsigned int value, char *str); 138 | int rx_utf8_char_size (int str_size, char *str, int pos); 139 | void rx_matcher_free (matcher_t *m); 140 | void rx_free (rx_t *rx); 141 | 142 | #endif 143 | 144 | -------------------------------------------------------------------------------- /hash.c: -------------------------------------------------------------------------------- 1 | // This is a simple hash table implementation. 2 | 3 | #include "rx.h" 4 | #include "hash.h" 5 | #include 6 | #include 7 | #include 8 | 9 | hash_t *hash_init (hash_func_t *hash_func, equal_func_t *equal_func) { 10 | hash_t *h = malloc(sizeof(hash_t)); 11 | h->allocated = 10; 12 | h->count = 0; 13 | h->defined = calloc(1, h->allocated * sizeof(int)); 14 | h->hashes = malloc(h->allocated * sizeof(int)); 15 | h->keys = malloc(h->allocated * sizeof(void *)); 16 | h->values = malloc(h->allocated * sizeof(void *)); 17 | h->hash_func = hash_func; 18 | h->equal_func = equal_func; 19 | return h; 20 | } 21 | 22 | unsigned int hash_direct_hash (void *key) { 23 | return (unsigned int) key; 24 | } 25 | 26 | int hash_direct_equal (void *key1, void *key2) { 27 | return key1 == key2; 28 | } 29 | 30 | // This is the "djb" hash, originally created by Daniel Bernstein. 31 | // It will hash a string. 32 | unsigned int hash_str_hash (void *key) { 33 | const signed char *p; 34 | unsigned int hash = 5381; 35 | for (p = key; *p; p += 1) { 36 | hash = (hash << 5) + hash + *p; 37 | } 38 | return hash; 39 | } 40 | 41 | int hash_str_equal (void *key1, void *key2) { 42 | int retval = strcmp(key1, key2) == 0; 43 | return retval; 44 | } 45 | 46 | int hash_index (hash_t *h, void *key, unsigned int hash) { 47 | int index = hash % h->allocated; 48 | while (1) { 49 | if (!h->defined[index]) { 50 | return index; 51 | } 52 | if (h->hashes[index] == hash && h->equal_func(h->keys[index], key)) { 53 | return index; 54 | } 55 | index = (index + 1) % h->allocated; 56 | } 57 | return 0; 58 | } 59 | 60 | // This will clear the elements of the hash, but keep the memory. 61 | void hash_clear (hash_t *h) { 62 | if (h->count == 0) { 63 | return; 64 | } 65 | for (int i = 0; i < h->allocated; i += 1) { 66 | h->defined[i] = 0; 67 | } 68 | h->count = 0; 69 | } 70 | 71 | void hash_free (hash_t *h) { 72 | free(h->defined); 73 | free(h->hashes); 74 | free(h->keys); 75 | free(h->values); 76 | free(h); 77 | } 78 | 79 | void hash_resize (hash_t *h, int new_allocated) { 80 | int allocated = h->allocated; 81 | int *defined = h->defined; 82 | unsigned int *hashes = h->hashes; 83 | void **keys = h->keys; 84 | void **values = h->values; 85 | h->allocated = new_allocated; 86 | h->defined = calloc(1, new_allocated * sizeof(int)); 87 | h->hashes = malloc(new_allocated * sizeof(int)); 88 | h->keys = malloc(new_allocated * sizeof(void *)); 89 | h->values = malloc(new_allocated * sizeof(void *)); 90 | for (int i = 0; i < allocated; i += 1) { 91 | if (!defined[i]) { 92 | continue; 93 | } 94 | unsigned int hash = hashes[i]; 95 | void *key = keys[i]; 96 | void *value = values[i]; 97 | int index = hash_index(h, key, hash); 98 | h->defined[index] = 1; 99 | h->hashes[index] = hash; 100 | h->keys[index] = key; 101 | h->values[index] = value; 102 | } 103 | free(defined); 104 | free(hashes); 105 | free(keys); 106 | free(values); 107 | } 108 | 109 | void hash_insert (hash_t *h, void *key, void *value) { 110 | unsigned int hash = h->hash_func(key); 111 | int index = hash_index(h, key, hash); 112 | if (h->defined[index]) { 113 | h->values[index] = value; 114 | } else { 115 | if (h->count * 2 >= h->allocated) { 116 | hash_resize(h, h->allocated * 2); 117 | index = hash_index(h, key, hash); 118 | } 119 | h->defined[index] = 1; 120 | h->hashes[index] = hash; 121 | h->keys[index] = key; 122 | h->values[index] = value; 123 | h->count += 1; 124 | } 125 | } 126 | 127 | void *hash_lookup (hash_t *h, void *key) { 128 | unsigned int hash = h->hash_func(key); 129 | int index = hash_index(h, key, hash); 130 | void *value = NULL; 131 | if (h->defined[index]) { 132 | value = h->values[index]; 133 | } 134 | return value; 135 | } 136 | 137 | -------------------------------------------------------------------------------- /tabs.c: -------------------------------------------------------------------------------- 1 | #include "les.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | size_t tabs_len = 0; 10 | size_t tabs_size = 0; 11 | tab_t **tabs; 12 | int current_tab = 0; 13 | tab_t *tabb; 14 | 15 | void add_tab (const char *name, int fd, int state) { 16 | tab_t *tabb = malloc(sizeof (tab_t)); 17 | tabb->name = name; 18 | tabb->name_width = strwidth(name); 19 | tabb->name2 = strdup(name); 20 | tabb->fd = fd; 21 | tabb->pos = 0; 22 | tabb->state = state; 23 | tabb->buf_size = 8192; 24 | tabb->buf = malloc(tabb->buf_size); 25 | tabb->buf_len = 0; 26 | tabb->stragglers = malloc(16); 27 | tabb->stragglers_len = 0; 28 | tabb->line = 1; 29 | tabb->nlines = 0; 30 | tabb->column = 0; 31 | tabb->mesg_size = 16; 32 | tabb->mesg_len = 0; 33 | tabb->mesg = malloc(tabb->mesg_size); 34 | tabb->mark = 0; 35 | tabb->opened = time(NULL); 36 | tabb->realpath = NULL; 37 | tabb->search_version = 0; 38 | tabb->matches = NULL; 39 | tabb->matches_len = 0; 40 | tabb->matches_size = 0; 41 | tabb->current_match = 0; 42 | tabb->highlights = NULL; 43 | tabb->highlights_len = 0; 44 | tabb->highlights_size = 0; 45 | tabb->highlights_processed = 0; 46 | if (tabs_size == 0) { 47 | tabs_size = 4; 48 | tabs = malloc(tabs_size * sizeof (tab_t *)); 49 | } 50 | else if (tabs_len == tabs_size) { 51 | tabs_size *= 2; 52 | tabs = realloc(tabs, tabs_size * sizeof (tab_t *)); 53 | } 54 | tabs[tabs_len] = tabb; 55 | tabs_len++; 56 | } 57 | 58 | void init_line1 () { 59 | if (tabs_len > 1) { 60 | line1 = 1; 61 | } 62 | else { 63 | line1 = 0; 64 | } 65 | stage_cat(tparm(change_scroll_region, line1, lines - 2)); 66 | } 67 | 68 | void generate_tab_names () { 69 | int i; 70 | int width = (tabs_len - 1) * 2; 71 | for (i = 0; i < tabs_len; i++) { 72 | strcpy(tabs[i]->name2, tabs[i]->name); 73 | width += tabs[i]->name_width; 74 | } 75 | if (width <= columns) { 76 | return; 77 | } 78 | width = (tabs_len - 1) * 2; 79 | for (i = 0; i < tabs_len; i++) { 80 | char *name = basename(tabs[i]->name2); 81 | strcpy(tabs[i]->name2, name); 82 | width += strwidth(name); 83 | } 84 | if (width <= columns) { 85 | return; 86 | } 87 | width = 4 * tabs_len + 2 * (tabs_len - 1); 88 | int width2 = (columns - 2 * (tabs_len - 1)) / tabs_len; 89 | if (width2 < 6) { 90 | width2 = 10; 91 | } 92 | for (i = 0; i < tabs_len; i++) { 93 | char *name = tabs[i]->name2; 94 | shorten(name, width2); 95 | } 96 | } 97 | 98 | void stage_tabs () { 99 | if (tabs_len == 1) { 100 | return; 101 | } 102 | generate_tab_names(); 103 | stage_cat(tparm(cursor_address, 0, 0)); 104 | stage_cat(tparm(set_a_background, 232 + 2)); 105 | int i; 106 | int width = 0; 107 | for (i = 0; i < tabs_len; i++) { 108 | char *name = tabs[i]->name2; 109 | int width2 = strwidth(name); 110 | if (width + width2 > columns) { 111 | break; 112 | } 113 | if (i == current_tab) { 114 | stage_cat(enter_bold_mode); 115 | stage_cat(name); 116 | stage_cat(exit_attribute_mode); 117 | stage_cat(tparm(set_a_background, 232 + 2)); 118 | } 119 | else { 120 | stage_cat(name); 121 | } 122 | width += width2; 123 | if (width + 2 > columns) { 124 | break; 125 | } 126 | if (i < tabs_len - 1) { 127 | stage_cat(" "); 128 | } 129 | } 130 | for (i = 0; i < columns - width; i++) { 131 | stage_cat(" "); 132 | } 133 | stage_cat(exit_attribute_mode); 134 | stage_cat("\n"); 135 | } 136 | 137 | void next_tab () { 138 | current_tab = (current_tab + 1) % tabs_len; 139 | tabb = tabs[current_tab]; 140 | change_tab(); 141 | draw_tab(); 142 | } 143 | 144 | void prev_tab () { 145 | current_tab = current_tab > 0 ? (current_tab - 1) : (tabs_len - 1); 146 | tabb = tabs[current_tab]; 147 | change_tab(); 148 | draw_tab(); 149 | } 150 | 151 | void change_tab () { 152 | open_tab_file(); 153 | if (tabb->search_version != search_version) { 154 | tabb->matches_len = 0; 155 | // Can't search here since the buffer might not be loaded 156 | // yet, best wait till user presses n. 157 | } 158 | stage_tabs(); 159 | } 160 | 161 | void close_tab () { 162 | if (tabs_len == 1) { 163 | exit(0); 164 | return; 165 | } 166 | tab_t *tabb2 = tabb; 167 | add_recent_tab(tabb2); 168 | int i; 169 | for (i = current_tab; i < tabs_len - 1; i++) { 170 | tabs[i] = tabs[i + 1]; 171 | } 172 | tabs_len--; 173 | if (current_tab == tabs_len) { 174 | current_tab--; 175 | } 176 | tabb = tabs[current_tab]; 177 | 178 | free(tabb2->realpath); 179 | free(tabb2->name2); 180 | free(tabb2->buf); 181 | free(tabb2->stragglers); 182 | free(tabb2->mesg); 183 | free(tabb2->matches); 184 | free(tabb2->highlights); 185 | if (tabb2->state & OPENED && tabb2->fd) { 186 | close(tabb2->fd); 187 | } 188 | free(tabb2); 189 | 190 | init_line1(); 191 | change_tab(); 192 | draw_tab(); 193 | } 194 | 195 | -------------------------------------------------------------------------------- /movement.c: -------------------------------------------------------------------------------- 1 | #include "les.h" 2 | #include 3 | #include 4 | #include 5 | 6 | void move_forward_long (int n) { 7 | size_t pos = tlines[tlines_len - 1].end_pos; 8 | get_tlines(tabb->buf, tabb->buf_len, pos, n - tlines_len + 1, &tlines2, &tlines2_len, &tlines2_size); 9 | tabb->pos = tlines2[tlines2_len - 1].pos; 10 | int i; 11 | for (i = 0; i < tlines_len; i++) { 12 | if (tabb->buf[tlines[i].end_pos - 1] == '\n') { 13 | tabb->line++; 14 | } 15 | } 16 | for (i = 0; i < tlines2_len - 1; i++) { 17 | if (tabb->buf[tlines2[i].end_pos - 1] == '\n') { 18 | tabb->line++; 19 | } 20 | } 21 | draw_tab(); 22 | } 23 | 24 | void move_forward_short (int n) { 25 | int m = n > (tlines_len - 1) ? (tlines_len - 1) : n; 26 | int i; 27 | for (i = 0; i < m; i++) { 28 | if (tabb->buf[tlines[i].end_pos - 1] == '\n') { 29 | tabb->line++; 30 | } 31 | } 32 | for (i = 0; i < tlines_len - m; i++) { 33 | tlines[i] = tlines[i + m]; 34 | } 35 | tlines_len = tlines_len - m; 36 | size_t pos = tlines[tlines_len - 1].end_pos; 37 | get_tlines(tabb->buf, tabb->buf_len, pos, m, &tlines2, &tlines2_len, &tlines2_size); 38 | for (i = 0; i < tlines2_len; i++) { 39 | tlines_len++; 40 | tlines[tlines_len - 1] = tlines2[i]; 41 | } 42 | tabb->pos = tlines[0].pos; 43 | stage_cat(cursor_up); 44 | stage_cat(tparm(parm_index, m)); 45 | stage_cat(tparm(cursor_address, lines - 1 - m, 0)); 46 | stage_tab2(m, tlines2, tlines2_len); 47 | draw_status(); 48 | } 49 | 50 | void move_forward (int n) { 51 | if (tlines_len <= 1) { 52 | return; 53 | } 54 | if (n < lines - line1 - 1 || tlines[tlines_len - 1].end_pos == tabb->buf_len) { 55 | move_forward_short(n); 56 | } 57 | else { 58 | move_forward_long(n); 59 | } 60 | } 61 | 62 | void move_backward (int n) { 63 | if (tabb->pos == 0) { 64 | return; 65 | } 66 | get_tlines_backward(tabb->buf, tabb->buf_len, tabb->pos, n); 67 | int m = tlines3_len; 68 | tabb->pos = tlines3[tlines3_len - 1].pos; 69 | int i; 70 | for (i = 0; i < tlines3_len; i++) { 71 | if (tabb->buf[tlines3[i].end_pos - 1] == '\n') { 72 | tabb->line--; 73 | } 74 | } 75 | if (m >= lines - line1 - 1) { 76 | draw_tab(); 77 | return; 78 | } 79 | for (i = 0; i < m; i++) { 80 | tlines2[m - i - 1] = tlines3[i]; 81 | } 82 | tlines2_len = m; 83 | 84 | int n2 = (lines - line1 - 1 - m) > tlines_len ? (tlines_len) : (lines - line1 - 1 - m); 85 | for (i = n2 - 1; i >= 0; i--) { 86 | tlines[i + m] = tlines[i]; 87 | } 88 | tlines_len = n2 + m; 89 | for (i = 0; i < m; i++) { 90 | tlines[i] = tlines2[i]; 91 | } 92 | 93 | stage_cat(tparm(cursor_address, line1, 0)); 94 | stage_cat(tparm(parm_rindex, m)); 95 | stage_tab2(m, tlines2, tlines2_len); 96 | draw_status(); 97 | } 98 | 99 | void move_start () { 100 | tabb->pos = 0; 101 | tabb->line = 1; 102 | tabb->column = 0; 103 | draw_tab(); 104 | } 105 | 106 | void move_end () { 107 | if (tabb->nlines < lines - line1 - 1) { 108 | tabb->pos = 0; 109 | tabb->line = 1; 110 | draw_tab(); 111 | return; 112 | } 113 | tabb->pos = tabb->buf_len; 114 | tabb->line = tabb->nlines + 1; 115 | move_backward(lines - line1 - 1); 116 | } 117 | 118 | void move_to_pos (size_t pos) { 119 | pos = line_before_pos(pos, 0); 120 | tabb->line += count_lines_atob(tabb->buf, tabb->pos, pos); 121 | tabb->pos = pos; 122 | draw_tab(); 123 | } 124 | 125 | void move_to_line (int line) { 126 | if (line == 1) { 127 | tabb->pos = 0; 128 | tabb->line = 1; 129 | tlines_len = 0; 130 | return; 131 | } 132 | int line2 = 1; 133 | int pos = 0; 134 | int i; 135 | for (i = 0; i < tabb->buf_len; i++) { 136 | if (tabb->buf[i] == '\n') { 137 | pos = i + 1; 138 | line2++; 139 | if (line == line2) { 140 | tabb->pos = pos; 141 | tabb->line = line2; 142 | tlines_len = 0; 143 | return; 144 | } 145 | } 146 | } 147 | tabb->pos = pos; 148 | tabb->line = line2; 149 | tlines_len = 0; 150 | } 151 | 152 | void move_left (int n) { 153 | if (line_wrap) { 154 | return; 155 | } 156 | if (tabb->column == 0) { 157 | return; 158 | } 159 | tabb->column -= n; 160 | if (tabb->column < 0) { 161 | tabb->column = 0; 162 | } 163 | draw_tab(); 164 | } 165 | 166 | void move_right (int n) { 167 | if (line_wrap) { 168 | return; 169 | } 170 | tabb->column += n; 171 | draw_tab(); 172 | } 173 | 174 | void move_line_left () { 175 | if (line_wrap) { 176 | return; 177 | } 178 | if (tabb->column == 0) { 179 | return; 180 | } 181 | tabb->column = 0; 182 | draw_tab(); 183 | } 184 | 185 | void move_line_right () { 186 | if (line_wrap) { 187 | return; 188 | } 189 | int i; 190 | int width = 0; 191 | for (i = 0; i < tlines_len; i++) { 192 | int width2 = strnwidth(tabb->buf + tlines[i].pos, tlines[i].end_pos - tlines[i].pos); 193 | if (width2 > width) { 194 | width = width2; 195 | } 196 | } 197 | int column = width - columns; 198 | if (column < 0) { 199 | column = 0; 200 | } 201 | tabb->column = column; 202 | draw_tab(); 203 | } 204 | 205 | -------------------------------------------------------------------------------- /les.1: -------------------------------------------------------------------------------- 1 | .TH LES 1 "2017-03-18" 2 | .SH NAME 3 | les - A pager program similar to less and more 4 | .SH SYNOPSIS 5 | \fBles\fP [\fB-fhmw\fP] [\fB-e\fP=\fIencoding\fP] [\fB-p\fP=\fIscript\fP] [\fB-t\fP=\fIwidth\fP] \fIfile\fP... 6 | .SH DESCRIPTION 7 | Les is a program for paging through files and stdin. It is similar 8 | to the \fBless\fP(1) program but with some features added and some 9 | removed. 10 | .SH OPTIONS 11 | .TP 12 | \fB-e\fP=\fIencoding\fP 13 | input file encoding 14 | .TP 15 | \fB-f\fP 16 | load forever 17 | .TP 18 | \fB-h\fP 19 | help 20 | .TP 21 | \fB-m\fP 22 | input is a man page 23 | .TP 24 | \fB-p\fP=\fIscript\fP 25 | lespipe script 26 | .TP 27 | \fB-t\fP=\fIwidth\fP 28 | tab width (default 4) 29 | .TP 30 | \fB-w\fP 31 | disable line wrap 32 | .SH FEATURES / TAB BAR 33 | .PP 34 | If you open multiple files, the top line will show them all as a 35 | list of tabs. Press t and T to cycle through them, q to close one, 36 | and Q to close all of them. 37 | .SH STATUS BAR 38 | .PP 39 | At the bottom of the screen is a status bar that shows information 40 | like the filename, line numbers, and the file size. The color in the 41 | background shows how far through the file you are. 42 | .SH UNICODE 43 | .PP 44 | This program handles Unicode in order to get the right number of 45 | characters on a line. This requires the program to know the number 46 | of bytes in each character, and the screen width of the character 47 | (characters in Unicode can take 0, 1, or 2 widths). the input file 48 | is assumed to be UTF-8. If it isn't, then you can give the correct 49 | encoding with the -e (encoding) option. 50 | .SH SEARCHING 51 | .PP 52 | When you search, all matches will be highlighted in blue, the one 53 | you are up to in green. If the match is on the page you are currently 54 | viewing the page will not move. If it is less than a page away, 55 | then it will only move as far as it needs to show the next match. 56 | If the match is further away then it will position the screen with 57 | the match in the center. If you move the screen away from the current 58 | match, the next time you press n, the program will search forward 59 | for a match from the beginning of the screen you are currently 60 | looking at. 61 | .PP 62 | The number of matches is shown in the status bar. 63 | .PP 64 | You can recall your previous search entries by pressing up or down 65 | at the search prompt. Search history is stored in the ~/.les_history 66 | file. 67 | .SH MAN PAGES 68 | .PP 69 | You can set your man page output to be piped through les by adding 70 | the following to your .bashrc: 71 | .PP 72 | .RS 73 | .nf 74 | alias man="man -P \\"les -m\\"" 75 | .fi 76 | .RE 77 | .PP 78 | The -m option puts the name of the man page into the status line 79 | and recognizes backspace escapes specially. 80 | .SH GIT DIFF 81 | .PP 82 | You can set les to be your git diff viewer by adding the following 83 | to your .gitconfig: 84 | .PP 85 | .RS 86 | .nf 87 | [core] 88 | pager = les 89 | .fi 90 | .RE 91 | .SH LESPIPE SCRIPT 92 | .PP 93 | Files opened in les are processed through the lespipe script. This 94 | allows the program to print out the contents of tar files or 95 | decompress gziped files on the fly. 96 | .PP 97 | The default script is installed in /usr/local/share/les/lespipe, 98 | but it can be overridden by specifying an alternate program name 99 | with the -p option. If the script is specified as "none", then there 100 | will be no processing of inputs. 101 | .PP 102 | The lespipe script is given the file name as its first argument. 103 | If the script doesn't want to process the file, it needs to print 104 | nothing to stdout. If the script wants to process the file, it needs 105 | to print one line describing the type of processing, followed by 106 | the processed content. Here is a short working example of a lespipe 107 | script: 108 | .PP 109 | .RS 110 | .nf 111 | #!/bin/bash 112 | if [ -d "$1" ]; then 113 | echo directory 114 | ls -l "$1" 115 | elif [[ "$1" = *.gz ]]; then 116 | echo gzip 117 | gzcat "$1" 118 | fi 119 | .fi 120 | .RE 121 | .SH MOUSEWHEEL SCROLL 122 | .PP 123 | This program allows you to scroll the page using the mousewheel. 124 | It works by putting the terminal into "keyboard transmit mode" which 125 | makes the terminal send up and down arrows when you use the mousewheel. 126 | .SH RECENT FILES LIST 127 | .PP 128 | When you close a file, that file is remembered in a recent files 129 | list, stored in ~/.les_recents. You can view the list by pressing F2. 130 | .SH RESUMING AT YOUR LAST POSITION 131 | .PP 132 | When you reopen a file that you viewed before, you will be positioned 133 | at the line you last left off. 134 | .SH LOADING FOREVER 135 | .PP 136 | If you are reading a file that grows such as a log file, you can 137 | instruct the program to load forever with the -f option or the F 138 | key bind. When additional data is read, your screen will move to 139 | the end. When in load forever mode, the status bar will show "..." 140 | in it. This is similar to the "tail -f" command. 141 | .SH KEY BINDS 142 | .TP 143 | \fBd\fP 144 | go down half a screen 145 | .TP 146 | \fBD,pgdn\fP 147 | go down a screen 148 | .TP 149 | \fBF\fP 150 | load forever 151 | .TP 152 | \fBg\fP 153 | go to the top 154 | .TP 155 | \fBG\fP 156 | go to the bottom 157 | .TP 158 | \fBh,left\fP 159 | go left one third a screen 160 | .TP 161 | \fBH,home\fP 162 | go left all the way 163 | .TP 164 | \fBj,down\fP 165 | go down one line 166 | .TP 167 | \fBk,up\fP 168 | go up one line 169 | .TP 170 | \fBl,right\fP 171 | go right one third a screen 172 | .TP 173 | \fBL,end\fP 174 | go right all the way 175 | .TP 176 | \fBm\fP 177 | mark position 178 | .TP 179 | \fBM\fP 180 | go to marked position 181 | .TP 182 | \fBn\fP 183 | go to next match 184 | .TP 185 | \fBN\fP 186 | go to previous match 187 | .TP 188 | \fBq\fP 189 | close file 190 | .TP 191 | \fBQ\fP 192 | close all files 193 | .TP 194 | \fBt\fP 195 | go to next tab 196 | .TP 197 | \fBT\fP 198 | go to previous tab 199 | .TP 200 | \fBu\fP 201 | go up half a screen 202 | .TP 203 | \fBU,pgup\fP 204 | go up a screen 205 | .TP 206 | \fBw\fP 207 | toggle line wrap 208 | .TP 209 | \fB/\fP 210 | search 211 | .TP 212 | \fBF1\fP 213 | view help 214 | .TP 215 | \fBF2\fP 216 | view recently opened files 217 | .SH SEE ALSO 218 | \fBless\fP(1), \fBmore\fP(1) 219 | .SH AUTHOR 220 | Jacob Gelbman 221 | -------------------------------------------------------------------------------- /les.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Jacob Gelbman 2 | // This file is licensed under the LGPL 3 | 4 | #ifndef __LES_H__ 5 | #define __LES_H__ 6 | 7 | #define _GNU_SOURCE 8 | #include 9 | #include 10 | #include 11 | #include "defines.h" 12 | 13 | typedef struct { 14 | int len; 15 | int mask; 16 | int error; 17 | unsigned int codepoint; 18 | int width; 19 | } charinfo_t; 20 | 21 | typedef struct { 22 | unsigned int from; 23 | unsigned int to; 24 | int width; 25 | } width_range_t; 26 | 27 | typedef struct { 28 | size_t start; 29 | size_t end; 30 | } match_t; 31 | 32 | #define UNDERLINED 1 33 | #define BOLD 2 34 | 35 | typedef struct { 36 | size_t start; 37 | size_t end; 38 | int type; 39 | } highlight_t; 40 | 41 | typedef struct { 42 | const char *name; 43 | int name_width; 44 | char *name2; 45 | int fd; 46 | char *buf; 47 | size_t buf_size; 48 | size_t buf_len; 49 | char *stragglers; 50 | size_t stragglers_len; 51 | size_t pos; 52 | int state; 53 | int line; 54 | int nlines; 55 | int column; 56 | char *mesg; 57 | size_t mesg_size; 58 | size_t mesg_len; 59 | size_t mark; 60 | time_t opened; 61 | char *realpath; 62 | int last_line; 63 | int search_version; 64 | match_t *matches; 65 | size_t matches_len; 66 | size_t matches_size; 67 | size_t current_match; 68 | highlight_t *highlights; 69 | size_t highlights_len; 70 | size_t highlights_size; 71 | size_t highlights_processed; 72 | } tab_t; 73 | 74 | typedef struct { 75 | size_t pos; 76 | size_t end_pos; 77 | } tline_t; 78 | 79 | typedef struct { 80 | char *buf; 81 | size_t len; 82 | size_t size; 83 | const char *prompt; 84 | size_t prompt_len; 85 | int nlines; 86 | int nlines2; 87 | size_t cursor; 88 | char **history; 89 | size_t history_len; 90 | size_t history_size; 91 | size_t history_skip; 92 | size_t history_new; 93 | char *hcstr; 94 | size_t hcstr_len; 95 | size_t hcstr_size; 96 | } prompt_t; 97 | 98 | #define UTF8_LENGTH(c) \ 99 | ((c & 0x80) == 0x00 ? 1 : \ 100 | (c & 0xc0) == 0x80 ? 1 : \ 101 | (c & 0xe0) == 0xc0 ? 2 : \ 102 | (c & 0xf0) == 0xe0 ? 3 : \ 103 | (c & 0xf8) == 0xf0 ? 4 : \ 104 | (c & 0xfc) == 0xf8 ? 5 : \ 105 | (c & 0xfe) == 0xfc ? 6 : \ 106 | 6) 107 | 108 | #define READY 0 109 | #define OPENED 1 110 | #define LOADED 2 111 | #define MARKED 4 112 | #define RECENTS 8 113 | #define HELP 16 114 | #define ERROR 32 115 | #define POSITIONED 64 116 | #define FILEBACKED 128 117 | #define LOADFOREVER 256 118 | #define SPECIAL 512 119 | 120 | extern int tty; 121 | extern int line1; 122 | extern int line_wrap; 123 | extern int tab_width; 124 | extern prompt_t *pr; 125 | extern int interrupt; 126 | extern size_t tabs_len; 127 | extern tab_t **tabs; 128 | extern int current_tab; 129 | extern tab_t *tabb; 130 | extern char *lespipe; 131 | extern int active_search; 132 | extern int search_version; 133 | extern int man_page; 134 | 135 | extern tline_t *tlines; 136 | extern size_t tlines_len; 137 | extern size_t tlines_size; 138 | extern tline_t *tlines2; 139 | extern size_t tlines2_len; 140 | extern size_t tlines2_size; 141 | extern tline_t *tlines3; 142 | extern size_t tlines3_len; 143 | extern size_t tlines3_size; 144 | 145 | // main.c 146 | int read_key (char *buf, int len); 147 | 148 | // charinfo.c 149 | void shorten (char *str, int n); 150 | int strwidth (const char *str); 151 | int strnwidth (const char *str, size_t len); 152 | void get_char_info (charinfo_t *cinfo, const char *buf, int i); 153 | 154 | // prompt.c 155 | void draw_prompt (); 156 | char *gets_prompt (prompt_t *ppr); 157 | prompt_t *init_prompt (const char *prompt); 158 | void alert (char *fmt, ...); 159 | void add_history (prompt_t *pr, char *str, size_t len); 160 | 161 | // linewrap.c 162 | void get_tlines (char *buf, size_t len, size_t pos, int max, tline_t **tlines, size_t *tlines_len, size_t *tlines_size); 163 | void get_wrap_tlines (char *buf, size_t len, size_t pos, int max, tline_t **tlines, size_t *tlines_len, size_t *tlines_size); 164 | void get_nowrap_tlines (char *buf, size_t len, size_t pos, int max, tline_t **tlines, size_t *tlines_len, size_t *tlines_size); 165 | int prev_line (char *buf, size_t len, size_t pos, int n); 166 | int next_line (char *buf, size_t len, size_t pos, int n); 167 | void get_tlines_backward (char *buf, size_t len, size_t pos, int max); 168 | int find_pos_backward (int max, size_t pos2, size_t *pos3); 169 | int find_pos_forward (int max, size_t pos2, size_t *pos3); 170 | size_t line_before_pos (size_t pos, int n); 171 | 172 | // movement.c 173 | void move_forward (int n); 174 | void move_backward (int n); 175 | void move_start (); 176 | void move_end (); 177 | void move_left (int n); 178 | void move_right (int n); 179 | void move_line_left (); 180 | void move_line_right (); 181 | void move_to_pos (size_t pos); 182 | void move_to_line (int line); 183 | 184 | // stage.c 185 | void stage_init (); 186 | void stage_cat (const char *str); 187 | void stage_ncat (const char *str, size_t n); 188 | void stage_vprintf (const char *fmt, va_list args); 189 | void stage_printf (const char *fmt, ...); 190 | void stage_write (); 191 | 192 | // page.c 193 | void draw_tab (); 194 | void stage_tab2 (int n, tline_t *tlines, size_t tlines_len); 195 | void draw_status (); 196 | void init_page (); 197 | void set_ttybuf (charinfo_t *cinfo, char *buf, int len); 198 | 199 | // tabs.c 200 | void stage_tabs (); 201 | void close_tab (); 202 | void next_tab (); 203 | void prev_tab (); 204 | void add_tab (const char *name, int fd, int state); 205 | void change_tab (); 206 | void init_line1 (); 207 | 208 | // readfile.c 209 | void read_file (); 210 | void set_input_encoding (char *encoding); 211 | void open_tab_file (); 212 | int count_lines (char *buf, size_t len); 213 | int count_lines_atob (char *buf, size_t a, size_t b); 214 | void reload (); 215 | void set_man_page_name (); 216 | 217 | // recentfiles.c 218 | void add_recent_tab (tab_t *tabb); 219 | void save_recents_file (); 220 | void add_recents_tab (); 221 | void load_recents_file (); 222 | void get_last_line (); 223 | 224 | // search.c 225 | void search (); 226 | void next_match (); 227 | void prev_match (); 228 | void load_search_history (); 229 | void save_search_history (); 230 | void clear_matches (); 231 | 232 | #endif 233 | 234 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | les 2 | === 3 | 4 | This is a pager program similar to less and more, with several 5 | improved features. 6 | 7 | Screenshot 8 | ========== 9 | 10 | ![Screenshot](https://github.com/zorgnax/les/wiki/screenshot.png) 11 | 12 | Options 13 | ======= 14 | 15 | -e=encoding input file encoding 16 | -f load forever 17 | -h help 18 | -m input is a man page 19 | -p=script lespipe script 20 | -t=width tab width (default 4) 21 | -w disable line wrap 22 | 23 | Tab Bar 24 | ======= 25 | 26 | If you open multiple files, the top line will show them all as a 27 | list of tabs. Press t and T to cycle through them, q to close one, 28 | and Q to close all of them. 29 | 30 | Status Bar 31 | ========== 32 | 33 | At the bottom of the screen is a status bar that shows information 34 | like the filename, line numbers, and the file size. The color in the 35 | background shows how far through the file you are. 36 | 37 | Unicode 38 | ======= 39 | 40 | This program handles Unicode in order to get the right number of 41 | characters on a line. This requires the program to know the number 42 | of bytes in each character, and the screen width of the character 43 | (characters in Unicode can take 0, 1, or 2 widths). the input file 44 | is assumed to be UTF-8. If it isn't, then you can give the correct 45 | encoding with the -e (encoding) option. 46 | 47 | Searching 48 | ========= 49 | 50 | When you search, all matches will be highlighted in blue, the one 51 | you are up to in green. If the match is on the page you are currently 52 | viewing the page will not move. If it is less than a page away, 53 | then it will only move as far as it needs to show the next match. 54 | If the match is further away then it will position the screen with 55 | the match in the center. If you move the screen away from the current 56 | match, the next time you press n, the program will search forward 57 | for a match from the beginning of the screen you are currently 58 | looking at. 59 | 60 | The number of matches is shown in the status bar. 61 | 62 | You can recall your previous search entries by pressing up or down 63 | at the search prompt. Search history is stored in the ~/.les_history 64 | file. 65 | 66 | Man Pages 67 | ========= 68 | 69 | You can set your man page output to be piped through les by adding 70 | the following to your .bashrc: 71 | 72 | alias man="man -P \"les -m\"" 73 | 74 | The -m option puts the name of the man page into the status line 75 | and recognizes backspace escapes specially. 76 | 77 | Git Diff 78 | ======== 79 | 80 | You can set les to be your git diff viewer by adding the following 81 | to your .gitconfig: 82 | 83 | [core] 84 | pager = les 85 | 86 | lespipe Script 87 | ============== 88 | 89 | Files opened in les are processed through the lespipe script. This 90 | allows the program to print out the contents of tar files or 91 | decompress gziped files on the fly. 92 | 93 | The default script is installed in /usr/local/share/les/lespipe, 94 | but it can be overridden by specifying an alternate program name 95 | with the -p option. If the script is specified as "none", then there 96 | will be no processing of inputs. 97 | 98 | The lespipe script is given the file name as its first argument. 99 | If the script doesn't want to process the file, it needs to print 100 | nothing to stdout. If the script wants to process the file, it needs 101 | to print one line describing the type of processing, followed by 102 | the processed content. Here is a short working example of a lespipe 103 | script: 104 | 105 | #!/bin/bash 106 | if [ -d "$1" ]; then 107 | echo directory 108 | ls -l "$1" 109 | elif [[ "$1" = *.gz ]]; then 110 | echo gzip 111 | gzcat "$1" 112 | fi 113 | 114 | Mousewheel Scroll 115 | ================= 116 | 117 | This program allows you to scroll the page using the mousewheel. 118 | It works by putting the terminal into "keyboard transmit mode" which 119 | makes the terminal send up and down arrows when you use the mousewheel. 120 | 121 | Recent Files List 122 | ================= 123 | 124 | When you close a file, that file is remembered in a recent files 125 | list, stored in ~/.les_recents. You can view the list by pressing F2. 126 | 127 | Resuming At Your Last Position 128 | ============================== 129 | 130 | When you reopen a file that you viewed before, you will be positioned 131 | at the line you last left off. 132 | 133 | Loading Forever 134 | =============== 135 | 136 | If you are reading a file that grows such as a log file, you can 137 | instruct the program to load forever with the -f option or the F 138 | key bind. When additional data is read, your screen will move to 139 | the end. When in load forever mode, the status bar will show "..." 140 | in it. This is similar to the "tail -f" command. 141 | 142 | Key Binds 143 | ========= 144 | 145 | d go down half a screen 146 | D,⇟ go down a screen 147 | F load forever 148 | g go to the top 149 | G go to the bottom 150 | h,← go left one third a screen 151 | H,⇱ go left all the way 152 | j,↓ go down one line 153 | k,↑ go up one line 154 | l,→ go right one third a screen 155 | L,⇲ go right all the way 156 | m mark position 157 | M go to marked position 158 | n go to next match 159 | N go to previous match 160 | q close file 161 | Q close all files 162 | t go to next tab 163 | T go to previous tab 164 | u go up half a screen 165 | U,⇞ go up a screen 166 | w toggle line wrap 167 | / search 168 | c clear search matches 169 | F1 view help 170 | F2 view recently opened files 171 | 172 | Improvements Over less 173 | ====================== 174 | 175 | les allows keyboard commands while loading the file. 176 | 177 | les will close the program when Ctrl-C is pressed. 178 | 179 | les will wrap a line at word boundaries. 180 | 181 | In less, you sometimes have to press j multiple times to go down 182 | one wrapped line. 183 | 184 | In less, if the line is wrapped and you move right, line wrap is 185 | turned off and you move right, which shuffles the screen and looks 186 | confusing. 187 | 188 | In less, left and right movement are bound to the arrow keys when 189 | they should be bound to h and l. 190 | 191 | In less, there are a lot of useless command line options and key 192 | binds that no one will use ever. For example (-a, -A, -b, -B, -c, 193 | -C, -d, -D, -e, -E, -f, -g, -G) 194 | 195 | In less, if you start searching and want to cancel, pressing esc 196 | doesn't work, it just adds more text to your search prompt. 197 | 198 | In less if you do a search it will move the screen so the match is 199 | at the top line, even if the match is already on screen. 200 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /search.c: -------------------------------------------------------------------------------- 1 | #include "les.h" 2 | #include "rx.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | prompt_t *spr = NULL; 10 | rx_t *rx = NULL; 11 | matcher_t *m = NULL; 12 | int active_search = 0; 13 | int search_version = 0; 14 | 15 | // First one that starts after tabb->pos, or the first match 16 | void get_current_match () { 17 | tabb->current_match = 0; 18 | int i; 19 | for (i = 0; i < tabb->matches_len; i++) { 20 | if (tabb->matches[i].start >= tabb->pos) { 21 | tabb->current_match = i; 22 | return; 23 | } 24 | } 25 | } 26 | 27 | // first one at the bottom of the screen, or the last match 28 | void get_current_match_backward () { 29 | tabb->current_match = tabb->matches_len - 1; 30 | int i; 31 | for (i = tabb->matches_len - 1; i >= 0; i--) { 32 | if (tabb->matches[i].start < tlines[tlines_len - 1].end_pos) { 33 | tabb->current_match = i; 34 | return; 35 | } 36 | } 37 | } 38 | 39 | void move_to_match () { 40 | size_t pos = tabb->matches[tabb->current_match].start; 41 | size_t pos2; 42 | int retval; 43 | // Move the page to the right to be able to see the match 44 | if (!line_wrap) { 45 | int bol = prev_line(tabb->buf, tabb->buf_len, pos, 0); 46 | int width = strnwidth(tabb->buf + bol, pos - bol); 47 | if (width < columns) { 48 | tabb->column = 0; 49 | } 50 | else { 51 | int end = tabb->matches[tabb->current_match].end; 52 | int eol = next_line(tabb->buf, tabb->buf_len, pos, 1); 53 | if (eol < end) { 54 | end = eol; 55 | } 56 | int width2 = strnwidth(tabb->buf + pos, end - pos); 57 | if (width2 > columns) { 58 | tabb->column = width - (columns / 2); 59 | } 60 | else { 61 | tabb->column = width - columns + width2; 62 | } 63 | } 64 | } 65 | if (pos >= tabb->pos && pos < tlines[tlines_len - 1].end_pos) { 66 | return; 67 | } 68 | else if (pos < tabb->pos) { 69 | retval = find_pos_backward(lines - line1 - 1, pos, &pos2); 70 | if (retval) { 71 | tabb->line += count_lines_atob(tabb->buf, tabb->pos, pos2); 72 | tabb->pos = pos2; 73 | } 74 | else { 75 | pos2 = line_before_pos(pos, (lines - line1 - 1) / 2); 76 | tabb->line += count_lines_atob(tabb->buf, tabb->pos, pos2); 77 | tabb->pos = pos2; 78 | } 79 | } 80 | else if (pos >= tlines[tlines_len - 1].end_pos) { 81 | retval = find_pos_forward(lines - line1 - 1, pos, &pos2); 82 | if (retval) { 83 | tabb->line += count_lines_atob(tabb->buf, tabb->pos, pos2); 84 | tabb->pos = pos2; 85 | } 86 | else { 87 | pos2 = line_before_pos(pos, (lines - line1 - 1) / 2); 88 | tabb->line += count_lines_atob(tabb->buf, tabb->pos, pos2); 89 | tabb->pos = pos2; 90 | } 91 | } 92 | } 93 | 94 | void find_matches () { 95 | active_search = 0; 96 | tabb->search_version = search_version; 97 | tabb->matches_len = 0; 98 | tabb->current_match = 0; 99 | active_search = 1; 100 | 101 | int i = 0; 102 | while (i < tabb->buf_len) { 103 | rx_match(rx, m, tabb->buf_len, tabb->buf, i); 104 | if (!m->success) { 105 | break; 106 | } 107 | if (tabb->matches_len + 1 > tabb->matches_size) { 108 | if (!tabb->matches_size) { 109 | tabb->matches_size = 16; 110 | tabb->matches = malloc(tabb->matches_size * sizeof (match_t)); 111 | } 112 | else { 113 | tabb->matches_size *= 2; 114 | tabb->matches = realloc(tabb->matches, tabb->matches_size * sizeof (match_t)); 115 | } 116 | } 117 | tabb->matches_len++; 118 | tabb->matches[tabb->matches_len - 1].start = m->cap_start[0]; 119 | tabb->matches[tabb->matches_len - 1].end = m->cap_end[0]; 120 | i = m->cap_end[0]; 121 | if (m->cap_size[0] == 0) { 122 | i += UTF8_LENGTH(tabb->buf[i]); 123 | } 124 | } 125 | 126 | if (tabb->matches_len == 0) { 127 | goto end; 128 | } 129 | 130 | get_current_match(); 131 | move_to_match(); 132 | 133 | end: 134 | draw_tab(); 135 | } 136 | 137 | char *history_file () { 138 | char *home = getenv("HOME"); 139 | if (!home) { 140 | return NULL; 141 | } 142 | static char file[256]; 143 | snprintf(file, sizeof file, "%s/.les_history", home); 144 | return file; 145 | } 146 | 147 | void load_search_history () { 148 | spr = init_prompt("/"); 149 | char *file = history_file(); 150 | if (!file) { 151 | return; 152 | } 153 | FILE *fp = fopen(file, "r"); 154 | if (!fp) { 155 | return; 156 | } 157 | 158 | char str[512]; 159 | while (fgets(str, sizeof str, fp)) { 160 | int len = strlen(str); 161 | if (str[len - 1] == '\n') { 162 | str[len - 1] = '\0'; 163 | len--; 164 | } 165 | if (len) { 166 | add_history(spr, str, len); 167 | spr->history_new++; 168 | } 169 | } 170 | 171 | fclose(fp); 172 | } 173 | 174 | void save_search_history () { 175 | if (spr->history_len == spr->history_new) { 176 | return; 177 | } 178 | char *file = history_file(); 179 | if (!file) { 180 | return; 181 | } 182 | FILE *fp = fopen(file, "a"); 183 | if (!fp) { 184 | fprintf(stderr, "Couldn't open %s: %s\n", file, strerror(errno)); 185 | exit(1); 186 | } 187 | int i; 188 | for (i = spr->history_new; i < spr->history_len; i++) { 189 | fprintf(fp, "%s\n", spr->history[i]); 190 | } 191 | fclose(fp); 192 | } 193 | 194 | void clear_matches () { 195 | tabb->matches = NULL; 196 | tabb->matches_len = 0; 197 | tabb->matches_size = 0; 198 | tabb->current_match = 0; 199 | tabb->highlights = NULL; 200 | tabb->highlights_len = 0; 201 | tabb->highlights_size = 0; 202 | tabb->highlights_processed = 0; 203 | draw_tab(); 204 | } 205 | 206 | void search2 (char *pattern) { 207 | active_search = 0; 208 | search_version++; 209 | tabb->search_version = search_version; 210 | tabb->matches_len = 0; 211 | tabb->current_match = 0; 212 | if (!pattern || !pattern[0]) { 213 | draw_tab(); 214 | return; 215 | } 216 | 217 | if (!rx) { 218 | rx = rx_alloc(); 219 | m = rx_matcher_alloc(); 220 | } 221 | rx_init(rx, strlen(pattern), pattern); 222 | if (rx->error) { 223 | alert(rx->errorstr); 224 | return; 225 | } 226 | 227 | find_matches(); 228 | } 229 | 230 | void search () { 231 | char *pattern = gets_prompt(spr); 232 | if (interrupt) { 233 | draw_tab(); 234 | return; 235 | } 236 | search2(pattern); 237 | } 238 | 239 | void next_match () { 240 | if (search_version == 0 && spr->history_len) { 241 | search2(spr->history[spr->history_len - 1]); 242 | return; 243 | } 244 | if (tabb->search_version != search_version) { 245 | find_matches(); 246 | return; 247 | } 248 | if (!tabb->matches_len) { 249 | return; 250 | } 251 | size_t start = tabb->matches[tabb->current_match].start; 252 | if (start >= tlines[0].pos && start < tlines[tlines_len - 1].end_pos) { 253 | tabb->current_match = (tabb->current_match + 1) % tabb->matches_len; 254 | } 255 | else { 256 | get_current_match(); 257 | } 258 | move_to_match(); 259 | draw_tab(); 260 | } 261 | 262 | void prev_match () { 263 | if (search_version == 0 && spr->history_len) { 264 | search2(spr->history[spr->history_len - 1]); 265 | return; 266 | } 267 | if (tabb->search_version != search_version) { 268 | find_matches(); 269 | return; 270 | } 271 | if (!tabb->matches_len) { 272 | return; 273 | } 274 | if (tabb->matches[tabb->current_match].start >= tlines[0].pos && 275 | tabb->matches[tabb->current_match].end < tlines[tlines_len - 1].end_pos) { 276 | tabb->current_match = (tabb->matches_len + tabb->current_match - 1) % tabb->matches_len; 277 | } 278 | else { 279 | get_current_match_backward(); 280 | } 281 | move_to_match(); 282 | draw_tab(); 283 | } 284 | 285 | -------------------------------------------------------------------------------- /linewrap.c: -------------------------------------------------------------------------------- 1 | #include "les.h" 2 | #include 3 | #include 4 | 5 | tline_t *tlines = NULL; 6 | size_t tlines_len = 0; 7 | size_t tlines_size = 0; 8 | tline_t *tlines2 = NULL; 9 | size_t tlines2_len = 0; 10 | size_t tlines2_size = 0; 11 | tline_t *tlines3 = NULL; 12 | size_t tlines3_len = 0; 13 | size_t tlines3_size = 0; 14 | 15 | 16 | void get_nowrap_tlines (char *buf, size_t len, size_t pos, int max, tline_t **tlines, size_t *tlines_len, size_t *tlines_size) { 17 | int i; 18 | unsigned char c; 19 | 20 | *tlines_len = 1; 21 | (*tlines)[*tlines_len - 1].pos = pos; 22 | 23 | for (i = pos; i < len; i++) { 24 | c = buf[i]; 25 | if (c == '\n' || i == len - 1) { 26 | (*tlines)[*tlines_len - 1].end_pos = i + 1; 27 | if (i == len - 1 || (max && *tlines_len == max)) { 28 | break; 29 | } 30 | if (*tlines_len + 1 > *tlines_size) { 31 | *tlines_size *= 2; 32 | *tlines = realloc(*tlines, *tlines_size * sizeof (tline_t)); 33 | } 34 | (*tlines_len)++; 35 | (*tlines)[*tlines_len - 1].pos = i + 1; 36 | } 37 | } 38 | } 39 | 40 | void get_wrap_tlines (char *buf, size_t len, size_t pos, int max, tline_t **tlines, size_t *tlines_len, size_t *tlines_size) { 41 | int i, j, k; 42 | unsigned char c1, c2; 43 | int whitespace1, whitespace2; 44 | charinfo_t cinfo1, cinfo2; 45 | int column = 0; 46 | int width; 47 | 48 | *tlines_len = 1; 49 | (*tlines)[*tlines_len - 1].pos = pos; 50 | 51 | for (i = pos; i < len;) { 52 | c1 = buf[i]; 53 | 54 | if (c1 == '\n') { 55 | i++; 56 | (*tlines)[*tlines_len - 1].end_pos = i; 57 | if (i == len || (max && *tlines_len == max)) { 58 | goto end; 59 | } 60 | if (*tlines_len + 1 > *tlines_size) { 61 | *tlines_size *= 2; 62 | *tlines = realloc(*tlines, *tlines_size * sizeof (tline_t)); 63 | } 64 | (*tlines_len)++; 65 | (*tlines)[*tlines_len - 1].pos = i; 66 | 67 | column = 0; 68 | continue; 69 | } 70 | 71 | // get the word starting at byte i to byte j 72 | get_char_info(&cinfo1, buf, i); 73 | whitespace1 = c1 == ' ' || c1 == '\t'; 74 | width = cinfo1.width; 75 | for (j = i + cinfo1.len; j < len;) { 76 | unsigned char c2 = buf[j]; 77 | if (c2 == '\n') { 78 | break; 79 | } 80 | int whitespace2 = c2 == ' ' || c2 == '\t'; 81 | if (whitespace1 ^ whitespace2) { 82 | break; 83 | } 84 | get_char_info(&cinfo2, buf, j); 85 | width += cinfo2.width; 86 | j += cinfo2.len; 87 | } 88 | 89 | // width of the word fits on the line 90 | if (column + width <= columns) { 91 | column += width; 92 | i = j; 93 | continue; 94 | } 95 | 96 | // it doesn't fit on this line, but would fit on a line by itself 97 | if (width <= columns) { 98 | (*tlines)[*tlines_len - 1].end_pos = i; 99 | if (max && *tlines_len == max) { 100 | goto end; 101 | } 102 | if (*tlines_len + 1 > *tlines_size) { 103 | *tlines_size *= 2; 104 | *tlines = realloc(*tlines, *tlines_size * sizeof (tline_t)); 105 | } 106 | (*tlines_len)++; 107 | (*tlines)[*tlines_len - 1].pos = i; 108 | column = width; 109 | i = j; 110 | continue; 111 | } 112 | 113 | // wouldn't fit on a line by itself, so display as much as 114 | // you can on this line, then more on the next line 115 | for (k = 0; k < j - i;) { 116 | get_char_info(&cinfo2, buf, i + k); 117 | if (column + cinfo2.width > columns) { 118 | (*tlines)[*tlines_len - 1].end_pos = i + k; 119 | if (max && *tlines_len == max) { 120 | goto end; 121 | } 122 | if (*tlines_len + 1 > *tlines_size) { 123 | *tlines_size *= 2; 124 | *tlines = realloc(*tlines, *tlines_size * sizeof (tline_t)); 125 | } 126 | (*tlines_len)++; 127 | (*tlines)[*tlines_len - 1].pos = i + k; 128 | column = 0; 129 | } 130 | column += cinfo2.width; 131 | k += cinfo2.len; 132 | } 133 | i = j; 134 | } 135 | (*tlines)[*tlines_len - 1].end_pos = i; 136 | 137 | end: 138 | return; 139 | } 140 | 141 | void get_tlines (char *buf, size_t len, size_t pos, int max, tline_t **tlines, size_t *tlines_len, size_t *tlines_size) { 142 | *tlines_len = 0; 143 | if (!*tlines_size) { 144 | *tlines_size = lines; 145 | *tlines = malloc(*tlines_size * sizeof (tline_t)); 146 | } 147 | if (pos >= len) { 148 | return; 149 | } 150 | if (line_wrap) { 151 | get_wrap_tlines(buf, len, pos, max, tlines, tlines_len, tlines_size); 152 | } 153 | else { 154 | get_nowrap_tlines(buf, len, pos, max, tlines, tlines_len, tlines_size); 155 | } 156 | } 157 | 158 | int prev_line (char *buf, size_t len, size_t pos, int n) { 159 | int i; 160 | int line = 0; 161 | if (pos == 0) { 162 | return 0; 163 | } 164 | for (i = pos - 1; i > 0; i--) { 165 | if (buf[i] == '\n' || i == len - 1) { 166 | line++; 167 | if (line == n + 1) { 168 | return i + 1; 169 | } 170 | } 171 | } 172 | line++; 173 | return 0; 174 | } 175 | 176 | int next_line (char *buf, size_t len, size_t pos, int n) { 177 | int i; 178 | int line = 0; 179 | for (i = pos; i < len; i++) { 180 | if (buf[i] == '\n' || i == len - 1) { 181 | line++; 182 | if (line == n) { 183 | return i + 1; 184 | } 185 | } 186 | } 187 | return len; 188 | } 189 | 190 | // uses tlines2 and puts answer into tlines3 for convenience 191 | void get_tlines_backward (char *buf, size_t len, size_t pos, int max) { 192 | int i; 193 | if (tlines3_size < max) { 194 | tlines3_size = max; 195 | tlines3 = realloc(tlines3, tlines3_size * sizeof (tline_t)); 196 | } 197 | tlines3_len = 0; 198 | while (1) { 199 | int prev = prev_line(buf, len, pos, 1); 200 | get_tlines(buf, pos, prev, 0, &tlines2, &tlines2_len, &tlines2_size); 201 | if (!tlines2_len) { 202 | break; 203 | } 204 | for (i = tlines2_len - 1; i >= 0; i--) { 205 | tlines3_len++; 206 | tlines3[tlines3_len - 1] = tlines2[i]; 207 | if (tlines3_len == max) { 208 | return; 209 | } 210 | } 211 | pos = prev; 212 | } 213 | } 214 | 215 | // This is the position of the screen line before a position, if 216 | // the position occurs in the middle of the line, it will go to the 217 | // beginning of the screen line first. 218 | size_t line_before_pos (size_t pos, int n) { 219 | // If pos occurs inside of a line, find the beginning of the 220 | // line that contains it 221 | if (pos != 0 && tabb->buf[pos - 1] != '\n') { 222 | n++; 223 | } 224 | if (n == 0 || pos == 0) { 225 | return pos; 226 | } 227 | get_tlines_backward(tabb->buf, tabb->buf_len, pos, n); 228 | size_t pos2 = tlines3[tlines3_len - 1].pos; 229 | return pos2; 230 | } 231 | 232 | // Find pos2 backwards in max number of lines, number of lines it 233 | // actually took goes into nlines, and the position of the beginning 234 | // of that line goes into pos3 235 | int find_pos_backward (int max, size_t pos2, size_t *pos3) { 236 | size_t pos = tabb->pos; 237 | int i; 238 | if (tlines3_size < max) { 239 | tlines3_size = max; 240 | tlines3 = realloc(tlines3, tlines3_size * sizeof (tline_t)); 241 | } 242 | tlines3_len = 0; 243 | while (1) { 244 | int prev = prev_line(tabb->buf, tabb->buf_len, pos, 1); 245 | get_tlines(tabb->buf, pos, prev, 0, &tlines2, &tlines2_len, &tlines2_size); 246 | if (!tlines2_len) { 247 | return 0; 248 | } 249 | for (i = tlines2_len - 1; i >= 0; i--) { 250 | tlines3_len++; 251 | tlines3[tlines3_len - 1] = tlines2[i]; 252 | if (tlines3[tlines3_len - 1].pos <= pos2) { 253 | *pos3 = tlines3[tlines3_len - 1].pos; 254 | return 1; 255 | } 256 | if (tlines3_len == max) { 257 | return 0; 258 | } 259 | } 260 | pos = prev; 261 | } 262 | return 0; 263 | } 264 | 265 | // Finds the position of the top of the page to show pos at the bottom of it 266 | int find_pos_forward (int max, size_t pos2, size_t *pos3) { 267 | int i; 268 | size_t pos = tlines[tlines_len - 1].end_pos; 269 | for (i = 0; i < max; i++) { 270 | get_tlines(tabb->buf, tabb->buf_len, pos, 1, &tlines2, &tlines2_len, &tlines2_size); 271 | if (!tlines2_len) { 272 | return 0; 273 | } 274 | if (tlines2[0].end_pos > pos2) { 275 | *pos3 = tlines[i + 1].pos; 276 | return 1; 277 | } 278 | pos = tlines2[0].end_pos; 279 | } 280 | return 0; 281 | } 282 | 283 | -------------------------------------------------------------------------------- /recentfiles.c: -------------------------------------------------------------------------------- 1 | #include "les.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct { 9 | time_t opened; 10 | time_t closed; 11 | int line; 12 | const char *name; 13 | int new; 14 | int deleted; 15 | } recent_t; 16 | 17 | recent_t *recents; 18 | size_t recents_len = 0; 19 | size_t recents_size = 0; 20 | int recents_loaded = 0; 21 | char *home; 22 | 23 | recent_t *add_recent () { 24 | if (recents_len == recents_size) { 25 | if (recents_size == 0) { 26 | recents_size = 16; 27 | recents = malloc(recents_size * sizeof (recent_t)); 28 | } 29 | else { 30 | recents_size *= 2; 31 | recents = realloc(recents, recents_size * sizeof (recent_t)); 32 | } 33 | } 34 | recents_len++; 35 | recent_t *r = recents + recents_len - 1; 36 | r->new = 0; 37 | r->deleted = 0; 38 | return r; 39 | } 40 | 41 | int recentscmp (const void *a, const void *b) { 42 | const recent_t *a2 = a; 43 | const recent_t *b2 = b; 44 | return strcmp(a2->name, b2->name); 45 | } 46 | 47 | void delete_prior_entry (recent_t *r) { 48 | int i; 49 | for (i = recents_len - 2; i >= 0; i--) { 50 | recent_t *r2 = recents + i; 51 | if (strcmp(r2->name, r->name) == 0) { 52 | r2->deleted = 1; 53 | break; 54 | } 55 | } 56 | } 57 | 58 | void add_recent_tab (tab_t *tabb) { 59 | if (!tabb->fd || !tabb->realpath) { 60 | return; 61 | } 62 | recent_t *r = add_recent(); 63 | r->opened = tabb->opened; 64 | r->closed = time(NULL); 65 | r->line = tabb->line; 66 | r->name = strdup(tabb->realpath); 67 | r->new = 1; 68 | delete_prior_entry(r); 69 | } 70 | 71 | char *recents_file () { 72 | char *home = getenv("HOME"); 73 | if (!home) { 74 | return NULL; 75 | } 76 | static char file[256]; 77 | snprintf(file, sizeof file, "%s/.les_recents", home); 78 | return file; 79 | } 80 | 81 | void save_recents_file () { 82 | load_recents_file(); 83 | int i; 84 | for (i = 0; i < tabs_len; i++) { 85 | add_recent_tab(tabs[i]); 86 | } 87 | char *file = recents_file(); 88 | if (!file) { 89 | return; 90 | } 91 | FILE *fp = fopen(file, "w"); 92 | if (!fp) { 93 | fprintf(stderr, "Couldn't open %s: %s\n", file, strerror(errno)); 94 | exit(1); 95 | } 96 | for (i = 0; i < recents_len; i++) { 97 | recent_t *r = recents + i; 98 | if (r->deleted) { 99 | continue; 100 | } 101 | fprintf(fp, "%ld %ld %d %s\n", r->opened, r->closed, r->line, r->name); 102 | } 103 | fclose(fp); 104 | } 105 | 106 | int ws (char *str) { 107 | int i; 108 | for (i = 0; str[i]; i++) { 109 | if (str[i] != ' ' && str[i] != '\t') { 110 | break; 111 | } 112 | } 113 | return i; 114 | } 115 | 116 | int digits (char *str) { 117 | int i; 118 | for (i = 0; str[i]; i++) { 119 | if (str[i] < '0' || str[i] > '9') { 120 | break; 121 | } 122 | } 123 | return i; 124 | } 125 | 126 | int therest (char *str) { 127 | int i; 128 | for (i = strlen(str) - 1; i >= 0; i--) { 129 | if (!(str[i] == ' ' || str[i] == '\t' || str[i] == '\n')) { 130 | break; 131 | } 132 | } 133 | return i + 1; 134 | } 135 | 136 | void parse_recent_files_line (char *str) { 137 | int i = 0; 138 | int len; 139 | 140 | // optional leading whitespace 141 | len = ws(str + i); 142 | i += len; 143 | 144 | // opened timestamp 145 | char *opened = str + i; 146 | int opened_len = digits(str + i); 147 | if (!opened_len) { 148 | return; 149 | } 150 | i += opened_len; 151 | 152 | // whitespace 153 | len = ws(str + i); 154 | if (!len) { 155 | return; 156 | } 157 | i += len; 158 | 159 | // closed timestamp 160 | char *closed = str + i; 161 | int closed_len = digits(str + i); 162 | if (!closed_len) { 163 | return; 164 | } 165 | i += closed_len; 166 | 167 | // whitespace 168 | len = ws(str + i); 169 | if (!len) { 170 | return; 171 | } 172 | i += len; 173 | 174 | // last line you were on 175 | char *line = str + i; 176 | int line_len = digits(str + i); 177 | if (!line_len) { 178 | return; 179 | } 180 | i += line_len; 181 | 182 | // whitespace 183 | len = ws(str + i); 184 | if (!len) { 185 | return; 186 | } 187 | i += len; 188 | 189 | // file name 190 | char *name = str + i; 191 | int name_len = therest(str + i); 192 | if (!name_len) { 193 | return; 194 | } 195 | 196 | recent_t *r = add_recent(); 197 | opened[opened_len] = '\0'; 198 | r->opened = atoll(opened); 199 | closed[closed_len] = '\0'; 200 | r->closed = atoll(closed); 201 | line[line_len] ='\0'; 202 | r->line = atoi(line); 203 | name[name_len] = '\0'; 204 | r->name = strdup(name); 205 | } 206 | 207 | // Returns the most recent line from the last time you opened this file 208 | void get_last_line () { 209 | if (!tabb->realpath) { 210 | tabb->realpath = realpath(tabb->name, NULL); 211 | } 212 | if (!tabb->realpath) { 213 | return; 214 | } 215 | if (!recents_loaded) { 216 | load_recents_file(); 217 | } 218 | int i; 219 | for (i = recents_len - 1; i >= 0; i--) { 220 | recent_t *r = recents + i; 221 | if (strcmp(r->name, tabb->realpath) == 0) { 222 | tabb->last_line = r->line; 223 | return; 224 | } 225 | } 226 | } 227 | 228 | void load_recents_file () { 229 | recents_loaded = 1; 230 | 231 | recent_t *recents2 = recents; 232 | size_t recents2_len = recents_len; 233 | size_t recents2_size = recents_size; 234 | recents = NULL; 235 | recents_len = 0; 236 | recents_size = 0; 237 | 238 | char *file = recents_file(); 239 | if (!file) { 240 | return; 241 | } 242 | FILE *fp = fopen(file, "r"); 243 | if (!fp) { 244 | return; 245 | } 246 | 247 | char str[512]; 248 | while (fgets(str, sizeof str, fp)) { 249 | parse_recent_files_line(str); 250 | } 251 | 252 | fclose(fp); 253 | 254 | int i; 255 | for (i = 0; i < recents2_len; i++) { 256 | recent_t *r2 = recents2 + i; 257 | if (!r2->new) { 258 | continue; 259 | } 260 | recent_t *r = add_recent(); 261 | r->opened = r2->opened; 262 | r->closed = r2->closed; 263 | r->line = r2->line; 264 | r->name = r2->name; 265 | r->new = r2->new; 266 | delete_prior_entry(r); 267 | } 268 | free(recents2); 269 | } 270 | 271 | void add_recents_tab_line (recent_t *r) { 272 | int len = 0; 273 | static char str[32]; 274 | if (r->new) { 275 | len = snprintf(str, sizeof str, "* "); 276 | } 277 | struct tm *opened = localtime(&(r->opened)); 278 | len += strftime(str + len, sizeof str - len, "%Y-%m-%d %I:%M%p", opened); 279 | len = snprintf(tabb->buf + tabb->buf_len, tabb->buf_size - tabb->buf_len, "%-18s ", str); 280 | tabb->buf_len += len; 281 | int dur = r->closed - r->opened; 282 | if (dur < 60) { 283 | len = snprintf(str, sizeof str, "%ds", dur); 284 | } 285 | else if (dur < 60 * 60) { 286 | len = snprintf(str, sizeof str, "%dm", dur / 60); 287 | } 288 | else { 289 | dur /= (60 * 60); 290 | len = snprintf(str, sizeof str, "%dh", dur / (60 * 60)); 291 | } 292 | const char *name = r->name; 293 | char *hdir = ""; 294 | if (home) { 295 | size_t home_len = strlen(home); 296 | size_t name_len = strlen(name); 297 | if (name_len > home_len + 1 && strncmp(name, home, home_len) == 0 && name[home_len] == '/') { 298 | name += home_len + 1; 299 | hdir = "~/"; 300 | } 301 | } 302 | len = snprintf(tabb->buf + tabb->buf_len, tabb->buf_size - tabb->buf_len, "%-3s line %-5d %s%s\n", str, r->line, hdir, name); 303 | tabb->buf_len += len; 304 | 305 | tabb->nlines++; 306 | } 307 | 308 | void add_recents_tab () { 309 | int i; 310 | if (tabb->state & RECENTS) { 311 | close_tab(); 312 | return; 313 | } 314 | for (i = 0; i < tabs_len; i++) { 315 | if (tabs[i]->state & RECENTS) { 316 | current_tab = i; 317 | tabb = tabs[current_tab]; 318 | change_tab(); 319 | draw_tab(); 320 | return; 321 | } 322 | } 323 | 324 | static char str[32]; 325 | time_t today = time(NULL); 326 | struct tm *todaytm = malloc(sizeof (struct tm)); 327 | localtime_r(&today, todaytm); 328 | todaytm->tm_sec = 0; 329 | todaytm->tm_min = 0; 330 | todaytm->tm_hour = 0; 331 | todaytm->tm_isdst = -1; 332 | today = mktime(todaytm); 333 | struct tm *daytm = malloc(sizeof (struct tm)); 334 | home = getenv("HOME"); 335 | 336 | add_tab("[Recent Files]", 0, LOADED|RECENTS|SPECIAL); 337 | current_tab = tabs_len - 1; 338 | tabb = tabs[current_tab]; 339 | if (!recents_loaded) { 340 | load_recents_file(); 341 | } 342 | 343 | int day_line = 8; 344 | for (i = 0; i < recents_len; i++) { 345 | recent_t *r = recents + i; 346 | if (r->deleted) { 347 | continue; 348 | } 349 | if (tabb->buf_len + 512 > tabb->buf_size) { 350 | tabb->buf_size *= 2; 351 | tabb->buf = realloc(tabb->buf, tabb->buf_size); 352 | } 353 | if (day_line) { 354 | memcpy(daytm, todaytm, sizeof (struct tm)); 355 | daytm->tm_mday -= day_line - 1; 356 | daytm->tm_isdst = -1; 357 | time_t day = mktime(daytm); 358 | if (r->opened >= day) { 359 | day_line--; 360 | while (1) { 361 | daytm->tm_mday++; 362 | daytm->tm_isdst = -1; 363 | day = mktime(daytm); 364 | if (r->opened < day) { 365 | break; 366 | } 367 | day_line--; 368 | } 369 | memcpy(daytm, todaytm, sizeof (struct tm)); 370 | daytm->tm_mday -= day_line; 371 | daytm->tm_isdst = -1; 372 | day = mktime(daytm); 373 | strftime(str, sizeof str, "%A", daytm); 374 | char *day_nickname = ""; 375 | if (day_line == 0) { 376 | day_nickname = " (Today)"; 377 | } 378 | else if (day_line == 1) { 379 | day_nickname = " (Yesterday)"; 380 | } 381 | 382 | tabb->buf_len += snprintf(tabb->buf + tabb->buf_len, tabb->buf_size - tabb->buf_len, "--------- %s%s ---------\n", str, day_nickname); 383 | tabb->nlines++; 384 | } 385 | } 386 | add_recents_tab_line(r); 387 | } 388 | 389 | free(daytm); 390 | free(todaytm); 391 | init_line1(); 392 | change_tab(); 393 | move_end(); 394 | } 395 | 396 | -------------------------------------------------------------------------------- /readfile.c: -------------------------------------------------------------------------------- 1 | #include "les.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | char readbuf[8192]; 11 | iconv_t cd = NULL; 12 | char *input_encoding = NULL; 13 | char *lespipe = PREFIX "/share/les/lespipe"; 14 | 15 | int count_lines (char *buf, size_t len) { 16 | int i; 17 | int nlines = 0; 18 | for (i = 0; i < len; i++) { 19 | if (buf[i] == '\n') { 20 | nlines++; 21 | } 22 | } 23 | return nlines; 24 | } 25 | 26 | int count_lines_atob (char *buf, size_t a, size_t b) { 27 | int nlines = 0; 28 | if (a < b) { 29 | nlines = count_lines(buf + a, b - a); 30 | } 31 | else { 32 | nlines = -count_lines(buf + b, a - b); 33 | } 34 | return nlines; 35 | } 36 | 37 | void process_backspace_highlights () { 38 | if (!man_page) { 39 | return; 40 | } 41 | int i = tabb->highlights_processed; 42 | int p1 = i; 43 | if (p1 >= tabb->buf_len) { 44 | return; 45 | } 46 | int p2 = p1 + UTF8_LENGTH(tabb->buf[p1]); 47 | if (p2 >= tabb->buf_len) { 48 | return; 49 | } 50 | int p3 = p2 + UTF8_LENGTH(tabb->buf[p2]); 51 | while (p3 < tabb->buf_len) { 52 | int p4 = p3 + UTF8_LENGTH(tabb->buf[p3]); 53 | if (tabb->buf[p2] == '\b') { 54 | int type = 0; 55 | if (tabb->buf[p1] == '_') { 56 | type = UNDERLINED; 57 | } 58 | else if (tabb->buf[p1] == '+') { 59 | type = BOLD; 60 | } 61 | else if ((p2 - p1 == p4 - p3) && strncmp(tabb->buf + p1, tabb->buf + p3, p2 - p1) == 0) { 62 | type = BOLD; 63 | } 64 | if (type) { 65 | highlight_t *h = NULL; 66 | if (tabb->highlights_len) { 67 | highlight_t *h2 = tabb->highlights + tabb->highlights_len - 1; 68 | if (h2->end == i && h2->type == type) { 69 | h = h2; 70 | } 71 | } 72 | if (!h) { 73 | if (tabb->highlights_len == tabb->highlights_size) { 74 | if (tabb->highlights_size == 0) { 75 | tabb->highlights_size = 32; 76 | tabb->highlights = malloc(tabb->highlights_size * sizeof (highlight_t)); 77 | } 78 | else { 79 | tabb->highlights_size *= 2; 80 | tabb->highlights = realloc(tabb->highlights, tabb->highlights_size * sizeof (highlight_t)); 81 | } 82 | } 83 | tabb->highlights_len++; 84 | h = tabb->highlights + tabb->highlights_len - 1; 85 | h->start = i; 86 | h->type = type; 87 | } 88 | h->end = i + p4 - p3; 89 | } 90 | memmove(tabb->buf + i, tabb->buf + p3, p4 - p3); 91 | i += p4 - p3; 92 | p1 = p4; 93 | if (p1 >= tabb->buf_len) { 94 | break; 95 | } 96 | p2 = p1 + UTF8_LENGTH(tabb->buf[p1]); 97 | if (p2 >= tabb->buf_len) { 98 | break; 99 | } 100 | p3 = p2 + UTF8_LENGTH(tabb->buf[p2]); 101 | continue; 102 | } 103 | memmove(tabb->buf + i, tabb->buf + p1, p2 - p1); 104 | i += p2 - p1; 105 | p1 = p2; 106 | p2 = p3; 107 | p3 = p4; 108 | } 109 | tabb->highlights_processed = i; 110 | int j; 111 | for (j = p1; j < tabb->buf_len; j++, i++) { 112 | tabb->buf[i] = tabb->buf[j]; 113 | } 114 | tabb->buf_len = i; 115 | } 116 | 117 | void add_encoded_input (char *buf, size_t buf_len) { 118 | char *buf_ptr = buf; 119 | size_t buf_left = buf_len; 120 | 121 | size_t tabb_buf_left = tabb->buf_size - tabb->buf_len; 122 | char *tabb_buf_ptr = tabb->buf + tabb->buf_len; 123 | size_t tabb_buf_len_orig = tabb->buf_len; 124 | 125 | while (1) { 126 | size_t tabb_buf_left_orig = tabb_buf_left; 127 | int retval = iconv(cd, &buf_ptr, &buf_left, &tabb_buf_ptr, &tabb_buf_left); 128 | size_t n = tabb_buf_left_orig - tabb_buf_left; 129 | tabb->buf_len += n; 130 | if (retval == -1) { 131 | if (errno == E2BIG) { 132 | tabb_buf_left += tabb->buf_size; 133 | tabb->buf_size += tabb->buf_size; 134 | tabb->buf = realloc(tabb->buf, tabb->buf_size); 135 | tabb_buf_ptr = tabb->buf + tabb->buf_len; 136 | continue; 137 | } 138 | else if (errno == EINVAL) { 139 | memcpy(tabb->stragglers, buf_ptr, buf_left); 140 | tabb->stragglers_len = buf_left; 141 | } 142 | else if (errno == EILSEQ) { 143 | fprintf(stderr, "Cannot encode character.\n"); 144 | exit(1); 145 | } 146 | else { 147 | fprintf(stderr, "iconv error.\n"); 148 | exit(1); 149 | } 150 | } 151 | break; 152 | } 153 | tabb->nlines += count_lines(tabb->buf + tabb_buf_len_orig, tabb->buf_len - tabb_buf_len_orig); 154 | process_backspace_highlights(); 155 | } 156 | 157 | // makes sure buffer only contains whole UTF-8 characters, if any 158 | // are incomplete then they are stored in the stragglers array 159 | void add_unencoded_input (char *buf, size_t buf_len) { 160 | if (tabb->buf_size - tabb->buf_len < buf_len) { 161 | tabb->buf_size *= 2; 162 | tabb->buf = realloc(tabb->buf, tabb->buf_size); 163 | } 164 | int i; 165 | for (i = 0; i < buf_len;) { 166 | unsigned char c = buf[i]; 167 | int len = UTF8_LENGTH(c); 168 | if (i + len > buf_len) { 169 | memcpy(tabb->stragglers, buf + i, buf_len - i); 170 | tabb->stragglers_len = buf_len - i; 171 | } 172 | else { 173 | memcpy(tabb->buf + tabb->buf_len, buf + i, len); 174 | tabb->buf_len += len; 175 | } 176 | i += len; 177 | } 178 | tabb->nlines += count_lines(tabb->buf + tabb->buf_len - i, i); 179 | process_backspace_highlights(); 180 | } 181 | 182 | void read_file2 (char *readbuf, int nread) { 183 | if (input_encoding) { 184 | add_encoded_input(readbuf, nread); 185 | } 186 | else { 187 | add_unencoded_input(readbuf, nread); 188 | } 189 | if (tabb->buf_len == tabb->buf_size) { 190 | tabb->buf_size += tabb->buf_size; 191 | tabb->buf = realloc(tabb->buf, tabb->buf_size); 192 | } 193 | tabb->buf[tabb->buf_len] = '\0'; 194 | if (!(tabb->state & POSITIONED) && tabb->last_line > 1 && tabb->nlines >= tabb->last_line) { 195 | move_to_line(tabb->last_line); 196 | tabb->state |= POSITIONED; 197 | } 198 | } 199 | 200 | void read_file () { 201 | if (tabb->stragglers_len) { 202 | memcpy(readbuf, tabb->stragglers, tabb->stragglers_len); 203 | } 204 | int nread = read(tabb->fd, readbuf + tabb->stragglers_len, sizeof readbuf - tabb->stragglers_len); 205 | if (nread < 0 && (errno == EAGAIN || errno == EINTR)) { 206 | return; 207 | } 208 | if (nread < 0) { 209 | // For example trying to read a directory 210 | tabb->buf_len += snprintf(tabb->buf + tabb->buf_len, tabb->buf_size - tabb->buf_len, "Cannot read %s: %s\n", tabb->name, strerror(errno)); 211 | tabb->state |= ERROR; 212 | tabb->state |= LOADED; 213 | draw_tab(); 214 | return; 215 | } 216 | if (nread == 0) { 217 | tabb->state |= LOADED; 218 | if (tabb->buf_len && tabb->buf[tabb->buf_len - 1] != '\n') { 219 | tabb->nlines++; 220 | } 221 | draw_status(); 222 | return; 223 | } 224 | nread += tabb->stragglers_len; 225 | tabb->stragglers_len = 0; 226 | read_file2(readbuf, nread); 227 | if (tabb->state & LOADED && tabb->state & LOADFOREVER) { 228 | move_end(); 229 | } 230 | else if (tlines_len == lines - line1 - 1) { 231 | draw_status(); 232 | } 233 | else { 234 | draw_tab(); 235 | } 236 | } 237 | 238 | int open_with_lespipe () { 239 | if (strcmp(lespipe, "") == 0 || strcmp(lespipe, "none") == 0) { 240 | return 0; 241 | } 242 | 243 | int pipefd[2]; 244 | int retval = pipe(pipefd); 245 | if (retval < 0) { 246 | return 0; 247 | } 248 | int cpid = fork(); 249 | if (cpid < 0) { 250 | return 0; 251 | } 252 | if (cpid == 0) { 253 | dup2(pipefd[1], 1); 254 | dup2(pipefd[1], 2); 255 | close(pipefd[0]); 256 | close(pipefd[1]); 257 | execl(lespipe, lespipe, tabb->name, NULL); 258 | _exit(1); 259 | } 260 | 261 | close(pipefd[1]); 262 | int nread = read(pipefd[0], readbuf, sizeof readbuf); 263 | if (nread <= 0) { 264 | close(pipefd[0]); 265 | return 0; 266 | } 267 | int i; 268 | for (i = 0; i < nread; i++) { 269 | if (readbuf[i] == '\n') { 270 | break; 271 | } 272 | } 273 | if (i == 0 || i == nread) { 274 | close(pipefd[0]); 275 | return 0; 276 | } 277 | read_file2(readbuf + i + 1, nread - i - 1); 278 | tabb->fd = pipefd[0]; 279 | tabb->state |= OPENED; 280 | return 1; 281 | } 282 | 283 | void set_man_page_name () { 284 | int nread = read(tabb->fd, readbuf, sizeof readbuf); 285 | if (nread <= 0) { 286 | return; 287 | } 288 | if (nread < 3) { 289 | nread += read(tabb->fd, readbuf + nread, sizeof readbuf - nread); 290 | } 291 | int i; 292 | for (i = 0; i < nread; i++) { 293 | if (readbuf[i] != '\n' && readbuf[i] != ' ' && readbuf[i] != '\t') { 294 | break; 295 | } 296 | } 297 | int start = i; 298 | for (; i < nread; i++) { 299 | if (readbuf[i] == '\n' || readbuf[i] == ' ' || readbuf[i] == '\t') { 300 | break; 301 | } 302 | } 303 | if (i > start) { 304 | free(tabb->name2); 305 | tabb->name2 = strndup(readbuf + start, i - start); 306 | tabb->name = strdup(tabb->name2); 307 | tabb->name_width = strwidth(tabb->name); 308 | } 309 | read_file2(readbuf, nread); 310 | } 311 | 312 | void open_tab_file () { 313 | if (tabb->state & (OPENED|LOADED)) { 314 | return; 315 | } 316 | get_last_line(); 317 | if (open_with_lespipe()) { 318 | return; 319 | } 320 | int fd = open(tabb->name, O_RDONLY); 321 | if (fd < 0) { 322 | tabb->buf_len += snprintf(tabb->buf + tabb->buf_len, tabb->buf_size - tabb->buf_len, "Cannot open %s: %s\n", tabb->name, strerror(errno)); 323 | tabb->state |= ERROR; 324 | tabb->state |= LOADED; 325 | return; 326 | } 327 | tabb->fd = fd; 328 | tabb->state |= OPENED; 329 | } 330 | 331 | void set_input_encoding (char *encoding) { 332 | input_encoding = encoding; 333 | cd = iconv_open("UTF-8", input_encoding); 334 | if (cd == (iconv_t) -1) { 335 | fprintf(stderr, "Invalid encoding \"%s\"\n", encoding); 336 | exit(1); 337 | } 338 | } 339 | 340 | void reload () { 341 | if (!(tabb->state & FILEBACKED)) { 342 | return; 343 | } 344 | if (tabb->state & OPENED && !(tabb->state & LOADED) && tabb->fd) { 345 | close(tabb->fd); 346 | } 347 | tabb->state &= ~(OPENED|LOADED|ERROR); 348 | tabb->nlines = 0; 349 | tabb->buf_len = 0; 350 | tabb->stragglers_len = 0; 351 | tabb->matches_len = 0; 352 | tabb->search_version = 0; 353 | open_tab_file(); 354 | draw_tab(); 355 | } 356 | 357 | -------------------------------------------------------------------------------- /prompt.c: -------------------------------------------------------------------------------- 1 | #include "les.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | prompt_t *pr = NULL; 11 | int prompt_done = 0; 12 | 13 | void stage_prompt_line (tline_t *tline) { 14 | charinfo_t cinfo; 15 | int i; 16 | int width = 0; 17 | for (i = tline->pos; i < tline->end_pos;) { 18 | get_char_info(&cinfo, pr->buf, i); 19 | if (pr->buf[i] == '\r' || pr->buf[i] == '\n') { 20 | break; 21 | } 22 | else if (pr->buf[i] == '\t') { 23 | int j; 24 | for (j = 0; j < tab_width; j++) { 25 | stage_cat(" "); 26 | } 27 | } 28 | else { 29 | stage_ncat(pr->buf + i, cinfo.len); 30 | } 31 | width += cinfo.width; 32 | i += cinfo.len; 33 | } 34 | if (width < columns) { 35 | stage_cat(clr_eol); 36 | } 37 | } 38 | 39 | void draw_prompt () { 40 | stage_cat(tparm(cursor_address, lines - pr->nlines, 0)); 41 | tlines2_len = 0; 42 | get_wrap_tlines(pr->buf, pr->len, 0, 0, &tlines2, &tlines2_len, &tlines2_size); 43 | if (tlines_len < pr->nlines2) { 44 | stage_cat(clr_eos); 45 | } 46 | pr->nlines2 = tlines2_len; 47 | if (tlines2_len > pr->nlines) { 48 | pr->nlines = tlines2_len; 49 | } 50 | int i; 51 | for (i = 0; i < tlines2_len; i++) { 52 | stage_prompt_line(tlines2 + i); 53 | if (i != tlines2_len - 1) { 54 | stage_cat("\n"); 55 | } 56 | } 57 | int cursory = 0; 58 | int cursorx = 0; 59 | for (i = 0; i < tlines2_len; i++) { 60 | if (pr->cursor <= tlines2[i].end_pos) { 61 | cursory = i; 62 | cursorx = strnwidth(pr->buf + tlines2[i].pos, pr->cursor - tlines2[i].pos); 63 | if (cursorx >= columns) { 64 | cursory++; 65 | cursorx = 0; 66 | } 67 | break; 68 | } 69 | } 70 | if (cursory + 1 > pr->nlines) { 71 | pr->nlines++; 72 | stage_cat("\n"); 73 | } 74 | stage_cat(tparm(cursor_address, lines - pr->nlines + cursory, cursorx)); 75 | stage_write(); 76 | } 77 | 78 | void prompt_left () { 79 | if (pr->cursor == pr->prompt_len) { 80 | return; 81 | } 82 | int i = pr->cursor; 83 | while (i--) { 84 | if ((pr->buf[i] & 0xc0) != 0x80) { 85 | break; 86 | } 87 | } 88 | pr->cursor = i; 89 | } 90 | 91 | void prompt_right () { 92 | if (pr->cursor == pr->len) { 93 | return; 94 | } 95 | pr->cursor += UTF8_LENGTH(pr->buf[pr->cursor]); 96 | } 97 | 98 | void prompt_left_word () { 99 | if (pr->cursor == pr->prompt_len) { 100 | return; 101 | } 102 | int i, j, whitespace1, whitespace2; 103 | for (i = pr->cursor - 1; (pr->buf[i] & 0xc0) == 0x80; i--) 104 | ; 105 | for (; i > pr->prompt_len;) { 106 | j = i - 1; 107 | while ((pr->buf[j] & 0xc0) == 0x80) { 108 | j--; 109 | } 110 | whitespace1 = pr->buf[i] == ' ' || pr->buf[i] == '\t'; 111 | whitespace2 = pr->buf[j] == ' ' || pr->buf[j] == '\t'; 112 | if (!whitespace1 && whitespace2) { 113 | break; 114 | } 115 | i = j; 116 | } 117 | pr->cursor = i; 118 | } 119 | 120 | void prompt_right_word () { 121 | if (pr->cursor == pr->len) { 122 | return; 123 | } 124 | int i, j, whitespace1, whitespace2; 125 | for (i = pr->cursor; i < pr->len;) { 126 | j = i + UTF8_LENGTH(pr->buf[i]); 127 | if (j == pr->len) { 128 | break; 129 | } 130 | whitespace1 = pr->buf[i] == ' ' || pr->buf[i] == '\t'; 131 | whitespace2 = pr->buf[j] == ' ' || pr->buf[j] == '\t'; 132 | if (!whitespace1 && whitespace2) { 133 | break; 134 | } 135 | i = j; 136 | } 137 | pr->cursor = j; 138 | } 139 | 140 | void prompt_backspace () { 141 | size_t cursor1 = pr->cursor; 142 | prompt_left(); 143 | int len = cursor1 - pr->cursor; 144 | if (!len) { 145 | return; 146 | } 147 | int i; 148 | for (i = cursor1; i < pr->len; i++) { 149 | pr->buf[i - len] = pr->buf[i]; 150 | } 151 | pr->len -= len; 152 | } 153 | 154 | void prompt_backspace_word () { 155 | size_t cursor1 = pr->cursor; 156 | prompt_left_word(); 157 | int len = cursor1 - pr->cursor; 158 | if (!len) { 159 | return; 160 | } 161 | int i; 162 | for (i = cursor1; i < pr->len; i++) { 163 | pr->buf[i - len] = pr->buf[i]; 164 | } 165 | pr->len -= len; 166 | } 167 | 168 | void prompt_delete () { 169 | size_t cursor1 = pr->cursor; 170 | prompt_right(); 171 | int len = pr->cursor - cursor1; 172 | if (!len) { 173 | return; 174 | } 175 | int i; 176 | for (i = pr->cursor; i < pr->len; i++) { 177 | pr->buf[i - len] = pr->buf[i]; 178 | } 179 | pr->len -= len; 180 | pr->cursor -= len; 181 | } 182 | 183 | void prompt_delete_word () { 184 | size_t cursor1 = pr->cursor; 185 | prompt_right_word(); 186 | int len = pr->cursor - cursor1; 187 | if (!len) { 188 | return; 189 | } 190 | int i; 191 | for (i = pr->cursor; i < pr->len; i++) { 192 | pr->buf[i - len] = pr->buf[i]; 193 | } 194 | pr->len -= len; 195 | pr->cursor -= len; 196 | } 197 | 198 | void prompt_kill_forward () { 199 | pr->len = pr->cursor; 200 | } 201 | 202 | void prompt_kill_backward () { 203 | int i; 204 | int len = pr->cursor - pr->prompt_len; 205 | for (i = pr->cursor; i < pr->len; i++) { 206 | pr->buf[i - len] = pr->buf[i]; 207 | } 208 | pr->len -= len; 209 | pr->cursor -= len; 210 | } 211 | 212 | void puts_prompt (char *buf, int len) { 213 | int i; 214 | while (pr->len + len >= pr->size) { 215 | pr->size *= 2; 216 | pr->buf = realloc(pr->buf, pr->size); 217 | } 218 | for (i = pr->len - 1; i >= pr->cursor; i--) { 219 | pr->buf[i + len] = pr->buf[i]; 220 | } 221 | for (i = 0; i < len; i++) { 222 | pr->buf[pr->cursor + i] = buf[i]; 223 | } 224 | pr->len += len; 225 | pr->cursor += len; 226 | } 227 | 228 | void prev_history () { 229 | if (!pr->history_len) { 230 | return; 231 | } 232 | if (pr->history_skip >= pr->history_len) { 233 | return; 234 | } 235 | if (pr->history_skip == 0) { 236 | // Save current string 237 | while (pr->len - pr->prompt_len + 1 > pr->hcstr_size) { 238 | pr->hcstr_size *= 2; 239 | pr->hcstr = realloc(pr->hcstr, pr->hcstr_size); 240 | } 241 | strncpy(pr->hcstr, pr->buf + pr->prompt_len, pr->len - pr->prompt_len); 242 | pr->hcstr_len = pr->len - pr->prompt_len; 243 | pr->hcstr[pr->hcstr_len] = '\0'; 244 | } 245 | size_t orig_cursor = pr->cursor; 246 | char *str2 = pr->buf + pr->prompt_len; 247 | size_t str2_len = pr->cursor - pr->prompt_len; 248 | int i; 249 | for (i = pr->history_skip + 1; i <= pr->history_len; i++) { 250 | char *str = pr->history[pr->history_len - i]; 251 | size_t len = strlen(str); 252 | if (len < str2_len) { 253 | continue; 254 | } 255 | if (strncmp(str, str2, str2_len) == 0) { 256 | pr->len = pr->prompt_len; 257 | pr->cursor = pr->prompt_len; 258 | puts_prompt(str, len); 259 | pr->cursor = orig_cursor; 260 | pr->history_skip = i; 261 | return; 262 | } 263 | } 264 | } 265 | 266 | void next_history () { 267 | if (!pr->history_len) { 268 | return; 269 | } 270 | if (pr->history_skip < 1) { 271 | return; 272 | } 273 | size_t orig_cursor = pr->cursor; 274 | char *str2 = pr->buf + pr->prompt_len; 275 | size_t str2_len = pr->cursor - pr->prompt_len; 276 | 277 | while (pr->history_skip > 1) { 278 | pr->history_skip--; 279 | char *str = pr->history[pr->history_len - pr->history_skip]; 280 | size_t len = strlen(str); 281 | if (len < str2_len) { 282 | continue; 283 | } 284 | if (strncmp(str, str2, str2_len) == 0) { 285 | pr->len = pr->prompt_len; 286 | pr->cursor = pr->prompt_len; 287 | puts_prompt(str, len); 288 | pr->cursor = orig_cursor; 289 | return; 290 | } 291 | } 292 | 293 | // Show the set aside current string 294 | pr->history_skip--; 295 | pr->len = pr->prompt_len; 296 | pr->cursor = pr->prompt_len; 297 | puts_prompt(pr->hcstr, pr->hcstr_len); 298 | pr->cursor = orig_cursor; 299 | } 300 | 301 | int getc_prompt (char *buf, int len) { 302 | unsigned char c = buf[0]; 303 | int len2 = len; 304 | int history = 0; 305 | if ((c > 0x1f && c < 0x7f) || (c > 0x7f)) { 306 | int len2 = UTF8_LENGTH(c); 307 | puts_prompt(buf, len2); 308 | } 309 | else if (buf[0] == '\n') { 310 | prompt_done = 1; 311 | } 312 | else if (buf[0] == '\e' && len == 1) { 313 | interrupt = 1; 314 | } 315 | else if (strncmp(buf, "\eOB", 3) == 0) { // down 316 | next_history(); 317 | history = 1; 318 | } 319 | else if (strncmp(buf, "\eOA", 3) == 0) { // up 320 | prev_history(); 321 | history = 1; 322 | } 323 | else if (strncmp(buf, "\eOD", 3) == 0) { // left 324 | prompt_left(); 325 | } 326 | else if (strncmp(buf, "\eb", 2) == 0) { // alt-left 327 | prompt_left_word(); 328 | } 329 | else if (strncmp(buf, "\eOC", 3) == 0) { // right 330 | prompt_right(); 331 | } 332 | else if (strncmp(buf, "\ef", 2) == 0) { // alt-right 333 | prompt_right_word(); 334 | } 335 | else if (strncmp(buf, "\x7f", 1) == 0) { // backspace 336 | prompt_backspace(); 337 | } 338 | else if (strncmp(buf, "\e\x7f", 2) == 0) { // alt-backspace 339 | prompt_backspace_word(); 340 | } 341 | else if (strncmp(buf, "\e[3~", 4) == 0) { // delete 342 | prompt_delete(); 343 | } 344 | else if (strncmp(buf, "\e(", 2) == 0) { // alt-delete 345 | prompt_delete_word(); 346 | } 347 | else if (buf[0] == -0x40 + 'K') { 348 | prompt_kill_forward(); 349 | } 350 | else if (buf[0] == -0x40 + 'U') { 351 | prompt_kill_backward(); 352 | } 353 | else if (buf[0] == -0x40 + 'A') { 354 | pr->cursor = pr->prompt_len; 355 | } 356 | else if (buf[0] == -0x40 + 'E') { 357 | pr->cursor = pr->len; 358 | } 359 | else if (strncmp(buf, "\eOH", 3) == 0) { // home 360 | pr->cursor = pr->prompt_len; 361 | } 362 | else if (strncmp(buf, "\eOF", 3) == 0) { // end 363 | pr->cursor = pr->len; 364 | } 365 | 366 | if (!history) { 367 | pr->history_skip = 0; 368 | } 369 | return len2; 370 | } 371 | 372 | void alert (char *fmt, ...) { 373 | va_list args; 374 | interrupt = 0; 375 | stage_cat(tparm(change_scroll_region, 0, lines - 1)); 376 | stage_cat(tparm(cursor_address, lines - 1, 0)); 377 | stage_cat("\n"); 378 | va_start(args, fmt); 379 | stage_vprintf(fmt, args); 380 | va_end(args); 381 | stage_write(); 382 | 383 | char buf[16]; 384 | int nread = read(tty, buf, sizeof buf); 385 | stage_cat(tparm(change_scroll_region, line1, lines - 2)); 386 | stage_tabs(); 387 | draw_tab(); 388 | 389 | if (nread > 0) { 390 | read_key(buf, nread); 391 | } 392 | } 393 | 394 | void gets1_prompt () { 395 | prompt_done = 0; 396 | interrupt = 0; 397 | pr->len = pr->prompt_len; 398 | pr->cursor = pr->len; 399 | pr->nlines = 1; 400 | pr->history_skip = 0; 401 | if (!tlines2_size) { 402 | tlines2_size = lines; 403 | tlines2 = malloc(tlines_size * sizeof (tline_t)); 404 | } 405 | stage_cat(tparm(change_scroll_region, 0, lines - 1)); 406 | stage_cat(cursor_normal); 407 | stage_cat(tparm(cursor_address, lines - 1, 0)); 408 | stage_cat("\r"); 409 | stage_cat(clr_eol); 410 | stage_cat(pr->prompt); 411 | stage_write(); 412 | } 413 | 414 | void add_history (prompt_t *pr, char *str, size_t len) { 415 | // Don't add duplicates 416 | if (pr->history_len) { 417 | if (strcmp(pr->history[pr->history_len - 1], str) == 0) { 418 | return; 419 | } 420 | } 421 | if (pr->history_len == pr->history_size) { 422 | pr->history_size *= 2; 423 | pr->history = realloc(pr->history, pr->history_size * sizeof (char *)); 424 | } 425 | pr->history_len++; 426 | pr->history[pr->history_len - 1] = strndup(str, len); 427 | } 428 | 429 | void gets2_prompt () { 430 | if (pr->len == pr->size) { 431 | pr->size *= 2; 432 | pr->buf = realloc(pr->buf, pr->size); 433 | } 434 | pr->buf[pr->len] = '\0'; 435 | char *str = pr->buf + pr->prompt_len; 436 | size_t len = pr->len - pr->prompt_len; 437 | if (len) { 438 | add_history(pr, str, len); 439 | } 440 | stage_cat(cursor_invisible); 441 | stage_cat(tparm(change_scroll_region, line1, lines - 2)); 442 | stage_tabs(); 443 | } 444 | 445 | char *gets_prompt (prompt_t *ppr) { 446 | pr = ppr; 447 | gets1_prompt(); 448 | static char buf[256]; 449 | int len = 0; 450 | int i, nread, clen, processed; 451 | while (1) { 452 | nread = read(tty, buf + len, sizeof buf - len); 453 | if (interrupt) { 454 | break; 455 | } 456 | if (nread < 0 && (errno == EINTR || errno == EAGAIN)) { 457 | continue; 458 | } 459 | if (nread < 0) { 460 | perror("read"); 461 | exit(1); 462 | } 463 | if (nread == 0) { 464 | exit(1); 465 | } 466 | for (i = 0; i < nread;) { 467 | clen = UTF8_LENGTH(buf[i]); 468 | if (clen > nread - i) { 469 | memmove(buf, buf + i, nread - i); 470 | len = nread - i; 471 | break; 472 | } 473 | processed = getc_prompt(buf + i, nread - i); 474 | i += processed; 475 | if (prompt_done || interrupt) { 476 | goto end; 477 | } 478 | } 479 | draw_prompt(); 480 | } 481 | end: 482 | gets2_prompt(); 483 | char *str = pr->buf + pr->prompt_len; 484 | pr = NULL; 485 | return str; 486 | } 487 | 488 | prompt_t *init_prompt (const char *prompt) { 489 | prompt_t *pr = malloc(sizeof (prompt_t)); 490 | pr->prompt = prompt; 491 | pr->prompt_len = strlen(prompt); 492 | pr->size = 1024; 493 | pr->buf = malloc(pr->size); 494 | strcpy(pr->buf, pr->prompt); 495 | pr->len = pr->prompt_len; 496 | pr->cursor = pr->len; 497 | pr->nlines = 1; 498 | pr->history_size = 128; 499 | pr->history_len = 0; 500 | pr->history = malloc(pr->history_size * sizeof (char *)); 501 | pr->history_skip = 0; 502 | pr->history_new = 0; 503 | pr->hcstr_size = 128; 504 | pr->hcstr_len = 0; 505 | pr->hcstr = malloc(pr->hcstr_size); 506 | return pr; 507 | } 508 | 509 | -------------------------------------------------------------------------------- /charinfo.c: -------------------------------------------------------------------------------- 1 | #include "les.h" 2 | #include 3 | 4 | int get_char_width (unsigned int codepoint) { 5 | static width_range_t width_ranges[] = { 6 | {0x00, 0x00, 1}, 7 | {0x01, 0x07, 2}, 8 | {0x08, 0x08, -1}, 9 | {0x09, 0x1f, 2}, 10 | {0x7f, 0x7f, 2}, 11 | {0x80, 0x9f, 2}, 12 | {0x300, 0x36f, 0}, 13 | {0x483, 0x489, 0}, 14 | {0x591, 0x5bd, 0}, 15 | {0x5bf, 0x5bf, 0}, 16 | {0x5c1, 0x5c2, 0}, 17 | {0x5c4, 0x5c5, 0}, 18 | {0x5c7, 0x5c7, 0}, 19 | {0x610, 0x61a, 0}, 20 | {0x64b, 0x65f, 0}, 21 | {0x670, 0x670, 0}, 22 | {0x6d6, 0x6dc, 0}, 23 | {0x6df, 0x6e4, 0}, 24 | {0x6e7, 0x6e8, 0}, 25 | {0x6ea, 0x6ed, 0}, 26 | {0x70f, 0x70f, 0}, 27 | {0x711, 0x711, 0}, 28 | {0x730, 0x74a, 0}, 29 | {0x7a6, 0x7b0, 0}, 30 | {0x7eb, 0x7f3, 0}, 31 | {0x816, 0x819, 0}, 32 | {0x81b, 0x823, 0}, 33 | {0x825, 0x827, 0}, 34 | {0x829, 0x82d, 0}, 35 | {0x859, 0x85b, 0}, 36 | {0x8e3, 0x903, 0}, 37 | {0x93a, 0x93c, 0}, 38 | {0x93e, 0x94f, 0}, 39 | {0x951, 0x957, 0}, 40 | {0x962, 0x963, 0}, 41 | {0x981, 0x983, 0}, 42 | {0x9bc, 0x9bc, 0}, 43 | {0x9be, 0x9c4, 0}, 44 | {0x9c7, 0x9c8, 0}, 45 | {0x9cb, 0x9cd, 0}, 46 | {0x9d7, 0x9d7, 0}, 47 | {0x9e2, 0x9e3, 0}, 48 | {0xa01, 0xa03, 0}, 49 | {0xa3c, 0xa3c, 0}, 50 | {0xa3e, 0xa42, 0}, 51 | {0xa47, 0xa48, 0}, 52 | {0xa4b, 0xa4d, 0}, 53 | {0xa51, 0xa51, 0}, 54 | {0xa70, 0xa71, 0}, 55 | {0xa75, 0xa75, 0}, 56 | {0xa81, 0xa83, 0}, 57 | {0xabc, 0xabc, 0}, 58 | {0xabe, 0xac5, 0}, 59 | {0xac7, 0xac9, 0}, 60 | {0xacb, 0xacd, 0}, 61 | {0xae2, 0xae3, 0}, 62 | {0xb01, 0xb03, 0}, 63 | {0xb3c, 0xb3c, 0}, 64 | {0xb3e, 0xb44, 0}, 65 | {0xb47, 0xb48, 0}, 66 | {0xb4b, 0xb4d, 0}, 67 | {0xb56, 0xb57, 0}, 68 | {0xb62, 0xb63, 0}, 69 | {0xb82, 0xb82, 0}, 70 | {0xbbe, 0xbc2, 0}, 71 | {0xbc6, 0xbc8, 0}, 72 | {0xbca, 0xbcd, 0}, 73 | {0xbd7, 0xbd7, 0}, 74 | {0xc00, 0xc03, 0}, 75 | {0xc3e, 0xc44, 0}, 76 | {0xc46, 0xc48, 0}, 77 | {0xc4a, 0xc4d, 0}, 78 | {0xc55, 0xc56, 0}, 79 | {0xc62, 0xc63, 0}, 80 | {0xc81, 0xc83, 0}, 81 | {0xcbc, 0xcbc, 0}, 82 | {0xcbe, 0xcc4, 0}, 83 | {0xcc6, 0xcc8, 0}, 84 | {0xcca, 0xccd, 0}, 85 | {0xcd5, 0xcd6, 0}, 86 | {0xce2, 0xce3, 0}, 87 | {0xd01, 0xd03, 0}, 88 | {0xd3e, 0xd44, 0}, 89 | {0xd46, 0xd48, 0}, 90 | {0xd4a, 0xd4d, 0}, 91 | {0xd57, 0xd57, 0}, 92 | {0xd62, 0xd63, 0}, 93 | {0xd82, 0xd83, 0}, 94 | {0xdca, 0xdca, 0}, 95 | {0xdcf, 0xdd4, 0}, 96 | {0xdd6, 0xdd6, 0}, 97 | {0xdd8, 0xddf, 0}, 98 | {0xdf2, 0xdf3, 0}, 99 | {0xe31, 0xe31, 0}, 100 | {0xe34, 0xe3a, 0}, 101 | {0xe47, 0xe4e, 0}, 102 | {0xeb1, 0xeb1, 0}, 103 | {0xeb4, 0xeb9, 0}, 104 | {0xebb, 0xebc, 0}, 105 | {0xec8, 0xecd, 0}, 106 | {0xf18, 0xf19, 0}, 107 | {0xf35, 0xf35, 0}, 108 | {0xf37, 0xf37, 0}, 109 | {0xf39, 0xf39, 0}, 110 | {0xf3e, 0xf3f, 0}, 111 | {0xf71, 0xf84, 0}, 112 | {0xf86, 0xf87, 0}, 113 | {0xf8d, 0xf97, 0}, 114 | {0xf99, 0xfbc, 0}, 115 | {0xfc6, 0xfc6, 0}, 116 | {0x102b, 0x103e, 0}, 117 | {0x1056, 0x1059, 0}, 118 | {0x105e, 0x1060, 0}, 119 | {0x1062, 0x1064, 0}, 120 | {0x1067, 0x106d, 0}, 121 | {0x1071, 0x1074, 0}, 122 | {0x1082, 0x108d, 0}, 123 | {0x108f, 0x108f, 0}, 124 | {0x109a, 0x109d, 0}, 125 | {0x1100, 0x115f, 2}, 126 | {0x135d, 0x135f, 0}, 127 | {0x1712, 0x1714, 0}, 128 | {0x1732, 0x1734, 0}, 129 | {0x1752, 0x1753, 0}, 130 | {0x1772, 0x1773, 0}, 131 | {0x17b4, 0x17d3, 0}, 132 | {0x17dd, 0x17dd, 0}, 133 | {0x180b, 0x180d, 0}, 134 | {0x180b, 0x180e, 0}, 135 | {0x18a9, 0x18a9, 0}, 136 | {0x1920, 0x192b, 0}, 137 | {0x1930, 0x193b, 0}, 138 | {0x1a17, 0x1a1b, 0}, 139 | {0x1a55, 0x1a5e, 0}, 140 | {0x1a60, 0x1a7c, 0}, 141 | {0x1a7f, 0x1a7f, 0}, 142 | {0x1ab0, 0x1abe, 0}, 143 | {0x1b00, 0x1b04, 0}, 144 | {0x1b34, 0x1b44, 0}, 145 | {0x1b6b, 0x1b73, 0}, 146 | {0x1b80, 0x1b82, 0}, 147 | {0x1ba1, 0x1bad, 0}, 148 | {0x1be6, 0x1bf3, 0}, 149 | {0x1c24, 0x1c37, 0}, 150 | {0x1cd0, 0x1cd2, 0}, 151 | {0x1cd4, 0x1ce8, 0}, 152 | {0x1ced, 0x1ced, 0}, 153 | {0x1cf2, 0x1cf4, 0}, 154 | {0x1cf8, 0x1cf9, 0}, 155 | {0x1dc0, 0x1df5, 0}, 156 | {0x1dfc, 0x1dff, 0}, 157 | {0x200b, 0x200f, 0}, 158 | {0x202a, 0x202e, 0}, 159 | {0x206a, 0x206f, 0}, 160 | {0x20d0, 0x20f0, 0}, 161 | {0x2329, 0x232a, 2}, 162 | {0x2cef, 0x2cf1, 0}, 163 | {0x2d7f, 0x2d7f, 0}, 164 | {0x2de0, 0x2dff, 0}, 165 | {0x2e80, 0x2e99, 2}, 166 | {0x2e9b, 0x2ef3, 2}, 167 | {0x2f00, 0x2fd5, 2}, 168 | {0x2ff0, 0x2ffb, 2}, 169 | {0x3000, 0x303e, 2}, 170 | {0x302a, 0x302f, 0}, 171 | {0x3041, 0x3096, 2}, 172 | {0x3099, 0x30ff, 2}, 173 | {0x3099, 0x309a, 0}, 174 | {0x3105, 0x312d, 2}, 175 | {0x3131, 0x318e, 2}, 176 | {0x3190, 0x31ba, 2}, 177 | {0x31c0, 0x31e3, 2}, 178 | {0x31f0, 0x321e, 2}, 179 | {0x3220, 0x3247, 2}, 180 | {0x3250, 0x32fe, 2}, 181 | {0x3300, 0x4dbf, 2}, 182 | {0x4e00, 0xa48c, 2}, 183 | {0xa490, 0xa4c6, 2}, 184 | {0xa66f, 0xa672, 0}, 185 | {0xa674, 0xa67d, 0}, 186 | {0xa69e, 0xa69f, 0}, 187 | {0xa6f0, 0xa6f1, 0}, 188 | {0xa802, 0xa802, 0}, 189 | {0xa806, 0xa806, 0}, 190 | {0xa80b, 0xa80b, 0}, 191 | {0xa823, 0xa827, 0}, 192 | {0xa880, 0xa881, 0}, 193 | {0xa8b4, 0xa8c4, 0}, 194 | {0xa8e0, 0xa8f1, 0}, 195 | {0xa926, 0xa92d, 0}, 196 | {0xa947, 0xa953, 0}, 197 | {0xa960, 0xa97c, 2}, 198 | {0xa980, 0xa983, 0}, 199 | {0xa9b3, 0xa9c0, 0}, 200 | {0xa9e5, 0xa9e5, 0}, 201 | {0xaa29, 0xaa36, 0}, 202 | {0xaa43, 0xaa43, 0}, 203 | {0xaa4c, 0xaa4d, 0}, 204 | {0xaa7b, 0xaa7d, 0}, 205 | {0xaab0, 0xaab0, 0}, 206 | {0xaab2, 0xaab4, 0}, 207 | {0xaab7, 0xaab8, 0}, 208 | {0xaabe, 0xaabf, 0}, 209 | {0xaac1, 0xaac1, 0}, 210 | {0xaaeb, 0xaaef, 0}, 211 | {0xaaf5, 0xaaf6, 0}, 212 | {0xabe3, 0xabea, 0}, 213 | {0xabec, 0xabed, 0}, 214 | {0xac00, 0xd7a3, 2}, 215 | {0xd800, 0xdfff, 0}, 216 | {0xf900, 0xfaff, 2}, 217 | {0xfb1e, 0xfb1e, 0}, 218 | {0xfe00, 0xfe0f, 0}, 219 | {0xfe10, 0xfe19, 2}, 220 | {0xfe20, 0xfe2f, 0}, 221 | {0xfe30, 0xfe52, 2}, 222 | {0xfe54, 0xfe66, 2}, 223 | {0xfe68, 0xfe6b, 2}, 224 | {0xfeff, 0xfeff, 0}, 225 | {0xff01, 0xff60, 2}, 226 | {0xffe0, 0xffe6, 2}, 227 | {0xfff9, 0xfffb, 0}, 228 | {0xfffe, 0xffff, 0}, 229 | {0x101fd, 0x101fd, 0}, 230 | {0x102e0, 0x102e0, 0}, 231 | {0x10376, 0x1037a, 0}, 232 | {0x10a01, 0x10a03, 0}, 233 | {0x10a05, 0x10a06, 0}, 234 | {0x10a0c, 0x10a0f, 0}, 235 | {0x10a38, 0x10a3a, 0}, 236 | {0x10a3f, 0x10a3f, 0}, 237 | {0x10ae5, 0x10ae6, 0}, 238 | {0x11000, 0x11002, 0}, 239 | {0x11038, 0x11046, 0}, 240 | {0x1107f, 0x11082, 0}, 241 | {0x110b0, 0x110ba, 0}, 242 | {0x11100, 0x11102, 0}, 243 | {0x11127, 0x11134, 0}, 244 | {0x11173, 0x11173, 0}, 245 | {0x11180, 0x11182, 0}, 246 | {0x111b3, 0x111c0, 0}, 247 | {0x111ca, 0x111cc, 0}, 248 | {0x1122c, 0x11237, 0}, 249 | {0x112df, 0x112ea, 0}, 250 | {0x11300, 0x11303, 0}, 251 | {0x1133c, 0x1133c, 0}, 252 | {0x1133e, 0x11344, 0}, 253 | {0x11347, 0x11348, 0}, 254 | {0x1134b, 0x1134d, 0}, 255 | {0x11357, 0x11357, 0}, 256 | {0x11362, 0x11363, 0}, 257 | {0x11366, 0x1136c, 0}, 258 | {0x11370, 0x11374, 0}, 259 | {0x114b0, 0x114c3, 0}, 260 | {0x115af, 0x115b5, 0}, 261 | {0x115b8, 0x115c0, 0}, 262 | {0x115dc, 0x115dd, 0}, 263 | {0x11630, 0x11640, 0}, 264 | {0x116ab, 0x116b7, 0}, 265 | {0x1171d, 0x1172b, 0}, 266 | {0x16af0, 0x16af4, 0}, 267 | {0x16b30, 0x16b36, 0}, 268 | {0x16f51, 0x16f7e, 0}, 269 | {0x16f8f, 0x16f92, 0}, 270 | {0x1b000, 0x1b001, 2}, 271 | {0x1bc9d, 0x1bc9e, 0}, 272 | {0x1d165, 0x1d169, 0}, 273 | {0x1d16d, 0x1d172, 0}, 274 | {0x1d17b, 0x1d182, 0}, 275 | {0x1d185, 0x1d18b, 0}, 276 | {0x1d1aa, 0x1d1ad, 0}, 277 | {0x1d242, 0x1d244, 0}, 278 | {0x1da00, 0x1da36, 0}, 279 | {0x1da3b, 0x1da6c, 0}, 280 | {0x1da75, 0x1da75, 0}, 281 | {0x1da84, 0x1da84, 0}, 282 | {0x1da9b, 0x1da9f, 0}, 283 | {0x1daa1, 0x1daaf, 0}, 284 | {0x1e8d0, 0x1e8d6, 0}, 285 | {0x1f200, 0x1f202, 2}, 286 | {0x1f210, 0x1f23a, 2}, 287 | {0x1f240, 0x1f248, 2}, 288 | {0x1f250, 0x1f251, 2}, 289 | {0x20000, 0x2fffd, 2}, 290 | {0x30000, 0x3fffd, 2}, 291 | {0xe0100, 0xe01ef, 0}, 292 | }; 293 | 294 | if (codepoint == 0x09) { 295 | return tab_width; 296 | } 297 | else if (0x20 <= codepoint && codepoint < 0x7f) { 298 | return 1; 299 | } 300 | 301 | int n = sizeof width_ranges / sizeof width_ranges[0]; 302 | int i = 0, j = n - 1; 303 | while (i <= j) { 304 | int k = (i + j) / 2; 305 | if (codepoint < width_ranges[k].from) { 306 | j = k - 1; 307 | } 308 | else if (codepoint > width_ranges[k].to) { 309 | i = k + 1; 310 | } 311 | else { 312 | return width_ranges[k].width; 313 | } 314 | } 315 | 316 | return 1; 317 | } 318 | 319 | void get_escape_len (charinfo_t *cinfo, const char *buf, int i) { 320 | int j; 321 | for (j = 1;; j++) { 322 | char c = buf[i + j]; 323 | if (c == '[' || c == ';' || (c >= '0' && c <= '9')) { 324 | continue; 325 | } 326 | else if (c == 'm' || c == 'K') { 327 | cinfo->len = j + 1; 328 | cinfo->width = 0; 329 | return; 330 | } 331 | else { 332 | cinfo->len = 1; 333 | cinfo->width = 2; 334 | return; 335 | } 336 | } 337 | cinfo->len = 1; 338 | cinfo->width = 2; 339 | } 340 | 341 | void get_backspace_len (charinfo_t *cinfo, const char *buf, int i) { 342 | if (i == 0) { 343 | cinfo->width = 2; 344 | cinfo->len = 1; 345 | } 346 | else if (buf[i - 1] == '_' || 347 | buf[i - 1] == '+' || 348 | buf[i - 1] == buf[i + 1]) { 349 | cinfo->width = -1; 350 | cinfo->len = 1; 351 | } 352 | else { 353 | cinfo->width = 2; 354 | cinfo->len = 1; 355 | } 356 | } 357 | 358 | void get_char_info (charinfo_t *cinfo, const char *buf, int i) { 359 | char c = buf[i]; 360 | cinfo->error = 0; 361 | if ((c & 0x80) == 0x00) { 362 | cinfo->len = 1; 363 | cinfo->mask = 0x7f; 364 | } 365 | else if ((c & 0xe0) == 0xc0) { 366 | cinfo->len = 2; 367 | cinfo->mask = 0x1f; 368 | } 369 | else if ((c & 0xf0) == 0xe0) { 370 | cinfo->len = 3; 371 | cinfo->mask = 0x0f; 372 | } 373 | else if ((c & 0xf8) == 0xf0) { 374 | cinfo->len = 4; 375 | cinfo->mask = 0x07; 376 | } 377 | else if ((c & 0xfc) == 0xf8) { 378 | cinfo->len = 5; 379 | cinfo->mask = 0x03; 380 | } 381 | else if ((c & 0xfe) == 0xfc) { 382 | cinfo->len = 6; 383 | cinfo->mask = 0x01; 384 | } 385 | else { 386 | cinfo->len = 1; 387 | cinfo->width = 4; 388 | cinfo->mask = 0x00; 389 | cinfo->error = 1; 390 | return; 391 | } 392 | 393 | unsigned int codepoint = buf[i] & cinfo->mask; 394 | int j; 395 | for (j = 1; j < cinfo->len; j++) { 396 | char c2 = buf[i + j]; 397 | if ((c2 & 0xc0) != 0x80) { 398 | cinfo->error = 1; 399 | cinfo->len = 1; 400 | cinfo->width = 4; 401 | return; 402 | } 403 | codepoint <<= 6; 404 | codepoint |= c2 & 0x3f; 405 | } 406 | cinfo->codepoint = codepoint; 407 | if ((cinfo->len > 1 && codepoint < 0x80) || 408 | (cinfo->len > 2 && codepoint < 0x800) || 409 | (cinfo->len > 3 && codepoint < 0x10000) || 410 | (cinfo->len > 4 && codepoint < 0x200000) || 411 | (cinfo->len > 5 && codepoint < 0x4000000)) { 412 | cinfo->error = 1; 413 | cinfo->len = 1; 414 | cinfo->width = 4; 415 | return; 416 | } 417 | if (c == 0x08) { 418 | get_backspace_len(cinfo, buf, i); 419 | } 420 | else if (c == 0x1b) { 421 | get_escape_len(cinfo, buf, i); 422 | } 423 | else { 424 | cinfo->width = get_char_width(codepoint); 425 | } 426 | } 427 | 428 | int strwidth (const char *str) { 429 | int i, width = 0; 430 | charinfo_t cinfo; 431 | for (i = 0; str[i];) { 432 | get_char_info(&cinfo, str, i); 433 | width += cinfo.width; 434 | i += cinfo.len; 435 | } 436 | return width; 437 | } 438 | 439 | int strnwidth (const char *str, size_t len) { 440 | int i, width = 0; 441 | charinfo_t cinfo; 442 | for (i = 0; i < len;) { 443 | get_char_info(&cinfo, str, i); 444 | width += cinfo.width; 445 | i += cinfo.len; 446 | } 447 | return width; 448 | } 449 | 450 | void shorten (char *str, int n) { 451 | int i, j; 452 | int width = 0; 453 | charinfo_t cinfo; 454 | for (i = 0; str[i];) { 455 | get_char_info(&cinfo, str, i); 456 | if (width + cinfo.width > n / 2) { 457 | break; 458 | } 459 | width += cinfo.width; 460 | i += cinfo.len; 461 | } 462 | int len = strlen(str); 463 | for (j = strlen(str) - 1; j > i; j--) { 464 | if ((str[j] & 0xc0) == 0x80) { 465 | j--; 466 | } 467 | get_char_info(&cinfo, str, j); 468 | width += cinfo.width; 469 | if (width >= n) { 470 | break; 471 | } 472 | } 473 | if (j > i) { 474 | memmove(str + i, str + j, len - j + 1); 475 | } 476 | } 477 | 478 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include "les.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int line_wrap = 1; 17 | int tty; 18 | struct termios tcattr1, tcattr2; 19 | int line1; 20 | int tab_width = 4; 21 | int interrupt = 0; 22 | int load_forever = 0; 23 | int man_page = 0; 24 | 25 | void reset () { 26 | tcsetattr(tty, TCSANOW, &tcattr1); 27 | stage_cat(tparm(change_scroll_region, 0, lines - 1)); 28 | stage_cat(cursor_normal); 29 | stage_cat(keypad_local); 30 | stage_cat(exit_ca_mode); 31 | stage_write(); 32 | } 33 | 34 | void bye () { 35 | reset(); 36 | save_recents_file(); 37 | save_search_history(); 38 | } 39 | 40 | void bye2 () { 41 | exit(1); 42 | } 43 | 44 | void sigint () { 45 | if (pr) { 46 | interrupt = 1; 47 | } 48 | else { 49 | exit(1); 50 | } 51 | } 52 | 53 | void set_tcattr () { 54 | tcattr2 = tcattr1; 55 | tcattr2.c_lflag &= ~(ICANON|ECHO); 56 | tcattr2.c_cc[VMIN] = 1; 57 | tcattr2.c_cc[VTIME] = 0; 58 | tcsetattr(tty, TCSAFLUSH, &tcattr2); 59 | } 60 | 61 | void sigchld () { 62 | int status; 63 | int cpid = wait3(&status, WNOHANG, NULL); 64 | } 65 | 66 | void sigtstp () { 67 | signal(SIGTTOU, SIG_IGN); 68 | reset(); 69 | signal(SIGTTOU, SIG_DFL); 70 | kill(getpid(), SIGSTOP); 71 | } 72 | 73 | void sigcont () { 74 | set_tcattr(); 75 | stage_cat(enter_ca_mode); 76 | stage_cat(keypad_xmit); 77 | stage_cat(cursor_invisible); 78 | init_line1(); 79 | if (pr) { 80 | draw_prompt(); 81 | } 82 | else { 83 | draw_tab(); 84 | } 85 | } 86 | 87 | void sigwinch () { 88 | struct winsize w; 89 | ioctl(tty, TIOCGWINSZ, &w); 90 | lines = w.ws_row; 91 | columns = w.ws_col; 92 | if (pr) { 93 | draw_prompt(); 94 | } 95 | else { 96 | stage_tabs(); 97 | draw_tab(); 98 | } 99 | } 100 | 101 | void save_mark () { 102 | tabb->state |= MARKED; 103 | tabb->mark = tabb->pos; 104 | } 105 | 106 | void restore_mark () { 107 | if (!(tabb->state & MARKED)) { 108 | return; 109 | } 110 | move_to_pos(tabb->mark); 111 | } 112 | 113 | char *usage_text () { 114 | static char *str = 115 | "Usage: les [-fhmw] [-e=encoding] [-p=script] [-t=width] file...\n" 116 | "\n" 117 | "Options:\n" 118 | " -e=encoding input file encoding\n" 119 | " -f load forever\n" 120 | " -h help\n" 121 | " -m input is a man page\n" 122 | " -p=script lespipe script\n" 123 | " -t=width tab width (default 4)\n" 124 | " -w disable line wrap\n" 125 | "\n" 126 | "Key Binds:\n" 127 | " d go down half a screen\n" 128 | " D,⇟ go down a screen\n" 129 | " F load forever\n" 130 | " g go to the top\n" 131 | " G go to the bottom\n" 132 | " h,← go left one third a screen\n" 133 | " H,⇱ go left all the way\n" 134 | " j,↓ go down one line\n" 135 | " k,↑ go up one line\n" 136 | " l,→ go right one third a screen\n" 137 | " L,⇲ go right all the way\n" 138 | " m mark position\n" 139 | " M go to marked position\n" 140 | " n go to next match\n" 141 | " N go to previous match\n" 142 | " q close file\n" 143 | " Q close all files\n" 144 | " t go to next tab\n" 145 | " T go to previous tab\n" 146 | " u go up half a screen\n" 147 | " U,⇞ go up a screen\n" 148 | " w toggle line wrap\n" 149 | " / search\n" 150 | " c clear search matches\n" 151 | " F1 view help\n" 152 | " F2 view recently opened files\n"; 153 | return str; 154 | } 155 | 156 | void usage () { 157 | stage_cat(usage_text()); 158 | stage_write(); 159 | } 160 | 161 | void add_help_tab () { 162 | int i; 163 | if (tabb->state & HELP) { 164 | close_tab(); 165 | return; 166 | } 167 | for (i = 0; i < tabs_len; i++) { 168 | if (tabs[i]->state & HELP) { 169 | current_tab = i; 170 | tabb = tabs[current_tab]; 171 | change_tab(); 172 | draw_tab(); 173 | return; 174 | } 175 | } 176 | 177 | add_tab("[Help]", 0, LOADED|HELP|SPECIAL); 178 | current_tab = tabs_len - 1; 179 | tabb = tabs[current_tab]; 180 | 181 | char *str = usage_text(); 182 | int len = strlen(str); 183 | if (tabb->buf_size < len + 1) { 184 | tabb->buf_size = len + 1; 185 | tabb->buf = realloc(tabb->buf, tabb->buf_size); 186 | } 187 | strcpy(tabb->buf, str); 188 | tabb->buf_len = len; 189 | tabb->nlines = count_lines(tabb->buf, tabb->buf_len); 190 | 191 | init_line1(); 192 | change_tab(); 193 | move_end(); 194 | } 195 | 196 | void toggle_line_wrap () { 197 | if (line_wrap) { 198 | line_wrap = 0; 199 | tabb->column = 0; 200 | } 201 | else { 202 | line_wrap = 1; 203 | } 204 | draw_tab(); 205 | } 206 | 207 | void toggle_load_forever (tab_t *tabb) { 208 | if (tabb->state & SPECIAL) { 209 | return; 210 | } 211 | if (tabb->state & LOADFOREVER) { 212 | tabb->state &= ~LOADFOREVER; 213 | tabb->mesg_len = 0; 214 | } 215 | else { 216 | tabb->state |= LOADFOREVER; 217 | tabb->mesg_len = sprintf(tabb->mesg, " ..."); 218 | } 219 | } 220 | 221 | int read_key (char *buf, int len) { 222 | charinfo_t cinfo; 223 | get_char_info(&cinfo, buf, 0); 224 | // set_ttybuf(&cinfo, buf, len); 225 | int extended = 0; 226 | switch (buf[0]) { 227 | case 'd': 228 | move_forward((lines - line1 - 1) / 2); 229 | break; 230 | case 'D': 231 | move_forward(lines - line1 - 2); 232 | break; 233 | case 'F': 234 | toggle_load_forever(tabb); 235 | draw_status(); 236 | break; 237 | case 'g': 238 | move_start(); 239 | break; 240 | case 'G': 241 | move_end(); 242 | break; 243 | case 'h': 244 | move_left(columns / 3); 245 | break; 246 | case 'H': 247 | move_line_left(); 248 | break; 249 | case 'j': 250 | move_forward(1); 251 | break; 252 | case 'J': 253 | move_forward(2); 254 | break; 255 | case 'k': 256 | move_backward(1); 257 | break; 258 | case 'K': 259 | move_backward(2); 260 | break; 261 | case 'l': 262 | move_right(columns / 3); 263 | break; 264 | case 'L': 265 | move_line_right(); 266 | break; 267 | case 'm': 268 | save_mark(); 269 | break; 270 | case 'M': 271 | restore_mark(); 272 | break; 273 | case 'n': 274 | next_match(); 275 | break; 276 | case 'N': 277 | prev_match(); 278 | break; 279 | case 'q': 280 | close_tab(); 281 | break; 282 | case 'Q': 283 | exit(0); 284 | break; 285 | case 'r': 286 | stage_tabs(); 287 | draw_tab(); 288 | break; 289 | case 'R': 290 | reload(); 291 | break; 292 | case 't': 293 | next_tab(); 294 | break; 295 | case 'T': 296 | prev_tab(); 297 | break; 298 | case 'u': 299 | move_backward((lines - line1 - 1) / 2); 300 | break; 301 | case 'U': 302 | move_backward(lines - line1 - 2); 303 | break; 304 | case 'w': 305 | toggle_line_wrap(); 306 | break; 307 | case ' ': 308 | move_forward(lines - line1 - 2); 309 | break; 310 | case '%': 311 | printf("%.2f%% \n", (double) tabb->pos / tabb->buf_len * 100); 312 | break; 313 | case '/': 314 | search(); 315 | break; 316 | case 'c': 317 | clear_matches(); 318 | break; 319 | case -0x40 + 'D': 320 | move_forward(10000); 321 | break; 322 | case -0x40 + 'H': 323 | move_left(1); 324 | break; 325 | case -0x40 + 'L': 326 | move_right(1); 327 | break; 328 | case -0x40 + 'U': 329 | move_backward(10000); 330 | break; 331 | default: 332 | extended = 1; 333 | } 334 | if (!extended) { 335 | return 1; 336 | } 337 | if (len == 1) { 338 | draw_status(); 339 | return 1; 340 | } 341 | if (strncmp(buf, "\eOB", 3) == 0) { // down 342 | move_forward(1); 343 | } 344 | else if (strncmp(buf, "\eOA", 3) == 0) { // up 345 | move_backward(1); 346 | } 347 | else if (strncmp(buf, "\eOD", 3) == 0) { // left 348 | move_left(columns / 3); 349 | } 350 | else if (strncmp(buf, "\eb", 2) == 0) { // alt-left 351 | move_line_left(); 352 | } 353 | else if (strncmp(buf, "\eOC", 3) == 0) { // right 354 | move_right(columns / 3); 355 | } 356 | else if (strncmp(buf, "\ef", 2) == 0) { // alt-right 357 | move_line_right(); 358 | } 359 | else if (strncmp(buf, "\eOH", 3) == 0) { // home 360 | move_line_left(); 361 | } 362 | else if (strncmp(buf, "\eOF", 3) == 0) { // end 363 | move_line_right(); 364 | } 365 | else if (strncmp(buf, "\e[5~", 4) == 0) { // pgup 366 | move_backward(lines - line1 - 2); 367 | } 368 | else if (strncmp(buf, "\e[6~", 4) == 0) { // pgdn 369 | move_forward(lines - line1 - 2); 370 | } 371 | else if (strncmp(buf, "\eOP", 3) == 0) { // F1 372 | add_help_tab(); 373 | } 374 | else if (strncmp(buf, "\eOQ", 3) == 0) { // F2 375 | add_recents_tab(); 376 | } 377 | else { 378 | draw_status(); 379 | } 380 | return len; 381 | } 382 | 383 | void read_terminal () { 384 | static char buf[256]; 385 | int nread = read(tty, buf, sizeof buf); 386 | if (nread < 0 && (errno == EAGAIN || errno == EINTR)) { 387 | return; 388 | } 389 | if (nread < 0) { 390 | perror("read"); 391 | exit(1); 392 | } 393 | if (nread == 0) { 394 | exit(1); 395 | } 396 | int i; 397 | for (i = 0; i < nread;) { 398 | int plen = read_key(buf + i, nread - i); 399 | i += plen; 400 | } 401 | } 402 | 403 | void read_loop () { 404 | fd_set fds; 405 | int i, nfds, retval; 406 | for (i = 0;; i++) { 407 | FD_ZERO(&fds); 408 | FD_SET(tty, &fds); 409 | if (tabb->state & OPENED && (!(tabb->state & LOADED) || tabb->state & LOADFOREVER)) { 410 | FD_SET(tabb->fd, &fds); 411 | nfds = tty > tabb->fd ? (tty + 1) : (tabb->fd + 1); 412 | } 413 | else { 414 | nfds = tty + 1; 415 | } 416 | retval = select(nfds, &fds, NULL, NULL, NULL); 417 | if (retval < 0 && (errno == EAGAIN || errno == EINTR)) { 418 | continue; 419 | } 420 | if (retval < 0) { 421 | perror("select"); 422 | exit(1); 423 | } 424 | if (FD_ISSET(tabb->fd, &fds)) { 425 | read_file(); 426 | } 427 | if (FD_ISSET(tty, &fds)) { 428 | read_terminal(); 429 | } 430 | } 431 | } 432 | 433 | void parse_args (int argc, char **argv) { 434 | struct option longopts[] = { 435 | {"e", required_argument, NULL, 'e'}, 436 | {"f", no_argument, NULL, 'f'}, 437 | {"h", no_argument, NULL, 'h'}, 438 | {"m", no_argument, NULL, 'm'}, 439 | {"p", required_argument, NULL, 'p'}, 440 | {"t", required_argument, NULL, 't'}, 441 | {"w", no_argument, NULL, 'w'}, 442 | {NULL, 0, NULL, 0} 443 | }; 444 | 445 | int c; 446 | while ((c = getopt_long_only(argc, argv, "", longopts, NULL)) != -1) { 447 | switch (c) { 448 | case 'e': 449 | set_input_encoding(optarg); 450 | break; 451 | case 'f': 452 | load_forever = 1; 453 | break; 454 | case 'h': 455 | usage(); 456 | exit(0); 457 | case 'm': 458 | man_page = 1; 459 | break; 460 | case 'p': 461 | lespipe = optarg; 462 | break; 463 | case 't': 464 | tab_width = atoi(optarg); 465 | break; 466 | case 'w': 467 | line_wrap = 0; 468 | break; 469 | default: 470 | exit(1); 471 | } 472 | } 473 | 474 | int i; 475 | for (i = optind; i < argc; i++) { 476 | add_tab(argv[i], 0, READY|FILEBACKED); 477 | if (load_forever) { 478 | toggle_load_forever(tabs[tabs_len - 1]); 479 | } 480 | } 481 | if (!tabs_len) { 482 | fprintf(stderr, "No files\n"); 483 | exit(1); 484 | } 485 | } 486 | 487 | // sigaction is used because I want there to be a read() call 488 | // blocking till it has input, I hit ^C, the read call should then 489 | // return EINTR, check values, then resume. The standard signal() 490 | // function will return from the interrupt handler to the same read 491 | // call, with no chance to affect it. 492 | void signal2 (int sig, void (*func)(int)) { 493 | struct sigaction *sa = calloc(1, sizeof (struct sigaction)); 494 | sa->sa_handler = func; 495 | sigaction(sig, sa, 0); 496 | } 497 | 498 | int main (int argc, char **argv) { 499 | stage_init(); 500 | int retval = 0; 501 | char *term = getenv("TERM"); 502 | if (setupterm(term, 1, &retval) < 0) { 503 | fprintf(stderr, "Can't find \"%s\" in the terminfo database.\n", term); 504 | exit(1); 505 | } 506 | 507 | if (!isatty(0)) { 508 | add_tab("stdin", 0, OPENED|SPECIAL); 509 | } 510 | 511 | parse_args(argc, argv); 512 | tabb = tabs[0]; 513 | open_tab_file(); 514 | if (man_page) { 515 | set_man_page_name(); 516 | } 517 | if (tabb->state & ERROR) { 518 | write(1, tabb->buf, tabb->buf_len); 519 | exit(1); 520 | } 521 | 522 | atexit(bye); 523 | signal2(SIGINT, sigint); 524 | signal2(SIGTERM, bye2); 525 | signal2(SIGQUIT, bye2); 526 | signal2(SIGCONT, sigcont); 527 | signal2(SIGTSTP, sigtstp); 528 | signal2(SIGCHLD, sigchld); 529 | signal2(SIGWINCH, sigwinch); 530 | 531 | tty = open("/dev/tty", O_RDONLY); 532 | tcgetattr(tty, &tcattr1); 533 | set_tcattr(); 534 | 535 | stage_cat(enter_ca_mode); 536 | stage_cat(keypad_xmit); 537 | stage_cat(cursor_invisible); 538 | stage_write(); 539 | 540 | load_search_history(); 541 | init_page(); 542 | init_line1(); 543 | stage_tabs(); 544 | draw_tab(); 545 | 546 | read_loop(); 547 | return 0; 548 | } 549 | 550 | -------------------------------------------------------------------------------- /page.c: -------------------------------------------------------------------------------- 1 | #include "les.h" 2 | #include 3 | #include 4 | #include 5 | 6 | typedef struct { 7 | char *buf; 8 | size_t buf_size; 9 | size_t buf_len; 10 | char *left; 11 | size_t left_size; 12 | size_t left_len; 13 | int left_width; 14 | char *right; 15 | size_t right_size; 16 | size_t right_len; 17 | int right_width; 18 | char *tty; 19 | size_t tty_size; 20 | size_t tty_len; 21 | char *matches; 22 | size_t matches_size; 23 | size_t matches_len; 24 | } status_t; 25 | 26 | status_t *status = NULL; 27 | int atmatch = 0; 28 | int athighlight = 0; 29 | char *escapes = NULL; 30 | size_t escapes_len = 0; 31 | size_t escapes_size = 0; 32 | 33 | char *human_readable (double size) { 34 | static char buf[32]; 35 | static char power[] = "BKMGTPEZY"; 36 | int i; 37 | for (i = 0; i < sizeof power / sizeof power[0]; i++) { 38 | if (size < 1024) 39 | break; 40 | size /= 1024; 41 | } 42 | int hundredths = (int)(size * 100) % 100; 43 | if (hundredths) { 44 | snprintf(buf, sizeof buf, "%.2f%c", size, power[i]); 45 | } 46 | else { 47 | snprintf(buf, sizeof buf, "%.0f%c", size, power[i]); 48 | } 49 | return buf; 50 | } 51 | 52 | void stage_status () { 53 | status->matches_len = 0; 54 | if (active_search && tabb->search_version == search_version) { 55 | if (tabb->matches_len == 0) { 56 | status->matches_len = snprintf(status->matches, status->matches_size, " 0 matches"); 57 | } 58 | else if (tabb->matches_len == 1) { 59 | status->matches_len = snprintf(status->matches, status->matches_size, " 1 match"); 60 | } 61 | else { 62 | status->matches_len = snprintf(status->matches, status->matches_size, " %lu/%lu matches", (long unsigned int) tabb->current_match + 1, (long unsigned int) tabb->matches_len); 63 | } 64 | } 65 | char *hrsize = human_readable(tabb->buf_len); 66 | status->right_len = snprintf( 67 | status->right, status->right_size, 68 | "%.*s%.*s %d/%d %s", 69 | (int) status->tty_len, status->tty, 70 | (int) status->matches_len, status->matches, 71 | tabb->line, tabb->nlines, hrsize); 72 | status->right_width = strnwidth(status->right, status->right_len); 73 | 74 | status->left_len = snprintf( 75 | status->left, status->left_size, 76 | "%s%.*s", 77 | tabb->name, (int) tabb->mesg_len, tabb->mesg); 78 | 79 | status->buf_len = 0; 80 | while (status->buf_size < columns + status->right_len + status->left_len) { 81 | status->buf_size *= 2; 82 | status->buf = realloc(status->buf, status->buf_size); 83 | } 84 | 85 | int i; 86 | int width = 0; 87 | charinfo_t cinfo; 88 | int right = columns - status->right_width; 89 | 90 | for (i = 0; i < status->left_len;) { 91 | if (width >= right) { 92 | break; 93 | } 94 | get_char_info(&cinfo, status->left, i); 95 | memcpy(status->buf + status->buf_len, status->left + i, cinfo.len); 96 | status->buf_len += cinfo.len; 97 | width += cinfo.width; 98 | i += cinfo.len; 99 | } 100 | 101 | while (width < right) { 102 | status->buf[status->buf_len] = ' '; 103 | status->buf_len++; 104 | width++; 105 | } 106 | 107 | for (i = 0; i < status->right_len; i++) { 108 | status->buf[status->buf_len] = status->right[i]; 109 | status->buf_len++; 110 | } 111 | 112 | stage_cat(tparm(cursor_address, lines - 1, 0)); 113 | stage_cat(tparm(set_a_background, 16 + 36*0 + 6*0 + 2)); 114 | width = 0; 115 | int j = 0; 116 | for (i = 0; i < status->buf_len;) { 117 | get_char_info(&cinfo, status->buf, i); 118 | if (j == 0 && (!tabb->buf_len || width >= columns * tabb->pos / tabb->buf_len)) { 119 | stage_cat(tparm(set_a_background, 16 + 36*0 + 6*1 + 5)); 120 | j++; 121 | } 122 | else if (j == 1 && tabb->buf_len && width >= columns * tlines[tlines_len - 1].end_pos / tabb->buf_len) { 123 | stage_cat(exit_attribute_mode); 124 | j++; 125 | } 126 | stage_ncat(status->buf + i, cinfo.len); 127 | width += cinfo.width; 128 | i += cinfo.len; 129 | } 130 | stage_cat(exit_attribute_mode); 131 | } 132 | 133 | void draw_status () { 134 | stage_status(); 135 | stage_write(); 136 | } 137 | 138 | // In man page output, text is underlined by placing and underscore 139 | // followed by a backspace followed by the character to be underlined. 140 | // text is bolded by placing the character followed by a backspace 141 | // then the same character again. 142 | void stage_backspace (charinfo_t *cinfo, char *buf, int i) { 143 | if (i == 0) { 144 | cinfo->width = 2; 145 | cinfo->len = 1; 146 | stage_cat("^H"); 147 | } 148 | else if (buf[i - 1] == '_') { 149 | get_char_info(cinfo, buf, i + 1); 150 | stage_cat("\b"); 151 | stage_cat(enter_underline_mode); 152 | stage_ncat(buf + i + 1, cinfo->len); 153 | stage_cat(exit_underline_mode); 154 | cinfo->width = 0; 155 | cinfo->len += 1; 156 | } 157 | else if (buf[i - 1] == buf[i + 1] || buf[i - 1] == '+') { 158 | get_char_info(cinfo, buf, i + 1); 159 | stage_cat("\b"); 160 | stage_cat(enter_bold_mode); 161 | stage_ncat(buf + i + 1, cinfo->len); 162 | stage_cat(exit_attribute_mode); 163 | cinfo->width = 0; 164 | cinfo->len += 1; 165 | } 166 | else { 167 | cinfo->width = 2; 168 | cinfo->len = 1; 169 | stage_cat("^H"); 170 | } 171 | } 172 | 173 | void escapes_record (charinfo_t *cinfo, char *buf, int i) { 174 | if (buf[i] == '\e' && cinfo->len > 1) { 175 | if (strncmp(buf + i, "\e[0m", cinfo->len) == 0 || 176 | strncmp(buf + i, "\e[m", cinfo->len) == 0) { 177 | escapes_len = 0; 178 | } 179 | else { 180 | if (escapes_len + cinfo->len <= escapes_size) { 181 | strncpy(escapes + escapes_len, buf + i, cinfo->len); 182 | escapes_len += cinfo->len; 183 | } 184 | } 185 | } 186 | } 187 | 188 | void escapes_start () { 189 | if (escapes_len) { 190 | stage_ncat(escapes, escapes_len); 191 | } 192 | } 193 | 194 | void escapes_end () { 195 | if (escapes_len) { 196 | stage_cat(exit_attribute_mode); 197 | } 198 | } 199 | 200 | void highlight_match3 () { 201 | if (escapes_len) { 202 | stage_cat(exit_attribute_mode); 203 | } 204 | stage_cat(tparm(set_a_foreground, 0)); 205 | stage_cat(enter_bold_mode); 206 | if (atmatch == tabb->current_match) { 207 | stage_cat(tparm(set_a_background, 10)); 208 | } 209 | else { 210 | stage_cat(tparm(set_a_background, 12)); 211 | } 212 | } 213 | 214 | void highlight_match_start (char *buf, int i) { 215 | if (!tabb->matches_len || atmatch >= tabb->matches_len) { 216 | return; 217 | } 218 | if (i > tabb->matches[atmatch].start && i < tabb->matches[atmatch].end) { 219 | highlight_match3(); 220 | } 221 | } 222 | 223 | void highlight_match_end (char *buf, int i) { 224 | if (!tabb->matches_len || atmatch >= tabb->matches_len) { 225 | return; 226 | } 227 | if (i > tabb->matches[atmatch].start && i <= tabb->matches[atmatch].end) { 228 | stage_cat(exit_attribute_mode); 229 | } 230 | } 231 | 232 | void highlight_match (char *buf, int i) { 233 | if (!tabb->matches_len) { 234 | return; 235 | } 236 | if (atmatch < tabb->matches_len && tabb->matches[atmatch].start != tabb->matches[atmatch].end && i == tabb->matches[atmatch].end) { 237 | stage_cat(exit_attribute_mode); 238 | escapes_start(); 239 | atmatch++; 240 | } 241 | if (atmatch < tabb->matches_len && i == tabb->matches[atmatch].start) { 242 | highlight_match3(); 243 | } 244 | } 245 | 246 | void highlight_match2 (char *buf, int i) { 247 | if (!tabb->matches_len || atmatch >= tabb->matches_len) { 248 | return; 249 | } 250 | if (i == tabb->matches[atmatch].end) { 251 | stage_cat(exit_attribute_mode); 252 | escapes_start(); 253 | atmatch++; 254 | } 255 | } 256 | 257 | void highlight2 () { 258 | if (tabb->highlights[athighlight].type == UNDERLINED) { 259 | stage_cat(enter_underline_mode); 260 | } 261 | else { 262 | stage_cat(enter_bold_mode); 263 | } 264 | } 265 | 266 | void highlight_start (char *buf, int i) { 267 | if (!tabb->highlights_len || athighlight >= tabb->highlights_len) { 268 | return; 269 | } 270 | if (i > tabb->highlights[athighlight].start && i < tabb->highlights[athighlight].end) { 271 | highlight2(); 272 | } 273 | } 274 | 275 | void highlight_end (char *buf, int i) { 276 | if (!tabb->highlights_len || athighlight >= tabb->highlights_len) { 277 | return; 278 | } 279 | if (i > tabb->highlights[athighlight].start && i <= tabb->highlights[athighlight].end) { 280 | stage_cat(exit_attribute_mode); 281 | } 282 | } 283 | 284 | void highlight (char *buf, int i) { 285 | if (!tabb->highlights_len) { 286 | return; 287 | } 288 | if (athighlight < tabb->highlights_len && i == tabb->highlights[athighlight].end) { 289 | stage_cat(exit_attribute_mode); 290 | escapes_start(); 291 | athighlight++; 292 | } 293 | if (athighlight < tabb->highlights_len && i == tabb->highlights[athighlight].start) { 294 | highlight2(); 295 | } 296 | } 297 | 298 | void stage_character (charinfo_t *cinfo, char *buf, int i) { 299 | static char str[16]; 300 | int j; 301 | unsigned char c = buf[i]; 302 | if (cinfo->error) { 303 | sprintf(str, "[%02X]", c); 304 | stage_cat(str); 305 | } 306 | else if (c == '\t') { 307 | for (j = 0; j < tab_width; j++) { 308 | stage_cat(" "); 309 | } 310 | } 311 | else if (c == '\b') { 312 | stage_backspace(cinfo, buf, i); 313 | } 314 | else if (buf[i] == '\e' && cinfo->len > 1) { 315 | stage_ncat(buf + i, cinfo->len); 316 | } 317 | else if (c == 0x00) { 318 | stage_cat("·"); 319 | } 320 | else if (c < 0x20) { 321 | sprintf(str, "^%c", 0x40 + c); 322 | stage_cat(str); 323 | } 324 | else if (c == 0x7f) { 325 | sprintf(str, "^%c", -0x40 + c); 326 | stage_cat(str); 327 | } 328 | else if (cinfo->codepoint >= 0x80 && cinfo->codepoint <= 0x9f) { 329 | sprintf(str, "*%c", 0x40 + cinfo->codepoint - 0x80); 330 | stage_cat(str); 331 | } 332 | else { 333 | stage_ncat(buf + i, cinfo->len); 334 | } 335 | } 336 | 337 | void stage_line_wrap (tline_t *tline) { 338 | charinfo_t cinfo; 339 | int width = 0; 340 | int i = tline->pos; 341 | escapes_start(); 342 | highlight_start(tabb->buf, i); 343 | highlight_match_start(tabb->buf, i); 344 | while (i < tline->end_pos) { 345 | get_char_info(&cinfo, tabb->buf, i); 346 | highlight(tabb->buf, i); 347 | highlight_match(tabb->buf, i); 348 | if ((tabb->buf[i] == '\r' && tabb->buf[i + 1] == '\n') || tabb->buf[i] == '\n') { 349 | highlight_match2(tabb->buf, i); 350 | break; 351 | } 352 | escapes_record(&cinfo, tabb->buf, i); 353 | stage_character(&cinfo, tabb->buf, i); 354 | highlight_match2(tabb->buf, i); 355 | width += cinfo.width; 356 | i += cinfo.len; 357 | } 358 | highlight_end(tabb->buf, i); 359 | highlight_match_end(tabb->buf, i); 360 | escapes_end(); 361 | if (width < columns) { 362 | stage_cat(clr_eol); 363 | } 364 | } 365 | 366 | void stage_line_nowrap (tline_t *tline) { 367 | int width = 0; 368 | charinfo_t cinfo; 369 | int i = tline->pos; 370 | escapes_start(); 371 | if (atmatch < tabb->matches_len && tabb->matches[atmatch].end < i) { 372 | atmatch++; 373 | } 374 | if (athighlight < tabb->highlights_len && tabb->highlights[athighlight].end < i) { 375 | athighlight++; 376 | } 377 | highlight_start(tabb->buf, i); 378 | highlight_match_start(tabb->buf, i); 379 | while (i < tline->end_pos) { 380 | get_char_info(&cinfo, tabb->buf, i); 381 | if (width >= tabb->column && (!tabb->column || cinfo.width)) { 382 | break; 383 | } 384 | if (tabb->buf[i] == 0x1b && cinfo.len > 1) { 385 | escapes_record(&cinfo, tabb->buf, i); 386 | stage_ncat(tabb->buf + i, cinfo.len); 387 | } 388 | highlight(tabb->buf, i); 389 | highlight_match(tabb->buf, i); 390 | highlight_match2(tabb->buf, i); 391 | width += cinfo.width; 392 | i += cinfo.len; 393 | } 394 | if (i == tline->end_pos) { 395 | highlight_end(tabb->buf, i); 396 | highlight_match_end(tabb->buf, i); 397 | escapes_end(); 398 | stage_cat(clr_eol); 399 | return; 400 | } 401 | while (i < tline->end_pos) { 402 | get_char_info(&cinfo, tabb->buf, i); 403 | if (width + cinfo.width > columns + tabb->column) { 404 | break; 405 | } 406 | if (tabb->buf[i] == 0x1b && cinfo.len > 1) { 407 | escapes_record(&cinfo, tabb->buf, i); 408 | } 409 | highlight(tabb->buf, i); 410 | highlight_match(tabb->buf, i); 411 | if ((tabb->buf[i] == '\r' && tabb->buf[i + 1] == '\n') || tabb->buf[i] == '\n') { 412 | highlight_match2(tabb->buf, i); 413 | break; 414 | } 415 | stage_character(&cinfo, tabb->buf, i); 416 | highlight_match2(tabb->buf, i); 417 | width += cinfo.width; 418 | i += cinfo.len; 419 | } 420 | highlight_end(tabb->buf, i); 421 | highlight_match_end(tabb->buf, i); 422 | escapes_end(); 423 | if (width < columns + tabb->column) { 424 | stage_cat(clr_eol); 425 | } 426 | } 427 | 428 | void stage_tab2 (int n, tline_t *tlines, size_t tlines_len) { 429 | int i; 430 | if (tabb->matches_len) { 431 | atmatch = 0; 432 | for (i = 0; i < tabb->matches_len; i++) { 433 | if (tabb->matches[i].end >= tlines[0].pos) { 434 | atmatch = i; 435 | break; 436 | } 437 | } 438 | } 439 | if (tabb->highlights_len) { 440 | athighlight = 0; 441 | for (i = 0; i < tabb->highlights_len; i++) { 442 | if (tabb->highlights[i].end >= tlines[0].pos) { 443 | athighlight = i; 444 | break; 445 | } 446 | } 447 | } 448 | escapes_len = 0; 449 | for (i = 0; i < n; i++) { 450 | if (i < tlines_len) { 451 | if (line_wrap) { 452 | stage_line_wrap(tlines + i); 453 | } 454 | else { 455 | stage_line_nowrap(tlines + i); 456 | } 457 | } 458 | else { 459 | stage_cat(tparm(set_a_foreground, 4)); 460 | stage_cat("~"); 461 | stage_cat(clr_eol); 462 | stage_cat(exit_attribute_mode); 463 | } 464 | if (i != n - 1) { 465 | stage_cat("\n"); 466 | } 467 | } 468 | } 469 | 470 | void stage_tab () { 471 | stage_cat(tparm(cursor_address, line1, 0)); 472 | get_tlines(tabb->buf, tabb->buf_len, tabb->pos, lines - line1 - 1, &tlines, &tlines_len, &tlines_size); 473 | stage_tab2(lines - line1 - 1, tlines, tlines_len); 474 | stage_status(); 475 | } 476 | 477 | void draw_tab () { 478 | stage_tab(); 479 | stage_write(); 480 | } 481 | 482 | void init_page () { 483 | status = malloc(sizeof (status_t)); 484 | status->buf_size = 1024; 485 | status->buf_len = 0; 486 | status->buf = malloc(status->buf_size); 487 | status->left_size = 1024; 488 | status->left_len = 0; 489 | status->left = malloc(status->left_size); 490 | status->right_size = 1024; 491 | status->right_len = 0; 492 | status->right = malloc(status->right_size); 493 | status->tty_size = 64; 494 | status->tty_len = 0; 495 | status->tty = malloc(status->tty_size); 496 | status->matches_size = 64; 497 | status->matches_len = 0; 498 | status->matches = malloc(status->matches_size); 499 | escapes_size = 256; 500 | escapes_len = 0; 501 | escapes = malloc(escapes_size); 502 | } 503 | 504 | void set_ttybuf (charinfo_t *cinfo, char *buf, int len) { 505 | int i; 506 | status->tty[0] = ' '; 507 | status->tty_len = 1; 508 | int retval; 509 | for (i = 0; i < len; i++) { 510 | if (status->tty_len + 3 > status->tty_size) { 511 | break; 512 | } 513 | unsigned char c = buf[i]; 514 | if (c < 0x20) { 515 | retval = sprintf(status->tty + status->tty_len, "^%c", 0x40 + c); 516 | status->tty_len += retval; 517 | } 518 | else if (c == 0x7f) { 519 | retval = sprintf(status->tty + status->tty_len, "^?"); 520 | status->tty_len += retval; 521 | } 522 | else if (c == 0x20) { 523 | retval = sprintf(status->tty + status->tty_len, "␣"); 524 | status->tty_len += retval; 525 | } 526 | else { 527 | status->tty[status->tty_len] = c; 528 | status->tty_len++; 529 | } 530 | } 531 | } 532 | 533 | -------------------------------------------------------------------------------- /rx.c: -------------------------------------------------------------------------------- 1 | // 2 | // librx 3 | // 4 | 5 | #include "rx.h" 6 | #include "hash.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // Reads a utf8 character from str and determines how many bytes it is. If the str 13 | // doesn't contain a proper utf8 character, it returns 1. str needs to have at 14 | // least one byte in it, but can end right after that, even if the byte sequence is 15 | // invalid. 16 | int rx_utf8_char_size (int str_size, char *str, int pos) { 17 | char c = str[pos]; 18 | int size = 0; 19 | if ((c & 0x80) == 0x00) { 20 | size = 1; 21 | } else if ((c & 0xe0) == 0xc0) { 22 | size = 2; 23 | } else if ((c & 0xf0) == 0xe0) { 24 | size = 3; 25 | } else if ((c & 0xf8) == 0xf0) { 26 | size = 4; 27 | } else { 28 | // Invalid utf8 starting byte 29 | size = 1; 30 | } 31 | if (pos + size > str_size) { 32 | return 1; 33 | } 34 | for (int i = 1; i < size; i += 1) { 35 | c = str[pos + i]; 36 | if ((c & 0xc0) != 0x80) { 37 | // Didn't find a proper utf8 continuation byte 10xx_xxxx 38 | return 1; 39 | } 40 | } 41 | return size; 42 | } 43 | 44 | int rx_int_to_utf8 (unsigned int value, char *str) { 45 | if (value < 0x80) { 46 | str[0] = value; 47 | return 1; 48 | } else if (value < 0x800) { 49 | str[0] = 0x80 | ((value & 0x7c) >> 6); 50 | str[1] = 0x80 | ((value & 0x3f) >> 0); 51 | return 2; 52 | } else if (value < 0x10000) { 53 | str[0] = 0xe0 | ((value & 0xf000) >> 12); 54 | str[1] = 0x80 | ((value & 0x0fc0) >> 6); 55 | str[2] = 0x80 | ((value & 0x003f) >> 0); 56 | return 3; 57 | } else if (value < 0x200000) { 58 | str[0] = 0xf0 | ((value & 0x1c0000) >> 18); 59 | str[1] = 0x80 | ((value & 0x03f000) >> 12); 60 | str[2] = 0x80 | ((value & 0x000fc0) >> 6); 61 | str[3] = 0x80 | ((value & 0x00003f) >> 0); 62 | return 4; 63 | } else { 64 | return 0; 65 | } 66 | } 67 | 68 | int rx_hex_to_int (char *str, int size, unsigned int *dest) { 69 | unsigned int value = 0; 70 | for (int i = 0; i < size; i += 1) { 71 | char c = str[i]; 72 | unsigned char b = 0; 73 | if (c >= '0' && c <= '9') { 74 | b = c - '0'; 75 | } else if (c >= 'a' && c <= 'f') { 76 | b = c - 'a' + 10; 77 | } else if (c >= 'A' && c <= 'F') { 78 | b = c - 'A' + 10; 79 | } else { 80 | return 0; 81 | } 82 | value = (value << 4) | b; 83 | } 84 | *dest = value; 85 | return 1; 86 | } 87 | 88 | node_t *rx_node_create (rx_t *rx) { 89 | node_t *n = malloc(sizeof(node_t)); 90 | n->type = EMPTY; 91 | n->next = NULL; 92 | if (rx->nodes_count >= rx->nodes_allocated) { 93 | rx->nodes_allocated *= 2; 94 | rx->nodes = realloc(rx->nodes, rx->nodes_allocated * sizeof(node_t *)); 95 | } 96 | rx->nodes[rx->nodes_count] = n; 97 | rx->nodes_count += 1; 98 | return n; 99 | } 100 | 101 | int rx_node_index (rx_t *rx, node_t *n) { 102 | // This is slow. 103 | for (int i = 0; i < rx->nodes_count; i += 1) { 104 | if (rx->nodes[i] == n) { 105 | return i; 106 | } 107 | } 108 | return 0; 109 | } 110 | 111 | void rx_match_print (matcher_t *m) { 112 | if (m->success) { 113 | printf("matched\n"); 114 | } else { 115 | printf("it didn't match\n"); 116 | return; 117 | } 118 | 119 | for (int i = 0; i < m->cap_count; i += 1) { 120 | if (m->cap_defined[i]) { 121 | printf("%d: %.*s\n", i, m->cap_size[i], m->cap_str[i]); 122 | } else { 123 | printf("%d: ~\n", i); 124 | } 125 | } 126 | 127 | for (int i = 0; i < m->path_count; i += 1) { 128 | path_t *p = m->path + i; 129 | if (p->node->type == CAPTURE_START) { 130 | printf("capture %d start %d\n", p->node->value, p->pos); 131 | } else if (p->node->type == CAPTURE_END) { 132 | printf("capture %d end %d\n", p->node->value, p->pos); 133 | } 134 | } 135 | } 136 | 137 | void rx_print (rx_t *rx) { 138 | FILE *fp = fopen("/tmp/nfa.txt", "w"); 139 | fprintf(fp, "graph g {\n"); 140 | for (int i = 0; i < rx->nodes_count; i += 1) { 141 | node_t *n = rx->nodes[i]; 142 | int i1 = rx_node_index(rx, n); 143 | int i2 = rx_node_index(rx, n->next); 144 | 145 | if (n->type == TAKE) { 146 | char label[6]; 147 | if (n->value == '\x1b') { 148 | strcpy(label, "\u29f9e"); 149 | } else if (n->value == '\r') { 150 | strcpy(label, "\u29f9r"); 151 | } else if (n->value == '\n') { 152 | strcpy(label, "\u29f9n"); 153 | } else if (n->value == '\t') { 154 | strcpy(label, "\u29f9t"); 155 | } else { 156 | label[0] = n->value; 157 | label[1] = '\0'; 158 | } 159 | fprintf(fp, " %d -> %d [label=\"%s\",style=solid]\n", i1, i2, label); 160 | } else if (n->type == CAPTURE_START) { 161 | fprintf(fp, " %d -> %d [label=\"(%d\",style=solid]\n", i1, i2, n->value); 162 | } else if (n->type == CAPTURE_END) { 163 | fprintf(fp, " %d -> %d [label=\")%d\",style=solid]\n", i1, i2, n->value); 164 | } else if (n->type == GROUP_START) { 165 | fprintf(fp, " %d -> %d [label=\"(?\",style=solid]\n", i1, i2); 166 | } else if (n->type == GROUP_END) { 167 | fprintf(fp, " %d -> %d [label=\")?\",style=solid]\n", i1, i2); 168 | } else if (n->type == BRANCH) { 169 | int i3 = rx_node_index(rx, n->next2); 170 | fprintf(fp, " %d [label=\"%dB\"]\n", i1, i1); 171 | fprintf(fp, " %d -> %d [style=solid]\n", i1, i2); 172 | fprintf(fp, " %d -> %d [style=dotted]\n", i1, i3); 173 | } else if (n->type == ASSERTION) { 174 | fprintf(fp, " %d [label=\"%dA\"]\n", i1, i1); 175 | char *labels[] = { 176 | "^", // ASSERT_SOS 177 | "^^", // ASSERT_SOL 178 | "$", // ASSERT_EOS 179 | "$$", // ASSERT_EOL 180 | "\u29f9G", // ASSERT_SOP 181 | "\\<", // ASSERT_SOW 182 | "\\>", // ASSERT_EOW 183 | }; 184 | char *label = labels[n->value]; 185 | fprintf(fp, " %d -> %d [label=\"%s\"]\n", i1, i2, label); 186 | } else if (n->type == CHAR_CLASS) { 187 | char_class_t *ccval = n->ccval; 188 | fprintf(fp, " %d [label=\"%dC\"]\n", i1, i1); 189 | fprintf(fp, " %d -> %d [label=\"%.*s\"]\n", i1, i2, ccval->str_size, ccval->str); 190 | } else if (n->type == CHAR_SET) { 191 | char *labels[] = { 192 | ".", // CS_ANY 193 | "\u29f9N", // CS_NOTNL 194 | "\u29f9d", // CS_DIGIT 195 | "\u29f9D", // CS_NOTDIGIT 196 | "\u29f9w", // CS_WORD 197 | "\u29f9W", // CS_NOTWORD 198 | "\u29f9s", // CS_SPACE 199 | "\u29f9S", // CS_NOTSPACE 200 | }; 201 | char *label = labels[n->value]; 202 | fprintf(fp, " %d [label=\"%dC\"]\n", i1, i1); 203 | fprintf(fp, " %d -> %d [label=\"%s\"]\n", i1, i2, label); 204 | } else if (n->type == MATCH_END) { 205 | fprintf(fp, " %d [label=\"%dE\"]\n", i1, i1); 206 | } else if (n->next) { 207 | fprintf(fp, " %d -> %d [style=solid]\n", i1, i2); 208 | } 209 | } 210 | fprintf(fp, "}\n"); 211 | fclose(fp); 212 | system("graph-easy -as=boxart /tmp/nfa.txt"); 213 | } 214 | 215 | int rx_error (rx_t *rx, char *fmt, ...) { 216 | rx->error = 1; 217 | if (!rx->errorstr) { 218 | rx->errorstr = malloc(64); 219 | } 220 | va_list args; 221 | va_start(args, fmt); 222 | vsnprintf(rx->errorstr, 64, fmt, args); 223 | va_end(args); 224 | return 0; 225 | } 226 | 227 | int rx_char_class_parse (rx_t *rx, int pos, int *pos2, int save, char_class_t *ccval) { 228 | int values_count = 0, ranges_count = 0, char_sets_count = 0; 229 | char *regexp = rx->regexp; 230 | int regexp_size = rx->regexp_size; 231 | char c1, c2; 232 | char char1[4], char2[4]; 233 | int char1_size = 0, char2_size = 0; 234 | char seen_dash = 0; 235 | char seen_special = '\0'; 236 | 237 | while (pos < regexp_size) { 238 | c1 = regexp[pos]; 239 | if (c1 == ']') { 240 | break; 241 | } else if (c1 == '-' && !seen_dash) { 242 | seen_dash = 1; 243 | pos += 1; 244 | continue; 245 | } else if (c1 == '\\') { 246 | if (pos + 1 >= regexp_size) { 247 | return rx_error(rx, "Expected character after \\."); 248 | } 249 | c2 = regexp[pos + 1]; 250 | if (c2 == 'd' || c2 == 'D' || c2 == 'w' || c2 == 'W' || c2 == 's' || c2 == 'S' || c2 == 'N') { 251 | // Something like \d or \D 252 | if (seen_dash) { 253 | return rx_error(rx, "Can't have \\%c after -.", c2); 254 | } 255 | if (save) { 256 | ccval->char_sets[char_sets_count] 257 | = c2 == 'N' ? CS_NOTNL 258 | : c2 == 'd' ? CS_DIGIT 259 | : c2 == 'D' ? CS_NOTDIGIT 260 | : c2 == 'w' ? CS_WORD 261 | : c2 == 'W' ? CS_NOTWORD 262 | : c2 == 's' ? CS_SPACE 263 | : c2 == 'S' ? CS_NOTSPACE : 0; 264 | } 265 | char_sets_count += 1; 266 | seen_special = c2; 267 | pos += 2; 268 | continue; 269 | 270 | } else if (c2 == 'e' || c2 == 'r' || c2 == 'n' || c2 == 't') { 271 | // Something like \n or \t 272 | char2[0] = c2 == 'e' ? '\x1b' 273 | : c2 == 'r' ? '\r' 274 | : c2 == 'n' ? '\n' 275 | : c2 == 't' ? '\t' : '\0'; 276 | char2_size = 1; 277 | pos += 2; 278 | 279 | } else if (c2 == 'x') { 280 | // \x3f 281 | if (pos + 3 >= regexp_size) { 282 | return rx_error(rx, "Expected 2 characters after \\x."); 283 | } 284 | unsigned int i; 285 | if (!rx_hex_to_int(regexp + pos + 2, 2, &i)) { 286 | return rx_error(rx, "Expected 2 hex digits after \\x."); 287 | } 288 | char2_size = 1; 289 | char2[0] = i; 290 | pos += 4; 291 | 292 | } else if (c2 == 'u' || c2 == 'U') { 293 | // \u2603 or \U00002603 294 | int count = c2 == 'u' ? 4 : 8; 295 | if (pos + 1 + count >= regexp_size) { 296 | return rx_error(rx, "Expected %d characters after \\%c.", count, c2); 297 | } 298 | unsigned int i; 299 | if (!rx_hex_to_int(regexp + pos + 2, count, &i)) { 300 | return rx_error(rx, "Expected %d hex digits after \\%c.", count, c2); 301 | } 302 | char2_size = rx_int_to_utf8(i, char2); 303 | if (!char2_size) { 304 | return rx_error(rx, "Invalid \\%c sequence.", c2); 305 | } 306 | pos += 2 + count; 307 | 308 | } else { 309 | // Any unrecognized backslash escape, for example \] 310 | pos += 1; 311 | char2_size = rx_utf8_char_size(regexp_size, regexp, pos); 312 | memcpy(char2, regexp + pos, char2_size); 313 | pos += char2_size; 314 | } 315 | 316 | } else { 317 | // An unspecial character, for example a or ☃ 318 | char2_size = rx_utf8_char_size(regexp_size, regexp, pos); 319 | memcpy(char2, regexp + pos, char2_size); 320 | pos += char2_size; 321 | } 322 | 323 | // At this point we can assume we have a character and pos was incremented. 324 | if (char1_size && seen_dash) { 325 | // Range 326 | if (save) { 327 | memcpy(ccval->ranges + ranges_count, char1, char1_size); 328 | ranges_count += char1_size; 329 | memcpy(ccval->ranges + ranges_count, char2, char2_size); 330 | ranges_count += char2_size; 331 | } 332 | else { 333 | if (seen_special) { 334 | return rx_error(rx, "Can't have - after \\%c.", seen_special); 335 | } 336 | if (char1_size > char2_size || strncmp(char1, char2, char1_size) >= 0) { 337 | return rx_error(rx, "End of range must be higher than start."); 338 | } 339 | ranges_count += char1_size + char2_size; 340 | } 341 | seen_dash = 0; 342 | char1_size = 0; 343 | } else if (seen_dash) { 344 | return rx_error(rx, "Unexpected -."); 345 | } else { 346 | // Value 347 | if (char1_size) { 348 | if (save) { 349 | memcpy(ccval->values + values_count, char1, char1_size); 350 | } 351 | values_count += char1_size; 352 | } 353 | memcpy(char1, char2, char2_size); 354 | char1_size = char2_size; 355 | } 356 | seen_special = '\0'; 357 | } 358 | if (char1_size) { 359 | if (save) { 360 | memcpy(ccval->values + values_count, char1, char1_size); 361 | } 362 | values_count += char1_size; 363 | } 364 | if (seen_dash) { 365 | if (save) { 366 | ccval->values[values_count] = '-'; 367 | } 368 | values_count += 1; 369 | } 370 | if (pos >= regexp_size || regexp[pos] != ']') { 371 | return rx_error(rx, "Expected ]."); 372 | } 373 | if (save) { 374 | *pos2 = pos; 375 | } 376 | ccval->values_count = values_count; 377 | ccval->ranges_count = ranges_count; 378 | ccval->char_sets_count = char_sets_count; 379 | return 1; 380 | } 381 | 382 | void char_class_free (char_class_t *ccval) { 383 | free(ccval->values); 384 | free(ccval->ranges); 385 | free(ccval->char_sets); 386 | free(ccval); 387 | } 388 | 389 | // This construct is character oriented. If you write [☃], it will match the 3 390 | // byte sequence \xe2\e98\x83, and not individual bytes in that sequnce. Similarly, 391 | // if you specify a range like [Α-Ω], Greek alpha to omega, it will match only 392 | // characters in that range, and not invalid bytes in that sequence. You can also 393 | // enter invalid bytes, and they will only match 1 byte at a time. 394 | int rx_char_class_init (rx_t *rx, int pos, int *pos2, char_class_t **ccval2) { 395 | char *regexp = rx->regexp; 396 | int regexp_size = rx->regexp_size; 397 | 398 | if (pos + 1 >= regexp_size) { 399 | rx_error(rx, "Expected a character after [."); 400 | return 0; 401 | } 402 | char_class_t *ccval = calloc(1, sizeof(char_class_t)); 403 | ccval->str = regexp + pos; 404 | pos += 1; 405 | char c1 = regexp[pos]; 406 | if (c1 == '^') { 407 | ccval->negated = 1; 408 | if (pos + 1 >= regexp_size) { 409 | rx_error(rx, "Expected a character in [."); 410 | char_class_free(ccval); 411 | return 0; 412 | } 413 | pos += 1; 414 | } 415 | 416 | // Count the number of values and ranges needed before allocating them. 417 | if(!rx_char_class_parse(rx, pos, &pos, 0, ccval)) { 418 | char_class_free(ccval); 419 | return 0; 420 | } 421 | 422 | // Allocate the arrays. 423 | if (ccval->values_count) { 424 | ccval->values = malloc(ccval->values_count + 1); 425 | ccval->values[ccval->values_count] = '\0'; 426 | } 427 | if (ccval->ranges_count) { 428 | ccval->ranges = malloc(ccval->ranges_count + 1); 429 | ccval->ranges[ccval->ranges_count] = '\0'; 430 | } 431 | if (ccval->char_sets_count) { 432 | ccval->char_sets = malloc(ccval->char_sets_count); 433 | } 434 | 435 | // Fill in the arrays. 436 | rx_char_class_parse(rx, pos, &pos, 1, ccval); 437 | ccval->str_size = regexp + pos - ccval->str + 1; 438 | if (rx->char_classes_count >= rx->char_classes_allocated) { 439 | rx->char_classes_allocated *= 2; 440 | rx->char_classes = realloc(rx->char_classes, rx->char_classes_allocated * sizeof(char_class_t *)); 441 | } 442 | rx->char_classes[rx->char_classes_count] = ccval; 443 | rx->char_classes_count += 1; 444 | 445 | *pos2 = pos; 446 | *ccval2 = ccval; 447 | return 1; 448 | } 449 | 450 | int rx_quantifier_init (rx_t *rx, int pos, int *pos2, quantifier_t *qval) { 451 | int regexp_size = rx->regexp_size; 452 | char *regexp = rx->regexp; 453 | char c; 454 | int min = 0, seen_min = 0, max = 0, seen_max = 0, greedy = 1; 455 | pos += 1; 456 | for (; pos < regexp_size; pos += 1) { 457 | c = regexp[pos]; 458 | if (c >= '0' && c <= '9') { 459 | min = 10 * min + (c - '0'); 460 | seen_min = 1; 461 | } else if (c == ',') { 462 | if (!seen_min) { 463 | return rx_error(rx, "Expected a number before ,."); 464 | } 465 | pos += 1; 466 | break; 467 | } else if (c == '}') { 468 | if (!seen_min) { 469 | return rx_error(rx, "Expected a number before }."); 470 | } 471 | max = min; 472 | goto check_greedy; 473 | } else { 474 | return rx_error(rx, "Unexpected character in quantifier."); 475 | } 476 | } 477 | for (; pos < regexp_size; pos += 1) { 478 | c = regexp[pos]; 479 | if (c >= '0' && c <= '9') { 480 | max = 10 * max + (c - '0'); 481 | seen_max = 1; 482 | } else if (c == '}') { 483 | if (!seen_max) { 484 | max = -1; // -1 means infinite 485 | } 486 | goto check_greedy; 487 | } else { 488 | return rx_error(rx, "Unexpected character in quantifier."); 489 | } 490 | } 491 | return rx_error(rx, "Quantifier not closed."); 492 | 493 | check_greedy: 494 | c = (pos + 1 < regexp_size) ? regexp[pos + 1] : '\0'; 495 | if (c == '?') { 496 | // non greedy 497 | pos += 1; 498 | greedy = 0; 499 | } 500 | else { 501 | // greedy 502 | greedy = 1; 503 | } 504 | qval->min = min; 505 | qval->max = max; 506 | qval->greedy = greedy; 507 | *pos2 = pos; 508 | return 1; 509 | } 510 | 511 | static void rx_partial_free (rx_t *rx) { 512 | int i; 513 | for (i = 0; i < rx->nodes_count; i += 1) { 514 | node_t *n = rx->nodes[i]; 515 | free(n); 516 | } 517 | rx->nodes_count = 0; 518 | for (i = 0; i < rx->char_classes_count; i += 1) { 519 | char_class_t *c = rx->char_classes[i]; 520 | char_class_free(c); 521 | } 522 | rx->char_classes_count = 0; 523 | rx->error = 0; 524 | rx->cap_count = 0; 525 | rx->ignorecase = 0; 526 | } 527 | 528 | void rx_free (rx_t *rx) { 529 | rx_partial_free(rx); 530 | hash_free(rx->dfs_map); 531 | free(rx->nodes); 532 | free(rx->cap_start); 533 | free(rx->or_end); 534 | free(rx->char_classes); 535 | free(rx->dfs_stack); 536 | free(rx->errorstr); 537 | free(rx); 538 | } 539 | 540 | rx_t *rx_alloc () { 541 | rx_t *rx = calloc(1, sizeof(rx_t)); 542 | rx->nodes_allocated = 10; 543 | rx->nodes = malloc(rx->nodes_allocated * sizeof(node_t *)); 544 | rx->char_classes_allocated = 10; 545 | rx->char_classes = malloc(rx->char_classes_allocated * sizeof(char_class_t *)); 546 | rx->cap_allocated = 10; 547 | rx->cap_start = malloc(rx->cap_allocated * sizeof(node_t *)); 548 | rx->or_end = malloc(rx->cap_allocated * sizeof(node_t *)); 549 | rx->dfs_stack_allocated = 10; 550 | rx->dfs_stack = malloc(rx->dfs_stack_allocated * sizeof(node_t *)); 551 | rx->dfs_map = hash_init(hash_direct_hash, hash_direct_equal); 552 | return rx; 553 | } 554 | 555 | matcher_t *rx_matcher_alloc () { 556 | matcher_t *m = calloc(1, sizeof(matcher_t)); 557 | m->path_allocated = 10; 558 | m->path = malloc(m->path_allocated * sizeof(path_t)); 559 | m->cap_allocated = 10; 560 | m->cap_start = realloc(m->cap_start, m->cap_allocated * sizeof(int)); 561 | m->cap_end = realloc(m->cap_end, m->cap_allocated * sizeof(int)); 562 | m->cap_defined = realloc(m->cap_defined, m->cap_allocated * sizeof(char)); 563 | m->cap_str = realloc(m->cap_str, m->cap_allocated * sizeof(char *)); 564 | m->cap_size = realloc(m->cap_size, m->cap_allocated * sizeof(int)); 565 | return m; 566 | } 567 | 568 | // Copies the subgraph starting at sg_start and going no furthur than sg_end into 569 | // the node starting at new_start. Returns the new_end node. Performs a depth first 570 | // search iteratively. 571 | node_t *copy_subgraph (rx_t *rx, node_t *sg_start, node_t *sg_end, node_t *new_start) { 572 | rx->dfs_stack_count = 0; 573 | hash_clear(rx->dfs_map); 574 | node_t *node, *new_node, *new_node2, *new_end; 575 | if (rx->dfs_stack_count >= rx->dfs_stack_allocated) { 576 | rx->dfs_stack_allocated *= 2; 577 | rx->dfs_stack = realloc(rx->dfs_stack, rx->dfs_stack_allocated * sizeof(node_t *)); 578 | } 579 | rx->dfs_stack[rx->dfs_stack_count] = sg_start; 580 | rx->dfs_stack_count += 1; 581 | hash_insert(rx->dfs_map, sg_start, new_start); 582 | 583 | while (1) { 584 | if (rx->dfs_stack_count == 0) { 585 | break; 586 | } 587 | rx->dfs_stack_count -= 1; 588 | node = rx->dfs_stack[rx->dfs_stack_count]; 589 | new_node = hash_lookup(rx->dfs_map, node); 590 | if (node == sg_end) { 591 | new_end = new_node; 592 | node->type |= 0x80; // visited 593 | continue; 594 | } 595 | *new_node = *node; 596 | node->type |= 0x80; // visited 597 | 598 | if (rx->dfs_stack_count + 1 >= rx->dfs_stack_allocated) { 599 | rx->dfs_stack_allocated *= 2; 600 | rx->dfs_stack = realloc(rx->dfs_stack, rx->dfs_stack_allocated * sizeof(node_t *)); 601 | } 602 | 603 | if (node->next->type & 0x80) { 604 | new_node->next = hash_lookup(rx->dfs_map, node->next); 605 | } else { 606 | new_node2 = rx_node_create(rx); 607 | new_node->next = new_node2; 608 | hash_insert(rx->dfs_map, node->next, new_node2); 609 | rx->dfs_stack[rx->dfs_stack_count] = node->next; 610 | rx->dfs_stack_count += 1; 611 | } 612 | 613 | if (new_node->type == BRANCH) { 614 | if (node->next2->type & 0x80) { 615 | new_node->next2 = hash_lookup(rx->dfs_map, node->next2); 616 | } else { 617 | new_node2 = rx_node_create(rx); 618 | new_node->next2 = new_node2; 619 | hash_insert(rx->dfs_map, node->next2, new_node2); 620 | rx->dfs_stack[rx->dfs_stack_count] = node->next2; 621 | rx->dfs_stack_count += 1; 622 | } 623 | } 624 | } 625 | 626 | // Unset all the visited flags. 627 | for (int i = 0; i < rx->dfs_map->allocated; i += 1) { 628 | if (rx->dfs_map->defined[i]) { 629 | node = rx->dfs_map->keys[i]; 630 | node->type &= ~0x80; 631 | } 632 | } 633 | 634 | return new_end; 635 | } 636 | 637 | // Returns 1 on success. 638 | int rx_init (rx_t *rx, int regexp_size, char *regexp) { 639 | rx_partial_free(rx); 640 | rx->start = rx_node_create(rx); 641 | return rx_init_start(rx, regexp_size, regexp, rx->start, 0); 642 | } 643 | 644 | int rx_init_start (rx_t *rx, int regexp_size, char *regexp, node_t *start, int value) { 645 | rx->regexp_size = regexp_size; 646 | rx->regexp = regexp; 647 | 648 | node_t *node = start; 649 | node_t *atom_start = NULL; 650 | node_t *or_end = NULL; 651 | int cap_depth = 0; 652 | int cap_count = 0; 653 | 654 | for (int pos = 0; pos < regexp_size; pos += 1) { 655 | unsigned char c = regexp[pos]; 656 | if (c == '(') { 657 | if (pos + 2 < regexp_size && regexp[pos + 1] == '?' && regexp[pos + 2] == ':') { 658 | pos += 2; 659 | node->type = GROUP_START; 660 | } 661 | else { 662 | cap_count += 1; 663 | node->value = cap_count; 664 | node->type = CAPTURE_START; 665 | } 666 | node_t *node2 = rx_node_create(rx); 667 | node->next = node2; 668 | if (cap_depth >= rx->cap_allocated) { 669 | rx->cap_allocated *= 2; 670 | rx->cap_start = realloc(rx->cap_start, rx->cap_allocated * sizeof(node_t *)); 671 | rx->or_end = realloc(rx->or_end, rx->cap_allocated * sizeof(node_t *)); 672 | } 673 | rx->cap_start[cap_depth] = node; 674 | rx->or_end[cap_depth] = or_end; 675 | or_end = NULL; 676 | cap_depth += 1; 677 | atom_start = NULL; 678 | node = node2; 679 | 680 | } else if (c == ')') { 681 | if (!cap_depth) { 682 | return rx_error(rx, ") was unexpected."); 683 | } 684 | if (or_end) { 685 | node->next = or_end; 686 | node = or_end; 687 | } 688 | cap_depth -= 1; 689 | or_end = rx->or_end[cap_depth]; 690 | atom_start = rx->cap_start[cap_depth]; 691 | node_t *node2 = rx_node_create(rx); 692 | if (atom_start->type == CAPTURE_START) { 693 | node->type = CAPTURE_END; 694 | } else { 695 | node->type = GROUP_END; 696 | } 697 | node->value = atom_start->value; 698 | node->next = node2; 699 | node = node2; 700 | 701 | } else if (c == '|') { 702 | node_t *node2 = rx_node_create(rx); 703 | node_t *node3 = rx_node_create(rx); 704 | node_t *or_start; 705 | if (cap_depth) { 706 | or_start = rx->cap_start[cap_depth - 1]->next; 707 | } else { 708 | or_start = start; 709 | } 710 | *node2 = *or_start; 711 | or_start->type = BRANCH; 712 | or_start->next = node2; 713 | or_start->next2 = node3; 714 | if (or_end) { 715 | node->next = or_end; 716 | } else { 717 | or_end = node; 718 | } 719 | node = node3; 720 | 721 | } else if (c == '*') { 722 | if (!atom_start) { 723 | return rx_error(rx, "Expected something to apply the *."); 724 | } 725 | node_t *node2 = rx_node_create(rx); 726 | node_t *node3 = rx_node_create(rx); 727 | *node2 = *atom_start; 728 | atom_start->type = BRANCH; 729 | node->type = BRANCH; 730 | char c2 = (pos + 1 < regexp_size) ? regexp[pos + 1] : '\0'; 731 | if (c2 == '?') { 732 | // non greedy 733 | pos += 1; 734 | atom_start->next = node3; 735 | atom_start->next2 = node2; 736 | node->next = node3; 737 | node->next2 = node2; 738 | } else { 739 | // greedy 740 | atom_start->next = node2; 741 | atom_start->next2 = node3; 742 | node->next = node2; 743 | node->next2 = node3; 744 | } 745 | node = node3; 746 | 747 | } else if (c == '+') { 748 | if (!atom_start) { 749 | return rx_error(rx, "Expected something to apply the +."); 750 | } 751 | node_t *node2 = rx_node_create(rx); 752 | node->type = BRANCH; 753 | char c2 = (pos + 1 < regexp_size) ? regexp[pos + 1] : '\0'; 754 | if (c2 == '?') { 755 | // non greedy 756 | pos += 1; 757 | node->next = node2; 758 | node->next2 = atom_start; 759 | } else { 760 | // greedy 761 | node->next = atom_start; 762 | node->next2 = node2; 763 | } 764 | node = node2; 765 | 766 | } else if (c == '?') { 767 | if (!atom_start) { 768 | return rx_error(rx, "Expected something to apply the ?."); 769 | } 770 | node_t *node2 = rx_node_create(rx); 771 | *node2 = *atom_start; 772 | atom_start->type = BRANCH; 773 | char c2 = (pos + 1 < regexp_size) ? regexp[pos + 1] : '\0'; 774 | if (c2 == '?') { 775 | // non greedy 776 | pos += 1; 777 | atom_start->next = node; 778 | atom_start->next2 = node2; 779 | } else { 780 | // greedy 781 | atom_start->next = node2; 782 | atom_start->next2 = node; 783 | } 784 | 785 | } else if (c == '{') { 786 | if (!atom_start) { 787 | return rx_error(rx, "Expected something to apply the {."); 788 | } 789 | quantifier_t qval; 790 | if (!rx_quantifier_init(rx, pos, &pos, &qval)) { 791 | return 0; 792 | } 793 | node_t *sg_start = atom_start; 794 | node_t *sg_end = node; 795 | int i = 0; 796 | if (qval.min == 0) { 797 | sg_start = rx_node_create(rx); 798 | *sg_start = *atom_start; 799 | atom_start->type = EMPTY; 800 | atom_start->next = NULL; 801 | node = atom_start; 802 | } 803 | else { 804 | for (i = 1; i < qval.min; i += 1) { 805 | node = copy_subgraph(rx, sg_start, sg_end, node); 806 | } 807 | } 808 | 809 | node_t *sg_start2, *sg_end2; 810 | if (qval.max == -1) { 811 | if (i == 0) { 812 | sg_start2 = sg_start; 813 | sg_end2 = sg_end; 814 | } else { 815 | sg_start2 = rx_node_create(rx); 816 | sg_end2 = copy_subgraph(rx, sg_start, sg_end, sg_start2); 817 | } 818 | 819 | node_t *node2 = rx_node_create(rx); 820 | node->type = BRANCH; 821 | sg_end2->type = BRANCH; 822 | if (qval.greedy) { 823 | node->next = sg_start2; 824 | node->next2 = node2; 825 | sg_end2->next = sg_start2; 826 | sg_end2->next2 = node2; 827 | } else { 828 | node->next = node2; 829 | node->next2 = sg_start2; 830 | sg_end2->next = node2; 831 | sg_end2->next2 = sg_start2; 832 | } 833 | node = node2; 834 | continue; 835 | } 836 | 837 | for (; i < qval.max; i += 1) { 838 | if (i == 0) { 839 | sg_start2 = sg_start; 840 | sg_end2 = sg_end; 841 | } else { 842 | sg_start2 = rx_node_create(rx); 843 | sg_end2 = copy_subgraph(rx, sg_start, sg_end, sg_start2); 844 | } 845 | node->type = BRANCH; 846 | if (qval.greedy) { 847 | node->next = sg_start2; 848 | node->next2 = sg_end2; 849 | } else { 850 | node->next = sg_end2; 851 | node->next2 = sg_start2; 852 | } 853 | node = sg_end2; 854 | } 855 | 856 | } else if (c == '\\') { 857 | if (pos + 1 == regexp_size) { 858 | return rx_error(rx, "Expected character after \\."); 859 | } 860 | pos += 1; 861 | char c2 = regexp[pos]; 862 | if (c2 == 'G') { 863 | node_t *node2 = rx_node_create(rx); 864 | node->type = ASSERTION; 865 | node->value = ASSERT_SOP; 866 | node->next = node2; 867 | node = node2; 868 | } else if (c2 == '<') { 869 | node_t *node2 = rx_node_create(rx); 870 | node->type = ASSERTION; 871 | node->value = ASSERT_SOW; 872 | node->next = node2; 873 | node = node2; 874 | } else if (c2 == '>') { 875 | node_t *node2 = rx_node_create(rx); 876 | node->type = ASSERTION; 877 | node->value = ASSERT_EOW; 878 | node->next = node2; 879 | node = node2; 880 | } else if (c2 == 'c') { 881 | rx->ignorecase = 1; 882 | } else if (c2 == 'e' || c2 == 'r' || c2 == 'n' || c2 == 't') { 883 | node_t *node2 = rx_node_create(rx); 884 | node->type = TAKE; 885 | node->value = c2 == 'e' ? '\x1b' 886 | : c2 == 'r' ? '\r' 887 | : c2 == 'n' ? '\n' 888 | : c2 == 't' ? '\t' : '\0'; 889 | node->next = node2; 890 | atom_start = node; 891 | node = node2; 892 | } else if (c2 == 'N' || c2 == 'd' || c2 == 'D' || c2 == 'w' || c2 == 'W' || c2 == 's' || c2 == 'S') { 893 | node_t *node2 = rx_node_create(rx); 894 | node->type = CHAR_SET; 895 | node->value = c2 == 'N' ? CS_NOTNL 896 | : c2 == 'd' ? CS_DIGIT 897 | : c2 == 'D' ? CS_NOTDIGIT 898 | : c2 == 'w' ? CS_WORD 899 | : c2 == 'W' ? CS_NOTWORD 900 | : c2 == 's' ? CS_SPACE 901 | : c2 == 'S' ? CS_NOTSPACE : 0; 902 | node->next = node2; 903 | atom_start = node; 904 | node = node2; 905 | } else if (c2 == 'x') { 906 | if (pos + 2 >= regexp_size) { 907 | return rx_error(rx, "Expected 2 characters after \\x."); 908 | } 909 | unsigned int i; 910 | if (!rx_hex_to_int(regexp + pos + 1, 2, &i)) { 911 | return rx_error(rx, "Expected 2 hex digits after \\x."); 912 | } 913 | pos += 2; 914 | node_t *node2 = rx_node_create(rx); 915 | node->type = TAKE; 916 | node->value = i; 917 | node->next = node2; 918 | atom_start = node; 919 | node = node2; 920 | } else if (c2 == 'u' || c2 == 'U') { 921 | int count = c2 == 'u' ? 4 : 8; 922 | if (pos + count >= regexp_size) { 923 | return rx_error(rx, "Expected %d characters after \\%c.", count, c2); 924 | } 925 | unsigned int i; 926 | if (!rx_hex_to_int(regexp + pos + 1, count, &i)) { 927 | return rx_error(rx, "Expected %d hex digits after \\%c.", count, c2); 928 | } 929 | pos += count; 930 | char str[4]; 931 | int str_size = rx_int_to_utf8(i, str); 932 | if (!str_size) { 933 | return rx_error(rx, "Invalid \\%c sequence.", c2); 934 | } 935 | atom_start = node; 936 | for (i = 0; i < str_size; i += 1) { 937 | node_t *node2 = rx_node_create(rx); 938 | node->type = TAKE; 939 | node->value = (unsigned char) str[i]; 940 | node->next = node2; 941 | node = node2; 942 | } 943 | 944 | } else { 945 | // Unrecognized backslash escape will match itself, for example \\ or \* 946 | node_t *node2 = rx_node_create(rx); 947 | node->type = TAKE; 948 | node->value = c2; 949 | node->next = node2; 950 | atom_start = node; 951 | node = node2; 952 | } 953 | 954 | } else if (c == '^') { 955 | node_t *node2 = rx_node_create(rx); 956 | node->type = ASSERTION; 957 | node->next = node2; 958 | char c2 = (pos + 1 < regexp_size) ? regexp[pos + 1] : '\0'; 959 | if (c2 == '^') { 960 | pos += 1; 961 | node->value = ASSERT_SOL; 962 | } else { 963 | node->value = ASSERT_SOS; 964 | } 965 | node = node2; 966 | 967 | } else if (c == '$') { 968 | node_t *node2 = rx_node_create(rx); 969 | node->type = ASSERTION; 970 | node->next = node2; 971 | char c2 = (pos + 1 < regexp_size) ? regexp[pos + 1] : '\0'; 972 | if (c2 == '$') { 973 | pos += 1; 974 | node->value = ASSERT_EOL; 975 | } else { 976 | node->value = ASSERT_EOS; 977 | } 978 | node = node2; 979 | } else if (c == '[') { 980 | char_class_t *ccval; 981 | if (!rx_char_class_init(rx, pos, &pos, &ccval)) { 982 | return 0; 983 | } 984 | node_t *node2 = rx_node_create(rx); 985 | node->type = CHAR_CLASS; 986 | node->ccval = ccval; 987 | node->next = node2; 988 | atom_start = node; 989 | node = node2; 990 | 991 | } else if (c == '.') { 992 | node_t *node2 = rx_node_create(rx); 993 | node->type = CHAR_SET; 994 | node->value = CS_ANY; 995 | node->next = node2; 996 | atom_start = node; 997 | node = node2; 998 | 999 | } else { 1000 | node_t *node2 = rx_node_create(rx); 1001 | node->type = TAKE; 1002 | node->next = node2; 1003 | node->value = c; 1004 | atom_start = node; 1005 | node = node2; 1006 | } 1007 | 1008 | } 1009 | if (cap_depth) { 1010 | return rx_error(rx, "Expected closing )."); 1011 | } 1012 | if (or_end) { 1013 | node->next = or_end; 1014 | node = or_end; 1015 | } 1016 | node->type = MATCH_END; 1017 | node->value = value; 1018 | if (cap_count > rx->cap_count) { 1019 | rx->cap_count = cap_count; 1020 | } 1021 | return 1; 1022 | } 1023 | 1024 | static int flip_case (unsigned char *c) { 1025 | if (*c >= 'a' && *c <= 'z') { 1026 | *c -= 'a' - 'A'; 1027 | return 1; 1028 | } else if (*c >= 'A' && *c <= 'Z') { 1029 | *c += 'a' - 'A'; 1030 | return 1; 1031 | } 1032 | else { 1033 | return 0; 1034 | } 1035 | } 1036 | 1037 | static int rx_match_char_class (rx_t *rx, char_class_t *ccval, int test_size, char *test) { 1038 | int matched = 0; 1039 | 1040 | // Check the individual values 1041 | for (int i = 0; i < ccval->values_count;) { 1042 | int char1_size = rx_utf8_char_size(ccval->values_count, ccval->values, i); 1043 | char *char1 = ccval->values + i; 1044 | if (test_size == char1_size && strncmp(test, char1, test_size) == 0) { 1045 | matched = 1; 1046 | goto out; 1047 | } 1048 | i += char1_size; 1049 | } 1050 | 1051 | // Check the character ranges 1052 | for (int i = 0; i < ccval->ranges_count;) { 1053 | int char1_size = rx_utf8_char_size(ccval->ranges_count, ccval->ranges, i); 1054 | char *char1 = ccval->ranges + i; 1055 | i += char1_size; 1056 | int char2_size = rx_utf8_char_size(ccval->ranges_count, ccval->ranges, i); 1057 | char *char2 = ccval->ranges + i; 1058 | i += char2_size; 1059 | 1060 | int ge = (test_size > char1_size) || 1061 | (test_size == char1_size && strncmp(test, char1, test_size) >= 0); 1062 | 1063 | int le = (test_size < char2_size) || 1064 | (test_size == char2_size && strncmp(test, char2, test_size) <= 0); 1065 | 1066 | if (ge && le) { 1067 | matched = 1; 1068 | goto out; 1069 | } 1070 | } 1071 | 1072 | // Check the character sets 1073 | char c = test[0]; 1074 | for (int i = 0; i < ccval->char_sets_count; i += 1) { 1075 | char cs = ccval->char_sets[i]; 1076 | if (cs == CS_NOTNL) { 1077 | if (c != '\n') { 1078 | matched = 1; 1079 | goto out; 1080 | } 1081 | } else if (cs == CS_DIGIT) { 1082 | if (c >= '0' && c <= '9') { 1083 | matched = 1; 1084 | goto out; 1085 | } 1086 | } else if (cs == CS_NOTDIGIT) { 1087 | if (!(c >= '0' && c <= '9')) { 1088 | matched = 1; 1089 | goto out; 1090 | } 1091 | } else if (cs == CS_WORD) { 1092 | if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')) { 1093 | matched = 1; 1094 | goto out; 1095 | } 1096 | } else if (cs == CS_NOTWORD) { 1097 | if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'))) { 1098 | matched = 1; 1099 | goto out; 1100 | } 1101 | } else if (cs == CS_SPACE) { 1102 | if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { 1103 | matched = 1; 1104 | goto out; 1105 | } 1106 | } else if (cs == CS_NOTSPACE) { 1107 | if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) { 1108 | matched = 1; 1109 | goto out; 1110 | } 1111 | } 1112 | } 1113 | 1114 | out: 1115 | if (ccval->negated) { 1116 | return !matched; 1117 | } else { 1118 | return matched; 1119 | } 1120 | } 1121 | 1122 | static int rx_match_assertion (int type, int start_pos, int str_size, char *str, int pos) { 1123 | if (type == ASSERT_SOS) { 1124 | if (pos == 0) { 1125 | return 1; 1126 | } 1127 | } else if (type == ASSERT_SOL) { 1128 | if (pos == 0 || str[pos - 1] == '\n') { 1129 | return 1; 1130 | } 1131 | } else if (type == ASSERT_EOS) { 1132 | if (pos == str_size) { 1133 | return 1; 1134 | } 1135 | } else if (type == ASSERT_EOL) { 1136 | if (pos == str_size || str[pos] == '\n' || str[pos] == '\r') { 1137 | return 1; 1138 | } 1139 | } else if (type == ASSERT_SOP) { 1140 | if (pos == start_pos) { 1141 | return 1; 1142 | } 1143 | } else if (type == ASSERT_SOW || type == ASSERT_EOW) { 1144 | char w1, w2; 1145 | if (pos == 0) { 1146 | w1 = 0; 1147 | } else { 1148 | char c1 = str[pos - 1]; 1149 | w1 = (c1 >= '0' && c1 <= '9') || (c1 >= 'A' && c1 <= 'Z') || (c1 >= 'a' && c1 <= 'z') || (c1 == '_'); 1150 | } 1151 | 1152 | if (pos >= str_size) { 1153 | w2 = 0; 1154 | } else { 1155 | char c2 = str[pos]; 1156 | w2 = (c2 >= '0' && c2 <= '9') || (c2 >= 'A' && c2 <= 'Z') || (c2 >= 'a' && c2 <= 'z') || (c2 == '_'); 1157 | } 1158 | 1159 | if (type == ASSERT_SOW) { 1160 | return !w1 && w2; 1161 | } else if (type == ASSERT_EOW) { 1162 | return w1 && !w2; 1163 | } 1164 | } 1165 | return 0; 1166 | } 1167 | 1168 | // rx_match() will match a regexp against a given string. The strings it finds 1169 | // will be stored in the matcher argument. The start position can be given, but usually 1170 | // it would be 0 for the start of the string. Returns 1 on success and 0 on failure. 1171 | // 1172 | // The same matcher object can be used multiple times which will reuse the 1173 | // memory allocated for previous matches. All the captures are references into the 1174 | // original string. 1175 | int rx_match (rx_t *rx, matcher_t *m, int str_size, char *str, int start_pos) { 1176 | m->success = 0; 1177 | m->path_count = 0; 1178 | node_t *node = rx->start; 1179 | int pos = start_pos; 1180 | unsigned char c; 1181 | 1182 | while (1) { 1183 | retry: 1184 | 1185 | switch (node->type) { 1186 | case TAKE: 1187 | if (pos >= str_size) { 1188 | goto try_alternative; 1189 | } 1190 | c = str[pos]; 1191 | if (c == node->value) { 1192 | node = node->next; 1193 | pos += 1; 1194 | continue; 1195 | } else if (rx->ignorecase && flip_case(&c) && c == node->value) { 1196 | node = node->next; 1197 | pos += 1; 1198 | continue; 1199 | } 1200 | break; 1201 | 1202 | case MATCH_END: 1203 | // End node found! 1204 | // Match cap count is one more than rx cap count since it counts the 1205 | // entire match as the 0 capture. 1206 | m->cap_count = rx->cap_count + 1; 1207 | if (m->cap_count > m->cap_allocated) { 1208 | m->cap_allocated = m->cap_count; 1209 | m->cap_start = realloc(m->cap_start, m->cap_allocated * sizeof(int)); 1210 | m->cap_end = realloc(m->cap_end, m->cap_allocated * sizeof(int)); 1211 | m->cap_defined = realloc(m->cap_defined, m->cap_allocated * sizeof(char)); 1212 | m->cap_str = realloc(m->cap_str, m->cap_allocated * sizeof(char *)); 1213 | m->cap_size = realloc(m->cap_size, m->cap_allocated * sizeof(int)); 1214 | } 1215 | m->cap_defined[0] = 1; 1216 | m->cap_start[0] = start_pos; 1217 | m->cap_end[0] = pos; 1218 | m->cap_str[0] = str + start_pos; 1219 | m->cap_size[0] = pos - start_pos; 1220 | for (int i = 1; i < m->cap_count; i++) { 1221 | m->cap_defined[i] = 0; 1222 | m->cap_start[i] = 0; 1223 | m->cap_end[i] = 0; 1224 | m->cap_str[i] = NULL; 1225 | m->cap_size[i] = 0; 1226 | } 1227 | for (int i = 0; i < m->path_count; i += 1) { 1228 | path_t *p = m->path + i; 1229 | if (p->node->type == CAPTURE_START) { 1230 | int j = p->node->value; 1231 | m->cap_defined[j] = 1; 1232 | m->cap_start[j] = p->pos; 1233 | m->cap_str[j] = str + p->pos; 1234 | } else if (p->node->type == CAPTURE_END) { 1235 | int j = p->node->value; 1236 | m->cap_end[j] = p->pos; 1237 | m->cap_size[j] = p->pos - m->cap_start[j]; 1238 | } 1239 | } 1240 | m->success = 1; 1241 | m->value = node->value; 1242 | return 1; 1243 | break; 1244 | 1245 | case BRANCH: 1246 | case CAPTURE_START: 1247 | case CAPTURE_END: 1248 | { 1249 | if (m->path_count == m->path_allocated) { 1250 | m->path_allocated *= 2; 1251 | m->path = realloc(m->path, m->path_allocated * sizeof(path_t)); 1252 | } 1253 | path_t *p = m->path + m->path_count; 1254 | p->pos = pos; 1255 | p->node = node; 1256 | m->path_count += 1; 1257 | node = node->next; 1258 | continue; 1259 | } 1260 | break; 1261 | 1262 | case GROUP_START: 1263 | case GROUP_END: 1264 | node = node->next; 1265 | continue; 1266 | break; 1267 | 1268 | case ASSERTION: 1269 | if (rx_match_assertion(node->value, start_pos, str_size, str, pos)) { 1270 | node = node->next; 1271 | continue; 1272 | } 1273 | // Do not try an alternative if it was a SOS or SOP assertion that failed 1274 | if (node->value == ASSERT_SOS || node->value == ASSERT_SOP) { 1275 | goto out; 1276 | } 1277 | break; 1278 | 1279 | case CHAR_CLASS: 1280 | if (pos >= str_size) { 1281 | goto try_alternative; 1282 | } 1283 | int test_size = rx_utf8_char_size(str_size, str, pos); 1284 | char *test = str + pos; 1285 | char_class_t *ccval = node->ccval; 1286 | 1287 | if (rx_match_char_class(rx, ccval, test_size, test)) { 1288 | pos += test_size; 1289 | node = node->next; 1290 | continue; 1291 | } else if (rx->ignorecase) { 1292 | // If retrying because of ignorecase, copy the char to a buffer to 1293 | // change the case of what's being tested. 1294 | unsigned char retry_buf[4]; 1295 | memcpy(retry_buf, test, test_size); 1296 | if (flip_case(retry_buf)) { 1297 | if (rx_match_char_class(rx, ccval, test_size, (char *) retry_buf)) { 1298 | pos += test_size; 1299 | node = node->next; 1300 | continue; 1301 | } 1302 | } 1303 | } 1304 | break; 1305 | 1306 | case CHAR_SET: 1307 | if (pos >= str_size) { 1308 | goto try_alternative; 1309 | } 1310 | c = str[pos]; 1311 | if (node->value == CS_ANY) { 1312 | pos += 1; 1313 | node = node->next; 1314 | continue; 1315 | } else if (node->value == CS_NOTNL) { 1316 | if (c != '\n') { 1317 | pos += 1; 1318 | node = node->next; 1319 | continue; 1320 | } 1321 | } else if (node->value == CS_DIGIT) { 1322 | if (c >= '0' && c <= '9') { 1323 | pos += 1; 1324 | node = node->next; 1325 | continue; 1326 | } 1327 | } else if (node->value == CS_NOTDIGIT) { 1328 | if (!(c >= '0' && c <= '9')) { 1329 | pos += 1; 1330 | node = node->next; 1331 | continue; 1332 | } 1333 | } else if (node->value == CS_WORD) { 1334 | if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')) { 1335 | pos += 1; 1336 | node = node->next; 1337 | continue; 1338 | } 1339 | } else if (node->value == CS_NOTWORD) { 1340 | if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'))) { 1341 | pos += 1; 1342 | node = node->next; 1343 | continue; 1344 | } 1345 | } else if (node->value == CS_SPACE) { 1346 | if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { 1347 | pos += 1; 1348 | node = node->next; 1349 | continue; 1350 | } 1351 | } else if (node->value == CS_NOTSPACE) { 1352 | if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) { 1353 | pos += 1; 1354 | node = node->next; 1355 | continue; 1356 | } 1357 | } 1358 | break; 1359 | 1360 | case EMPTY: 1361 | node = node->next; 1362 | continue; 1363 | break; 1364 | } 1365 | 1366 | try_alternative: 1367 | 1368 | for (int i = m->path_count - 1; i >= 0; i--) { 1369 | path_t *p = m->path + i; 1370 | if (p->node->type == BRANCH) { 1371 | node = p->node->next2; 1372 | pos = p->pos; 1373 | m->path_count = i; 1374 | goto retry; 1375 | } 1376 | } 1377 | 1378 | // Try another start position. 1379 | if (start_pos == str_size) { 1380 | break; 1381 | } 1382 | m->path_count = 0; 1383 | start_pos += 1; 1384 | pos = start_pos; 1385 | node = rx->start; 1386 | } 1387 | out: 1388 | return 0; 1389 | } 1390 | 1391 | void rx_matcher_free (matcher_t *m) { 1392 | free(m->path); 1393 | free(m->cap_start); 1394 | free(m->cap_end); 1395 | free(m->cap_defined); 1396 | free(m->cap_str); 1397 | free(m->cap_size); 1398 | free(m); 1399 | } 1400 | 1401 | --------------------------------------------------------------------------------