├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── array.c ├── array.h ├── backtrace.sh ├── block.c ├── cmd ├── Copy ├── Cut └── Paste ├── command.c ├── command.h ├── draw.c ├── draw.h ├── edit.c ├── edit.h ├── font.c ├── font.h ├── gen-tests.h.awk ├── pipe.c ├── pipe.h ├── test.h ├── tests.c ├── utf.c ├── utf.h ├── util.c ├── util.h ├── valgrind.sh ├── valgrind.supp ├── view.c ├── view.h ├── werf.c ├── window.c └── window.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2015 Hadrian Wegrzynowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPTIM = -g -O0 -Werror 2 | #OPTIM = -O2 3 | CFLAGS = -std=c1x $(OPTIM) -Wall -Wextra -pedantic \ 4 | -Wno-missing-field-initializers \ 5 | -Wno-missing-braces \ 6 | -Wno-overlength-strings \ 7 | `freetype-config --cflags` \ 8 | -D_POSIX_C_SOURCE=200809L 9 | LDFLAGS = -lrt -lcairo -lX11 `freetype-config --libs` -lfontconfig 10 | 11 | CC = gcc 12 | 13 | SRC = \ 14 | util.c \ 15 | array.c \ 16 | pipe.c \ 17 | command.c \ 18 | utf.c \ 19 | edit.c \ 20 | block.c \ 21 | font.c \ 22 | view.c \ 23 | draw.c \ 24 | window.c \ 25 | werf.c 26 | OBJ = $(SRC:.c=.o) 27 | 28 | all: werf 29 | 30 | .c.o: 31 | @echo CC $< 32 | @$(CC) -c $(CFLAGS) $< -o $@ 33 | 34 | $(OBJ): util.h Makefile 35 | window.o: window.h draw.h view.h 36 | draw.o: draw.h view.h 37 | view.o: view.h 38 | utf.o: utf.h 39 | font.o: font.h utf.h 40 | edit.o: edit.h utf.h array.h 41 | pipe.o: pipe.h array.h 42 | command.o: command.h array.h view.h edit.h 43 | werf.o: pipe.h edit.h font.h array.h 44 | 45 | tests.h: $(SRC) gen-tests.h.awk 46 | @echo GEN tests.h 47 | @./gen-tests.h.awk $(SRC) > tests.h 48 | tests.o: tests.c tests.h 49 | tests.passed: tests.o $(OBJ) 50 | @echo CC -o tests 51 | @$(CC) -o tests tests.o -Wl,--wrap=main $(OBJ) $(LDFLAGS) 52 | @echo testing 53 | valgrind --quiet --leak-check=full --error-exitcode=1 ./tests 54 | mv tests tests.passed 55 | 56 | werf: $(OBJ) tests.passed 57 | @echo CC -o $@ 58 | @$(CC) -o $@ $(OBJ) $(LDFLAGS) 59 | 60 | clean: 61 | rm -f werf tests tests.passed tests.h $(OBJ) 62 | 63 | .PHONY: all clean 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # werf - mouse driven text editor 2 | 3 | Werf is mouse driven experimental text editor inspired by Acme, text editor from the Plan 9 from Bell Labs. 4 | 5 | werf-0 6 | 7 | ## Requirements 8 | 9 | libX11, libcairo, libfreetype, libfontconfig 10 | 11 | Compile with GNU or BSD make. Uses C11 only for anonymous structs as rest is C99. 12 | 13 | ## Features and non-features 14 | 15 | - Mouse driven 16 | - Simple integration with shell 17 | - UTF-8 only 18 | - Unlimited undo 19 | - Uses proportional font by default 20 | - No syntax highlighting 21 | - No text wrapping yet 22 | - Can't save yet... 23 | 24 | ## Keybindings 25 | 26 | - Default X input method for text entry. 27 | - Enter for new line. 28 | - Same as in WIMP interfaces: 29 | - Arrows 30 | - Home/End 31 | - Page Up/Down 32 | - Backspace/Delete 33 | 34 | Will be removed or changed: 35 | 36 | - F2 - undo 37 | - F3 - redo 38 | 39 | ## Command toolbars 40 | 41 | Toolbar is a place for commands that can be applied to selection or the file as a whole. It is divided in two sections. First is for pinned commands and is by default populated by a few built-in commands. Second is for ad-hoc commands. It starts with "..." - a space where new command can be issued. On the right side of "..." is a list of previously issued non-pinned commands. The list is filtered while typing. 42 | 43 | There are two such toolbars: selection and top toolbar. 44 | 45 | ### Selection toolbar 46 | 47 | Text can be selected with mouse (left click and drag). After text is selected 48 | and LMB is released selection-toolbar is shown. Toolbar does not overlap the text it splits it. Toolbar appears between lines bordering with selection start or end just under mouse pointer. 49 | 50 | Default pinned commands: Cut, Copy, Paste, Delete, 51 | Find, Open, Exec. 52 | 53 | ### Top toolbar 54 | 55 | Top toolbar is for commands related more to the file as a whole. Default pinned commands: Save, Open, Undo, Redo, Paste, Find. 56 | 57 | ## Commands 58 | 59 | Most edit actions are done through commands. Commands can act as simple filters - getting selection content from standard input and replacing it by giving standard output. Except for built-in commands commands are run with ``sh -c``. 60 | 61 | - left click - execute command 62 | - double click - edit command and select word under pointer 63 | - triple click - edit command and select whole command line 64 | - middle click - append space and clipboard contents to command and execute it 65 | 66 | ### Built-in commands 67 | 68 | Built-in commands names are capitalized in CamelCase style. 69 | 70 | Available built-in commands: 71 | 72 | - Save [filename] 73 | - Open [filename] 74 | - Cut 75 | - Copy 76 | - Paste 77 | - Delete 78 | - Undo 79 | - Redo 80 | - Find [regex] 81 | 82 | ### Command pipes 83 | 84 | Commands have more options where to read from or write to a file. They are spawned with additional pipes that are exposed by environmental variables thanks to /dev/fd mechanism. 85 | 86 | Available pipes: 87 | 88 | - WERF_CONTROL_W - control pipe 89 | 90 | ### Control pipe 91 | 92 | Command can request special behavior from editor through control pipe (WERF_CONTROL_W). Control pipe expects available command name with new line at the end. 93 | 94 | Available control commands: 95 | 96 | - ReadOnly - disregard changes to selection 97 | 98 | It is also available as a simple command wrapper: 99 | 100 | $ cat cmd/ReadOnly 101 | #!/bin/sh 102 | echo ReadOnly >> "$werf_control_w" 103 | exec "$@" 104 | 105 | - disregard - selection is read only, disregard writes to selection - rename to ReadOnly 106 | - finish - finish as soon as write off selection is done, probably can be removed, as with jobs toolbar it would not have much use 107 | 108 | ## TODO 109 | 110 | ### Now 111 | 112 | - top toolbar 113 | - Save 114 | - Load 115 | - Find command 116 | - WERF_AFTER_SELECTION_R - wrap around end? 117 | - WERF_BEFORE_SELECTION_R - wrap around begin? 118 | - WERF_HIGHLIGHT_W 119 | - maybe fold to control pipe? same thing for range? 120 | - Highlight 12 0 13 0 # highlight whole line 12 121 | - single poll loop (also for command execution) 122 | - command issuing 123 | 124 | ### Internal 125 | 126 | - proper marking of dirty caches 127 | - optimize array grow strategy 128 | - use arrays in buckets for line and lines? 129 | - memory optimization 130 | - use uint32_t instead of size_t? 131 | - intrusive array: put nmemb and amemb before payload 132 | - split backend/frontend for ssh tunneling 133 | 134 | ### Presentation 135 | 136 | - cursor blinking 137 | - implement a bit of material design (shadows, but not animation) 138 | - limit direct use of Xlib to only necessary parts? 139 | - use SDL? 140 | - draw text to single alpha channel 141 | - disable subpixel hinting 142 | - it seems that SDL_Texture does not support single channel pixel formats 143 | 144 | ### Simpler 145 | 146 | - load from stdin and save to stdout 147 | - right click to show selection toolbar 148 | - save undo buffer as mmapped file like vim's swap files 149 | - double click to select word, triple to select line 150 | - extending selection: handles or shift+click 151 | - tab while selected - increase indentation level 152 | - proper tab stops 153 | - keeping indentation level (copy leading white space) 154 | - mark white space at the end of the line 155 | - detect and distinguish indentation style? 156 | - detect if tabs or spaces are used for indentation 157 | - mark not matching indentation 158 | 159 | ### Later 160 | 161 | - Jobs toolbar - shows running and exited commands 162 | - display standard error and exit code 163 | - Kill running command 164 | - Issue subcommands to running command (i.e. Next/Previous for Find) 165 | - show cursor for running job in text 166 | - backspace/delete the cursor to kill 167 | - WERF_BEFORE_SELECTION_W 168 | - WERF_AFTER_SELECTION_W 169 | - Command pinning - drag and drop before "..." 170 | - Scrollable toolbars 171 | - multiview and multifile 172 | - completion for: 173 | - command line 174 | - editing 175 | - line wrapping 176 | - smooth scrolling 177 | - drag and drop: 178 | - selection as text 179 | - files as filenames 180 | - visual scroll bar 181 | - with outline? 182 | - external process? 183 | - built-in commands as multicall binary (symlinks to werf) 184 | - shell mode 185 | - interactive shell 186 | - partial implementation of VT100 emulation? 187 | - syntax highlighting? 188 | - line numbers? 189 | 190 | ### After 1.0 191 | - touch events 192 | - elastic tabs? 193 | - undo/redo as external command? 194 | -------------------------------------------------------------------------------- /array.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "util.h" 6 | #include "array.h" 7 | 8 | void 9 | array_free(array_t *a) 10 | { 11 | free(a->ptr); 12 | a->ptr = 0; 13 | a->amemb = 0; 14 | a->nmemb = 0; 15 | } 16 | 17 | bool 18 | array_realloc(array_t *a, size_t size, size_t amemb) 19 | { 20 | a->amemb = amemb; 21 | a->ptr = realloc(a->ptr, a->amemb * size); 22 | if(!a->ptr) { 23 | a->amemb = 0; 24 | a->nmemb = 0; 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | bool 31 | array_resize(array_t *a, size_t size, size_t nmemb) 32 | { 33 | size_t next = next_size(nmemb, size * 8); 34 | a->nmemb = nmemb; 35 | if(a->nmemb > a->amemb || next * 4 < a->amemb) { 36 | return array_realloc(a, size, next); 37 | } 38 | return false; 39 | } 40 | 41 | bool 42 | array_extend(array_t *a, size_t size, size_t nmore) 43 | { 44 | if(!nmore) { 45 | return false; 46 | } 47 | 48 | return array_resize(a, size, a->nmemb + nmore); 49 | } 50 | 51 | bool 52 | array_shrink(array_t *a, size_t size, size_t nless) 53 | { 54 | if(!nless) { 55 | return false; 56 | } 57 | if(nless > a->nmemb) { 58 | return true; 59 | } 60 | 61 | return array_resize(a, size, a->nmemb - nless); 62 | } 63 | 64 | bool 65 | array_fragment_bounds(array_t *a, ssize_t start, ssize_t end, 66 | size_t *out_start, size_t *out_end) 67 | { 68 | ssize_t s; 69 | ssize_t e; 70 | 71 | if(start >= 0) { 72 | s = start; 73 | } else { 74 | size_t last = a->nmemb > 0 ? a->nmemb-1 : 0; 75 | s = last + start+1; 76 | } 77 | 78 | if(end >= 0) { 79 | e = end; 80 | } else { 81 | e = a->nmemb + end+1; 82 | } 83 | 84 | if(s < 0 || e < 0 || s > e || (size_t)e > a->nmemb) { 85 | return true; 86 | } 87 | 88 | *out_start = s; 89 | *out_end = e; 90 | 91 | return false; 92 | } 93 | 94 | void 95 | array_fragment_apply(array_t *a, size_t size, size_t start, size_t end, 96 | array_memb_func_t func) 97 | { 98 | char *ptr = a->ptr; 99 | ptr += start * size; 100 | for(size_t i = start; i < end; i++) { 101 | func(ptr); 102 | ptr += size; 103 | } 104 | } 105 | 106 | void 107 | array_fragment_shift(array_t *a, size_t size, size_t start, size_t end, 108 | ssize_t shift) 109 | { 110 | size_t nmemb = end - start; 111 | 112 | char *first = a->ptr; 113 | first += start * size; 114 | memshift(shift, first, nmemb, size); 115 | } 116 | 117 | bool 118 | array_fragment_resize(array_t *a, size_t size, size_t start, size_t end, 119 | size_t nmemb) 120 | { 121 | ssize_t shift = nmemb - (end - start); 122 | if(shift > 0) { 123 | if(array_extend(a, size, shift)) { 124 | return true; 125 | } 126 | } 127 | 128 | array_fragment_shift(a, size, start, a->nmemb, shift); 129 | 130 | if(shift < 0) { 131 | if(array_shrink(a, size, -shift)) { 132 | return true; 133 | } 134 | } 135 | return false; 136 | } 137 | 138 | /* 139 | void * 140 | slice_get(slice_t *slice, ssize_t idx) 141 | { 142 | size_t start; 143 | size_t end; 144 | slice_validate(slice, &start, &end); 145 | if(start == end) { 146 | return NULL; 147 | } 148 | 149 | if(idx >= 0) { 150 | idx += start; 151 | } else { 152 | size_t last = end > 0 ? end-1 : 0; 153 | idx = last + idx+1; 154 | } 155 | 156 | DIEIF(idx < 0); 157 | DIEIF((size_t)idx < start); 158 | DIEIF((size_t)idx > end); 159 | DIEIF((size_t)idx >= slice->array->nmemb); 160 | 161 | return (char*)slice->array->ptr + idx * slice->array->size; 162 | } 163 | */ 164 | -------------------------------------------------------------------------------- /array.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | void *ptr; 3 | size_t nmemb; 4 | size_t amemb; 5 | } array_t; 6 | 7 | typedef void (*array_memb_func_t)(void *memb); 8 | 9 | #define ARRAY(T) \ 10 | union { \ 11 | array_t array; \ 12 | struct { \ 13 | T *data; \ 14 | size_t nmemb; \ 15 | size_t amemb; \ 16 | }; \ 17 | } 18 | 19 | typedef ARRAY(char) string_t; 20 | 21 | #define ARR_FREE(t_arr) \ 22 | array_free(&(t_arr)->array) 23 | #define ARR_REALLOC(t_arr, amemb) \ 24 | array_realloc(&(t_arr)->array, sizeof((t_arr)->data[0]), amemb) 25 | #define ARR_RESIZE(t_arr, nmemb) \ 26 | array_extend(&(t_arr)->array, sizeof((t_arr)->data[0]), nmemb) 27 | #define ARR_EXTEND(t_arr, nmore) \ 28 | array_extend(&(t_arr)->array, sizeof((t_arr)->data[0]), nmore) 29 | #define ARR_SHRINK(t_arr, nless) \ 30 | array_shrink(&(t_arr)->array, sizeof((t_arr)->data[0]), nless) 31 | #define ARR_FRAG_BOUNDS(t_arr, start, end, out_start, out_end) \ 32 | array_fragment_bounds(&(t_arr)->array, in_start, in_end, out_start, out_end) 33 | #define ARR_FRAG_APPLY(t_arr, start, end, func) \ 34 | array_fragment_apply(&(t_arr)->array, sizeof((t_arr)->data[0]), start, end, func) 35 | #define ARR_FRAG_SHIFT(t_arr, start, end, shift) \ 36 | array_fragment_shift(&(t_arr)->array, sizeof((t_arr)->data[0]), start, end, shift) 37 | #define ARR_FRAG_RESIZE(t_arr, start, end, nmemb) \ 38 | array_fragment_resize(&(t_arr)->array, sizeof((t_arr)->data[0]), start, end, nmemb) 39 | 40 | void array_free(array_t *a); 41 | bool array_realloc(array_t *a, size_t size, size_t amemb); 42 | bool array_resize(array_t *a, size_t size, size_t nmemb); 43 | bool array_extend(array_t *a, size_t size, size_t nmore); 44 | bool array_shrink(array_t *a, size_t size, size_t nless); 45 | bool array_fragment_bounds(array_t *a, ssize_t start, ssize_t end, 46 | size_t *out_start, size_t *out_end); 47 | void array_fragment_apply(array_t *a, size_t size, size_t start, size_t end, 48 | array_memb_func_t func); 49 | void array_fragment_shift(array_t *a, size_t size, size_t start, size_t end, 50 | ssize_t shift); 51 | bool array_fragment_resize(array_t *a, size_t size, size_t start, size_t end, 52 | size_t nmemb); 53 | /* 54 | void *slice_get(slice_t *slice, ssize_t idx); 55 | */ 56 | -------------------------------------------------------------------------------- /backtrace.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | gdb -batch -ex run -ex bt --args ./werf "$@" 3 | -------------------------------------------------------------------------------- /block.c: -------------------------------------------------------------------------------- 1 | #if 0 2 | exec gcc -Og -g -std=c99 -Wall -Wextra -pedantic block.c -o block 3 | #endif 4 | 5 | #define _GNU_SOURCE 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "util.h" 18 | #include "test.h" 19 | 20 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 21 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 22 | #define LEN(a) (sizeof(a) / sizeof(a)[0]) 23 | 24 | #define BLOCK_SIZE 4096 25 | 26 | typedef struct { 27 | int len; // 0..BLOCK_SIZE 28 | int nlines; // 0..BLOCK_SIZE 29 | struct blockbuf { char buf[BLOCK_SIZE]; } *p; // != NULL 30 | } block_t; 31 | 32 | typedef struct { 33 | int nblocks; // 1..INT_MAX 34 | int64_t nlines; // 0..INT64_MAX 35 | block_t *block; // != NULL 36 | } buffer_t; 37 | 38 | typedef struct { 39 | int blk; // 0..INT_MAX 40 | int off; // 0..BLOCK_SIZE 41 | } address_t; 42 | 43 | // for undo use buffer wide offsets 44 | typedef struct { 45 | address_t start; 46 | address_t end; 47 | } range_t; 48 | 49 | void* 50 | xreallocarray(void *optr, size_t nmemb, size_t elem_size) 51 | { 52 | // FIXME: overflow check 53 | void *ptr = realloc(optr, nmemb*elem_size); 54 | if(ptr == NULL) { 55 | abort(); 56 | } 57 | return ptr; 58 | } 59 | 60 | void * 61 | blockmove(block_t *dest, const block_t *src, size_t n) 62 | { 63 | memmove(dest, src, n * sizeof(src[0])); 64 | return dest; 65 | } 66 | 67 | static int 68 | block_append(block_t *blk, int nblk, const int maxblk, char *buf, int len /* 0..BLOCK_SIZE */) 69 | { 70 | assert(nblk <= maxblk); 71 | assert(maxblk > 0); 72 | assert(len >= 0); 73 | assert(len <= BLOCK_SIZE); 74 | 75 | if(nblk <= 0) { 76 | nblk = 1; 77 | } 78 | 79 | if(len == 0) { 80 | return nblk; 81 | } 82 | 83 | int capacity = BLOCK_SIZE - blk[nblk-1].len; 84 | if(nblk < maxblk) { 85 | capacity += BLOCK_SIZE; 86 | } 87 | 88 | if(capacity < len) { 89 | return -1; 90 | } 91 | 92 | capacity = BLOCK_SIZE - blk[nblk-1].len; 93 | int head_len = MIN(capacity, len); 94 | memcpy(&blk[nblk-1].p->buf[blk[nblk-1].len], buf, head_len); 95 | blk[nblk-1].len += head_len; 96 | 97 | capacity = BLOCK_SIZE; 98 | int tail_len = len - head_len; 99 | if(tail_len > 0) { 100 | nblk++; 101 | memcpy(blk[nblk-1].p->buf, &buf[head_len], tail_len); 102 | blk[nblk-1].len = tail_len; 103 | } 104 | 105 | return nblk; 106 | } 107 | 108 | int 109 | TEST_block_append(void) 110 | { 111 | char call[BUFSIZ]; 112 | char buf0[] = "abc"; 113 | char buf1[] = "xyz"; 114 | char bufR[] = "abcxyz"; 115 | char bufB[] = 116 | "01 456789012345678901234567890123456789012345678901234567890123\n" 117 | "02 456789012345678901234567890123456789012345678901234567890123\n" 118 | "03 456789012345678901234567890123456789012345678901234567890123\n" 119 | "04 456789012345678901234567890123456789012345678901234567890123\n" 120 | "05 456789012345678901234567890123456789012345678901234567890123\n" 121 | "06 456789012345678901234567890123456789012345678901234567890123\n" 122 | "07 456789012345678901234567890123456789012345678901234567890123\n" 123 | "08 456789012345678901234567890123456789012345678901234567890123\n" 124 | "09 456789012345678901234567890123456789012345678901234567890123\n" 125 | "10 456789012345678901234567890123456789012345678901234567890123\n" 126 | "11 456789012345678901234567890123456789012345678901234567890123\n" 127 | "12 456789012345678901234567890123456789012345678901234567890123\n" 128 | "13 456789012345678901234567890123456789012345678901234567890123\n" 129 | "14 456789012345678901234567890123456789012345678901234567890123\n" 130 | "15 456789012345678901234567890123456789012345678901234567890123\n" 131 | "16 456789012345678901234567890123456789012345678901234567890123\n" 132 | "17 456789012345678901234567890123456789012345678901234567890123\n" 133 | "18 456789012345678901234567890123456789012345678901234567890123\n" 134 | "19 456789012345678901234567890123456789012345678901234567890123\n" 135 | "20 456789012345678901234567890123456789012345678901234567890123\n" 136 | "21 456789012345678901234567890123456789012345678901234567890123\n" 137 | "22 456789012345678901234567890123456789012345678901234567890123\n" 138 | "23 456789012345678901234567890123456789012345678901234567890123\n" 139 | "24 456789012345678901234567890123456789012345678901234567890123\n" 140 | "25 456789012345678901234567890123456789012345678901234567890123\n" 141 | "26 456789012345678901234567890123456789012345678901234567890123\n" 142 | "27 456789012345678901234567890123456789012345678901234567890123\n" 143 | "28 456789012345678901234567890123456789012345678901234567890123\n" 144 | "29 456789012345678901234567890123456789012345678901234567890123\n" 145 | "30 456789012345678901234567890123456789012345678901234567890123\n" 146 | "31 456789012345678901234567890123456789012345678901234567890123\n" 147 | "32 456789012345678901234567890123456789012345678901234567890123\n" 148 | "33 456789012345678901234567890123456789012345678901234567890123\n" 149 | "34 456789012345678901234567890123456789012345678901234567890123\n" 150 | "35 456789012345678901234567890123456789012345678901234567890123\n" 151 | "36 456789012345678901234567890123456789012345678901234567890123\n" 152 | "37 456789012345678901234567890123456789012345678901234567890123\n" 153 | "38 456789012345678901234567890123456789012345678901234567890123\n" 154 | "39 456789012345678901234567890123456789012345678901234567890123\n" 155 | "40 456789012345678901234567890123456789012345678901234567890123\n" 156 | "41 456789012345678901234567890123456789012345678901234567890123\n" 157 | "42 456789012345678901234567890123456789012345678901234567890123\n" 158 | "43 456789012345678901234567890123456789012345678901234567890123\n" 159 | "44 456789012345678901234567890123456789012345678901234567890123\n" 160 | "45 456789012345678901234567890123456789012345678901234567890123\n" 161 | "46 456789012345678901234567890123456789012345678901234567890123\n" 162 | "47 456789012345678901234567890123456789012345678901234567890123\n" 163 | "48 456789012345678901234567890123456789012345678901234567890123\n" 164 | "49 456789012345678901234567890123456789012345678901234567890123\n" 165 | "50 456789012345678901234567890123456789012345678901234567890123\n" 166 | "51 456789012345678901234567890123456789012345678901234567890123\n" 167 | "52 456789012345678901234567890123456789012345678901234567890123\n" 168 | "53 456789012345678901234567890123456789012345678901234567890123\n" 169 | "54 456789012345678901234567890123456789012345678901234567890123\n" 170 | "55 456789012345678901234567890123456789012345678901234567890123\n" 171 | "56 456789012345678901234567890123456789012345678901234567890123\n" 172 | "57 456789012345678901234567890123456789012345678901234567890123\n" 173 | "58 456789012345678901234567890123456789012345678901234567890123\n" 174 | "59 456789012345678901234567890123456789012345678901234567890123\n" 175 | "60 456789012345678901234567890123456789012345678901234567890123\n" 176 | "61 456789012345678901234567890123456789012345678901234567890123\n" 177 | "62 456789012345678901234567890123456789012345678901234567890123\n" 178 | "63 456789012345678901234567890123456789012345678901234567890123\n" 179 | "64 456789012345678901234567890123456789012345678901234567890123\n"; 180 | char bufBR0[] = 181 | "abcxyz" 182 | "01 456789012345678901234567890123456789012345678901234567890123\n" 183 | "02 456789012345678901234567890123456789012345678901234567890123\n" 184 | "03 456789012345678901234567890123456789012345678901234567890123\n" 185 | "04 456789012345678901234567890123456789012345678901234567890123\n" 186 | "05 456789012345678901234567890123456789012345678901234567890123\n" 187 | "06 456789012345678901234567890123456789012345678901234567890123\n" 188 | "07 456789012345678901234567890123456789012345678901234567890123\n" 189 | "08 456789012345678901234567890123456789012345678901234567890123\n" 190 | "09 456789012345678901234567890123456789012345678901234567890123\n" 191 | "10 456789012345678901234567890123456789012345678901234567890123\n" 192 | "11 456789012345678901234567890123456789012345678901234567890123\n" 193 | "12 456789012345678901234567890123456789012345678901234567890123\n" 194 | "13 456789012345678901234567890123456789012345678901234567890123\n" 195 | "14 456789012345678901234567890123456789012345678901234567890123\n" 196 | "15 456789012345678901234567890123456789012345678901234567890123\n" 197 | "16 456789012345678901234567890123456789012345678901234567890123\n" 198 | "17 456789012345678901234567890123456789012345678901234567890123\n" 199 | "18 456789012345678901234567890123456789012345678901234567890123\n" 200 | "19 456789012345678901234567890123456789012345678901234567890123\n" 201 | "20 456789012345678901234567890123456789012345678901234567890123\n" 202 | "21 456789012345678901234567890123456789012345678901234567890123\n" 203 | "22 456789012345678901234567890123456789012345678901234567890123\n" 204 | "23 456789012345678901234567890123456789012345678901234567890123\n" 205 | "24 456789012345678901234567890123456789012345678901234567890123\n" 206 | "25 456789012345678901234567890123456789012345678901234567890123\n" 207 | "26 456789012345678901234567890123456789012345678901234567890123\n" 208 | "27 456789012345678901234567890123456789012345678901234567890123\n" 209 | "28 456789012345678901234567890123456789012345678901234567890123\n" 210 | "29 456789012345678901234567890123456789012345678901234567890123\n" 211 | "30 456789012345678901234567890123456789012345678901234567890123\n" 212 | "31 456789012345678901234567890123456789012345678901234567890123\n" 213 | "32 456789012345678901234567890123456789012345678901234567890123\n" 214 | "33 456789012345678901234567890123456789012345678901234567890123\n" 215 | "34 456789012345678901234567890123456789012345678901234567890123\n" 216 | "35 456789012345678901234567890123456789012345678901234567890123\n" 217 | "36 456789012345678901234567890123456789012345678901234567890123\n" 218 | "37 456789012345678901234567890123456789012345678901234567890123\n" 219 | "38 456789012345678901234567890123456789012345678901234567890123\n" 220 | "39 456789012345678901234567890123456789012345678901234567890123\n" 221 | "40 456789012345678901234567890123456789012345678901234567890123\n" 222 | "41 456789012345678901234567890123456789012345678901234567890123\n" 223 | "42 456789012345678901234567890123456789012345678901234567890123\n" 224 | "43 456789012345678901234567890123456789012345678901234567890123\n" 225 | "44 456789012345678901234567890123456789012345678901234567890123\n" 226 | "45 456789012345678901234567890123456789012345678901234567890123\n" 227 | "46 456789012345678901234567890123456789012345678901234567890123\n" 228 | "47 456789012345678901234567890123456789012345678901234567890123\n" 229 | "48 456789012345678901234567890123456789012345678901234567890123\n" 230 | "49 456789012345678901234567890123456789012345678901234567890123\n" 231 | "50 456789012345678901234567890123456789012345678901234567890123\n" 232 | "51 456789012345678901234567890123456789012345678901234567890123\n" 233 | "52 456789012345678901234567890123456789012345678901234567890123\n" 234 | "53 456789012345678901234567890123456789012345678901234567890123\n" 235 | "54 456789012345678901234567890123456789012345678901234567890123\n" 236 | "55 456789012345678901234567890123456789012345678901234567890123\n" 237 | "56 456789012345678901234567890123456789012345678901234567890123\n" 238 | "57 456789012345678901234567890123456789012345678901234567890123\n" 239 | "58 456789012345678901234567890123456789012345678901234567890123\n" 240 | "59 456789012345678901234567890123456789012345678901234567890123\n" 241 | "60 456789012345678901234567890123456789012345678901234567890123\n" 242 | "61 456789012345678901234567890123456789012345678901234567890123\n" 243 | "62 456789012345678901234567890123456789012345678901234567890123\n" 244 | "63 456789012345678901234567890123456789012345678901234567890123\n" 245 | "64 4567890123456789012345678901234567890123456789012345678"; 246 | char bufBR1[] = "90123\n"; 247 | int ret; 248 | 249 | struct blockbuf blkbuf0; 250 | struct blockbuf blkbuf1; 251 | block_t blk[2] = {{.p = &blkbuf0}, {.p = &blkbuf1}}; 252 | 253 | ret = TEST_CALL(call, sizeof(call), "%p, %d, %d, \"%s\", %d", 254 | block_append, ((void*)blk, 0, (int)LEN(blk), buf0, (int)sizeof(buf0)-1)); 255 | TEST_OP("%d", ret, ==, 1, "%s", call); 256 | TEST_OP("%d", blk[0].len, ==, (int)sizeof(buf0)-1, "%s", call); 257 | TEST_MEMCMP_OP(blkbuf0.buf, ==, buf0, sizeof(buf0)-1, "%s", call); 258 | TEST_OP("%d", blk[1].len, ==, 0, "%s", call); 259 | 260 | ret = TEST_CALL(call, sizeof(call), "%p, %d, %d, \"%s\", %d", 261 | block_append, ((void*)blk, ret, (int)LEN(blk), buf1, (int)sizeof(buf1)-1)); 262 | TEST_OP("%d", ret, ==, 1, "%s", call); 263 | TEST_OP("%d", blk[0].len, ==, (int)sizeof(bufR)-1, "%s", call); 264 | TEST_MEMCMP_OP(blkbuf0.buf, ==, bufR, sizeof(bufR)-1, "%s", call); 265 | TEST_OP("%d", blk[1].len, ==, 0, "%s", call); 266 | 267 | ret = TEST_CALL(call, sizeof(call), "%p, %d, %d, \"%s\", %d", 268 | block_append, ((void*)blk, ret, (int)LEN(blk), bufB, (int)sizeof(bufB)-1)); 269 | TEST_OP("%d", ret, ==, 2, "%s", call); 270 | TEST_OP("%d", blk[0].len, ==, BLOCK_SIZE, "%s", call); 271 | TEST_MEMCMP_OP(blkbuf0.buf, ==, bufBR0, (int)sizeof(bufBR0)-1, "%s", call); 272 | TEST_OP("%d", blk[1].len, ==, (int)sizeof(bufBR1)-1, "%s", call); 273 | TEST_MEMCMP_OP(blkbuf1.buf, ==, bufBR1, blk[1].len, "%s", call); 274 | 275 | return 0; 276 | } 277 | 278 | void 279 | buffer_init(buffer_t *buffer, int nblocks) 280 | { 281 | // FIXME: check for NULL / xrealloc 282 | buffer->block = xcalloc(nblocks, sizeof(*buffer->block)); 283 | for(int i = 0; i < nblocks; i++) { 284 | // FIXME: check for NULL / xmalloc 285 | buffer->block[i].p = xmalloc(1, BLOCK_SIZE); 286 | } 287 | buffer->nlines = 0; 288 | buffer->nblocks = nblocks; 289 | } 290 | 291 | void 292 | buffer_free(buffer_t *buffer) 293 | { 294 | for(int i = 0; i < buffer->nblocks; i++) { 295 | free(buffer->block[i].p); 296 | } 297 | free(buffer->block); 298 | } 299 | 300 | #define LEN_TO_NBLOCKS(x) (((x) + BLOCK_SIZE - 1) / BLOCK_SIZE) 301 | 302 | int buffer_read_blocks(buffer_t *buffer, range_t *rng, block_t *blk, int nblk, int maxblk, int len); 303 | 304 | int 305 | buffer_read(buffer_t *buffer, range_t *rng, char *mod, int len /* 0..BLOCK_SIZE */) 306 | { 307 | // one extra block may be needed for the tail of the selection 308 | block_t blk[4]; 309 | 310 | for(unsigned i = 0; i < LEN(blk); i++) { 311 | blk[i].p = xmalloc(1, BLOCK_SIZE); 312 | blk[i].len = 0; 313 | blk[i].nlines = 0; 314 | } 315 | 316 | // headSEL SEL SELtail 317 | // copy the head of the first selected block 318 | int nblk = block_append(blk, 1, LEN(blk), buffer->block[rng->start.blk].p->buf, rng->start.off); 319 | 320 | nblk = block_append(blk, nblk, LEN(blk), mod, len); 321 | 322 | return buffer_read_blocks(buffer, rng, blk, nblk, LEN(blk), len); 323 | } 324 | 325 | int 326 | TEST_buffer_read(void) 327 | { 328 | char call[BUFSIZ]; 329 | buffer_t buffer = {0}; 330 | range_t range = {0}; 331 | int ret; 332 | 333 | buffer_init(&buffer, 1); 334 | 335 | { 336 | char mod[] = "abc"; 337 | 338 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 339 | buffer_read, ((void*)&buffer, (void*)&range, mod, sizeof(mod)-1)); 340 | TEST_OP("%d", ret, ==, (int)sizeof(mod)-1, call); 341 | TEST_OP("%d", buffer.nblocks, ==, 1, "%s", call); 342 | TEST_OP("%d", buffer.block[0].len, ==, (int)sizeof(mod)-1, "%s", call); 343 | TEST_OP("%d", buffer.block[0].nlines, ==, 0, "%s", call); 344 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, mod, sizeof(mod)-1, "%s", call); 345 | TEST_OP("%d", range.start.blk, ==, 0, "%s", call); 346 | TEST_OP("%d", range.start.off, ==, 0, "%s", call); 347 | TEST_OP("%d", range.end.blk, ==, 0, "%s", call); 348 | TEST_OP("%d", range.end.off, ==, (int)sizeof(mod)-1, "%s", call); 349 | } 350 | 351 | { 352 | /* replace */ 353 | char mod[] = "\nxyz\n"; 354 | 355 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 356 | buffer_read, ((void*)&buffer, (void*)&range, mod, sizeof(mod)-1)); 357 | TEST_OP("%d", ret, ==, (int)sizeof(mod)-1, call); 358 | TEST_OP("%d", buffer.nblocks, ==, 1, "%s", call); 359 | TEST_OP("%d", buffer.block[0].len, ==, (int)sizeof(mod)-1, "%s", call); 360 | TEST_OP("%d", buffer.block[0].nlines, ==, 2, "%s", call); 361 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, mod, sizeof(mod)-1, "%s", call); 362 | TEST_OP("%d", range.start.blk, ==, 0, "%s", call); 363 | TEST_OP("%d", range.start.off, ==, 0, "%s", call); 364 | TEST_OP("%d", range.end.blk, ==, 0, "%s", call); 365 | TEST_OP("%d", range.end.off, ==, (int)sizeof(mod)-1, "%s", call); 366 | } 367 | 368 | { 369 | /* append */ 370 | char mod[] = "dapo\n"; 371 | char blk0[] = "\nxyz\ndapo\n"; 372 | 373 | range.start = range.end; 374 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 375 | buffer_read, ((void*)&buffer, (void*)&range, mod, sizeof(mod)-1)); 376 | TEST_OP("%d", ret, ==, (int)sizeof(mod)-1, call); 377 | TEST_OP("%d", buffer.nblocks, ==, 1, "%s", call); 378 | TEST_OP("%d", buffer.block[0].len, ==, (int)sizeof(blk0)-1, "%s", call); 379 | TEST_OP("%d", buffer.block[0].nlines, ==, 3, "%s", call); 380 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, blk0, sizeof(blk0)-1, "%s", call); 381 | TEST_OP("%d", range.start.blk, ==, 0, "%s", call); 382 | TEST_OP("%d", range.start.off, ==, 5, "%s", call); 383 | TEST_OP("%d", range.end.blk, ==, 0, "%s", call); 384 | TEST_OP("%d", range.end.off, ==, (int)sizeof(blk0)-1, "%s", call); 385 | } 386 | 387 | { 388 | /* replace in the middle */ 389 | char mod[] = "rub"; 390 | char blk0[] = "\nxyrubo\n"; 391 | 392 | range.start.off = 3; 393 | range.end.off = 8; 394 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 395 | buffer_read, ((void*)&buffer, (void*)&range, mod, sizeof(mod)-1)); 396 | TEST_OP("%d", ret, ==, (int)sizeof(mod)-1, call); 397 | TEST_OP("%d", buffer.nblocks, ==, 1, "%s", call); 398 | TEST_OP("%d", buffer.block[0].len, ==, (int)sizeof(blk0)-1, "%s", call); 399 | TEST_OP("%d", buffer.block[0].nlines, ==, 2, "%s", call); 400 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, blk0, sizeof(blk0)-1, "%s", call); 401 | TEST_OP("%d", range.start.blk, ==, 0, "%s", call); 402 | TEST_OP("%d", range.start.off, ==, 3, "%s", call); 403 | TEST_OP("%d", range.end.blk, ==, 0, "%s", call); 404 | TEST_OP("%d", range.end.off, ==, 3+(int)sizeof(mod)-1, "%s", call); 405 | 406 | range.end.off = sizeof(blk0)-1; 407 | } 408 | 409 | { 410 | /* append block sized mod */ 411 | char mod[] = 412 | "01 456789012345678901234567890123456789012345678901234567890123\n" 413 | "02 456789012345678901234567890123456789012345678901234567890123\n" 414 | "03 456789012345678901234567890123456789012345678901234567890123\n" 415 | "04 456789012345678901234567890123456789012345678901234567890123\n" 416 | "05 456789012345678901234567890123456789012345678901234567890123\n" 417 | "06 456789012345678901234567890123456789012345678901234567890123\n" 418 | "07 456789012345678901234567890123456789012345678901234567890123\n" 419 | "08 456789012345678901234567890123456789012345678901234567890123\n" 420 | "09 456789012345678901234567890123456789012345678901234567890123\n" 421 | "10 456789012345678901234567890123456789012345678901234567890123\n" 422 | "11 456789012345678901234567890123456789012345678901234567890123\n" 423 | "12 456789012345678901234567890123456789012345678901234567890123\n" 424 | "13 456789012345678901234567890123456789012345678901234567890123\n" 425 | "14 456789012345678901234567890123456789012345678901234567890123\n" 426 | "15 456789012345678901234567890123456789012345678901234567890123\n" 427 | "16 456789012345678901234567890123456789012345678901234567890123\n" 428 | "17 456789012345678901234567890123456789012345678901234567890123\n" 429 | "18 456789012345678901234567890123456789012345678901234567890123\n" 430 | "19 456789012345678901234567890123456789012345678901234567890123\n" 431 | "20 456789012345678901234567890123456789012345678901234567890123\n" 432 | "21 456789012345678901234567890123456789012345678901234567890123\n" 433 | "22 456789012345678901234567890123456789012345678901234567890123\n" 434 | "23 456789012345678901234567890123456789012345678901234567890123\n" 435 | "24 456789012345678901234567890123456789012345678901234567890123\n" 436 | "25 456789012345678901234567890123456789012345678901234567890123\n" 437 | "26 456789012345678901234567890123456789012345678901234567890123\n" 438 | "27 456789012345678901234567890123456789012345678901234567890123\n" 439 | "28 456789012345678901234567890123456789012345678901234567890123\n" 440 | "29 456789012345678901234567890123456789012345678901234567890123\n" 441 | "30 456789012345678901234567890123456789012345678901234567890123\n" 442 | "31 456789012345678901234567890123456789012345678901234567890123\n" 443 | "32 456789012345678901234567890123456789012345678901234567890123\n" 444 | "33 456789012345678901234567890123456789012345678901234567890123\n" 445 | "34 456789012345678901234567890123456789012345678901234567890123\n" 446 | "35 456789012345678901234567890123456789012345678901234567890123\n" 447 | "36 456789012345678901234567890123456789012345678901234567890123\n" 448 | "37 456789012345678901234567890123456789012345678901234567890123\n" 449 | "38 456789012345678901234567890123456789012345678901234567890123\n" 450 | "39 456789012345678901234567890123456789012345678901234567890123\n" 451 | "40 456789012345678901234567890123456789012345678901234567890123\n" 452 | "41 456789012345678901234567890123456789012345678901234567890123\n" 453 | "42 456789012345678901234567890123456789012345678901234567890123\n" 454 | "43 456789012345678901234567890123456789012345678901234567890123\n" 455 | "44 456789012345678901234567890123456789012345678901234567890123\n" 456 | "45 456789012345678901234567890123456789012345678901234567890123\n" 457 | "46 456789012345678901234567890123456789012345678901234567890123\n" 458 | "47 456789012345678901234567890123456789012345678901234567890123\n" 459 | "48 456789012345678901234567890123456789012345678901234567890123\n" 460 | "49 456789012345678901234567890123456789012345678901234567890123\n" 461 | "50 456789012345678901234567890123456789012345678901234567890123\n" 462 | "51 456789012345678901234567890123456789012345678901234567890123\n" 463 | "52 456789012345678901234567890123456789012345678901234567890123\n" 464 | "53 456789012345678901234567890123456789012345678901234567890123\n" 465 | "54 456789012345678901234567890123456789012345678901234567890123\n" 466 | "55 456789012345678901234567890123456789012345678901234567890123\n" 467 | "56 456789012345678901234567890123456789012345678901234567890123\n" 468 | "57 456789012345678901234567890123456789012345678901234567890123\n" 469 | "58 456789012345678901234567890123456789012345678901234567890123\n" 470 | "59 456789012345678901234567890123456789012345678901234567890123\n" 471 | "60 456789012345678901234567890123456789012345678901234567890123\n" 472 | "61 456789012345678901234567890123456789012345678901234567890123\n" 473 | "62 456789012345678901234567890123456789012345678901234567890123\n" 474 | "63 456789012345678901234567890123456789012345678901234567890123\n" 475 | "64 456789012345678901234567890123456789012345678901234567890123\n"; 476 | char blk0[] = 477 | "\nxyrubo\n" 478 | "01 456789012345678901234567890123456789012345678901234567890123\n" 479 | "02 456789012345678901234567890123456789012345678901234567890123\n" 480 | "03 456789012345678901234567890123456789012345678901234567890123\n" 481 | "04 456789012345678901234567890123456789012345678901234567890123\n" 482 | "05 456789012345678901234567890123456789012345678901234567890123\n" 483 | "06 456789012345678901234567890123456789012345678901234567890123\n" 484 | "07 456789012345678901234567890123456789012345678901234567890123\n" 485 | "08 456789012345678901234567890123456789012345678901234567890123\n" 486 | "09 456789012345678901234567890123456789012345678901234567890123\n" 487 | "10 456789012345678901234567890123456789012345678901234567890123\n" 488 | "11 456789012345678901234567890123456789012345678901234567890123\n" 489 | "12 456789012345678901234567890123456789012345678901234567890123\n" 490 | "13 456789012345678901234567890123456789012345678901234567890123\n" 491 | "14 456789012345678901234567890123456789012345678901234567890123\n" 492 | "15 456789012345678901234567890123456789012345678901234567890123\n" 493 | "16 456789012345678901234567890123456789012345678901234567890123\n" 494 | "17 456789012345678901234567890123456789012345678901234567890123\n" 495 | "18 456789012345678901234567890123456789012345678901234567890123\n" 496 | "19 456789012345678901234567890123456789012345678901234567890123\n" 497 | "20 456789012345678901234567890123456789012345678901234567890123\n" 498 | "21 456789012345678901234567890123456789012345678901234567890123\n" 499 | "22 456789012345678901234567890123456789012345678901234567890123\n" 500 | "23 456789012345678901234567890123456789012345678901234567890123\n" 501 | "24 456789012345678901234567890123456789012345678901234567890123\n" 502 | "25 456789012345678901234567890123456789012345678901234567890123\n" 503 | "26 456789012345678901234567890123456789012345678901234567890123\n" 504 | "27 456789012345678901234567890123456789012345678901234567890123\n" 505 | "28 456789012345678901234567890123456789012345678901234567890123\n" 506 | "29 456789012345678901234567890123456789012345678901234567890123\n" 507 | "30 456789012345678901234567890123456789012345678901234567890123\n" 508 | "31 456789012345678901234567890123456789012345678901234567890123\n" 509 | "32 456789012345678901234567890123456789012345678901234567890123\n" 510 | "33 456789012345678901234567890123456789012345678901234567890123\n" 511 | "34 456789012345678901234567890123456789012345678901234567890123\n" 512 | "35 456789012345678901234567890123456789012345678901234567890123\n" 513 | "36 456789012345678901234567890123456789012345678901234567890123\n" 514 | "37 456789012345678901234567890123456789012345678901234567890123\n" 515 | "38 456789012345678901234567890123456789012345678901234567890123\n" 516 | "39 456789012345678901234567890123456789012345678901234567890123\n" 517 | "40 456789012345678901234567890123456789012345678901234567890123\n" 518 | "41 456789012345678901234567890123456789012345678901234567890123\n" 519 | "42 456789012345678901234567890123456789012345678901234567890123\n" 520 | "43 456789012345678901234567890123456789012345678901234567890123\n" 521 | "44 456789012345678901234567890123456789012345678901234567890123\n" 522 | "45 456789012345678901234567890123456789012345678901234567890123\n" 523 | "46 456789012345678901234567890123456789012345678901234567890123\n" 524 | "47 456789012345678901234567890123456789012345678901234567890123\n" 525 | "48 456789012345678901234567890123456789012345678901234567890123\n" 526 | "49 456789012345678901234567890123456789012345678901234567890123\n" 527 | "50 456789012345678901234567890123456789012345678901234567890123\n" 528 | "51 456789012345678901234567890123456789012345678901234567890123\n" 529 | "52 456789012345678901234567890123456789012345678901234567890123\n" 530 | "53 456789012345678901234567890123456789012345678901234567890123\n" 531 | "54 456789012345678901234567890123456789012345678901234567890123\n" 532 | "55 456789012345678901234567890123456789012345678901234567890123\n" 533 | "56 456789012345678901234567890123456789012345678901234567890123\n" 534 | "57 456789012345678901234567890123456789012345678901234567890123\n" 535 | "58 456789012345678901234567890123456789012345678901234567890123\n" 536 | "59 456789012345678901234567890123456789012345678901234567890123\n" 537 | "60 456789012345678901234567890123456789012345678901234567890123\n" 538 | "61 456789012345678901234567890123456789012345678901234567890123\n" 539 | "62 456789012345678901234567890123456789012345678901234567890123\n" 540 | "63 456789012345678901234567890123456789012345678901234567890123\n" 541 | "64 45678901234567890123456789012345678901234567890123456"; 542 | char blk1[] = "7890123\n"; 543 | 544 | range.start = range.end; 545 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 546 | buffer_read, ((void*)&buffer, (void*)&range, mod, sizeof(mod)-1)); 547 | TEST_OP("%d", ret, ==, (int)sizeof(mod)-1, call); 548 | TEST_OP("%d", buffer.nblocks, ==, 2, "%s", call); 549 | TEST_OP("%d", buffer.block[0].len, ==, BLOCK_SIZE, "%s", call); 550 | TEST_OP("%d", buffer.block[0].nlines, ==, 65, "%s", call); 551 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, blk0, BLOCK_SIZE, "%s", call); 552 | TEST_OP("%d", buffer.block[1].len, ==, (int)sizeof(blk1)-1, "%s", call); 553 | TEST_OP("%d", buffer.block[1].nlines, ==, 1, "%s", call); 554 | TEST_MEMCMP_OP(buffer.block[1].p->buf, ==, blk1, buffer.block[1].len, "%s", call); 555 | TEST_OP("%d", range.start.blk, ==, 0, "%s", call); 556 | TEST_OP("%d", range.start.off, ==, 8, "%s", call); 557 | TEST_OP("%d", range.end.blk, ==, 1, "%s", call); 558 | TEST_OP("%d", range.end.off, ==, buffer.block[1].len, "%s", call); 559 | } 560 | 561 | { 562 | /* replace on the block boundary */ 563 | char mod[] = "fg"; 564 | char blk0[] = 565 | "\nxyrubo\n" 566 | "01 456789012345678901234567890123456789012345678901234567890123\n" 567 | "02 456789012345678901234567890123456789012345678901234567890123\n" 568 | "03 456789012345678901234567890123456789012345678901234567890123\n" 569 | "04 456789012345678901234567890123456789012345678901234567890123\n" 570 | "05 456789012345678901234567890123456789012345678901234567890123\n" 571 | "06 456789012345678901234567890123456789012345678901234567890123\n" 572 | "07 456789012345678901234567890123456789012345678901234567890123\n" 573 | "08 456789012345678901234567890123456789012345678901234567890123\n" 574 | "09 456789012345678901234567890123456789012345678901234567890123\n" 575 | "10 456789012345678901234567890123456789012345678901234567890123\n" 576 | "11 456789012345678901234567890123456789012345678901234567890123\n" 577 | "12 456789012345678901234567890123456789012345678901234567890123\n" 578 | "13 456789012345678901234567890123456789012345678901234567890123\n" 579 | "14 456789012345678901234567890123456789012345678901234567890123\n" 580 | "15 456789012345678901234567890123456789012345678901234567890123\n" 581 | "16 456789012345678901234567890123456789012345678901234567890123\n" 582 | "17 456789012345678901234567890123456789012345678901234567890123\n" 583 | "18 456789012345678901234567890123456789012345678901234567890123\n" 584 | "19 456789012345678901234567890123456789012345678901234567890123\n" 585 | "20 456789012345678901234567890123456789012345678901234567890123\n" 586 | "21 456789012345678901234567890123456789012345678901234567890123\n" 587 | "22 456789012345678901234567890123456789012345678901234567890123\n" 588 | "23 456789012345678901234567890123456789012345678901234567890123\n" 589 | "24 456789012345678901234567890123456789012345678901234567890123\n" 590 | "25 456789012345678901234567890123456789012345678901234567890123\n" 591 | "26 456789012345678901234567890123456789012345678901234567890123\n" 592 | "27 456789012345678901234567890123456789012345678901234567890123\n" 593 | "28 456789012345678901234567890123456789012345678901234567890123\n" 594 | "29 456789012345678901234567890123456789012345678901234567890123\n" 595 | "30 456789012345678901234567890123456789012345678901234567890123\n" 596 | "31 456789012345678901234567890123456789012345678901234567890123\n" 597 | "32 456789012345678901234567890123456789012345678901234567890123\n" 598 | "33 456789012345678901234567890123456789012345678901234567890123\n" 599 | "34 456789012345678901234567890123456789012345678901234567890123\n" 600 | "35 456789012345678901234567890123456789012345678901234567890123\n" 601 | "36 456789012345678901234567890123456789012345678901234567890123\n" 602 | "37 456789012345678901234567890123456789012345678901234567890123\n" 603 | "38 456789012345678901234567890123456789012345678901234567890123\n" 604 | "39 456789012345678901234567890123456789012345678901234567890123\n" 605 | "40 456789012345678901234567890123456789012345678901234567890123\n" 606 | "41 456789012345678901234567890123456789012345678901234567890123\n" 607 | "42 456789012345678901234567890123456789012345678901234567890123\n" 608 | "43 456789012345678901234567890123456789012345678901234567890123\n" 609 | "44 456789012345678901234567890123456789012345678901234567890123\n" 610 | "45 456789012345678901234567890123456789012345678901234567890123\n" 611 | "46 456789012345678901234567890123456789012345678901234567890123\n" 612 | "47 456789012345678901234567890123456789012345678901234567890123\n" 613 | "48 456789012345678901234567890123456789012345678901234567890123\n" 614 | "49 456789012345678901234567890123456789012345678901234567890123\n" 615 | "50 456789012345678901234567890123456789012345678901234567890123\n" 616 | "51 456789012345678901234567890123456789012345678901234567890123\n" 617 | "52 456789012345678901234567890123456789012345678901234567890123\n" 618 | "53 456789012345678901234567890123456789012345678901234567890123\n" 619 | "54 456789012345678901234567890123456789012345678901234567890123\n" 620 | "55 456789012345678901234567890123456789012345678901234567890123\n" 621 | "56 456789012345678901234567890123456789012345678901234567890123\n" 622 | "57 456789012345678901234567890123456789012345678901234567890123\n" 623 | "58 456789012345678901234567890123456789012345678901234567890123\n" 624 | "59 456789012345678901234567890123456789012345678901234567890123\n" 625 | "60 456789012345678901234567890123456789012345678901234567890123\n" 626 | "61 456789012345678901234567890123456789012345678901234567890123\n" 627 | "62 456789012345678901234567890123456789012345678901234567890123\n" 628 | "63 456789012345678901234567890123456789012345678901234567890123\n" 629 | "64 4567890123456789012345678901234567890123456789012fg12"; 630 | char blk1[] = "3\n"; 631 | 632 | range.start.blk = 0; 633 | range.start.off = BLOCK_SIZE - 4; 634 | range.end.blk = 1; 635 | range.end.off = 4; 636 | 637 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 638 | buffer_read, ((void*)&buffer, (void*)&range, mod, sizeof(mod)-1)); 639 | TEST_OP("%d", ret, ==, (int)sizeof(mod)-1, call); 640 | TEST_OP("%d", buffer.nblocks, ==, 2, "%s", call); 641 | TEST_OP("%d", buffer.block[0].len, ==, BLOCK_SIZE, "%s", call); 642 | TEST_OP("%d", buffer.block[0].nlines, ==, 65, "%s", call); 643 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, blk0, buffer.block[0].len, "%s", call); 644 | TEST_OP("%d", buffer.block[1].len, ==, (int)sizeof(blk1)-1, "%s", call); 645 | TEST_OP("%d", buffer.block[1].nlines, ==, 1, "%s", call); 646 | TEST_MEMCMP_OP(buffer.block[1].p->buf, ==, blk1, buffer.block[1].len, "%s", call); 647 | TEST_OP("%d", range.start.blk, ==, 0, "%s", call); 648 | TEST_OP("%d", range.start.off, ==, BLOCK_SIZE - 4, "%s", call); 649 | TEST_OP("%d", range.end.blk, ==, 0, "%s", call); 650 | TEST_OP("%d", range.end.off, ==, range.start.off + (int)sizeof(mod)-1, "%s", call); 651 | } 652 | 653 | { 654 | /* join next block */ 655 | char mod[] = "qwerty"; 656 | char blk0[] = 657 | "\nxyrubo\n" 658 | "01 4qwerty2345678901234567890123456789012345678901234567890123\n" 659 | "34 456789012345678901234567890123456789012345678901234567890123\n" 660 | "35 456789012345678901234567890123456789012345678901234567890123\n" 661 | "36 456789012345678901234567890123456789012345678901234567890123\n" 662 | "37 456789012345678901234567890123456789012345678901234567890123\n" 663 | "38 456789012345678901234567890123456789012345678901234567890123\n" 664 | "39 456789012345678901234567890123456789012345678901234567890123\n" 665 | "40 456789012345678901234567890123456789012345678901234567890123\n" 666 | "41 456789012345678901234567890123456789012345678901234567890123\n" 667 | "42 456789012345678901234567890123456789012345678901234567890123\n" 668 | "43 456789012345678901234567890123456789012345678901234567890123\n" 669 | "44 456789012345678901234567890123456789012345678901234567890123\n" 670 | "45 456789012345678901234567890123456789012345678901234567890123\n" 671 | "46 456789012345678901234567890123456789012345678901234567890123\n" 672 | "47 456789012345678901234567890123456789012345678901234567890123\n" 673 | "48 456789012345678901234567890123456789012345678901234567890123\n" 674 | "49 456789012345678901234567890123456789012345678901234567890123\n" 675 | "50 456789012345678901234567890123456789012345678901234567890123\n" 676 | "51 456789012345678901234567890123456789012345678901234567890123\n" 677 | "52 456789012345678901234567890123456789012345678901234567890123\n" 678 | "53 456789012345678901234567890123456789012345678901234567890123\n" 679 | "54 456789012345678901234567890123456789012345678901234567890123\n" 680 | "55 456789012345678901234567890123456789012345678901234567890123\n" 681 | "56 456789012345678901234567890123456789012345678901234567890123\n" 682 | "57 456789012345678901234567890123456789012345678901234567890123\n" 683 | "58 456789012345678901234567890123456789012345678901234567890123\n" 684 | "59 456789012345678901234567890123456789012345678901234567890123\n" 685 | "60 456789012345678901234567890123456789012345678901234567890123\n" 686 | "61 456789012345678901234567890123456789012345678901234567890123\n" 687 | "62 456789012345678901234567890123456789012345678901234567890123\n" 688 | "63 456789012345678901234567890123456789012345678901234567890123\n" 689 | "64 4567890123456789012345678901234567890123456789012fg123\n"; 690 | 691 | range.start.blk = 0; 692 | range.start.off = 12; 693 | range.end.blk = 0; 694 | range.end.off = range.start.off + sizeof(mod)-1 + BLOCK_SIZE/2 + 1; 695 | 696 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 697 | buffer_read, ((void*)&buffer, (void*)&range, mod, sizeof(mod)-1)); 698 | TEST_OP("%d", ret, ==, (int)sizeof(mod)-1, call); 699 | TEST_OP("%d", buffer.nblocks, ==, 1, "%s", call); 700 | TEST_OP("%d", buffer.block[0].len, ==, (int)sizeof(blk0)-1, "%s", call); 701 | TEST_OP("%d", buffer.block[0].nlines, ==, 34, "%s", call); 702 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, blk0, buffer.block[0].len, "%s", call); 703 | TEST_OP("%d", range.start.blk, ==, 0, "%s", call); 704 | TEST_OP("%d", range.start.off, ==, 12, "%s", call); 705 | TEST_OP("%d", range.end.blk, ==, 0, "%s", call); 706 | TEST_OP("%d", range.end.off, ==, range.start.off + (int)sizeof(mod)-1, "%s", call); 707 | } 708 | 709 | { 710 | /* replace with a block sized mod */ 711 | char mod[] = 712 | "01 456789012345678901234567890123456789012345678901234567890123\n" 713 | "02 456789012345678901234567890123456789012345678901234567890123\n" 714 | "03 456789012345678901234567890123456789012345678901234567890123\n" 715 | "04 456789012345678901234567890123456789012345678901234567890123\n" 716 | "05 456789012345678901234567890123456789012345678901234567890123\n" 717 | "06 456789012345678901234567890123456789012345678901234567890123\n" 718 | "07 456789012345678901234567890123456789012345678901234567890123\n" 719 | "08 456789012345678901234567890123456789012345678901234567890123\n" 720 | "09 456789012345678901234567890123456789012345678901234567890123\n" 721 | "10 456789012345678901234567890123456789012345678901234567890123\n" 722 | "11 456789012345678901234567890123456789012345678901234567890123\n" 723 | "12 456789012345678901234567890123456789012345678901234567890123\n" 724 | "13 456789012345678901234567890123456789012345678901234567890123\n" 725 | "14 456789012345678901234567890123456789012345678901234567890123\n" 726 | "15 456789012345678901234567890123456789012345678901234567890123\n" 727 | "16 456789012345678901234567890123456789012345678901234567890123\n" 728 | "17 456789012345678901234567890123456789012345678901234567890123\n" 729 | "18 456789012345678901234567890123456789012345678901234567890123\n" 730 | "19 456789012345678901234567890123456789012345678901234567890123\n" 731 | "20 456789012345678901234567890123456789012345678901234567890123\n" 732 | "21 456789012345678901234567890123456789012345678901234567890123\n" 733 | "22 456789012345678901234567890123456789012345678901234567890123\n" 734 | "23 456789012345678901234567890123456789012345678901234567890123\n" 735 | "24 456789012345678901234567890123456789012345678901234567890123\n" 736 | "25 456789012345678901234567890123456789012345678901234567890123\n" 737 | "26 456789012345678901234567890123456789012345678901234567890123\n" 738 | "27 456789012345678901234567890123456789012345678901234567890123\n" 739 | "28 456789012345678901234567890123456789012345678901234567890123\n" 740 | "29 456789012345678901234567890123456789012345678901234567890123\n" 741 | "30 456789012345678901234567890123456789012345678901234567890123\n" 742 | "31 456789012345678901234567890123456789012345678901234567890123\n" 743 | "32 456789012345678901234567890123456789012345678901234567890123\n" 744 | "33 456789012345678901234567890123456789012345678901234567890123\n" 745 | "34 456789012345678901234567890123456789012345678901234567890123\n" 746 | "35 456789012345678901234567890123456789012345678901234567890123\n" 747 | "36 456789012345678901234567890123456789012345678901234567890123\n" 748 | "37 456789012345678901234567890123456789012345678901234567890123\n" 749 | "38 456789012345678901234567890123456789012345678901234567890123\n" 750 | "39 456789012345678901234567890123456789012345678901234567890123\n" 751 | "40 456789012345678901234567890123456789012345678901234567890123\n" 752 | "41 456789012345678901234567890123456789012345678901234567890123\n" 753 | "42 456789012345678901234567890123456789012345678901234567890123\n" 754 | "43 456789012345678901234567890123456789012345678901234567890123\n" 755 | "44 456789012345678901234567890123456789012345678901234567890123\n" 756 | "45 456789012345678901234567890123456789012345678901234567890123\n" 757 | "46 456789012345678901234567890123456789012345678901234567890123\n" 758 | "47 456789012345678901234567890123456789012345678901234567890123\n" 759 | "48 456789012345678901234567890123456789012345678901234567890123\n" 760 | "49 456789012345678901234567890123456789012345678901234567890123\n" 761 | "50 456789012345678901234567890123456789012345678901234567890123\n" 762 | "51 456789012345678901234567890123456789012345678901234567890123\n" 763 | "52 456789012345678901234567890123456789012345678901234567890123\n" 764 | "53 456789012345678901234567890123456789012345678901234567890123\n" 765 | "54 456789012345678901234567890123456789012345678901234567890123\n" 766 | "55 456789012345678901234567890123456789012345678901234567890123\n" 767 | "56 456789012345678901234567890123456789012345678901234567890123\n" 768 | "57 456789012345678901234567890123456789012345678901234567890123\n" 769 | "58 456789012345678901234567890123456789012345678901234567890123\n" 770 | "59 456789012345678901234567890123456789012345678901234567890123\n" 771 | "60 456789012345678901234567890123456789012345678901234567890123\n" 772 | "61 456789012345678901234567890123456789012345678901234567890123\n" 773 | "62 456789012345678901234567890123456789012345678901234567890123\n" 774 | "63 456789012345678901234567890123456789012345678901234567890123\n" 775 | "64 456789012345678901234567890123456789012345678901234567890123\n"; 776 | char blk0[] = 777 | "\n" 778 | "01 456789012345678901234567890123456789012345678901234567890123\n" 779 | "02 456789012345678901234567890123456789012345678901234567890123\n" 780 | "03 456789012345678901234567890123456789012345678901234567890123\n" 781 | "04 456789012345678901234567890123456789012345678901234567890123\n" 782 | "05 456789012345678901234567890123456789012345678901234567890123\n" 783 | "06 456789012345678901234567890123456789012345678901234567890123\n" 784 | "07 456789012345678901234567890123456789012345678901234567890123\n" 785 | "08 456789012345678901234567890123456789012345678901234567890123\n" 786 | "09 456789012345678901234567890123456789012345678901234567890123\n" 787 | "10 456789012345678901234567890123456789012345678901234567890123\n" 788 | "11 456789012345678901234567890123456789012345678901234567890123\n" 789 | "12 456789012345678901234567890123456789012345678901234567890123\n" 790 | "13 456789012345678901234567890123456789012345678901234567890123\n" 791 | "14 456789012345678901234567890123456789012345678901234567890123\n" 792 | "15 456789012345678901234567890123456789012345678901234567890123\n" 793 | "16 456789012345678901234567890123456789012345678901234567890123\n" 794 | "17 456789012345678901234567890123456789012345678901234567890123\n" 795 | "18 456789012345678901234567890123456789012345678901234567890123\n" 796 | "19 456789012345678901234567890123456789012345678901234567890123\n" 797 | "20 456789012345678901234567890123456789012345678901234567890123\n" 798 | "21 456789012345678901234567890123456789012345678901234567890123\n" 799 | "22 456789012345678901234567890123456789012345678901234567890123\n" 800 | "23 456789012345678901234567890123456789012345678901234567890123\n" 801 | "24 456789012345678901234567890123456789012345678901234567890123\n" 802 | "25 456789012345678901234567890123456789012345678901234567890123\n" 803 | "26 456789012345678901234567890123456789012345678901234567890123\n" 804 | "27 456789012345678901234567890123456789012345678901234567890123\n" 805 | "28 456789012345678901234567890123456789012345678901234567890123\n" 806 | "29 456789012345678901234567890123456789012345678901234567890123\n" 807 | "30 456789012345678901234567890123456789012345678901234567890123\n" 808 | "31 456789012345678901234567890123456789012345678901234567890123\n" 809 | "32 456789012345678901234567890123456789012345678901234567890123\n" 810 | "33 456789012345678901234567890123456789012345678901234567890123\n" 811 | "34 456789012345678901234567890123456789012345678901234567890123\n" 812 | "35 456789012345678901234567890123456789012345678901234567890123\n" 813 | "36 456789012345678901234567890123456789012345678901234567890123\n" 814 | "37 456789012345678901234567890123456789012345678901234567890123\n" 815 | "38 456789012345678901234567890123456789012345678901234567890123\n" 816 | "39 456789012345678901234567890123456789012345678901234567890123\n" 817 | "40 456789012345678901234567890123456789012345678901234567890123\n" 818 | "41 456789012345678901234567890123456789012345678901234567890123\n" 819 | "42 456789012345678901234567890123456789012345678901234567890123\n" 820 | "43 456789012345678901234567890123456789012345678901234567890123\n" 821 | "44 456789012345678901234567890123456789012345678901234567890123\n" 822 | "45 456789012345678901234567890123456789012345678901234567890123\n" 823 | "46 456789012345678901234567890123456789012345678901234567890123\n" 824 | "47 456789012345678901234567890123456789012345678901234567890123\n" 825 | "48 456789012345678901234567890123456789012345678901234567890123\n" 826 | "49 456789012345678901234567890123456789012345678901234567890123\n" 827 | "50 456789012345678901234567890123456789012345678901234567890123\n" 828 | "51 456789012345678901234567890123456789012345678901234567890123\n" 829 | "52 456789012345678901234567890123456789012345678901234567890123\n" 830 | "53 456789012345678901234567890123456789012345678901234567890123\n" 831 | "54 456789012345678901234567890123456789012345678901234567890123\n" 832 | "55 456789012345678901234567890123456789012345678901234567890123\n" 833 | "56 456789012345678901234567890123456789012345678901234567890123\n" 834 | "57 456789012345678901234567890123456789012345678901234567890123\n" 835 | "58 456789012345678901234567890123456789012345678901234567890123\n" 836 | "59 456789012345678901234567890123456789012345678901234567890123\n" 837 | "60 456789012345678901234567890123456789012345678901234567890123\n" 838 | "61 456789012345678901234567890123456789012345678901234567890123\n" 839 | "62 456789012345678901234567890123456789012345678901234567890123\n" 840 | "63 456789012345678901234567890123456789012345678901234567890123\n" 841 | "64 456789012345678901234567890123456789012345678901234567890123"; 842 | char blk1[] = "\n\n"; 843 | 844 | range.start.blk = 0; 845 | range.start.off = 1; 846 | range.end.blk = 0; 847 | range.end.off = buffer.block[0].len - 1; 848 | 849 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 850 | buffer_read, ((void*)&buffer, (void*)&range, mod, sizeof(mod)-1)); 851 | TEST_OP("%d", ret, ==, (int)sizeof(mod)-1, call); 852 | TEST_OP("%d", buffer.nblocks, ==, 2, "%s", call); 853 | TEST_OP("%d", buffer.block[0].len, ==, (int)sizeof(blk0)-1, "%s", call); 854 | TEST_OP("%d", buffer.block[0].nlines, ==, 64, "%s", call); 855 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, blk0, buffer.block[0].len, "%s", call); 856 | TEST_OP("%d", buffer.block[1].len, ==, (int)sizeof(blk1)-1, "%s", call); 857 | TEST_OP("%d", buffer.block[1].nlines, ==, 2, "%s", call); 858 | TEST_MEMCMP_OP(buffer.block[1].p->buf, ==, blk1, buffer.block[1].len, "%s", call); 859 | TEST_OP("%d", range.start.blk, ==, 0, "%s", call); 860 | TEST_OP("%d", range.start.off, ==, 1, "%s", call); 861 | TEST_OP("%d", range.end.blk, ==, 1, "%s", call); 862 | TEST_OP("%d", range.end.off, ==, 1, "%s", call); 863 | } 864 | 865 | { 866 | /* remove at the beginning of the block */ 867 | char blk0[] = 868 | "01 456789012345678901234567890123456789012345678901234567890123\n" 869 | "02 456789012345678901234567890123456789012345678901234567890123\n" 870 | "03 456789012345678901234567890123456789012345678901234567890123\n" 871 | "04 456789012345678901234567890123456789012345678901234567890123\n" 872 | "05 456789012345678901234567890123456789012345678901234567890123\n" 873 | "06 456789012345678901234567890123456789012345678901234567890123\n" 874 | "07 456789012345678901234567890123456789012345678901234567890123\n" 875 | "08 456789012345678901234567890123456789012345678901234567890123\n" 876 | "09 456789012345678901234567890123456789012345678901234567890123\n" 877 | "10 456789012345678901234567890123456789012345678901234567890123\n" 878 | "11 456789012345678901234567890123456789012345678901234567890123\n" 879 | "12 456789012345678901234567890123456789012345678901234567890123\n" 880 | "13 456789012345678901234567890123456789012345678901234567890123\n" 881 | "14 456789012345678901234567890123456789012345678901234567890123\n" 882 | "15 456789012345678901234567890123456789012345678901234567890123\n" 883 | "16 456789012345678901234567890123456789012345678901234567890123\n" 884 | "17 456789012345678901234567890123456789012345678901234567890123\n" 885 | "18 456789012345678901234567890123456789012345678901234567890123\n" 886 | "19 456789012345678901234567890123456789012345678901234567890123\n" 887 | "20 456789012345678901234567890123456789012345678901234567890123\n" 888 | "21 456789012345678901234567890123456789012345678901234567890123\n" 889 | "22 456789012345678901234567890123456789012345678901234567890123\n" 890 | "23 456789012345678901234567890123456789012345678901234567890123\n" 891 | "24 456789012345678901234567890123456789012345678901234567890123\n" 892 | "25 456789012345678901234567890123456789012345678901234567890123\n" 893 | "26 456789012345678901234567890123456789012345678901234567890123\n" 894 | "27 456789012345678901234567890123456789012345678901234567890123\n" 895 | "28 456789012345678901234567890123456789012345678901234567890123\n" 896 | "29 456789012345678901234567890123456789012345678901234567890123\n" 897 | "30 456789012345678901234567890123456789012345678901234567890123\n" 898 | "31 456789012345678901234567890123456789012345678901234567890123\n" 899 | "32 456789012345678901234567890123456789012345678901234567890123\n" 900 | "33 456789012345678901234567890123456789012345678901234567890123\n" 901 | "34 456789012345678901234567890123456789012345678901234567890123\n" 902 | "35 456789012345678901234567890123456789012345678901234567890123\n" 903 | "36 456789012345678901234567890123456789012345678901234567890123\n" 904 | "37 456789012345678901234567890123456789012345678901234567890123\n" 905 | "38 456789012345678901234567890123456789012345678901234567890123\n" 906 | "39 456789012345678901234567890123456789012345678901234567890123\n" 907 | "40 456789012345678901234567890123456789012345678901234567890123\n" 908 | "41 456789012345678901234567890123456789012345678901234567890123\n" 909 | "42 456789012345678901234567890123456789012345678901234567890123\n" 910 | "43 456789012345678901234567890123456789012345678901234567890123\n" 911 | "44 456789012345678901234567890123456789012345678901234567890123\n" 912 | "45 456789012345678901234567890123456789012345678901234567890123\n" 913 | "46 456789012345678901234567890123456789012345678901234567890123\n" 914 | "47 456789012345678901234567890123456789012345678901234567890123\n" 915 | "48 456789012345678901234567890123456789012345678901234567890123\n" 916 | "49 456789012345678901234567890123456789012345678901234567890123\n" 917 | "50 456789012345678901234567890123456789012345678901234567890123\n" 918 | "51 456789012345678901234567890123456789012345678901234567890123\n" 919 | "52 456789012345678901234567890123456789012345678901234567890123\n" 920 | "53 456789012345678901234567890123456789012345678901234567890123\n" 921 | "54 456789012345678901234567890123456789012345678901234567890123\n" 922 | "55 456789012345678901234567890123456789012345678901234567890123\n" 923 | "56 456789012345678901234567890123456789012345678901234567890123\n" 924 | "57 456789012345678901234567890123456789012345678901234567890123\n" 925 | "58 456789012345678901234567890123456789012345678901234567890123\n" 926 | "59 456789012345678901234567890123456789012345678901234567890123\n" 927 | "60 456789012345678901234567890123456789012345678901234567890123\n" 928 | "61 456789012345678901234567890123456789012345678901234567890123\n" 929 | "62 456789012345678901234567890123456789012345678901234567890123\n" 930 | "63 456789012345678901234567890123456789012345678901234567890123\n" 931 | "64 456789012345678901234567890123456789012345678901234567890123"; 932 | char blk1[] = "\n\n"; 933 | 934 | range.start.blk = 0; 935 | range.start.off = 0; 936 | range.end.blk = 0; 937 | range.end.off = 1; 938 | 939 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 940 | buffer_read, ((void*)&buffer, (void*)&range, "", (size_t)0)); 941 | TEST_OP("%d", ret, ==, 0, call); 942 | TEST_OP("%d", buffer.nblocks, ==, 2, "%s", call); 943 | TEST_OP("%d", buffer.block[0].len, ==, (int)sizeof(blk0)-1, "%s", call); 944 | TEST_OP("%d", buffer.block[0].nlines, ==, 63, "%s", call); 945 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, blk0, buffer.block[0].len, "%s", call); 946 | TEST_OP("%d", buffer.block[1].len, ==, (int)sizeof(blk1)-1, "%s", call); 947 | TEST_OP("%d", buffer.block[1].nlines, ==, 2, "%s", call); 948 | TEST_MEMCMP_OP(buffer.block[1].p->buf, ==, blk1, buffer.block[1].len, "%s", call); 949 | TEST_OP("%d", range.start.blk, ==, 0, "%s", call); 950 | TEST_OP("%d", range.start.off, ==, 0, "%s", call); 951 | TEST_OP("%d", range.end.blk, ==, 0, "%s", call); 952 | TEST_OP("%d", range.end.off, ==, 0, "%s", call); 953 | } 954 | 955 | { 956 | /* replace with a block sized mod */ 957 | char mod[] = 958 | "01 456789012345678901234567890123456789012345678901234567890123\n" 959 | "02 456789012345678901234567890123456789012345678901234567890123\n" 960 | "03 456789012345678901234567890123456789012345678901234567890123\n" 961 | "04 456789012345678901234567890123456789012345678901234567890123\n" 962 | "05 456789012345678901234567890123456789012345678901234567890123\n" 963 | "06 456789012345678901234567890123456789012345678901234567890123\n" 964 | "07 456789012345678901234567890123456789012345678901234567890123\n" 965 | "08 456789012345678901234567890123456789012345678901234567890123\n" 966 | "09 456789012345678901234567890123456789012345678901234567890123\n" 967 | "10 456789012345678901234567890123456789012345678901234567890123\n" 968 | "11 456789012345678901234567890123456789012345678901234567890123\n" 969 | "12 456789012345678901234567890123456789012345678901234567890123\n" 970 | "13 456789012345678901234567890123456789012345678901234567890123\n" 971 | "14 456789012345678901234567890123456789012345678901234567890123\n" 972 | "15 456789012345678901234567890123456789012345678901234567890123\n" 973 | "16 456789012345678901234567890123456789012345678901234567890123\n" 974 | "17 456789012345678901234567890123456789012345678901234567890123\n" 975 | "18 456789012345678901234567890123456789012345678901234567890123\n" 976 | "19 456789012345678901234567890123456789012345678901234567890123\n" 977 | "20 456789012345678901234567890123456789012345678901234567890123\n" 978 | "21 456789012345678901234567890123456789012345678901234567890123\n" 979 | "22 456789012345678901234567890123456789012345678901234567890123\n" 980 | "23 456789012345678901234567890123456789012345678901234567890123\n" 981 | "24 456789012345678901234567890123456789012345678901234567890123\n" 982 | "25 456789012345678901234567890123456789012345678901234567890123\n" 983 | "26 456789012345678901234567890123456789012345678901234567890123\n" 984 | "27 456789012345678901234567890123456789012345678901234567890123\n" 985 | "28 456789012345678901234567890123456789012345678901234567890123\n" 986 | "29 456789012345678901234567890123456789012345678901234567890123\n" 987 | "30 456789012345678901234567890123456789012345678901234567890123\n" 988 | "31 456789012345678901234567890123456789012345678901234567890123\n" 989 | "32 456789012345678901234567890123456789012345678901234567890123\n" 990 | "33 456789012345678901234567890123456789012345678901234567890123\n" 991 | "34 456789012345678901234567890123456789012345678901234567890123\n" 992 | "35 456789012345678901234567890123456789012345678901234567890123\n" 993 | "36 456789012345678901234567890123456789012345678901234567890123\n" 994 | "37 456789012345678901234567890123456789012345678901234567890123\n" 995 | "38 456789012345678901234567890123456789012345678901234567890123\n" 996 | "39 456789012345678901234567890123456789012345678901234567890123\n" 997 | "40 456789012345678901234567890123456789012345678901234567890123\n" 998 | "41 456789012345678901234567890123456789012345678901234567890123\n" 999 | "42 456789012345678901234567890123456789012345678901234567890123\n" 1000 | "43 456789012345678901234567890123456789012345678901234567890123\n" 1001 | "44 456789012345678901234567890123456789012345678901234567890123\n" 1002 | "45 456789012345678901234567890123456789012345678901234567890123\n" 1003 | "46 456789012345678901234567890123456789012345678901234567890123\n" 1004 | "47 456789012345678901234567890123456789012345678901234567890123\n" 1005 | "48 456789012345678901234567890123456789012345678901234567890123\n" 1006 | "49 456789012345678901234567890123456789012345678901234567890123\n" 1007 | "50 456789012345678901234567890123456789012345678901234567890123\n" 1008 | "51 456789012345678901234567890123456789012345678901234567890123\n" 1009 | "52 456789012345678901234567890123456789012345678901234567890123\n" 1010 | "53 456789012345678901234567890123456789012345678901234567890123\n" 1011 | "54 456789012345678901234567890123456789012345678901234567890123\n" 1012 | "55 456789012345678901234567890123456789012345678901234567890123\n" 1013 | "56 456789012345678901234567890123456789012345678901234567890123\n" 1014 | "57 456789012345678901234567890123456789012345678901234567890123\n" 1015 | "58 456789012345678901234567890123456789012345678901234567890123\n" 1016 | "59 456789012345678901234567890123456789012345678901234567890123\n" 1017 | "60 456789012345678901234567890123456789012345678901234567890123\n" 1018 | "61 456789012345678901234567890123456789012345678901234567890123\n" 1019 | "62 456789012345678901234567890123456789012345678901234567890123\n" 1020 | "63 456789012345678901234567890123456789012345678901234567890123\n" 1021 | "64 456789012345678901234567890123456789012345678901234567890123\n"; 1022 | char blk0[] = 1023 | "01 456789012345678901234567890123456789012345678901234567890123\n" 1024 | "02 456789012345678901234567890123456789012345678901234567890123\n" 1025 | "03 456789012345678901234567890123456789012345678901234567890123\n" 1026 | "04 456789012345678901234567890123456789012345678901234567890123\n" 1027 | "05 456789012345678901234567890123456789012345678901234567890123\n" 1028 | "06 456789012345678901234567890123456789012345678901234567890123\n" 1029 | "07 456789012345678901234567890123456789012345678901234567890123\n" 1030 | "08 456789012345678901234567890123456789012345678901234567890123\n" 1031 | "09 456789012345678901234567890123456789012345678901234567890123\n" 1032 | "10 456789012345678901234567890123456789012345678901234567890123\n" 1033 | "11 456789012345678901234567890123456789012345678901234567890123\n" 1034 | "12 456789012345678901234567890123456789012345678901234567890123\n" 1035 | "13 456789012345678901234567890123456789012345678901234567890123\n" 1036 | "14 456789012345678901234567890123456789012345678901234567890123\n" 1037 | "15 456789012345678901234567890123456789012345678901234567890123\n" 1038 | "16 456789012345678901234567890123456789012345678901234567890123\n" 1039 | "17 456789012345678901234567890123456789012345678901234567890123\n" 1040 | "18 456789012345678901234567890123456789012345678901234567890123\n" 1041 | "19 456789012345678901234567890123456789012345678901234567890123\n" 1042 | "20 456789012345678901234567890123456789012345678901234567890123\n" 1043 | "21 456789012345678901234567890123456789012345678901234567890123\n" 1044 | "22 456789012345678901234567890123456789012345678901234567890123\n" 1045 | "23 456789012345678901234567890123456789012345678901234567890123\n" 1046 | "24 456789012345678901234567890123456789012345678901234567890123\n" 1047 | "25 456789012345678901234567890123456789012345678901234567890123\n" 1048 | "26 456789012345678901234567890123456789012345678901234567890123\n" 1049 | "27 456789012345678901234567890123456789012345678901234567890123\n" 1050 | "28 456789012345678901234567890123456789012345678901234567890123\n" 1051 | "29 456789012345678901234567890123456789012345678901234567890123\n" 1052 | "30 456789012345678901234567890123456789012345678901234567890123\n" 1053 | "31 456789012345678901234567890123456789012345678901234567890123\n" 1054 | "32 456789012345678901234567890123456789012345678901234567890123\n" 1055 | "33 456789012345678901234567890123456789012345678901234567890123\n" 1056 | "34 456789012345678901234567890123456789012345678901234567890123\n" 1057 | "35 456789012345678901234567890123456789012345678901234567890123\n" 1058 | "36 456789012345678901234567890123456789012345678901234567890123\n" 1059 | "37 456789012345678901234567890123456789012345678901234567890123\n" 1060 | "38 456789012345678901234567890123456789012345678901234567890123\n" 1061 | "39 456789012345678901234567890123456789012345678901234567890123\n" 1062 | "40 456789012345678901234567890123456789012345678901234567890123\n" 1063 | "41 456789012345678901234567890123456789012345678901234567890123\n" 1064 | "42 456789012345678901234567890123456789012345678901234567890123\n" 1065 | "43 456789012345678901234567890123456789012345678901234567890123\n" 1066 | "44 456789012345678901234567890123456789012345678901234567890123\n" 1067 | "45 456789012345678901234567890123456789012345678901234567890123\n" 1068 | "46 456789012345678901234567890123456789012345678901234567890123\n" 1069 | "47 456789012345678901234567890123456789012345678901234567890123\n" 1070 | "48 456789012345678901234567890123456789012345678901234567890123\n" 1071 | "49 456789012345678901234567890123456789012345678901234567890123\n" 1072 | "50 456789012345678901234567890123456789012345678901234567890123\n" 1073 | "51 456789012345678901234567890123456789012345678901234567890123\n" 1074 | "52 456789012345678901234567890123456789012345678901234567890123\n" 1075 | "53 456789012345678901234567890123456789012345678901234567890123\n" 1076 | "54 456789012345678901234567890123456789012345678901234567890123\n" 1077 | "55 456789012345678901234567890123456789012345678901234567890123\n" 1078 | "56 456789012345678901234567890123456789012345678901234567890123\n" 1079 | "57 456789012345678901234567890123456789012345678901234567890123\n" 1080 | "58 456789012345678901234567890123456789012345678901234567890123\n" 1081 | "59 456789012345678901234567890123456789012345678901234567890123\n" 1082 | "60 456789012345678901234567890123456789012345678901234567890123\n" 1083 | "61 456789012345678901234567890123456789012345678901234567890123\n" 1084 | "62 456789012345678901234567890123456789012345678901234567890123\n" 1085 | "63 456789012345678901234567890123456789012345678901234567890123\n" 1086 | "64 456789012345678901234567890123456789012345678901234567890123"; 1087 | char blk1[] = 1088 | "01 456789012345678901234567890123456789012345678901234567890123\n" 1089 | "02 456789012345678901234567890123456789012345678901234567890123\n" 1090 | "03 456789012345678901234567890123456789012345678901234567890123\n" 1091 | "04 456789012345678901234567890123456789012345678901234567890123\n" 1092 | "05 456789012345678901234567890123456789012345678901234567890123\n" 1093 | "06 456789012345678901234567890123456789012345678901234567890123\n" 1094 | "07 456789012345678901234567890123456789012345678901234567890123\n" 1095 | "08 456789012345678901234567890123456789012345678901234567890123\n" 1096 | "09 456789012345678901234567890123456789012345678901234567890123\n" 1097 | "10 456789012345678901234567890123456789012345678901234567890123\n" 1098 | "11 456789012345678901234567890123456789012345678901234567890123\n" 1099 | "12 456789012345678901234567890123456789012345678901234567890123\n" 1100 | "13 456789012345678901234567890123456789012345678901234567890123\n" 1101 | "14 456789012345678901234567890123456789012345678901234567890123\n" 1102 | "15 456789012345678901234567890123456789012345678901234567890123\n" 1103 | "16 456789012345678901234567890123456789012345678901234567890123\n" 1104 | "17 456789012345678901234567890123456789012345678901234567890123\n" 1105 | "18 456789012345678901234567890123456789012345678901234567890123\n" 1106 | "19 456789012345678901234567890123456789012345678901234567890123\n" 1107 | "20 456789012345678901234567890123456789012345678901234567890123\n" 1108 | "21 456789012345678901234567890123456789012345678901234567890123\n" 1109 | "22 456789012345678901234567890123456789012345678901234567890123\n" 1110 | "23 456789012345678901234567890123456789012345678901234567890123\n" 1111 | "24 456789012345678901234567890123456789012345678901234567890123\n" 1112 | "25 456789012345678901234567890123456789012345678901234567890123\n" 1113 | "26 456789012345678901234567890123456789012345678901234567890123\n" 1114 | "27 456789012345678901234567890123456789012345678901234567890123\n" 1115 | "28 456789012345678901234567890123456789012345678901234567890123\n" 1116 | "29 456789012345678901234567890123456789012345678901234567890123\n" 1117 | "30 456789012345678901234567890123456789012345678901234567890123\n" 1118 | "31 456789012345678901234567890123456789012345678901234567890123\n" 1119 | "32 456789012345678901234567890123456789012345678901234567890123\n" 1120 | "33 456789012345678901234567890123456789012345678901234567890123\n" 1121 | "34 456789012345678901234567890123456789012345678901234567890123\n" 1122 | "35 456789012345678901234567890123456789012345678901234567890123\n" 1123 | "36 456789012345678901234567890123456789012345678901234567890123\n" 1124 | "37 456789012345678901234567890123456789012345678901234567890123\n" 1125 | "38 456789012345678901234567890123456789012345678901234567890123\n" 1126 | "39 456789012345678901234567890123456789012345678901234567890123\n" 1127 | "40 456789012345678901234567890123456789012345678901234567890123\n" 1128 | "41 456789012345678901234567890123456789012345678901234567890123\n" 1129 | "42 456789012345678901234567890123456789012345678901234567890123\n" 1130 | "43 456789012345678901234567890123456789012345678901234567890123\n" 1131 | "44 456789012345678901234567890123456789012345678901234567890123\n" 1132 | "45 456789012345678901234567890123456789012345678901234567890123\n" 1133 | "46 456789012345678901234567890123456789012345678901234567890123\n" 1134 | "47 456789012345678901234567890123456789012345678901234567890123\n" 1135 | "48 456789012345678901234567890123456789012345678901234567890123\n" 1136 | "49 456789012345678901234567890123456789012345678901234567890123\n" 1137 | "50 456789012345678901234567890123456789012345678901234567890123\n" 1138 | "51 456789012345678901234567890123456789012345678901234567890123\n" 1139 | "52 456789012345678901234567890123456789012345678901234567890123\n" 1140 | "53 456789012345678901234567890123456789012345678901234567890123\n" 1141 | "54 456789012345678901234567890123456789012345678901234567890123\n" 1142 | "55 456789012345678901234567890123456789012345678901234567890123\n" 1143 | "56 456789012345678901234567890123456789012345678901234567890123\n" 1144 | "57 456789012345678901234567890123456789012345678901234567890123\n" 1145 | "58 456789012345678901234567890123456789012345678901234567890123\n" 1146 | "59 456789012345678901234567890123456789012345678901234567890123\n" 1147 | "60 456789012345678901234567890123456789012345678901234567890123\n" 1148 | "61 456789012345678901234567890123456789012345678901234567890123\n" 1149 | "62 456789012345678901234567890123456789012345678901234567890123\n" 1150 | "63 456789012345678901234567890123456789012345678901234567890123\n" 1151 | "64 456789012345678901234567890123456789012345678901234567890123\n"; 1152 | char blk2[] = "\n"; 1153 | 1154 | range.start.blk = 1; 1155 | range.start.off = 0; 1156 | range.end.blk = 1; 1157 | range.end.off = 1; 1158 | 1159 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 1160 | buffer_read, ((void*)&buffer, (void*)&range, mod, sizeof(mod)-1)); 1161 | TEST_OP("%d", ret, ==, (int)sizeof(mod)-1, call); 1162 | TEST_OP("%d", buffer.nblocks, ==, 3, "%s", call); 1163 | TEST_OP("%d", buffer.block[0].len, ==, (int)sizeof(blk0)-1, "%s", call); 1164 | TEST_OP("%d", buffer.block[0].nlines, ==, 63, "%s", call); 1165 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, blk0, buffer.block[0].len, "%s", call); 1166 | TEST_OP("%d", buffer.block[1].len, ==, (int)sizeof(blk1)-1, "%s", call); 1167 | TEST_OP("%d", buffer.block[1].nlines, ==, 64, "%s", call); 1168 | TEST_MEMCMP_OP(buffer.block[1].p->buf, ==, blk1, buffer.block[1].len, "%s", call); 1169 | TEST_OP("%d", buffer.block[2].len, ==, (int)sizeof(blk2)-1, "%s", call); 1170 | TEST_OP("%d", buffer.block[2].nlines, ==, 1, "%s", call); 1171 | TEST_MEMCMP_OP(buffer.block[2].p->buf, ==, blk2, buffer.block[2].len, "%s", call); 1172 | TEST_OP("%d", range.start.blk, ==, 1, "%s", call); 1173 | TEST_OP("%d", range.start.off, ==, 0, "%s", call); 1174 | // FIXME: end: {2, 0} ? 1175 | TEST_OP("%d", range.end.blk, ==, 1, "%s", call); 1176 | TEST_OP("%d", range.end.off, ==, BLOCK_SIZE, "%s", call); 1177 | } 1178 | 1179 | { 1180 | /* take from middle block */ 1181 | char blk0[] = 1182 | "01 456789012345678901234567890123456789012345678901234567890123\n" 1183 | "35 456789012345678901234567890123456789012345678901234567890123\n" 1184 | "36 456789012345678901234567890123456789012345678901234567890123\n" 1185 | "37 456789012345678901234567890123456789012345678901234567890123\n" 1186 | "38 456789012345678901234567890123456789012345678901234567890123\n" 1187 | "39 456789012345678901234567890123456789012345678901234567890123\n" 1188 | "40 456789012345678901234567890123456789012345678901234567890123\n" 1189 | "41 456789012345678901234567890123456789012345678901234567890123\n" 1190 | "42 456789012345678901234567890123456789012345678901234567890123\n" 1191 | "43 456789012345678901234567890123456789012345678901234567890123\n" 1192 | "44 456789012345678901234567890123456789012345678901234567890123\n" 1193 | "45 456789012345678901234567890123456789012345678901234567890123\n" 1194 | "46 456789012345678901234567890123456789012345678901234567890123\n" 1195 | "47 456789012345678901234567890123456789012345678901234567890123\n" 1196 | "48 456789012345678901234567890123456789012345678901234567890123\n" 1197 | "49 456789012345678901234567890123456789012345678901234567890123\n" 1198 | "50 456789012345678901234567890123456789012345678901234567890123\n" 1199 | "51 456789012345678901234567890123456789012345678901234567890123\n" 1200 | "52 456789012345678901234567890123456789012345678901234567890123\n" 1201 | "53 456789012345678901234567890123456789012345678901234567890123\n" 1202 | "54 456789012345678901234567890123456789012345678901234567890123\n" 1203 | "55 456789012345678901234567890123456789012345678901234567890123\n" 1204 | "56 456789012345678901234567890123456789012345678901234567890123\n" 1205 | "57 456789012345678901234567890123456789012345678901234567890123\n" 1206 | "58 456789012345678901234567890123456789012345678901234567890123\n" 1207 | "59 456789012345678901234567890123456789012345678901234567890123\n" 1208 | "60 456789012345678901234567890123456789012345678901234567890123\n" 1209 | "61 456789012345678901234567890123456789012345678901234567890123\n" 1210 | "62 456789012345678901234567890123456789012345678901234567890123\n" 1211 | "63 456789012345678901234567890123456789012345678901234567890123\n" 1212 | "64 456789012345678901234567890123456789012345678901234567890123" 1213 | "01 456789012345678901234567890123456789012345678901234567890123\n" 1214 | "02 456789012345678901234567890123456789012345678901234567890123\n" 1215 | "03 456789012345678901234567890123456789012345678901234567890123\n" 1216 | "04 456789012345678901234567890123456789012345678901234567890123\n" 1217 | "05 456789012345678901234567890123456789012345678901234567890123\n" 1218 | "06 456789012345678901234567890123456789012345678901234567890123\n" 1219 | "07 456789012345678901234567890123456789012345678901234567890123\n" 1220 | "08 456789012345678901234567890123456789012345678901234567890123\n" 1221 | "09 456789012345678901234567890123456789012345678901234567890123\n" 1222 | "10 456789012345678901234567890123456789012345678901234567890123\n" 1223 | "11 456789012345678901234567890123456789012345678901234567890123\n" 1224 | "12 456789012345678901234567890123456789012345678901234567890123\n" 1225 | "13 456789012345678901234567890123456789012345678901234567890123\n" 1226 | "14 456789012345678901234567890123456789012345678901234567890123\n" 1227 | "15 456789012345678901234567890123456789012345678901234567890123\n" 1228 | "16 456789012345678901234567890123456789012345678901234567890123\n" 1229 | "17 456789012345678901234567890123"; 1230 | char blk1[] = 1231 | "456789012345678901234567890123\n" 1232 | "18 456789012345678901234567890123456789012345678901234567890123\n" 1233 | "19 456789012345678901234567890123456789012345678901234567890123\n" 1234 | "20 456789012345678901234567890123456789012345678901234567890123\n" 1235 | "21 456789012345678901234567890123456789012345678901234567890123\n" 1236 | "22 456789012345678901234567890123456789012345678901234567890123\n" 1237 | "23 456789012345678901234567890123456789012345678901234567890123\n" 1238 | "24 456789012345678901234567890123456789012345678901234567890123\n" 1239 | "25 456789012345678901234567890123456789012345678901234567890123\n" 1240 | "26 456789012345678901234567890123456789012345678901234567890123\n" 1241 | "27 456789012345678901234567890123456789012345678901234567890123\n" 1242 | "28 456789012345678901234567890123456789012345678901234567890123\n" 1243 | "29 456789012345678901234567890123456789012345678901234567890123\n" 1244 | "30 456789012345678901234567890123456789012345678901234567890123\n" 1245 | "31 456789012345678901234567890123456789012345678901234567890123\n" 1246 | "32 456789012345678901234567890123456789012345678901234567890123\n" 1247 | "33 456789012345678901234567890123456789012345678901234567890123\n" 1248 | "34 456789012345678901234567890123456789012345678901234567890123\n" 1249 | "35 456789012345678901234567890123456789012345678901234567890123\n" 1250 | "36 456789012345678901234567890123456789012345678901234567890123\n" 1251 | "37 456789012345678901234567890123456789012345678901234567890123\n" 1252 | "38 456789012345678901234567890123456789012345678901234567890123\n" 1253 | "39 456789012345678901234567890123456789012345678901234567890123\n" 1254 | "40 456789012345678901234567890123456789012345678901234567890123\n" 1255 | "41 456789012345678901234567890123456789012345678901234567890123\n" 1256 | "42 456789012345678901234567890123456789012345678901234567890123\n" 1257 | "43 456789012345678901234567890123456789012345678901234567890123\n" 1258 | "44 456789012345678901234567890123456789012345678901234567890123\n" 1259 | "45 456789012345678901234567890123456789012345678901234567890123\n" 1260 | "46 456789012345678901234567890123456789012345678901234567890123\n" 1261 | "47 456789012345678901234567890123456789012345678901234567890123\n" 1262 | "48 456789012345678901234567890123456789012345678901234567890123\n" 1263 | "49 456789012345678901234567890123456789012345678901234567890123\n" 1264 | "50 456789012345678901234567890123456789012345678901234567890123\n" 1265 | "51 456789012345678901234567890123456789012345678901234567890123\n" 1266 | "52 456789012345678901234567890123456789012345678901234567890123\n" 1267 | "53 456789012345678901234567890123456789012345678901234567890123\n" 1268 | "54 456789012345678901234567890123456789012345678901234567890123\n" 1269 | "55 456789012345678901234567890123456789012345678901234567890123\n" 1270 | "56 456789012345678901234567890123456789012345678901234567890123\n" 1271 | "57 456789012345678901234567890123456789012345678901234567890123\n" 1272 | "58 456789012345678901234567890123456789012345678901234567890123\n" 1273 | "59 456789012345678901234567890123456789012345678901234567890123\n" 1274 | "60 456789012345678901234567890123456789012345678901234567890123\n" 1275 | "61 456789012345678901234567890123456789012345678901234567890123\n" 1276 | "62 456789012345678901234567890123456789012345678901234567890123\n" 1277 | "63 456789012345678901234567890123456789012345678901234567890123\n" 1278 | "64 456789012345678901234567890123456789012345678901234567890123\n"; 1279 | char blk2[] = "\n"; 1280 | 1281 | range.start.blk = 0; 1282 | range.start.off = 3; 1283 | range.end.blk = 0; 1284 | range.end.off = range.start.off + 64 * 33; 1285 | 1286 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 1287 | buffer_read, ((void*)&buffer, (void*)&range, "", (size_t)0)); 1288 | TEST_OP("%d", ret, ==, 0, call); 1289 | TEST_OP("%d", buffer.nblocks, ==, 3, "%s", call); 1290 | TEST_OP("%d", buffer.block[0].len, ==, (int)sizeof(blk0)-1, "%s", call); 1291 | TEST_OP("%d", buffer.block[0].nlines, ==, 46, "%s", call); 1292 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, blk0, buffer.block[0].len, "%s", call); 1293 | TEST_OP("%d", buffer.block[1].len, ==, (int)sizeof(blk1)-1, "%s", call); 1294 | TEST_OP("%d", buffer.block[1].nlines, ==, 48, "%s", call); 1295 | TEST_MEMCMP_OP(buffer.block[1].p->buf, ==, blk1, buffer.block[1].len, "%s", call); 1296 | TEST_OP("%d", buffer.block[2].len, ==, (int)sizeof(blk2)-1, "%s", call); 1297 | TEST_OP("%d", buffer.block[2].nlines, ==, 1, "%s", call); 1298 | TEST_MEMCMP_OP(buffer.block[2].p->buf, ==, blk2, buffer.block[2].len, "%s", call); 1299 | TEST_OP("%d", range.start.blk, ==, 0, "%s", call); 1300 | TEST_OP("%d", range.start.off, ==, 3, "%s", call); 1301 | TEST_OP("%d", range.end.blk, ==, 0, "%s", call); 1302 | TEST_OP("%d", range.end.off, ==, 3, "%s", call); 1303 | } 1304 | 1305 | { 1306 | /* join the middle block */ 1307 | char mod[] = "join"; 1308 | char blk0[] = 1309 | "01 456789012345678901234567890123456789012345678901234567890123\n" 1310 | "35 456789012345678901234567890123456789012345678901234567890123\n" 1311 | "36 456789012345678901234567890123456789012345678901234567890123\n" 1312 | "37 456789012345678901234567890123456789012345678901234567890123\n" 1313 | "38 456789012345678901234567890123456789012345678901234567890123\n" 1314 | "39 456789012345678901234567890123456789012345678901234567890123\n" 1315 | "40 456789012345678901234567890123456789012345678901234567890123\n" 1316 | "join56789012345678901234567890123456789012345678901234567890123\n" 1317 | "09 456789012345678901234567890123456789012345678901234567890123\n" 1318 | "10 456789012345678901234567890123456789012345678901234567890123\n" 1319 | "11 456789012345678901234567890123456789012345678901234567890123\n" 1320 | "12 456789012345678901234567890123456789012345678901234567890123\n" 1321 | "13 456789012345678901234567890123456789012345678901234567890123\n" 1322 | "14 456789012345678901234567890123456789012345678901234567890123\n" 1323 | "15 456789012345678901234567890123456789012345678901234567890123\n" 1324 | "16 456789012345678901234567890123456789012345678901234567890123\n" 1325 | "17 456789012345678901234567890123" 1326 | "456789012345678901234567890123\n" 1327 | "18 456789012345678901234567890123456789012345678901234567890123\n" 1328 | "19 456789012345678901234567890123456789012345678901234567890123\n" 1329 | "20 456789012345678901234567890123456789012345678901234567890123\n" 1330 | "21 456789012345678901234567890123456789012345678901234567890123\n" 1331 | "22 456789012345678901234567890123456789012345678901234567890123\n" 1332 | "23 456789012345678901234567890123456789012345678901234567890123\n" 1333 | "24 456789012345678901234567890123456789012345678901234567890123\n" 1334 | "25 456789012345678901234567890123456789012345678901234567890123\n" 1335 | "26 456789012345678901234567890123456789012345678901234567890123\n" 1336 | "27 456789012345678901234567890123456789012345678901234567890123\n" 1337 | "28 456789012345678901234567890123456789012345678901234567890123\n" 1338 | "29 456789012345678901234567890123456789012345678901234567890123\n" 1339 | "30 456789012345678901234567890123456789012345678901234567890123\n" 1340 | "31 456789012345678901234567890123456789012345678901234567890123\n" 1341 | "32 456789012345678901234567890123456789012345678901234567890123\n" 1342 | "33 456789012345678901234567890123456789012345678901234567890123\n" 1343 | "34 456789012345678901234567890123456789012345678901234567890123\n" 1344 | "35 456789012345678901234567890123456789012345678901234567890123\n" 1345 | "36 456789012345678901234567890123456789012345678901234567890123\n" 1346 | "37 456789012345678901234567890123456789012345678901234567890123\n" 1347 | "38 456789012345678901234567890123456789012345678901234567890123\n" 1348 | "39 456789012345678901234567890123456789012345678901234567890123\n" 1349 | "40 456789012345678901234567890123456789012345678901234567890123\n" 1350 | "41 456789012345678901234567890123456789012345678901234567890123\n" 1351 | "42 456789012345678901234567890123456789012345678901234567890123\n" 1352 | "43 456789012345678901234567890123456789012345678901234567890123\n" 1353 | "44 456789012345678901234567890123456789012345678901234567890123\n" 1354 | "45 456789012345678901234567890123456789012345678901234567890123\n" 1355 | "46 456789012345678901234567890123456789012345678901234567890123\n" 1356 | "47 456789012345678901234567890123456789012345678901234567890123\n" 1357 | "48 456789012345678901234567890123456789012345678901234567890123\n" 1358 | "49 456789012345678901234567890123456789012345678901234567890123\n" 1359 | "50 456789012345678901234567890123456789012345678901234567890123\n" 1360 | "51 456789012345678901234567890123456789012345678901234567890123\n" 1361 | "52 456789012345678901234567890123456789012345678901234567890123\n" 1362 | "53 456789012345678901234567890123456789012345678901234567890123\n" 1363 | "54 456789012345678901234567890123456789012345678901234567890123\n" 1364 | "55 456789012345678901234567890123456789012345678901234567890123\n" 1365 | "56 456789012345678901234567890123456789012345678901234567890123\n" 1366 | "57 456789012345678901234567890123456789012345678901234567890123\n" 1367 | "58 456789012345678901234567890123456789012345678901234567890123\n" 1368 | "59 456789012345678901234567890123456789012345678901234567890123\n" 1369 | "60 456789012345678901234567890123456789012345678901234567890123\n" 1370 | "61 456789012345678901234567890123456789012345678901234567890123\n" 1371 | "62 456789012345678901234567890123456789012345678901234567890123\n" 1372 | "63 456789012345678901234567890123456789012345678901234567890123\n" 1373 | "64 456789012345678901234567890123456789012345678901234567890123\n"; 1374 | char blk1[] = "\n"; 1375 | 1376 | range.start.blk = 0; 1377 | range.start.off = 64 * 7; 1378 | range.end.blk = 0; 1379 | range.end.off = range.start.off + sizeof(mod)-1 + 64 * 30 + 63; 1380 | 1381 | ret = TEST_CALL(call, sizeof(call), "%p, %p, \"%s\", %zu", 1382 | buffer_read, ((void*)&buffer, (void*)&range, mod, sizeof(mod)-1)); 1383 | TEST_OP("%d", ret, ==, (int)sizeof(mod)-1, call); 1384 | TEST_OP("%d", buffer.nblocks, ==, 2, "%s", call); 1385 | TEST_OP("%d", buffer.block[0].len, ==, (int)sizeof(blk0)-1, "%s", call); 1386 | TEST_OP("%d", buffer.block[0].nlines, ==, 64, "%s", call); 1387 | TEST_MEMCMP_OP(buffer.block[0].p->buf, ==, blk0, buffer.block[0].len, "%s", call); 1388 | TEST_OP("%d", buffer.block[1].len, ==, (int)sizeof(blk1)-1, "%s", call); 1389 | TEST_OP("%d", buffer.block[1].nlines, ==, 1, "%s", call); 1390 | TEST_MEMCMP_OP(buffer.block[1].p->buf, ==, blk1, buffer.block[1].len, "%s", call); 1391 | TEST_OP("%d", range.start.blk, ==, 0, "%s", call); 1392 | TEST_OP("%d", range.start.off, ==, 64 * 7, "%s", call); 1393 | TEST_OP("%d", range.end.blk, ==, 0, "%s", call); 1394 | TEST_OP("%d", range.end.off, ==, range.start.off + (int)sizeof(mod)-1, "%s", call); 1395 | } 1396 | 1397 | buffer_free(&buffer); 1398 | return 0; 1399 | } 1400 | 1401 | int 1402 | buffer_read_fd(buffer_t *buffer, range_t *rng, int fd) 1403 | { 1404 | struct iovec iov[8]; 1405 | // one extra block may be needed for the tail of the selection 1406 | block_t blk[LEN(iov)+1]; 1407 | 1408 | for(unsigned i = 0; i < LEN(blk); i++) { 1409 | // FIXME: check for NULL / xmalloc 1410 | blk[i].p = xmalloc(1, BLOCK_SIZE); 1411 | blk[i].len = 0; 1412 | blk[i].nlines = 0; 1413 | } 1414 | 1415 | // headSEL SEL SELtail 1416 | // copy the head of the first selected block 1417 | int nblk = block_append(blk, 1, LEN(blk), buffer->block[rng->start.blk].p->buf, rng->start.off); 1418 | iov[0].iov_base = &blk[0].p->buf[rng->start.off]; 1419 | iov[0].iov_len = BLOCK_SIZE - rng->start.off; 1420 | 1421 | for(unsigned i = 1; i < LEN(iov); i++) { 1422 | iov[i].iov_base = blk[i].p->buf; 1423 | iov[i].iov_len = BLOCK_SIZE; 1424 | } 1425 | ssize_t len = readv(fd, iov, LEN(iov)); 1426 | 1427 | if(len >= 0) { 1428 | int i; 1429 | nblk = LEN_TO_NBLOCKS(len); 1430 | for(i = 0; i < nblk-1; i++) { 1431 | blk[i].len = BLOCK_SIZE; 1432 | } 1433 | blk[i].len = len % BLOCK_SIZE; 1434 | } else { 1435 | nblk = 0; 1436 | } 1437 | 1438 | return buffer_read_blocks(buffer, rng, blk, nblk, LEN(blk), len); 1439 | } 1440 | 1441 | static size_t 1442 | count_chr(const void *buf, int c, size_t len) 1443 | { 1444 | size_t n = 0; 1445 | const char *pbuf = buf; 1446 | const char *orig_buf = buf; 1447 | const char *next; 1448 | for(;;) { 1449 | next = memchr(pbuf, c, len - (pbuf - orig_buf)); 1450 | if(next == NULL) { 1451 | break; 1452 | } 1453 | n++; 1454 | pbuf = next; 1455 | pbuf++; 1456 | } 1457 | return n; 1458 | } 1459 | 1460 | int 1461 | TEST_count_chr(void) 1462 | { 1463 | char call[BUFSIZ]; 1464 | char buf[] = " a a a"; 1465 | size_t ret; 1466 | 1467 | ret = TEST_CALL(call, sizeof(call), "\"%s\", '%c', %zu", 1468 | count_chr, (buf, 'a', sizeof(buf)-1)); 1469 | TEST_OP("%zu", ret, ==, (size_t)3, call); 1470 | 1471 | ret = TEST_CALL(call, sizeof(call), "\"%s\", '%c', %zu", 1472 | count_chr, (buf+1, 'a', sizeof(buf)-2)); 1473 | TEST_OP("%zu", ret, ==, (size_t)3, call); 1474 | return 0; 1475 | } 1476 | 1477 | static size_t 1478 | index_nrchr(const void *buf, int c, size_t len, size_t nr) 1479 | { 1480 | size_t n = 0; 1481 | const char *pbuf = buf; 1482 | const char *chr = NULL; 1483 | const char *orig_buf = buf; 1484 | 1485 | for(; ( pbuf = memchr(pbuf, c, len - (pbuf - orig_buf)) ); n++) { 1486 | if(n == nr) { 1487 | chr = pbuf; 1488 | break; 1489 | } 1490 | pbuf++; 1491 | } 1492 | 1493 | assert(chr != NULL); 1494 | return chr - orig_buf; 1495 | } 1496 | 1497 | int 1498 | TEST_index_nrchr(void) 1499 | { 1500 | char call[BUFSIZ]; 1501 | size_t ret; 1502 | char buf[] = " a a a"; 1503 | 1504 | ret = TEST_CALL(call, sizeof(call), "\"%s\", '%c', %zu, %zu", 1505 | index_nrchr, (buf, 'a', sizeof(buf)-1, (size_t)0)); 1506 | TEST_OP("%zu", ret, ==, (size_t)1, "%s", call); 1507 | 1508 | ret = TEST_CALL(call, sizeof(call), "\"%s\", '%c', %zu, %zu", 1509 | index_nrchr, (buf, 'a', sizeof(buf)-1, (size_t)2)); 1510 | TEST_OP("%zu", ret, ==, (size_t)5, "%s", call); 1511 | 1512 | ret = TEST_CALL(call, sizeof(call), "\"%s\", '%c', %zu, %zu", 1513 | index_nrchr, (buf+1, 'a', sizeof(buf)-2, (size_t)0)); 1514 | TEST_OP("%zu", ret, ==, (size_t)0, "%s", call); 1515 | 1516 | ret = TEST_CALL(call, sizeof(call), "\"%s\", '%c', %zu, %zu", 1517 | index_nrchr, (buf+1, 'a', sizeof(buf)-2, (size_t)2)); 1518 | TEST_OP("%zu", ret, ==, (size_t)4, "%s", call); 1519 | return 0; 1520 | } 1521 | 1522 | // it will write to the first block the head of the selection 1523 | // it expects extra unused block at the end? 1524 | int 1525 | buffer_read_blocks(buffer_t *buffer, range_t *rng, block_t *blk, int nmod, const int maxblk, int len) 1526 | { 1527 | if(len < 0) { 1528 | goto out; 1529 | } 1530 | 1531 | int nsel = rng->end.blk - rng->start.blk + 1; 1532 | 1533 | // prepare the new end 1534 | address_t new_end = {rng->start.blk + nmod - 1, blk[nmod - 1].len}; 1535 | 1536 | // SELtail 1537 | // copy the tail of the last selected block 1538 | block_t *last_sel = &buffer->block[rng->end.blk]; 1539 | int tail_len = last_sel->len - rng->end.off; 1540 | nmod = block_append(blk, nmod, maxblk, 1541 | &last_sel->p->buf[rng->end.off], tail_len 1542 | ); 1543 | 1544 | // if last modified block would be too small 1545 | if(rng->end.blk < buffer->nblocks-1 && 1546 | blk[nmod-1].len < BLOCK_SIZE/2 1547 | ) { 1548 | // take some from the next block (after the selected one) 1549 | block_t *next = &buffer->block[rng->end.blk+1]; 1550 | 1551 | if(blk[nmod-1].len + next->len > BLOCK_SIZE) { 1552 | int next_back_len = (blk[nmod-1].len + next->len) / 2; 1553 | int next_front_len = next->len - next_back_len; 1554 | // take the front of the next block 1555 | nmod = block_append(blk, nmod, maxblk, 1556 | next->p->buf, next_front_len 1557 | ); 1558 | // shift the back of the next block 1559 | memmove( 1560 | next->p->buf, 1561 | &next->p->buf[next_front_len], 1562 | next_back_len 1563 | ); 1564 | next->len = next_back_len; 1565 | next->nlines = count_chr(next->p->buf, '\n', next->len); 1566 | } else { 1567 | // join the next block 1568 | nmod = block_append(blk, nmod, maxblk, 1569 | next->p->buf, next->len 1570 | ); 1571 | // FIXME: undo does not need the next buffer, for now it would be copied because nsel is incremented 1572 | next->len = 0; 1573 | next->nlines = 0; 1574 | nsel++; 1575 | } 1576 | } 1577 | 1578 | // TODO: undo 1579 | 1580 | int sel_end = rng->start.blk + nsel; 1581 | int mod_end = rng->start.blk + nmod; 1582 | 1583 | if(nmod < nsel) { 1584 | for(int i = mod_end; i < sel_end; i++) { 1585 | free(buffer->block[i].p); 1586 | } 1587 | blockmove( 1588 | &buffer->block[mod_end], 1589 | &buffer->block[sel_end], 1590 | buffer->nblocks - sel_end 1591 | ); 1592 | } 1593 | 1594 | buffer->block = xreallocarray(buffer->block, 1595 | buffer->nblocks - nsel + nmod, sizeof(buffer->block[0]) 1596 | ); 1597 | 1598 | if(nmod > nsel) { 1599 | blockmove( 1600 | &buffer->block[mod_end], 1601 | &buffer->block[sel_end], 1602 | buffer->nblocks - sel_end 1603 | ); 1604 | for(int i = sel_end; i < mod_end; i++) { 1605 | buffer->block[i].len = 0; 1606 | buffer->block[i].nlines = 0; 1607 | buffer->block[i].p = xmalloc(1, BLOCK_SIZE); 1608 | } 1609 | } 1610 | 1611 | buffer->nblocks = buffer->nblocks - nsel + nmod; 1612 | 1613 | int mod_nlines_new = 0; 1614 | int mod_nlines_old = 0; 1615 | 1616 | for(int i = 0; i < nmod; i++) { 1617 | blk[i].nlines = count_chr(blk[i].p->buf, '\n', blk[i].len); 1618 | mod_nlines_new += blk[i].nlines; 1619 | 1620 | mod_nlines_old += buffer->block[rng->start.blk + i].nlines; 1621 | free(buffer->block[rng->start.blk + i].p); 1622 | } 1623 | memcpy(&buffer->block[rng->start.blk], blk, nmod * sizeof(blk[0])); 1624 | 1625 | buffer->nlines += mod_nlines_new - mod_nlines_old; 1626 | rng->end = new_end; 1627 | 1628 | out: 1629 | for(int i = nmod; i < maxblk; i++) { 1630 | free(blk[i].p); 1631 | } 1632 | return len; 1633 | } 1634 | 1635 | int 1636 | buffer_write_fd(buffer_t *buffer, range_t *rng, int fd) 1637 | { 1638 | struct iovec iov[8]; 1639 | int nsel = rng->end.blk - rng->start.blk + 1; 1640 | int niov = MIN(nsel, (int)LEN(iov)); 1641 | 1642 | iov[0].iov_base = &buffer->block[rng->start.blk].p->buf[rng->start.off]; 1643 | if(nsel == 1) { 1644 | iov[0].iov_len = rng->end.off - rng->start.off; 1645 | } else { 1646 | iov[0].iov_len = buffer->block[rng->start.blk].len - rng->start.off; 1647 | } 1648 | for(int i = 1; i < niov; i++) { 1649 | iov[i].iov_base = buffer->block[rng->start.blk + i].p->buf; 1650 | iov[i].iov_len = buffer->block[rng->start.blk + i].len; 1651 | } 1652 | if(nsel > 1 && nsel == niov) { 1653 | iov[nsel-1].iov_len = rng->end.off; 1654 | } 1655 | ssize_t len = writev(fd, iov, niov); 1656 | if(len < 0) { 1657 | return -1; 1658 | } 1659 | 1660 | int rest = len; 1661 | for(int i = 0; i < niov; i++) { 1662 | rest -= iov[i].iov_len; 1663 | if(rest <= 0) { 1664 | rng->start.blk += i; 1665 | if(i == 0) { 1666 | rest += rng->start.off; 1667 | } 1668 | rng->start.off = iov[i].iov_len + rest; 1669 | return len; 1670 | } 1671 | } 1672 | 1673 | // error? 1674 | return -2; 1675 | } 1676 | 1677 | int64_t 1678 | buffer_address_move_off(buffer_t *buffer, address_t *adr, int64_t move); 1679 | void 1680 | buffer_address_move_lines(buffer_t *buffer, address_t *adr, int64_t move); 1681 | void 1682 | buffer_nr_to_address(buffer_t *buffer, int64_t nr, address_t *adr); 1683 | void 1684 | buffer_nr_off_to_address(buffer_t *buffer, int64_t nr, int64_t off, address_t *adr); 1685 | static int 1686 | block_count_nl(char *buf, int len, int *nl_off); 1687 | void 1688 | buffer_address_to_nr_off(buffer_t *buffer, address_t *adr, int64_t *nr, int64_t *off); 1689 | 1690 | int 1691 | TEST_blocks(void) 1692 | { 1693 | buffer_t buf = {0}; 1694 | buffer_init(&buf, 1); 1695 | range_t rng = {0}; 1696 | int fd; 1697 | int len; 1698 | 1699 | fd = open("font.c", O_RDONLY); 1700 | if(fd < 0) { 1701 | perror("open"); 1702 | return -1; 1703 | } 1704 | do { 1705 | len = buffer_read_fd(&buf, &rng, fd); 1706 | rng.start = rng.end; 1707 | } while(len > 0); 1708 | 1709 | { 1710 | size_t len = 0; 1711 | size_t nl = 0; 1712 | for(int i = 0; i < buf.nblocks; i++) { 1713 | len += buf.block[i].len; 1714 | nl += buf.block[i].nlines; 1715 | } 1716 | fprintf(stderr, "len %zu nl %zu nb %d\n", len, nl, buf.nblocks); 1717 | fprintf(stderr, ".nl %ld\n", buf.nlines); 1718 | } 1719 | 1720 | // replace on a block boundary 1721 | rng = (range_t){{0, BLOCK_SIZE - 10}, {1, 10}}; 1722 | buffer_read(&buf, &rng, "dUPa", 4); 1723 | 1724 | for(int i = 0; i < buf.nblocks; i++) { 1725 | fprintf(stderr, "%d: %d %d\n", i, buf.block[i].len, buf.block[i].nlines); 1726 | } 1727 | fprintf(stderr, ".nl %ld\n", buf.nlines); 1728 | 1729 | // more blocks 1730 | // less blocks 1731 | 1732 | rng.start = (address_t){0, 0}; 1733 | rng.end = (address_t){buf.nblocks-1, buf.block[buf.nblocks-1].len}; 1734 | /* 1735 | do { 1736 | len = buffer_write_fd(&buf, &rng, 1); 1737 | } while(len > 0); 1738 | */ 1739 | 1740 | int64_t nr, off; 1741 | buffer_address_to_nr_off(&buf, &rng.end, &nr, &off); 1742 | fprintf(stderr, "adr %d %d n/o %ld %ld\n", 1743 | rng.end.blk, rng.end.off, 1744 | nr, off); 1745 | 1746 | buffer_free(&buf); 1747 | return 0; 1748 | } 1749 | 1750 | int64_t 1751 | buffer_address_move_off(buffer_t *buffer, address_t *adr, int64_t move) 1752 | { 1753 | assert(move >= 0); 1754 | 1755 | for(int i = adr->blk; i < buffer->nblocks; i++) { 1756 | int rest = buffer->block[i].len - adr->off; 1757 | if(rest >= move) { 1758 | adr->blk = i; 1759 | adr->off += move; 1760 | return 0; 1761 | } 1762 | move -= rest; 1763 | adr->off = 0; 1764 | } 1765 | adr->off = buffer->block[buffer->nblocks-1].len; 1766 | // move at this point will be > 0 1767 | // as it's a reminder over the end of file 1768 | return move; 1769 | } 1770 | 1771 | int 1772 | TEST_buffer_address_move_off(void) 1773 | { 1774 | char call[BUFSIZ]; 1775 | int64_t ret; 1776 | 1777 | block_t blks[] = {{ 1778 | .len = 2048 1779 | }, { 1780 | .len = 3000 1781 | }, { 1782 | .len = 2500 1783 | }}; 1784 | buffer_t buffer = { 1785 | .nblocks = LEN(blks), 1786 | .block = blks 1787 | }; 1788 | { 1789 | address_t adr = {0, 2047}; 1790 | 1791 | ret = TEST_CALL(call, sizeof(call), "%p, %p, %ld", 1792 | buffer_address_move_off, ((void*)&buffer, (void*)&adr, (int64_t)1)); 1793 | TEST_OP("%ld", ret, ==, (int64_t)0, "%s", call); 1794 | TEST_OP("%d", adr.blk, ==, 0, "%s", call); 1795 | TEST_OP("%d", adr.off, ==, 2048, "%s", call); 1796 | 1797 | ret = TEST_CALL(call, sizeof(call), "%p, %p, %ld", 1798 | buffer_address_move_off, ((void*)&buffer, (void*)&adr, (int64_t)1)); 1799 | TEST_OP("%ld", ret, ==, (int64_t)0, "%s", call); 1800 | TEST_OP("%d", adr.blk, ==, 1, "%s", call); 1801 | TEST_OP("%d", adr.off, ==, 1, "%s", call); 1802 | } 1803 | 1804 | { 1805 | address_t adr = {0, 2000}; 1806 | ret = TEST_CALL(call, sizeof(call), "%p, %p, %ld", 1807 | buffer_address_move_off, ((void*)&buffer, (void*)&adr, (int64_t)49)); 1808 | TEST_OP("%ld", ret, ==, (int64_t)0, "%s", call); 1809 | TEST_OP("%d", adr.blk, ==, 1, "%s", call); 1810 | TEST_OP("%d", adr.off, ==, 1, "%s", call); 1811 | } 1812 | 1813 | { 1814 | address_t adr = {0, 0}; 1815 | ret = TEST_CALL(call, sizeof(call), "%p, %p, %ld", 1816 | buffer_address_move_off, ((void*)&buffer, (void*)&adr, (int64_t)(2048+3000+512))); 1817 | TEST_OP("%ld", ret, ==, (int64_t)0, "%s", call); 1818 | TEST_OP("%d", adr.blk, ==, 2, "%s", call); 1819 | TEST_OP("%d", adr.off, ==, 512, "%s", call); 1820 | ret = TEST_CALL(call, sizeof(call), "%p, %p, %ld", 1821 | buffer_address_move_off, ((void*)&buffer, (void*)&adr, (int64_t)(2000))); 1822 | TEST_OP("%ld", ret, ==, (int64_t)12, "%s", call); 1823 | TEST_OP("%d", adr.blk, ==, 2, "%s", call); 1824 | TEST_OP("%d", adr.off, ==, 2500, "%s", call); 1825 | } 1826 | 1827 | return 0; 1828 | } 1829 | 1830 | void 1831 | buffer_address_move_lines(buffer_t *buffer, address_t *adr, int64_t move) 1832 | { 1833 | (void)move; 1834 | 1835 | char *nl; 1836 | 1837 | if(buffer->block[adr->blk].nlines != 0 && 1838 | (nl = memchr( 1839 | &buffer->block[adr->blk].p->buf[adr->off], 1840 | '\n', buffer->block[adr->blk].len - adr->off)) != NULL 1841 | ) { 1842 | adr->off = nl - buffer->block[adr->blk].p->buf; 1843 | buffer_address_move_off(buffer, adr, 1); 1844 | return; 1845 | } 1846 | 1847 | int i; 1848 | for(i = adr->blk + 1; i < buffer->nblocks && 1849 | buffer->block[i].nlines == 0; i++); 1850 | 1851 | if(i >= buffer->nblocks) { 1852 | // no next new lines 1853 | return; 1854 | } 1855 | 1856 | adr->blk = i; 1857 | nl = memchr(buffer->block[i].p->buf, '\n', buffer->block[i].len); 1858 | adr->off = nl - buffer->block[i].p->buf; 1859 | buffer_address_move_off(buffer, adr, 1); 1860 | } 1861 | 1862 | void 1863 | buffer_nr_to_address(buffer_t *buffer, int64_t nr, address_t *adr) 1864 | { 1865 | // OPTIM?: search backwards if nr > buffer->nlines/2 1866 | 1867 | if(nr == 0) { 1868 | adr->blk = 0; 1869 | adr->off = 0; 1870 | return; 1871 | } 1872 | 1873 | int64_t sofar = 0; 1874 | 1875 | for(int i = 0; i < buffer->nblocks; i++) { 1876 | sofar += buffer->block[i].nlines; 1877 | if(nr < sofar) { 1878 | adr->blk = i; 1879 | adr->off = index_nrchr(buffer->block[i].p->buf, '\n', buffer->block[i].len, sofar - nr); 1880 | buffer_address_move_off(buffer, adr, 1); 1881 | return; 1882 | } 1883 | } 1884 | } 1885 | 1886 | void 1887 | buffer_nr_off_to_address(buffer_t *buffer, int64_t nr, int64_t off, address_t *adr) 1888 | { 1889 | buffer_nr_to_address(buffer, nr, adr); 1890 | buffer_address_move_off(buffer, adr, off); 1891 | } 1892 | 1893 | static int 1894 | block_count_nl(char *buf, int len, int *nl_off) 1895 | { 1896 | int count; 1897 | char *nl; 1898 | 1899 | count = count_chr(buf, '\n', len); 1900 | if(count == 0) { 1901 | return 0; 1902 | } 1903 | 1904 | nl = memrchr(buf, '\n', len); 1905 | *nl_off = nl - buf; 1906 | if(*nl_off == len - 1) { 1907 | count--; 1908 | nl = memrchr(buf, '\n', *nl_off); 1909 | *nl_off = nl - buf; 1910 | if(nl == NULL) { 1911 | return 0; 1912 | } 1913 | } 1914 | return count; 1915 | } 1916 | 1917 | void 1918 | buffer_address_to_nr_off(buffer_t *buffer, address_t *adr, int64_t *nr, int64_t *off) 1919 | { 1920 | int i = 0; 1921 | char *nl; 1922 | int nl_off; 1923 | *nr = 0; 1924 | 1925 | for(; i < adr->blk; i++) { 1926 | *nr += buffer->block[i].nlines; 1927 | } 1928 | int count; 1929 | if(buffer->block[i].nlines > 0 && 1930 | (count = block_count_nl(buffer->block[i].p->buf, adr->off+1, &nl_off)) > 0 1931 | ) { 1932 | *nr += count; 1933 | *off = adr->off - (nl_off + 1); 1934 | } else { 1935 | *off = adr->off; 1936 | for(i--; i >=0 && buffer->block[i].nlines == 0; i--) { 1937 | *off += buffer->block[i].len; 1938 | } 1939 | if(i >= 0) { 1940 | nl = memrchr(buffer->block[i].p->buf, '\n', buffer->block[i].len); 1941 | nl_off = nl - buffer->block[i].p->buf; 1942 | *off += buffer->block[i].len - (nl_off + 1); 1943 | } 1944 | } 1945 | } 1946 | 1947 | /* 1948 | nr and offset to buffer address 1949 | // Get a block position for an address in a buffer 1950 | // Potential optimalizations: 1951 | // - begin search from block index and line number 1952 | // could be useful having just return a block index for a line number 1953 | // additional parameters: uint32_t start_block, uint64_t start_nr 1954 | // - search from the end for adr->nr > buffer->nlines/2 1955 | */ 1956 | 1957 | -------------------------------------------------------------------------------- /cmd/Copy: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -n "$werf_control_W" ]; then 3 | echo disregard > $werf_control_W 4 | echo finish > $werf_control_W 5 | fi 6 | 7 | exec xclip -i > /dev/null 8 | -------------------------------------------------------------------------------- /cmd/Cut: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -n "$werf_control_W" ]; then 3 | echo finish > $werf_control_W 4 | fi 5 | 6 | exec xclip -i > /dev/null 7 | -------------------------------------------------------------------------------- /cmd/Paste: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | xclip -o 3 | -------------------------------------------------------------------------------- /command.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "util.h" 6 | #include "array.h" 7 | 8 | #include "edit.h" 9 | #include "view.h" 10 | 11 | void 12 | command_undo(view_t *v) 13 | { 14 | file_undo(&v->range); 15 | v->last_x = view_address_to_x(v, &v->range.start); 16 | } 17 | 18 | void 19 | command_redo(view_t *v) 20 | { 21 | file_redo(&v->range); 22 | v->last_x = view_address_to_x(v, &v->range.start); 23 | } 24 | 25 | void 26 | command_page_up(view_t *v) 27 | { 28 | view_move_start(v, -v->nmemb); 29 | } 30 | 31 | void 32 | command_page_down(view_t *v) 33 | { 34 | view_move_start(v, v->nmemb); 35 | } 36 | 37 | void 38 | command_home(view_t *v) 39 | { 40 | v->range.start.offset = 0; 41 | v->range.end.offset = v->range.start.offset; 42 | v->last_x = view_address_to_x(v, &v->range.start); 43 | } 44 | 45 | void 46 | command_end(view_t *v) 47 | { 48 | glyphs_t *gl = view_get_glyphs(v, v->range.start.line); 49 | v->range.start.offset = gl->glyph_to_offset[gl->nmemb - 1]; 50 | v->range.end.offset = v->range.start.offset; 51 | v->last_x = view_address_to_x(v, &v->range.start); 52 | } 53 | 54 | void 55 | command_backspace(view_t *v) 56 | { 57 | optype_t type = OP_Replace; 58 | if(!address_cmp(&v->range.start, &v->range.end)) { 59 | view_move_address(v, &v->range.start, -1); 60 | type = OP_BackSpace; 61 | } 62 | range_push(&v->range, "", 0, type); 63 | v->last_x = view_address_to_x(v, &v->range.start); 64 | } 65 | 66 | void 67 | command_delete(view_t *v) 68 | { 69 | optype_t type = OP_Replace; 70 | if(!address_cmp(&v->range.start, &v->range.end)) { 71 | view_move_address(v, &v->range.end, 1); 72 | type = OP_Delete; 73 | } 74 | range_push(&v->range, "", 0, type); 75 | v->last_x = view_address_to_x(v, &v->range.start); 76 | } 77 | 78 | void 79 | command_new_line(view_t *v) 80 | { 81 | range_push(&v->range, "\n", 1, OP_Char); 82 | v->last_x = view_address_to_x(v, &v->range.start); 83 | } 84 | 85 | void 86 | command_left(view_t *v) 87 | { 88 | if(!address_cmp(&v->range.start, &v->range.end)) { 89 | view_move_address(v, &v->range.start, -1); 90 | } 91 | v->range.end = v->range.start; 92 | v->last_x = view_address_to_x(v, &v->range.start); 93 | } 94 | 95 | void 96 | command_right(view_t *v) 97 | { 98 | if(!address_cmp(&v->range.start, &v->range.end)) { 99 | view_move_address(v, &v->range.start, 1); 100 | } 101 | v->range.end = v->range.start; 102 | v->last_x = view_address_to_x(v, &v->range.start); 103 | } 104 | 105 | void 106 | command_up(view_t *v) 107 | { 108 | if(!address_cmp(&v->range.start, &v->range.end)) { 109 | view_move_address_line(v, &v->range.start, -1); 110 | } 111 | v->range.end = v->range.start; 112 | } 113 | 114 | void 115 | command_down(view_t *v) 116 | { 117 | if(!address_cmp(&v->range.start, &v->range.end)) { 118 | view_move_address_line(v, &v->range.start, 1); 119 | } 120 | v->range.end = v->range.start; 121 | } 122 | -------------------------------------------------------------------------------- /command.h: -------------------------------------------------------------------------------- 1 | void command_undo(view_t *v); 2 | void command_redo(view_t *v); 3 | void command_page_up(view_t *v); 4 | void command_page_down(view_t *v); 5 | void command_home(view_t *v); 6 | void command_end(view_t *v); 7 | void command_backspace(view_t *v); 8 | void command_delete(view_t *v); 9 | void command_new_line(view_t *v); 10 | void command_left(view_t *v); 11 | void command_right(view_t *v); 12 | void command_up(view_t *v); 13 | void command_down(view_t *v); 14 | -------------------------------------------------------------------------------- /draw.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "util.h" 6 | #include "array.h" 7 | 8 | #include "edit.h" 9 | #include "view.h" 10 | #include "draw.h" 11 | 12 | void 13 | draw_cursor(cairo_t *cr, view_t *v, address_t *adr) 14 | { 15 | double x = view_address_to_x(v, adr); 16 | 17 | cairo_save(cr); 18 | cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); 19 | 20 | double ds = v->extents.descent; 21 | if(ds > 0) { 22 | ds--; 23 | } 24 | 25 | cairo_move_to(cr, x, 1); 26 | cairo_line_to(cr, x, v->line_height - 1); 27 | 28 | cairo_move_to(cr, x-ds*0.5, 1); 29 | cairo_line_to(cr, x+ds*0.5, 1); 30 | 31 | cairo_move_to(cr, x-ds*0.5, v->line_height - 1); 32 | cairo_line_to(cr, x+ds*0.5, v->line_height - 1); 33 | 34 | cairo_set_line_width(cr, 1); 35 | cairo_stroke(cr); 36 | 37 | cairo_restore(cr); 38 | } 39 | 40 | void 41 | draw_line(cairo_t *cr, view_t *v, size_t nr) 42 | { 43 | glyphs_t *gl = view_get_glyphs(v, nr); 44 | 45 | cairo_save(cr); 46 | 47 | cairo_translate(cr, 0, view_line_to_y(v, nr)); 48 | 49 | double sel_s = 0; 50 | double sel_e = 0; 51 | 52 | if(nr == v->range.start.line) { 53 | sel_s = view_address_to_x(v, &v->range.start); 54 | } 55 | if(nr == v->range.end.line) { 56 | sel_e = view_address_to_x(v, &v->range.end); 57 | } else if(nr >= v->range.start.line && nr < v->range.end.line) { 58 | sel_e = v->width; 59 | } 60 | 61 | cairo_save(cr); 62 | cairo_set_source_rgb(cr, 0.625, 0.75, 1); 63 | cairo_rectangle(cr, sel_s, 0, sel_e - sel_s, v->line_height); 64 | cairo_clip(cr); 65 | cairo_paint(cr); 66 | cairo_restore(cr); 67 | 68 | if(nr == v->range.start.line && !address_cmp(&v->range.start, &v->range.end)) { 69 | draw_cursor(cr, v, &v->range.start); 70 | } 71 | 72 | cairo_translate(cr, v->left_margin, v->extents.ascent); 73 | cairo_show_glyphs(cr, gl->data, gl->nmemb); 74 | 75 | cairo_restore(cr); 76 | } 77 | 78 | void 79 | draw_button(cairo_t *cr, view_t *v, button_t *btn) 80 | { 81 | cairo_translate(cr, v->left_margin, 0); 82 | 83 | cairo_save(cr); 84 | { 85 | cairo_translate(cr, 0, v->extents.ascent); 86 | cairo_show_glyphs(cr, btn->glyphs.data, btn->glyphs.nmemb); 87 | } 88 | cairo_restore(cr); 89 | 90 | cairo_translate(cr, btn->glyphs.data[btn->glyphs.nmemb - 1].x + 91 | v->left_margin, 0); 92 | 93 | cairo_save(cr); 94 | { 95 | cairo_set_source_rgb(cr, 0.4375, 0.375, 0.25); 96 | cairo_move_to(cr, 0, 0); 97 | cairo_line_to(cr, 0, v->line_height); 98 | cairo_set_line_width(cr, 1); 99 | cairo_stroke(cr); 100 | } 101 | cairo_restore(cr); 102 | } 103 | 104 | void 105 | draw_toolbar(cairo_t *cr, view_t *v, toolbar_t *bar, double y) 106 | { 107 | cairo_save(cr); 108 | { 109 | cairo_translate(cr, 0, y); 110 | 111 | cairo_save(cr); 112 | { 113 | cairo_set_source_rgb(cr, 0.875, 0.75, 0.5); 114 | cairo_rectangle(cr, 0, 0, v->width, v->line_height); 115 | cairo_clip(cr); 116 | cairo_paint(cr); 117 | } 118 | cairo_restore(cr); 119 | 120 | double s = 0.625; 121 | cairo_matrix_t mat; 122 | cairo_get_font_matrix(cr, &mat); 123 | cairo_set_font_size(cr, mat.xx * s); 124 | 125 | cairo_save(cr); 126 | for(size_t i = 0; i < bar->buttons.nmemb; i++) { 127 | draw_button(cr, v, &bar->buttons.data[i]); 128 | } 129 | cairo_restore(cr); 130 | 131 | cairo_save(cr); 132 | { 133 | cairo_pattern_t *pat = cairo_pattern_create_linear(0.0, 0.0, 0.0, v->line_height); 134 | cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 0.25); 135 | cairo_pattern_add_color_stop_rgba(pat, 0.25, 0, 0, 0, 0); 136 | cairo_pattern_add_color_stop_rgba(pat, 1-0.25, 0, 0, 0, 0); 137 | cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, 0.25); 138 | cairo_rectangle(cr, 0, 0, v->width, v->line_height); 139 | cairo_set_source(cr, pat); 140 | cairo_fill(cr); 141 | cairo_pattern_destroy(pat); 142 | } 143 | cairo_restore(cr); 144 | } 145 | cairo_restore(cr); 146 | 147 | cairo_translate(cr, 0, v->line_height); 148 | } 149 | 150 | void 151 | draw_view(cairo_t *cr, view_t *v) 152 | { 153 | if(!v->nmemb) { 154 | return; 155 | } 156 | size_t start = clampss(v->start, 0, v->range.file->content.nmemb - 1); 157 | size_t end = view_clamp_start(v, v->start + v->nmemb - 1); 158 | 159 | if(start == 0) { 160 | cairo_save(cr); 161 | cairo_set_source_rgb(cr, 0.4375, 0.375, 0.25); 162 | cairo_rectangle(cr, 0, 0, v->width, view_line_to_y(v, start)); 163 | cairo_clip(cr); 164 | cairo_paint(cr); 165 | cairo_restore(cr); 166 | } 167 | if(end == v->range.file->content.nmemb-1) { 168 | cairo_save(cr); 169 | cairo_set_source_rgb(cr, 0.4375, 0.375, 0.25); 170 | cairo_rectangle(cr, 0, view_line_to_y(v, end), v->width, v->height); 171 | cairo_clip(cr); 172 | cairo_paint(cr); 173 | cairo_restore(cr); 174 | } 175 | 176 | size_t i = start; 177 | for(; i <= end; i++) { 178 | if(v->selbar_wrap.visible && i == v->selbar_wrap.line) { 179 | draw_toolbar(cr, v, &v->selbar_wrap.bar, view_line_to_y(v, v->selbar_wrap.line)); 180 | } 181 | draw_line(cr, v, i); 182 | } 183 | if(v->selbar_wrap.visible && i == v->selbar_wrap.line) { 184 | draw_toolbar(cr, v, &v->selbar_wrap.bar, view_line_to_y(v, v->selbar_wrap.line)); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /draw.h: -------------------------------------------------------------------------------- 1 | void draw_cursor(cairo_t *cr, view_t *v, address_t *adr); 2 | void draw_line(cairo_t *cr, view_t *v, size_t nr); 3 | void draw_button(cairo_t *cr, view_t *v, button_t *btn); 4 | void draw_toolbar(cairo_t *cr, view_t *v, toolbar_t *bar, double y); 5 | void draw_view(cairo_t *cr, view_t *v); 6 | -------------------------------------------------------------------------------- /edit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "util.h" 11 | #include "array.h" 12 | 13 | #include "edit.h" 14 | #include "utf.h" 15 | 16 | void 17 | file_insert_line(file_t *f, size_t line, char *buf, size_t buf_len) 18 | { 19 | ARR_FRAG_RESIZE(&f->content, line, line, 1); 20 | 21 | memset(f->content.data + line, 0, sizeof f->content.data[0]); 22 | ARR_RESIZE(&f->content.data[line], buf_len); 23 | 24 | memcpy(f->content.data[line].data, buf, buf_len); 25 | } 26 | 27 | void 28 | file_free(file_t *f) 29 | { 30 | ARR_FRAG_APPLY(&f->content, 0, f->content.nmemb, (array_memb_func_t)array_free); 31 | ARR_FREE(&f->content); 32 | } 33 | 34 | int 35 | address_cmp(address_t *a1, address_t *a2) 36 | { 37 | if(a1->line == a2->line && a1->offset == a2->offset) { 38 | return 0; 39 | } 40 | if( a1->line < a2->line || (a1->line == a2->line && a1->offset < a2->offset) ) { 41 | return -1; 42 | } else { 43 | return 1; 44 | } 45 | } 46 | 47 | int 48 | range_from_addresses(range_t *rng, address_t *a1, address_t *a2) 49 | { 50 | int cmp = address_cmp(a1, a2); 51 | if(cmp < 0) { 52 | rng->start = *a1; 53 | rng->end = *a2; 54 | } else { 55 | rng->start = *a2; 56 | rng->end = *a1; 57 | } 58 | return cmp; 59 | } 60 | 61 | void 62 | range_fix_start(range_t *rng) 63 | { 64 | if(address_cmp(&rng->start, &rng->end) > 0) { 65 | rng->start = rng->end; 66 | } 67 | } 68 | 69 | void 70 | range_fix_end(range_t *rng) 71 | { 72 | if(address_cmp(&rng->start, &rng->end) > 0) { 73 | rng->end = rng->start; 74 | } 75 | } 76 | 77 | static void 78 | range_mod_line(range_t *rng, char *mod_line, size_t mod_len) 79 | { 80 | string_t *line = rng->file->content.data + rng->start.line; 81 | 82 | size_t end_offset; 83 | size_t rest_len; 84 | bool insert_new_line; 85 | 86 | if(rng->start.line == rng->end.line) { 87 | end_offset = rng->end.offset; 88 | } else { 89 | end_offset = line->nmemb; 90 | } 91 | 92 | rest_len = line->nmemb - end_offset; 93 | insert_new_line = (rest_len > 0 || rng->start.line == rng->end.line) && 94 | mod_len > 0 && mod_line[mod_len - 1] == '\n'; 95 | 96 | if(insert_new_line) { 97 | char *rest = line->data + end_offset; 98 | file_insert_line(rng->file, rng->end.line+1, rest, rest_len); 99 | line = rng->file->content.data + rng->start.line; 100 | line->nmemb -= rest_len; 101 | } 102 | 103 | ARR_FRAG_RESIZE(line, rng->start.offset, end_offset, mod_len); 104 | memcpy(line->data + rng->start.offset, mod_line, mod_len); 105 | 106 | if(mod_len > 0 && mod_line[mod_len - 1] == '\n') { 107 | rng->start.line++; 108 | rng->start.offset = 0; 109 | range_fix_end(rng); 110 | return; 111 | } 112 | 113 | rng->start.offset += mod_len; 114 | 115 | bool join_end_line = rest_len == 0 && rng->start.line+1 < rng->file->content.nmemb; 116 | if(join_end_line) { 117 | if(rng->start.line == rng->end.line) { 118 | rng->end.line++; 119 | rng->end.offset = 0; 120 | } 121 | string_t *rest_line = rng->file->content.data + rng->end.line; 122 | char *rest = rest_line->data + rng->end.offset; 123 | rest_len = rest_line->nmemb - rng->end.offset; 124 | 125 | ARR_FRAG_RESIZE(line, rng->start.offset, line->nmemb, rest_len); 126 | memcpy(line->data + rng->start.offset, rest, rest_len); 127 | 128 | ARR_FRAG_APPLY(&rng->file->content, rng->start.line+1, rng->end.line+1, 129 | (array_memb_func_t)array_free); 130 | ARR_FRAG_RESIZE(&rng->file->content, rng->start.line+1, rng->end.line+1, 0); 131 | } 132 | 133 | rng->end.line = rng->start.line; 134 | rng->end.offset = rng->start.offset; 135 | } 136 | 137 | int 138 | TEST_range_mod_line(void) { 139 | file_t file = { 0 }; 140 | file_insert_line(&file, 0, "123\n", 4); 141 | file_insert_line(&file, 1, "456\n", 4); 142 | range_t rng = { 143 | {0, 1}, {1, 2}, &file 144 | }; 145 | { 146 | char mod_line[] = "abc\n"; 147 | range_mod_line(&rng, mod_line, sizeof(mod_line)-1); 148 | } { 149 | char mod_line[] = "def"; 150 | range_mod_line(&rng, mod_line, sizeof(mod_line)-1); 151 | } 152 | assert(is_str_eq(file.content.data[0].data, file.content.data[0].nmemb, 153 | "1abc\n", 5)); 154 | assert(is_str_eq(file.content.data[1].data, file.content.data[0].nmemb, 155 | "def6\n", 5)); 156 | file_free(&file); 157 | 158 | return 0; 159 | } 160 | 161 | static void 162 | range_mod(range_t *rng, char *mod, size_t mod_len) 163 | { 164 | size_t rest = mod_len; 165 | char *next; 166 | 167 | while(rest > 0) { 168 | next = memchr(mod, '\n', rest); 169 | if(next != NULL) { 170 | next++; 171 | mod_len = next - mod; 172 | } else { 173 | mod_len = rest; 174 | } 175 | 176 | range_mod_line(rng, mod, mod_len); 177 | 178 | mod = next; 179 | rest -= mod_len; 180 | } 181 | range_mod_line(rng, "", 0); 182 | } 183 | 184 | int 185 | TEST_range_mod(void) { 186 | file_t file = { 0 }; 187 | file_insert_line(&file, 0, "123\n", 4); 188 | file_insert_line(&file, 1, "456\n", 4); 189 | range_t rng = { 190 | {0, 1}, {1, 2}, &file 191 | }; 192 | char mod_line[] = "abc\ndef"; 193 | range_mod(&rng, mod_line, sizeof(mod_line)-1); 194 | assert(is_str_eq(file.content.data[0].data, file.content.data[0].nmemb, 195 | "1abc\n", 5)); 196 | assert(is_str_eq(file.content.data[1].data, file.content.data[0].nmemb, 197 | "def6\n", 5)); 198 | file_free(&file); 199 | 200 | return 0; 201 | } 202 | 203 | int 204 | range_read(range_t *rng, int fd) 205 | { 206 | char buf[BUFSIZ]; 207 | ssize_t buf_len; 208 | while( (buf_len = read(fd, buf, sizeof buf)) ) { 209 | if(buf_len < 0) { 210 | return -1; 211 | } 212 | range_mod(rng, buf, buf_len); 213 | } 214 | 215 | return 0; 216 | } 217 | 218 | size_t 219 | range_copy(range_t *rng, char *buf, size_t bufsiz) 220 | { 221 | size_t off = rng->start.offset; 222 | size_t siz; 223 | size_t len = 0; 224 | bool normal = true; 225 | 226 | for(; len < bufsiz && rng->start.line <= rng->end.line; ) { 227 | string_t *line = &rng->file->content.data[rng->start.line]; 228 | if(rng->start.line == rng->end.line) { 229 | siz = rng->end.offset - off; 230 | } else { 231 | siz = line->nmemb - off; 232 | } 233 | if(len + siz > bufsiz) { 234 | siz = bufsiz - len; 235 | rng->start.offset = off + siz; 236 | normal = false; 237 | } 238 | 239 | memcpy(buf + len, line->data + off, siz); 240 | len += siz; 241 | off = 0; 242 | 243 | if(normal) { 244 | if(rng->start.line != rng->end.line) { 245 | rng->start.line++; 246 | } else { 247 | rng->start.offset = rng->end.offset; 248 | break; 249 | } 250 | } 251 | } 252 | 253 | return len; 254 | } 255 | 256 | int 257 | TEST_range_copy(void) { 258 | { 259 | file_t file = { 0 }; 260 | file_insert_line(&file, 0, "123\n", 4); 261 | file_insert_line(&file, 1, "456\n", 4); 262 | range_t rng = { 263 | {0, 1}, {1, 2}, &file 264 | }; 265 | char buf[BUFSIZ]; 266 | size_t len = range_copy(&rng, buf, sizeof(buf)); 267 | assert(is_str_eq(buf, len, "23\n45", 5)); 268 | assert(!address_cmp(&rng.start, &rng.end)); 269 | file_free(&file); 270 | } { 271 | string_t lines[] = { 272 | {.data = "123\n", .nmemb = 4 }, 273 | {.data = "456\n", .nmemb = 4 } 274 | }; 275 | file_t file = { .content.data = lines, .content.nmemb = 2 }; 276 | range_t rng = { 277 | {0, 1}, {1, 2}, &file 278 | }; 279 | char buf[2]; 280 | size_t len = range_copy(&rng, buf, sizeof(buf)); 281 | assert(is_str_eq(buf, len, "23", 2)); 282 | assert(!address_cmp(&rng.start, &(address_t){0, 3})); 283 | } 284 | return 0; 285 | } 286 | 287 | static void 288 | opbuf_extend(opbuf_t *u, size_t ext) 289 | { 290 | u->nsiz += ext; 291 | if(u->nsiz > u->asiz) { 292 | u->asiz = next_size(u->nsiz, 128); 293 | u->first = xrealloc(u->first, u->asiz, 1); 294 | } 295 | } 296 | 297 | static void 298 | opbuf_next(opbuf_t *u, range_t *rng, optype_t type) 299 | { 300 | op_t *last = (op_t*)((char*)u->first + u->last); 301 | if(u->first != NULL && type != OP_Replace && type == last->type && 302 | (type != OP_BackSpace ? 303 | address_cmp(&last->dst.end, &rng->start) : 304 | address_cmp(&rng->end, &last->dst.start) ) == 0 ) { 305 | return; 306 | } 307 | 308 | size_t oldsiz = u->nsiz; 309 | opbuf_extend(u, sizeof u->first[0]); 310 | 311 | op_t *next = (op_t*)((char*)u->first + oldsiz); 312 | memset(next, 0, sizeof next[0]); 313 | 314 | next->prev = u->last; 315 | next->type = type; 316 | 317 | switch(type) { 318 | case OP_BackSpace: 319 | next->src.start = rng->end; 320 | next->src.end = rng->end; 321 | break; 322 | case OP_Delete: 323 | next->src.start = rng->start; 324 | next->src.end = rng->start; 325 | break; 326 | default: 327 | next->src.start = rng->start; 328 | next->src.end = rng->end; 329 | break; 330 | } 331 | next->dst.start = rng->start; 332 | next->buf_len = 0; 333 | u->last = (char*)next - (char*)u->first; 334 | } 335 | 336 | static void 337 | range_push_mod(range_t *rng, char *mod, size_t mod_len, opbuf_t *u, optype_t type) 338 | { 339 | opbuf_next(u, rng, type); 340 | 341 | op_t *last = (op_t*)((char*)u->first + u->last); 342 | size_t off = rng->start.offset; 343 | size_t siz; 344 | for(size_t i = rng->start.line; i <= rng->end.line; i++) { 345 | string_t *line = &rng->file->content.data[i]; 346 | if(i == rng->end.line) { 347 | siz = rng->end.offset - off; 348 | } else { 349 | siz = line->nmemb - off; 350 | } 351 | 352 | opbuf_extend(u, siz); 353 | last = (op_t*)((char*)u->first + u->last); 354 | 355 | if(type != OP_BackSpace) { 356 | memcpy(last->buf + last->buf_len, line->data + off, siz); 357 | } else { 358 | memmove(last->buf + siz, last->buf, last->buf_len); 359 | memcpy(last->buf, line->data + off, siz); 360 | } 361 | last->buf_len += siz; 362 | 363 | off = 0; 364 | } 365 | 366 | range_mod(rng, mod, mod_len); 367 | 368 | if(type == OP_BackSpace || type == OP_Delete) { 369 | last->dst.start = rng->start; 370 | } 371 | last->dst.end = rng->start; 372 | 373 | rng->file->dirty = true; 374 | } 375 | 376 | void 377 | undo(opbuf_t *u, opbuf_t *r, range_t *rng) 378 | { 379 | if(u->nsiz == 0) { 380 | return; 381 | } 382 | op_t *last = (op_t*)((char*)u->first + u->last); 383 | range_t dst = {last->dst.start, last->dst.end, rng->file}; 384 | range_push_mod(&dst, last->buf, last->buf_len, r, last->type); 385 | rng->start = last->src.start; 386 | rng->end = last->src.end; 387 | 388 | u->nsiz -= sizeof last[0] + last->buf_len; 389 | u->last = last->prev; 390 | } 391 | 392 | void 393 | range_push(range_t *rng, char *mod, size_t mod_len, optype_t type) 394 | { 395 | rng->file->redobuf.nsiz = 0; 396 | rng->file->redobuf.last = 0; 397 | range_push_mod(rng, mod, mod_len, &rng->file->undobuf, type); 398 | } 399 | 400 | void 401 | file_undo(range_t *rng) 402 | { 403 | undo(&rng->file->undobuf, &rng->file->redobuf, rng); 404 | } 405 | 406 | void 407 | file_redo(range_t *rng) 408 | { 409 | undo(&rng->file->redobuf, &rng->file->undobuf, rng); 410 | } 411 | -------------------------------------------------------------------------------- /edit.h: -------------------------------------------------------------------------------- 1 | typedef ARRAY(string_t) strarray_t; 2 | 3 | typedef struct { 4 | size_t line; 5 | size_t offset; 6 | } address_t; 7 | 8 | typedef enum { 9 | OP_None, 10 | OP_BackSpace, 11 | OP_Delete, 12 | OP_Char, 13 | OP_Replace 14 | } optype_t; 15 | 16 | typedef struct { 17 | size_t prev; 18 | optype_t type; 19 | struct { 20 | address_t start; 21 | address_t end; 22 | } src, dst; 23 | size_t buf_len; 24 | char buf[]; 25 | } op_t; 26 | 27 | typedef struct { 28 | size_t nsiz; 29 | size_t asiz; 30 | op_t *first; 31 | size_t last; 32 | } opbuf_t; 33 | 34 | typedef struct { 35 | strarray_t content; 36 | opbuf_t undobuf; 37 | opbuf_t redobuf; 38 | bool dirty; 39 | } file_t; 40 | 41 | typedef struct { 42 | address_t start; 43 | address_t end; 44 | file_t *file; 45 | } range_t; 46 | 47 | void file_insert_line(file_t *f, size_t line, char *buf, size_t buf_len); 48 | void file_free(file_t *f); 49 | 50 | int address_cmp(address_t *a1, address_t *a2); 51 | 52 | int range_from_addresses(range_t *rng, address_t *a1, address_t *a2); 53 | void range_fix_start(range_t *rng); 54 | void range_fix_end(range_t *rng); 55 | int range_read(range_t *rng, int fd); 56 | size_t range_copy(range_t *rng, char *buf, size_t bufsiz); 57 | 58 | void range_push(range_t *rng, char *mod, size_t mod_len, optype_t type); 59 | 60 | void file_undo(range_t *rng); 61 | void file_redo(range_t *rng); 62 | -------------------------------------------------------------------------------- /font.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "util.h" 6 | 7 | #include "font.h" 8 | #include FT_ADVANCES_H 9 | #include FT_GLYPH_H 10 | #include FT_OUTLINE_H 11 | #include 12 | 13 | #include "utf.h" 14 | 15 | static cairo_scaled_font_t * 16 | fontset_get_font(fontset_t *f, unsigned int index) 17 | { 18 | cairo_font_face_t *face; 19 | cairo_matrix_t mat; 20 | 21 | if(f->cache[index] != NULL) { 22 | return f->cache[index]; 23 | } 24 | 25 | face = cairo_ft_font_face_create_for_pattern(f->set->fonts[index]); 26 | if(!face) { 27 | fputs("cairo_ft_font_face_create_for_pattern failed\n", stderr); 28 | return NULL; 29 | } 30 | 31 | cairo_matrix_init_identity(&mat); 32 | cairo_font_options_t *opts = cairo_font_options_create(); 33 | f->cache[index] = cairo_scaled_font_create(face, &mat, &mat, opts); 34 | cairo_font_options_destroy(opts); 35 | cairo_font_face_destroy(face); 36 | 37 | if(f->cache[index] == NULL) { 38 | fputs("cairo_scaled_font_create failed\n", stderr); 39 | return NULL; 40 | } 41 | return f->cache[index]; 42 | } 43 | 44 | int 45 | fontset_init(fontset_t *f, FcPattern *pattern) 46 | { 47 | FcResult res; 48 | 49 | FcConfigSubstitute(NULL, pattern, FcMatchPattern); 50 | FcDefaultSubstitute(pattern); 51 | FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); 52 | 53 | f->set = FcFontSort(NULL, pattern, FcTrue, NULL, &res); 54 | if(f->set == NULL) { 55 | fputs("FcFontSort failed\n", stderr); 56 | return -1; 57 | } 58 | 59 | f->cache = xcalloc(f->set->nfont, sizeof f->cache[0]); 60 | 61 | f->pattern = pattern; 62 | fontset_get_font(f, 0); 63 | return 0; 64 | } 65 | 66 | static fontidx_t 67 | fontset_match_codepoint(fontset_t *f, FcChar32 codepoint) 68 | { 69 | FcCharSet *chset; 70 | int maxlen = MIN(FONTIDX_MAX, f->set->nfont); 71 | 72 | for(int i = 0; i < maxlen; i++) { 73 | if( !FcPatternGetCharSet(f->set->fonts[i], FC_CHARSET, 0, &chset) && 74 | FcCharSetHasChar(chset, codepoint)) { 75 | return i; 76 | } 77 | } 78 | return FONTIDX_MAX; 79 | } 80 | 81 | void 82 | fontset_free(fontset_t *f) 83 | { 84 | if(f == NULL) { 85 | return; 86 | } 87 | for(int i = 0; i < f->set->nfont; i++) { 88 | if(f->cache[i]) { 89 | cairo_scaled_font_destroy(f->cache[i]); 90 | } 91 | } 92 | free(f->cache); 93 | FcFontSetDestroy(f->set); 94 | FcPatternDestroy(f->pattern); 95 | if(f->onheap) { 96 | free(f); 97 | } 98 | } 99 | 100 | /* cairo user font */ 101 | 102 | static cairo_user_data_key_t face_key; 103 | 104 | static cairo_status_t 105 | font_init(cairo_scaled_font_t *scaled_font, 106 | cairo_t *cr, cairo_font_extents_t *out_extents) 107 | { 108 | fontset_t *fontset = cairo_font_face_get_user_data( 109 | cairo_scaled_font_get_font_face(scaled_font), &face_key); 110 | 111 | cairo_set_scaled_font(cr, fontset->cache[0]); 112 | cairo_font_extents(cr, out_extents); 113 | return CAIRO_STATUS_SUCCESS; 114 | } 115 | 116 | /* 117 | 0-1-2 118 | |X|X| 119 | 3-4-5 120 | |X|X| 121 | 6-7-8 122 | */ 123 | 124 | static struct { double x, y; } idx_to_coords[] = { 125 | [0] = {.0, -1.}, 126 | [1] = {.5, -1.}, 127 | [2] = {1., -1.}, 128 | [3] = {.0, -.5}, 129 | [4] = {.5, -.5}, 130 | [5] = {1., -.5}, 131 | [6] = {.0, -.0}, 132 | [7] = {.5, -.0}, 133 | [8] = {1., -.0}, 134 | }; 135 | 136 | static char *ch_to_idx[] = { 137 | ['0' - 0x30] = "02860,26", 138 | ['1' - 0x30] = "428", 139 | ['2' - 0x30] = "025368", 140 | ['3' - 0x30] = "0286,35", 141 | ['4' - 0x30] = "035,28", 142 | ['5' - 0x30] = "203586", 143 | ['6' - 0x30] = "206853", 144 | ['7' - 0x30] = "0247", 145 | ['8' - 0x30] = "02860,35", 146 | ['9' - 0x30] = "682035", 147 | ['A' - 0x30] = "63158,35", 148 | ['B' - 0x30] = "0286,17,45", 149 | ['C' - 0x30] = "2068", 150 | ['D' - 0x30] = "0286,17", 151 | ['E' - 0x30] = "2068,35", 152 | ['F' - 0x30] = "206,35", 153 | ['G' - 0x30] = "206854", 154 | ['H' - 0x30] = "06,28,35", 155 | ['I' - 0x30] = "02,68,17", 156 | ['J' - 0x30] = "2863", 157 | ['K' - 0x30] = "06,238", 158 | ['L' - 0x30] = "068", 159 | ['M' - 0x30] = "60428", 160 | ['N' - 0x30] = "6082", 161 | ['O' - 0x30] = "02860", 162 | ['P' - 0x30] = "60253", 163 | ['Q' - 0x30] = "860284", 164 | ['R' - 0x30] = "602538", 165 | ['S' - 0x30] = "213586", 166 | ['T' - 0x30] = "02,17", 167 | ['U' - 0x30] = "0682", 168 | ['V' - 0x30] = "072", 169 | ['W' - 0x30] = "06482", 170 | ['X' - 0x30] = "08,26", 171 | ['Y' - 0x30] = "047,24", 172 | ['Z' - 0x30] = "0268" 173 | }; 174 | 175 | static char *ctrl_to_id[] = { 176 | [0x00] = "NUL", 177 | [0x01] = "SOH", 178 | [0x02] = "STX", 179 | [0x03] = "ETX", 180 | [0x04] = "EOT", 181 | [0x05] = "ENQ", 182 | [0x06] = "ACK", 183 | [0x07] = "BEL", 184 | [0x08] = "BS", 185 | [0x09] = "HT", 186 | [0x0A] = "LF", 187 | [0x0B] = "VT", 188 | [0x0C] = "FF", 189 | [0x0D] = "CR", 190 | [0x0E] = "SO", 191 | [0x0F] = "SI", 192 | [0x10] = "DLE", 193 | [0x11] = "DC1", 194 | [0x12] = "DC2", 195 | [0x13] = "DC3", 196 | [0x14] = "DC4", 197 | [0x15] = "NAK", 198 | [0x16] = "SYN", 199 | [0x17] = "ETB", 200 | [0x18] = "CAN", 201 | [0x19] = "EM", 202 | [0x1A] = "SUB", 203 | [0x1B] = "ESC", 204 | [0x1C] = "FS", 205 | [0x1D] = "GS", 206 | [0x1E] = "RS", 207 | [0x1F] = "US" 208 | }; 209 | 210 | #define SPECIAL_WIDTH 1.5 211 | 212 | static void 213 | basic_text(cairo_t *cr, const char *str, size_t len, double x, double adv, double chw, double chh) 214 | { 215 | for(size_t i = 0; i < len; i++) { 216 | char *chidx = ch_to_idx[str[i] - 0x30]; 217 | DIEIF(chidx == NULL); 218 | void (*cr_line)(cairo_t*, double, double) = cairo_move_to; 219 | for(uchar j = 0; chidx[j]; j++) { 220 | if(chidx[j] == ',') { 221 | cr_line = cairo_move_to; 222 | continue; 223 | } 224 | 225 | double crdx = idx_to_coords[chidx[j] - 0x30].x * chw; 226 | double crdy = idx_to_coords[chidx[j] - 0x30].y * chh; 227 | cr_line(cr, x + crdx, crdy); 228 | cr_line = cairo_line_to; 229 | } 230 | x += adv; 231 | } 232 | } 233 | 234 | static cairo_status_t 235 | font_render_glyph(cairo_scaled_font_t *scaled_font, 236 | unsigned long glyph, cairo_t *cr, 237 | cairo_text_extents_t *extents) 238 | { 239 | fontset_t *fontset; 240 | 241 | if(get_fontidx(glyph) == FONTIDX_MAX) { 242 | memset(extents, 0, sizeof extents[0]); 243 | uint32_t codepoint = get_glyphidx(glyph); 244 | if(codepoint == '\n' || codepoint == '\t') { 245 | return CAIRO_STATUS_SUCCESS; 246 | } 247 | char *id; 248 | uchar len = 0; 249 | enum { BYTE_LEN = 2, CODEPOINT_LEN = 6 }; 250 | char buf[MAX(BYTE_LEN, CODEPOINT_LEN) + 1]; 251 | if(codepoint < LEN(ctrl_to_id)) { 252 | id = ctrl_to_id[codepoint]; 253 | len = strlen(id); 254 | } else if(codepoint < 256) { 255 | id = buf; 256 | len = BYTE_LEN; 257 | sprintf(id, "%0*X", (int)len, codepoint); 258 | } else if(codepoint & CODEPOINT_NOT_FOUND) { 259 | codepoint ^= CODEPOINT_NOT_FOUND; 260 | id = buf; 261 | len = snprintf(id, CODEPOINT_LEN+1, "%X", codepoint); 262 | } else { 263 | return CAIRO_STATUS_USER_FONT_ERROR; 264 | } 265 | 266 | double adv = SPECIAL_WIDTH / len * 0.875; 267 | double chw = adv * 0.6875; 268 | double chh = 0.6875; 269 | 270 | double x = ( SPECIAL_WIDTH - (len*adv - (adv-chw)) ) * 0.5; 271 | 272 | basic_text(cr, id, len, x, adv, chw, chh); 273 | 274 | cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL); 275 | cairo_matrix_t mat; 276 | cairo_scaled_font_get_font_matrix(scaled_font, &mat); 277 | cairo_set_line_width(cr, 1 / mat.xx); 278 | cairo_stroke(cr); 279 | return CAIRO_STATUS_SUCCESS; 280 | } 281 | 282 | fontset = cairo_font_face_get_user_data( 283 | cairo_scaled_font_get_font_face(scaled_font), &face_key); 284 | 285 | cairo_set_scaled_font(cr, fontset_get_font(fontset, get_fontidx(glyph))); 286 | 287 | cairo_glyph_t cr_glyph = {.index = get_glyphidx(glyph)}; 288 | cairo_show_glyphs(cr, &cr_glyph, 1); 289 | cairo_glyph_extents(cr, &cr_glyph, 1, extents); 290 | 291 | return CAIRO_STATUS_SUCCESS; 292 | } 293 | 294 | static uint32_t 295 | face_get_char_index(FT_Face face, FT_ULong charcode) 296 | { 297 | FT_UInt idx = FT_Get_Char_Index(face, charcode); 298 | if(idx > GLYPHIDX_MAX) { 299 | printf("%lu -> %u (> %u)\n", charcode, idx, GLYPHIDX_MAX); 300 | return 0; 301 | } 302 | return idx; 303 | } 304 | 305 | static double 306 | face_get_kerning(FT_Face face, unsigned long left, unsigned long right) 307 | { 308 | if(face == NULL || get_fontidx(left) == FONTIDX_MAX || 309 | get_fontidx(right) == FONTIDX_MAX || 310 | get_glyphidx(left) == 0 || get_glyphidx(right) == 0 || 311 | get_fontidx(left) != get_fontidx(right)) { 312 | return 0; 313 | } 314 | 315 | FT_Vector k; 316 | if(FT_Get_Kerning(face, get_glyphidx(left), get_glyphidx(right), 317 | FT_KERNING_UNFITTED, &k)) { 318 | return 0; 319 | } 320 | 321 | return (double)k.x / (1<<6); 322 | } 323 | 324 | static double 325 | face_get_advance(FT_Face face, unsigned long glyph) 326 | { 327 | if(face == NULL || get_fontidx(glyph) == FONTIDX_MAX) { 328 | //if(get_glyphidx(glyph) == '\n' || get_glyphidx(glyph) == '\t') { 329 | if(get_glyphidx(glyph) == '\n') { 330 | return 0; 331 | } 332 | return SPECIAL_WIDTH; 333 | } 334 | FT_Fixed v; 335 | FT_Int32 load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING; 336 | if(FT_Get_Advance(face, get_glyphidx(glyph), load_flags, &v)) { 337 | return 0; 338 | } 339 | return (double)v / (1<<16); 340 | } 341 | 342 | static size_t 343 | utf8glyph(fontset_t *fset, const char *utf8, size_t utf8_len, FT_Face *face, unsigned long *glyph) 344 | { 345 | long codepoint; 346 | size_t chsiz = utf8decode(utf8, &codepoint, utf8_len); 347 | if( codepoint == '\t' || codepoint == '\n' || codepoint == UTF_INVALID || 348 | (chsiz == 1 && !isprint(utf8[0])) ) { 349 | *glyph = make_cr_glyph(FONTIDX_MAX, (uchar)utf8[0]); 350 | *face = NULL; 351 | return 1; 352 | } 353 | 354 | fontidx_t fontidx = fontset_match_codepoint(fset, codepoint); 355 | if(fontidx == FONTIDX_MAX) { 356 | *glyph = make_cr_glyph(FONTIDX_MAX, codepoint | CODEPOINT_NOT_FOUND); 357 | *face = NULL; 358 | return chsiz; 359 | } 360 | 361 | *face = cairo_ft_scaled_font_lock_face(fontset_get_font(fset, fontidx)); 362 | uint32_t glyphidx = face_get_char_index(*face, codepoint); 363 | if(glyphidx == 0) { 364 | printf("0: %lu @ %u\n", codepoint, fontidx); 365 | } 366 | *glyph = make_cr_glyph(fontidx, glyphidx); 367 | return chsiz; 368 | } 369 | 370 | cairo_status_t 371 | font_text_to_glyphs(cairo_scaled_font_t *scaled_font, 372 | const char *utf8, int utf8_len, 373 | cairo_glyph_t **out_glyphs, int *out_num_glyphs, 374 | cairo_text_cluster_t **out_clusters, int *out_num_clusters, 375 | cairo_text_cluster_flags_t *cluster_flags) 376 | { 377 | fontset_t *fset = cairo_font_face_get_user_data( 378 | cairo_scaled_font_get_font_face(scaled_font), &face_key); 379 | 380 | size_t chsiz; 381 | int i = 0; 382 | cairo_glyph_t *aglyphs = NULL; 383 | cairo_glyph_t *glyphs = *out_glyphs; 384 | int max_num_glyphs = *out_num_glyphs; 385 | 386 | cairo_glyph_t glyph = {0}; 387 | unsigned long prev_glyph_idx; 388 | 389 | FT_Face face; 390 | 391 | for(int off = 0; off < utf8_len; off += chsiz, i++) { 392 | if(i == max_num_glyphs) { 393 | max_num_glyphs = MAX(max_num_glyphs * 3 / 2, utf8_len); 394 | aglyphs = reallocdup(aglyphs, max_num_glyphs, glyphs, i, 395 | sizeof glyphs[0]); 396 | glyphs = aglyphs; 397 | } 398 | 399 | prev_glyph_idx = glyph.index; 400 | chsiz = utf8glyph(fset, utf8+off, utf8_len-off, &face, &glyph.index); 401 | 402 | glyph.x += face_get_kerning(face, prev_glyph_idx, glyph.index); 403 | glyphs[i] = glyph; 404 | glyph.x += face_get_advance(face, glyph.index); 405 | 406 | if(face != NULL) { 407 | cairo_ft_scaled_font_unlock_face( fontset_get_font( 408 | fset, get_fontidx(glyph.index)) ); 409 | } 410 | } 411 | 412 | *out_glyphs = glyphs; 413 | *out_num_glyphs = i; 414 | 415 | (void)out_clusters; 416 | (void)out_num_clusters; 417 | (void)cluster_flags; 418 | 419 | return CAIRO_STATUS_SUCCESS; 420 | } 421 | 422 | cairo_font_face_t * 423 | font_cairo_font_face_create(fontset_t *fontset) 424 | { 425 | cairo_font_face_t *uface; 426 | cairo_status_t status; 427 | 428 | uface = cairo_user_font_face_create(); 429 | cairo_user_font_face_set_init_func(uface, font_init); 430 | cairo_user_font_face_set_render_glyph_func(uface, font_render_glyph); 431 | cairo_user_font_face_set_text_to_glyphs_func(uface, font_text_to_glyphs); 432 | 433 | status = cairo_font_face_set_user_data(uface, &face_key, fontset, 434 | (cairo_destroy_func_t)fontset_free); 435 | if(status) { 436 | fontset_free(fontset); 437 | return NULL; 438 | } 439 | 440 | return uface; 441 | } 442 | -------------------------------------------------------------------------------- /font.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include FT_FREETYPE_H 5 | 6 | #include 7 | 8 | #include 9 | 10 | typedef struct { 11 | FcPattern *pattern; 12 | FcFontSet *set; 13 | cairo_scaled_font_t **cache; 14 | bool onheap; 15 | } fontset_t; 16 | 17 | int fontset_load_font(fontset_t *f, unsigned int index); 18 | int fontset_init(fontset_t *f, FcPattern *pattern); 19 | void fontset_free(fontset_t *f); 20 | 21 | cairo_font_face_t *font_cairo_font_face_create(fontset_t *fontset); 22 | 23 | cairo_status_t font_text_to_glyphs(cairo_scaled_font_t *scaled_font, 24 | const char *utf8, int utf8_len, 25 | cairo_glyph_t **out_glyphs, int *out_num_glyphs, 26 | cairo_text_cluster_t **out_clusters, int *out_num_clusters, 27 | cairo_text_cluster_flags_t *cluster_flags); 28 | 29 | typedef uint8_t fontidx_t; 30 | enum { 31 | FONTIDX_MAX = (1 << sizeof(fontidx_t) * 8) - 1, 32 | GLYPHIDX_MAX = UINT32_MAX >> sizeof(fontidx_t) * 8, 33 | CODEPOINT_MAX = GLYPHIDX_MAX >> 1, 34 | CODEPOINT_NOT_FOUND = 1 << ((sizeof(uint32_t) - sizeof(fontidx_t)) * 8 - 1) 35 | }; 36 | 37 | static inline unsigned long make_cr_glyph(fontidx_t fontidx, uint32_t glyphidx) 38 | { 39 | return fontidx | (glyphidx << (sizeof(fontidx) * 8)); 40 | } 41 | 42 | static inline fontidx_t get_fontidx(unsigned long cr_glyph) 43 | { 44 | return cr_glyph & FONTIDX_MAX; 45 | } 46 | 47 | static inline uint32_t get_glyphidx(unsigned long cr_glyph) 48 | { 49 | return cr_glyph >> (sizeof(fontidx_t) * 8); 50 | } 51 | -------------------------------------------------------------------------------- /gen-tests.h.awk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/awk -f 2 | BEGIN { 3 | } 4 | 5 | match($0, /^TEST_[A-Za-z0-9_]+/) { 6 | name = substr($0, RSTART, RLENGTH) 7 | print "int " name "(void);" 8 | tests = tests "\t{" name ", \"" name "\"},\n" 9 | } 10 | 11 | END { 12 | print "\nstruct {" 13 | print "\tint (*func)(void);" 14 | print "\tconst char *name;" 15 | print "} TESTS[] = {" 16 | print tests "};" 17 | } 18 | -------------------------------------------------------------------------------- /pipe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "util.h" 11 | #include "array.h" 12 | 13 | #include "pipe.h" 14 | 15 | int 16 | pipe_init(pipe_t *p, size_t num, int write) 17 | { 18 | int err = 0; 19 | size_t n = 0; 20 | write = !!write; 21 | 22 | for(; n < num; n++) { 23 | int fds[2]; 24 | if(pipe(fds) < 0) { 25 | err = errno; 26 | break; 27 | } 28 | p[n].fd = fds[write]; 29 | p[n].child.fd = fds[!write]; 30 | } 31 | if(err) { 32 | for(size_t i = 0; i <= n; i++) { 33 | close(p[i].fd); 34 | close(p[i].child.fd); 35 | } 36 | errno = err; 37 | return -1; 38 | } 39 | return 0; 40 | } 41 | 42 | int 43 | pipe_set_env(pipe_t *p, size_t num) 44 | { 45 | #define DEVFD "/dev/fd/" 46 | static char buf[sizeof(DEVFD) + (sizeof(int) * 5 + 1) / 2] = DEVFD; 47 | static char *bufend = buf + sizeof(DEVFD) - 1; 48 | 49 | for(size_t i = 0; i < num; i++) { 50 | if(p[i].child.name) { 51 | if(sprintf(bufend, "%d", p[i].child.fd) < 0 || 52 | setenv(p[i].child.name, buf, 1) < 0) { 53 | return -1; 54 | } 55 | } 56 | } 57 | return 0; 58 | } 59 | 60 | int 61 | pipe_cmd_exec(pipe_t *p, size_t num, char *argv[]) 62 | { 63 | for(size_t i = 0; i < num; i++) { 64 | close(p[i].fd); 65 | } 66 | 67 | if(setenv("TERM", "dumb", 1) < 0 || 68 | pipe_set_env(p, num) < 0) { 69 | return -1; 70 | } 71 | 72 | execvp(argv[0], argv); 73 | return -1; 74 | } 75 | 76 | int 77 | pipe_select(control_t *control, fd_set *rfd, fd_set *wfd, 78 | pipe_t *r, size_t num_r, pipe_t *w, size_t num_w) 79 | { 80 | while(!control->pipe.done) { 81 | int max = -1; 82 | 83 | FD_ZERO(rfd); 84 | FD_ZERO(wfd); 85 | 86 | for(size_t i = 0; i < num_r; i++) { 87 | if(r[i].fd >= 0) { 88 | FD_SET(r[i].fd, rfd); 89 | max = MAX(max, r[i].fd); 90 | } 91 | } 92 | for(size_t i = 0; i < num_w; i++) { 93 | if(w[i].fd >= 0) { 94 | FD_SET(w[i].fd, wfd); 95 | max = MAX(max, w[i].fd); 96 | } 97 | } 98 | if(max < 0) { 99 | return 0; 100 | } 101 | 102 | if(pselect(max+1, rfd, wfd, NULL, NULL, NULL) < 0) { 103 | if(errno == EINTR) { 104 | continue; 105 | } 106 | control->error = 1; 107 | return 0; 108 | } 109 | return 1; 110 | } 111 | return 0; 112 | } 113 | 114 | void 115 | pipe_send(pipe_t *p, control_t *ctl) 116 | { 117 | if(p->handler) { 118 | p->handler(ctl, p->usr, &p->buf, 1); 119 | } 120 | 121 | ssize_t len; 122 | if(p->buf.nmemb == 0 || 123 | ( (len = write(p->fd, p->buf.data, p->buf.nmemb)) < 0 && 124 | (errno == EPIPE || errno == EAGAIN) )) { 125 | if(p->handler) { 126 | p->handler(ctl, p->usr, &p->buf, 0); 127 | } 128 | close(p->fd); 129 | p->fd = -1; 130 | return; 131 | } 132 | 133 | if(len < 0) { 134 | perror("write failed"); 135 | return; 136 | } 137 | 138 | ARR_FRAG_SHIFT(&p->buf, 0, p->buf.nmemb, -len); 139 | p->buf.nmemb -= len; 140 | } 141 | 142 | void 143 | pipe_recv(pipe_t *p, control_t *ctl) 144 | { 145 | enum { PIPE_BUF_SIZE = BUFSIZ * 2 }; 146 | 147 | size_t start = p->buf.nmemb; 148 | ARR_EXTEND(&p->buf, PIPE_BUF_SIZE); 149 | 150 | ssize_t len = read(p->fd, p->buf.data + start, PIPE_BUF_SIZE); 151 | 152 | if(len < 0) { 153 | if(errno != EAGAIN) { 154 | perror("read failed"); 155 | return; 156 | } 157 | len = 0; 158 | } 159 | 160 | p->buf.nmemb = start + len; 161 | 162 | if(p->handler) { 163 | p->handler(ctl, p->usr, &p->buf, len); 164 | } 165 | 166 | if(len == 0) { 167 | close(p->fd); 168 | p->fd = -1; 169 | } 170 | } 171 | 172 | -------------------------------------------------------------------------------- /pipe.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | struct { 3 | bool disregard; 4 | bool finish; 5 | bool write_end; 6 | bool done; 7 | } pipe; 8 | struct { 9 | bool exited; 10 | int status; 11 | pid_t pid; 12 | } child; 13 | bool error; 14 | } control_t; 15 | 16 | typedef struct { 17 | int fd; 18 | int (*handler)(control_t *control, void *usr, string_t *buf, size_t len); 19 | void *usr; 20 | string_t buf; 21 | 22 | struct { 23 | char *name; 24 | int fd; 25 | } child; 26 | } pipe_t; 27 | 28 | int pipe_init(pipe_t *p, size_t num, int write); 29 | int pipe_set_env(pipe_t *p, size_t num); 30 | int pipe_cmd_exec(pipe_t *p, size_t num, char *argv[]); 31 | int pipe_select(control_t *control, fd_set *rfd, fd_set *wfd, 32 | pipe_t *r, size_t num_r, pipe_t *w, size_t num_w); 33 | void pipe_send(pipe_t *p, control_t *ctl); 34 | void pipe_recv(pipe_t *p, control_t *ctl); 35 | -------------------------------------------------------------------------------- /test.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define TEST_STRCMP_OP(r, op, x, ...) \ 4 | do { if(! (strcmp(((r), (x)) op 0)) { \ 5 | printf(__FILE__ ":%d ", __LINE__); \ 6 | printf(__VA_ARGS__); \ 7 | printf("\n\t" #r " " #op " " #x "\n" \ 8 | "\treceived: %s\n" \ 9 | "\texpected: %s\n", \ 10 | (r), (x));\ 11 | return -1; \ 12 | } } while(0) 13 | 14 | #define TEST_MEMCMP_OP(r, op, x, n, ...) \ 15 | do { if(! (memcmp((r), (x), (n)) op 0)) { \ 16 | printf(__FILE__ ":%d ", __LINE__); \ 17 | printf(__VA_ARGS__); \ 18 | printf("\n\t" #r " " #op " " #x "\n" \ 19 | "\treceived: %.*s\n" \ 20 | "\texpected: %.*s\n", \ 21 | (int)(n), (r), (int)(n), (x));\ 22 | return -1; \ 23 | } } while(0) 24 | 25 | #define TEST_OP(f, r, op, x, ...) \ 26 | do { if(! ((r) op (x)) ) { \ 27 | printf(__FILE__ ":%d ", __LINE__); \ 28 | printf(__VA_ARGS__); \ 29 | printf("\n\t" #r " " #op " " #x "\n" \ 30 | "\treceived: " f "\n" \ 31 | "\texpected: " f "\n", \ 32 | (r), (x));\ 33 | return -1; \ 34 | } } while(0) 35 | 36 | static __attribute__ ((format (printf, 1, 2))) void 37 | test_check_format(const char *format, ...) 38 | { 39 | (void)format; 40 | } 41 | 42 | #define STRIP_PARENS(...) __VA_ARGS__ 43 | 44 | #define TEST_CALL(buf, size, format, func, args) \ 45 | (test_check_format((format), STRIP_PARENS args), \ 46 | snprintf((buf), (size), ("%s(" format ")"), #func, STRIP_PARENS args), \ 47 | func args) 48 | 49 | #define DEBUG_CALL(format, func, args) \ 50 | (test_check_format((format), STRIP_PARENS args), \ 51 | fprintf(stderr, ("%s(" format ")"), #func, STRIP_PARENS args), \ 52 | func args) 53 | -------------------------------------------------------------------------------- /tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "tests.h" 8 | #include "util.h" 9 | 10 | int 11 | skip_test(const char *name, int argc, char *argv[]) 12 | { 13 | int i; 14 | if(argc == 1) { 15 | return 0; 16 | } 17 | for(i = 1; i < argc; i++) { 18 | if(!strcmp(argv[i], name)) { 19 | return 0; 20 | } 21 | } 22 | return 1; 23 | } 24 | 25 | static size_t run_tests_count = LEN(TESTS); 26 | 27 | static size_t 28 | next_from_all(int argc, char *argv[]) { 29 | (void)argc; (void)argv; 30 | static size_t i = 0; 31 | return i++; 32 | } 33 | 34 | static size_t 35 | next_from_args(int argc, char *argv[]) 36 | { 37 | static int i = 1; 38 | static size_t run = 0; 39 | 40 | size_t j; 41 | 42 | for(; i < argc; i++) { 43 | for(j = 0; j < LEN(TESTS); j++) { 44 | if(!strcmp(argv[i], TESTS[j].name)) { 45 | i++; 46 | run++; 47 | return j; 48 | } 49 | } 50 | } 51 | run_tests_count = run; 52 | return LEN(TESTS); 53 | } 54 | 55 | int __wrap_main(int argc, char *argv[]) 56 | { 57 | size_t i; 58 | size_t success = 0; 59 | pid_t pid; 60 | int status; 61 | 62 | size_t (*next)(int, char*[]); 63 | 64 | next = (argc > 1) ? next_from_args : next_from_all; 65 | 66 | while((i = next(argc, argv)) < LEN(TESTS)) { 67 | printf("Running: %s\n", TESTS[i].name); 68 | pid = fork(); 69 | if(pid < 0) { 70 | perror("test suite failure, fork"); 71 | return -1; 72 | } 73 | if(pid == 0) { 74 | return TESTS[i].func(); 75 | } else { 76 | if(waitpid(pid, &status, 0) == -1) { 77 | perror("test suite failure, waitpid"); 78 | return -1; 79 | } 80 | if(status) { 81 | printf("failure: exit code %d signal %d\n\n", 82 | WIFEXITED(status) ? WEXITSTATUS(status) : 0, 83 | WIFSIGNALED(status) ? WTERMSIG(status) : -1); 84 | } else { 85 | puts("success"); 86 | success++; 87 | } 88 | } 89 | } 90 | printf("%s, tests ok: %zu / %zu\n", 91 | success == run_tests_count ? "success" : "failure", 92 | success, run_tests_count); 93 | 94 | return success == run_tests_count ? 0 : 1; 95 | } 96 | -------------------------------------------------------------------------------- /utf.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include "utf.h" 3 | 4 | /* taken from st (http://st.suckless.org/) */ 5 | 6 | static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0x00, 0xC0, 0xE0, 0xF0}; 7 | static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 8 | static long utfmin[UTF_SIZ + 1] = {0x000000, 0x00, 0x080, 0x0800, 0x010000}; 9 | static long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 10 | 11 | long 12 | utf8decodebyte(char c, size_t *i) 13 | { 14 | for(*i = 0; *i < LEN(utfmask); ++(*i)) 15 | if(((uchar)c & utfmask[*i]) == utfbyte[*i]) 16 | return (uchar)c & ~utfmask[*i]; 17 | return 0; 18 | } 19 | 20 | size_t 21 | utf8validate(long *u, size_t i) 22 | { 23 | if(!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 24 | *u = UTF_INVALID; 25 | for(i = 1; *u > utfmax[i]; ) 26 | i++; 27 | return i; 28 | } 29 | 30 | size_t 31 | utf8decode(const char *c, long *u, size_t clen) 32 | { 33 | size_t i, j, len, type; 34 | long udecoded; 35 | 36 | *u = UTF_INVALID; 37 | if(!clen) 38 | return 0; 39 | udecoded = utf8decodebyte(c[0], &len); 40 | if(!BETWEEN(len, 1, UTF_SIZ)) 41 | return 1; 42 | for(i = 1, j = 1; i < clen && j < len; ++i, ++j) { 43 | udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 44 | if(type != 0) 45 | return j; 46 | } 47 | if(j < len) 48 | return 0; 49 | *u = udecoded; 50 | utf8validate(u, len); 51 | return len; 52 | } 53 | 54 | size_t 55 | utf8chsiz(char *c, size_t clen) 56 | { 57 | if(clen <= 0) { 58 | return 0; 59 | } 60 | long cp; 61 | size_t chsiz = utf8decode(c, &cp, MIN(UTF_SIZ, clen)); 62 | return cp != UTF_INVALID ? chsiz : 1; 63 | } 64 | 65 | /* 66 | size_t 67 | utf8chsiz_backward(char *c, size_t clen) 68 | { 69 | if(clen <= 0) { 70 | return 0; 71 | } 72 | size_t len = MIN(UTF_SIZ, clen); 73 | 74 | size_t chsiz; 75 | long cp; 76 | for(size_t i = 1; i <= len; i++) { 77 | c--; 78 | chsiz = utf8decode(c, &cp, i); 79 | if(cp != UTF_INVALID && chsiz == i) { 80 | return chsiz; 81 | } 82 | } 83 | return 1; 84 | } 85 | */ 86 | 87 | char 88 | utf8encodebyte(long u, size_t i) 89 | { 90 | return utfbyte[i] | (u & ~utfmask[i]); 91 | } 92 | 93 | size_t 94 | utf8encode(long u, char *c, size_t clen) 95 | { 96 | size_t len, i; 97 | 98 | len = utf8validate(&u, 0); 99 | if(clen < len) 100 | return 0; 101 | for(i = len - 1; i != 0; --i) { 102 | c[i] = utf8encodebyte(u, 0); 103 | u >>= 6; 104 | } 105 | c[0] = utf8encodebyte(u, len); 106 | return len; 107 | } 108 | -------------------------------------------------------------------------------- /utf.h: -------------------------------------------------------------------------------- 1 | /* taken from st (http://st.suckless.org/) */ 2 | 3 | #define UTF_INVALID 0xFFFD 4 | #define UTF_SIZ 4 5 | 6 | long utf8decodebyte(char c, size_t *i); 7 | size_t utf8validate(long *u, size_t i); 8 | size_t utf8decode(const char *c, long *u, size_t clen); 9 | size_t utf8chsiz(char *c, size_t clen); 10 | //size_t utf8chsiz_backward(char *c, size_t clen); 11 | char utf8encodebyte(long u, size_t i); 12 | size_t utf8encode(long u, char *c, size_t clen); 13 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "util.h" 7 | 8 | void * 9 | xcalloc(size_t nmemb, size_t size) 10 | { 11 | void *ptr = calloc(nmemb, size); 12 | DIEIF(ptr == NULL); 13 | return ptr; 14 | } 15 | 16 | void * 17 | xmalloc(size_t nmemb, size_t size) 18 | { 19 | void *ptr = malloc(nmemb * size); 20 | DIEIF(ptr == NULL); 21 | return ptr; 22 | } 23 | 24 | void * 25 | xrealloc(void *ptr, size_t nmemb, size_t size) 26 | { 27 | ptr = realloc(ptr, nmemb * size); 28 | DIEIF(nmemb > 0 && ptr == NULL); 29 | return ptr; 30 | } 31 | 32 | void * 33 | reallocdup(void *heap, size_t nnew, const void *stack, size_t nstack, size_t size) 34 | { 35 | if(heap) { 36 | heap = xrealloc(heap, nnew, size); 37 | } else { 38 | heap = xmalloc(nnew, size); 39 | if(stack) { 40 | memcpy(heap, stack, size * nstack); 41 | } 42 | } 43 | return heap; 44 | } 45 | 46 | int 47 | TEST_memshift(void) { 48 | { 49 | char buf[] = "abc"; 50 | memshift(-1, buf, sizeof(buf)-1, sizeof(buf[0])); 51 | assert(!strcmp(buf, "bcc")); 52 | } { 53 | char buf[] = "abc"; 54 | memshift(1, buf, sizeof(buf)-1, sizeof(buf[0])); 55 | assert(!strcmp(buf, "aab")); 56 | } 57 | return 0; 58 | } 59 | 60 | void 61 | memshift(ssize_t shift, void *buf, size_t nmemb, size_t size) 62 | { 63 | size_t shiftabs = ABS(shift); 64 | if(shift == 0 || shiftabs >= nmemb) { 65 | return; 66 | } 67 | size_t tomove = nmemb - shiftabs; 68 | char *src = buf; 69 | char *dst = buf; 70 | 71 | if(shift > 0) { 72 | dst += shift * size; 73 | } else { 74 | src -= shift * size; 75 | } 76 | 77 | memmove(dst, src, tomove * size); 78 | } 79 | 80 | void 81 | die(const char *fmt, ...) { 82 | va_list ap; 83 | 84 | va_start(ap, fmt); 85 | vfprintf(stderr, fmt, ap); 86 | va_end(ap); 87 | exit(-1); 88 | } 89 | 90 | void 91 | dieif(const char *file, int line, const char *func, const char *msg, int die_req) 92 | { 93 | if(die_req) { 94 | die("%s:%d:%s: fatal error: %s (%s)\n", file, line, func, msg, strerror(errno)); 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define ABS(a) ((a) < (0) ? -(a) : (a)) 8 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 9 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 10 | #define LEN(a) (sizeof(a) / sizeof(a)[0]) 11 | #define DEFAULT(a, b) (a) = (a) ? (a) : (b) 12 | #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 13 | 14 | #define DIEIF(x) dieif(__FILE__, __LINE__, __func__, #x, (x)) 15 | 16 | typedef unsigned char uchar; 17 | 18 | static inline bool 19 | is_str_eq(const char *s1, size_t n1, const char *s2, size_t n2) 20 | { 21 | if(n1 != n2) { 22 | return false; 23 | } 24 | return !memcmp(s1, s2, MIN(n1, n2)); 25 | } 26 | 27 | static inline size_t 28 | next_pow2(size_t n) 29 | { 30 | n--; 31 | for(size_t i = 1; i < 8 * sizeof n; i*=2) { 32 | n |= i; 33 | } 34 | n++; 35 | return n; 36 | } 37 | 38 | static inline size_t 39 | next_size(size_t n, size_t min) 40 | { 41 | n = next_pow2(n); 42 | return n < min ? min : n * 3 / 2; 43 | } 44 | 45 | static inline size_t 46 | clamps(size_t v, size_t min, size_t max) 47 | { 48 | if(v < min) { 49 | return min; 50 | } else if(v > max) { 51 | return max; 52 | } 53 | return v; 54 | } 55 | 56 | static inline ssize_t 57 | clampss(ssize_t v, ssize_t min, ssize_t max) 58 | { 59 | if(v < min) { 60 | return min; 61 | } else if(v > max) { 62 | return max; 63 | } 64 | return v; 65 | } 66 | 67 | static inline size_t 68 | oneless(size_t v) { 69 | if(v > 0) { 70 | return v - 1; 71 | } 72 | return v; 73 | } 74 | 75 | void *xcalloc(size_t nmemb, size_t size); 76 | void *xmalloc(size_t nmemb, size_t size); 77 | void *xrealloc(void *ptr, size_t nmemb, size_t size); 78 | void *reallocdup(void *heap, size_t nnew, const void *stack, size_t nstack, size_t size); 79 | void memshift(ssize_t shift, void *src, size_t nmemb, size_t size); 80 | 81 | void die(const char *fmt, ...); 82 | void dieif(const char *file, int line, const char *func, const char *msg, int die_req); 83 | -------------------------------------------------------------------------------- /valgrind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | valgrind --leak-check=full --suppressions=valgrind.supp ./werf "$@" 3 | 4 | -------------------------------------------------------------------------------- /valgrind.supp: -------------------------------------------------------------------------------- 1 | { 2 | XrmGetStringDatabase is fairly obnoxious about leaving reachable memory around 3 | Memcheck:Leak 4 | fun:realloc 5 | obj:*/libX11.so* 6 | obj:*/libX11.so* 7 | obj:*/libX11.so* 8 | fun:_XlcCreateLC 9 | fun:_XlcDefaultLoader 10 | fun:_XOpenLC 11 | fun:_XrmInitParseInfo 12 | obj:*/libX11.so* 13 | fun:XrmGetStringDatabase 14 | } 15 | { 16 | fontconfig 2.8.0 version 17 | Memcheck:Addr4 18 | ... 19 | fun:FcConfigParseAndLoad 20 | ... 21 | } 22 | -------------------------------------------------------------------------------- /view.c: -------------------------------------------------------------------------------- 1 | #include 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 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include "util.h" 23 | #include "array.h" 24 | 25 | #include "utf.h" 26 | #include "edit.h" 27 | #include "font.h" 28 | #include "view.h" 29 | #include "command.h" 30 | 31 | ssize_t 32 | view_clamp_start(view_t *v, ssize_t nr) 33 | { 34 | return clampss(nr, -v->nmemb + 1, v->range.file->content.nmemb - 1); 35 | } 36 | 37 | void 38 | view_set_start(view_t *v, size_t nr) 39 | { 40 | ssize_t view_end = view_clamp_start(v, v->start + v->nmemb - 1); 41 | if((ssize_t)nr < v->start) { 42 | v->start = nr; 43 | v->range.file->dirty = true; 44 | } else if((ssize_t)nr > view_end) { 45 | v->start = nr - (v->nmemb - 1); 46 | v->range.file->dirty = true; 47 | } 48 | } 49 | 50 | glyphs_t * 51 | view_get_glyphs(view_t *v, size_t nr) 52 | { 53 | view_set_start(v, nr); 54 | if(v->range.file->dirty) { 55 | view_reshape(v); 56 | } 57 | 58 | return &v->lines[nr - v->start]; 59 | } 60 | 61 | ssize_t 62 | view_y_to_line(view_t *v, int y) 63 | { 64 | return y / v->line_height + v->start; 65 | } 66 | 67 | size_t 68 | view_x_to_offset(view_t *v, size_t nr, int x) 69 | { 70 | glyphs_t *gl = view_get_glyphs(v, nr); 71 | 72 | if(x < v->left_margin) { 73 | return 0; 74 | } 75 | 76 | double prevx = 0.0; 77 | for(int i = 1; i < gl->nmemb; i++) { 78 | if(x < v->left_margin + prevx + (gl->data[i].x - prevx) * 0.5f) { 79 | return gl->glyph_to_offset[i - 1]; 80 | } 81 | prevx = gl->data[i].x; 82 | } 83 | return gl->glyph_to_offset[gl->nmemb - 1]; 84 | } 85 | 86 | double 87 | view_address_to_x(view_t *v, address_t *adr) { 88 | glyphs_t *gl = view_get_glyphs(v, adr->line); 89 | return v->left_margin + gl->data[gl->offset_to_glyph[adr->offset]].x; 90 | } 91 | 92 | double 93 | view_line_to_y(view_t *v, size_t nr) { 94 | return v->line_height * (nr - v->start); 95 | } 96 | 97 | void 98 | view_move_address_line(view_t *v, address_t *adr, int move) 99 | { 100 | if(move > 0) { 101 | move = 1; 102 | } else if(move < 0) { 103 | move = -1; 104 | } 105 | if( (move < 0 && adr->line == 0) || 106 | (move > 0 && adr->line == v->range.file->content.nmemb - 1) ) { 107 | view_set_start(v, adr->line); 108 | return; 109 | } 110 | 111 | adr->line += move; 112 | adr->offset = view_x_to_offset(v, adr->line, v->last_x); 113 | } 114 | 115 | void 116 | view_move_address(view_t *v, address_t *adr, int move) 117 | { 118 | if(!move) { 119 | return; 120 | } 121 | if(move > 0) { 122 | move = 1; 123 | } else { 124 | move = -1; 125 | } 126 | 127 | glyphs_t *gl = view_get_glyphs(v, adr->line); 128 | 129 | int gi = gl->offset_to_glyph[adr->offset]; 130 | if( (move < 0 && gi > 0) || (move > 0 && gi < gl->nmemb - 1) ) { 131 | adr->offset = gl->glyph_to_offset[gi + move]; 132 | return; 133 | } 134 | 135 | if( (move < 0 && adr->line == 0) || 136 | (move > 0 && adr->line == v->range.file->content.nmemb - 1) ) { 137 | return; 138 | } 139 | 140 | adr->line += move; 141 | gl = view_get_glyphs(v, adr->line); 142 | adr->offset = gl->glyph_to_offset[move > 0 ? 0 : gl->nmemb - 1]; 143 | } 144 | 145 | bool 146 | view_move_start(view_t *v, ssize_t move) 147 | { 148 | ssize_t new_start = view_clamp_start(v, v->start + move); 149 | 150 | if(new_start != v->start) { 151 | v->start = new_start; 152 | v->range.file->dirty = true; 153 | return true; 154 | } 155 | return false; 156 | } 157 | 158 | void 159 | view_hide_toolbar(view_t *v, toolbar_wrap_t *bar_wrap, size_t line) 160 | { 161 | if(!bar_wrap->visible) { 162 | return; 163 | } 164 | 165 | if(line > bar_wrap->line) { 166 | v->start--; 167 | } 168 | size_t start = clampss(v->start, 0, v->range.file->content.nmemb - 1); 169 | size_t end = view_clamp_start(v, v->start + v->nmemb - 1) + 1; 170 | if(bar_wrap->line >= start && bar_wrap->line <= end) { 171 | v->range.file->dirty = true; 172 | } 173 | bar_wrap->visible = false; 174 | } 175 | 176 | static void 177 | glyphs_map(glyphs_t *gl, string_t *line) 178 | { 179 | gl->glyph_to_offset = xrealloc(gl->glyph_to_offset, gl->nmemb, 180 | sizeof gl->glyph_to_offset[0]); 181 | gl->offset_to_glyph = xrealloc(gl->offset_to_glyph, line->nmemb, 182 | sizeof gl->offset_to_glyph[0]); 183 | 184 | char *utf8 = line->data; 185 | size_t len = line->nmemb; 186 | 187 | int gi = 0; 188 | size_t oi = 0; 189 | for(size_t chsiz = 0; (chsiz = utf8chsiz(utf8, len)) != 0 && 190 | gi < gl->nmemb && oi < line->nmemb; 191 | utf8 += chsiz, len -= chsiz) { 192 | gl->glyph_to_offset[gi] = oi; 193 | gl->offset_to_glyph[oi] = gi; 194 | 195 | oi += chsiz; 196 | gi++; 197 | } 198 | } 199 | 200 | void 201 | glyphs_from_text(glyphs_t *gl, cairo_scaled_font_t *font, string_t *line) 202 | { 203 | if(line->nmemb > (size_t)gl->nmemb) { 204 | gl->nmemb = line->nmemb; 205 | gl->data = xrealloc(gl->data, gl->nmemb, sizeof gl->data[0]); 206 | } 207 | cairo_glyph_t *gl_initial = gl->data; 208 | 209 | bool last_line = line->nmemb == 0 || line->data[line->nmemb - 1] != '\n'; 210 | if(last_line) { 211 | ARR_EXTEND(line, 1); 212 | line->data[line->nmemb - 1] = '\n'; 213 | } 214 | 215 | font_text_to_glyphs(font, line->data, line->nmemb, 216 | &gl->data, &gl->nmemb, NULL, NULL, NULL); 217 | if(gl->data != gl_initial) { 218 | free(gl_initial); 219 | } 220 | 221 | cairo_matrix_t mat; 222 | cairo_scaled_font_get_font_matrix(font, &mat); 223 | for(int i = 0; i < gl->nmemb; i++) { 224 | gl->data[i].x *= mat.xx; 225 | } 226 | 227 | glyphs_map(gl, line); 228 | 229 | if(last_line) { 230 | line->nmemb--; 231 | } 232 | } 233 | 234 | void 235 | view_xy_to_address(view_t *v, int x, int y, address_t *adr) 236 | { 237 | adr->line = clampss(view_y_to_line(v, y), 0, v->range.file->content.nmemb - 1); 238 | adr->offset = view_x_to_offset(v, adr->line, x); 239 | v->last_x = view_address_to_x(v, adr); 240 | } 241 | 242 | // FIXME 243 | bool handle_command(char *); 244 | 245 | bool 246 | toolbar_click(toolbar_t *bar, view_t *v, int x) 247 | { 248 | double edge; 249 | for(size_t i = 0; i < bar->buttons.nmemb; i++) { 250 | button_t *btn = &bar->buttons.data[i]; 251 | edge += v->left_margin + btn->glyphs.data[btn->glyphs.nmemb - 1].x + 252 | v->left_margin; 253 | if(x < edge) { 254 | // FIXME: what if I don't want to hide toolbar? 255 | return handle_command(btn->label.data); 256 | } 257 | } 258 | return true; 259 | } 260 | 261 | void 262 | view_reshape(view_t *v) 263 | { 264 | if(!v->nmemb) { 265 | return; 266 | } 267 | size_t start = clampss(v->start, 0, v->range.file->content.nmemb - 1); 268 | size_t end = view_clamp_start(v, v->start + v->nmemb - 1); 269 | for(size_t i = start; i <= end; i++) { 270 | string_t *line = &v->range.file->content.data[i]; 271 | glyphs_t *gl = &v->lines[i - v->start]; 272 | glyphs_from_text(gl, v->font, line); 273 | } 274 | v->range.file->dirty = false; 275 | } 276 | 277 | void 278 | view_resize(view_t *v, int width, int height) 279 | { 280 | v->width = width; 281 | v->height = height; 282 | v->line_height = v->extents.ascent + v->extents.descent; 283 | v->left_margin = v->extents.descent; 284 | 285 | size_t nmemb = v->height / v->line_height; 286 | if(nmemb == v->nmemb) { 287 | return; 288 | } 289 | 290 | for(size_t i = nmemb; i < v->nmemb; i++) { 291 | free(v->lines[i].data); 292 | free(v->lines[i].glyph_to_offset); 293 | free(v->lines[i].offset_to_glyph); 294 | } 295 | v->lines = xrealloc(v->lines, nmemb, sizeof v->lines[0]); 296 | for(size_t i = v->nmemb; i < nmemb; i++) { 297 | memset(v->lines + i, 0, sizeof v->lines[0]); 298 | } 299 | 300 | size_t view_end = view_clamp_start(v, v->start + v->nmemb - 1); 301 | size_t pivot; 302 | ssize_t vis_sel_start = v->start; 303 | ssize_t vis_sel_end = view_end; 304 | if(v->range.start.line <= view_end && (ssize_t)v->range.end.line >= v->start) { 305 | if((ssize_t)v->range.start.line > v->start) { 306 | vis_sel_start = v->range.start.line; 307 | } 308 | if(v->range.end.line < view_end) { 309 | vis_sel_end = v->range.end.line; 310 | } 311 | } 312 | pivot = vis_sel_end - (vis_sel_end - vis_sel_start) / 2; 313 | 314 | /* TODO: smooth scrolling 315 | if(v->nmemb > 0) { 316 | v->start = pivot - (pivot - v->start) * nmemb / v->nmemb; 317 | printf(" p - (p-s) * N / n = %zu - (%zu-%zu) * %zu / %zu = %zu\n", 318 | pivot, pivot, v->start, nmemb, v->nmemb, pivot - (pivot - v->start) * nmemb / v->nmemb); 319 | v->start = pivot - (pivot - v->start) * nmemb / v->nmemb; 320 | } 321 | */ 322 | 323 | v->range.file->dirty = true; 324 | v->nmemb = nmemb; 325 | view_set_start(v, pivot); 326 | } 327 | 328 | bool 329 | view_keybinds(view_t *v, KeySym keysym) 330 | { 331 | switch(keysym) { 332 | case XK_F2: 333 | command_undo(v); 334 | break; 335 | case XK_F3: 336 | command_redo(v); 337 | break; 338 | case XK_Page_Up: 339 | command_page_up(v); 340 | break; 341 | case XK_Page_Down: 342 | command_page_down(v); 343 | break; 344 | case XK_Home: 345 | command_home(v); 346 | break; 347 | case XK_End: { 348 | command_end(v); 349 | break; 350 | } 351 | case XK_BackSpace: { 352 | command_backspace(v); 353 | break; 354 | } 355 | case XK_Delete: { 356 | command_delete(v); 357 | break; 358 | } 359 | case XK_Return: /* FALL THROUGH */ 360 | case XK_KP_Enter: 361 | command_new_line(v); 362 | break; 363 | case XK_Left: 364 | command_left(v); 365 | break; 366 | case XK_Right: 367 | command_right(v); 368 | break; 369 | case XK_Up: 370 | command_up(v); 371 | break; 372 | case XK_Down: 373 | command_down(v); 374 | break; 375 | default: 376 | return false; 377 | } 378 | 379 | return true; 380 | } 381 | 382 | bool 383 | view_keypress(view_t *v, KeySym keysym, char *buf, size_t len) 384 | { 385 | if(view_keybinds(v, keysym)) { 386 | } else if(len > 0) { 387 | range_push(&v->range, buf, len, OP_Char); 388 | v->last_x = view_address_to_x(v, &v->range.start); 389 | } else { 390 | return false; 391 | } 392 | 393 | view_hide_toolbar(v, &v->selbar_wrap, v->range.start.line); 394 | return true; 395 | } 396 | 397 | static int scroll_y = 0; 398 | static bool selecting; 399 | static address_t anchor; 400 | 401 | bool 402 | view_mouse_press(view_t *v, unsigned int btn, int x, int y) 403 | { 404 | switch(btn) { 405 | case Button1: { 406 | ssize_t nr = view_y_to_line(v, y); 407 | int corr = 0; 408 | if(v->selbar_wrap.visible) { 409 | if(nr == (ssize_t)v->selbar_wrap.line) { 410 | // FIXME: what if I don't want to hide toolbar? 411 | if(toolbar_click(&v->selbar_wrap.bar, v, x)) { 412 | // FIXME: should I call view_hide_toolbar? 413 | v->selbar_wrap.visible = false; 414 | v->range.file->dirty = true; 415 | } 416 | return true; 417 | } else if(nr > (ssize_t)v->selbar_wrap.line) { 418 | corr = -1; 419 | } 420 | } 421 | 422 | selecting = true; 423 | anchor.line = clampss(nr + corr, 0, v->range.file->content.nmemb - 1); 424 | anchor.offset = view_x_to_offset(v, anchor.line, x); 425 | v->last_x = view_address_to_x(v, &anchor); 426 | v->range.start = anchor; 427 | v->range.end = anchor; 428 | 429 | view_hide_toolbar(v, &v->selbar_wrap, nr); 430 | return true; 431 | break; 432 | } 433 | case Button3: 434 | scroll_y = 0; 435 | break; 436 | case Button4: 437 | view_move_start(v, -3); 438 | return true; 439 | case Button5: 440 | view_move_start(v, 3); 441 | return true; 442 | } 443 | return false; 444 | } 445 | 446 | bool 447 | view_mouse_motion(view_t *v, unsigned int state, int x, int y, int relx, int rely) 448 | { 449 | (void)relx; 450 | 451 | if(state & Button1Mask) { 452 | if(!selecting) { 453 | return false; 454 | } 455 | address_t target; 456 | view_xy_to_address(v, x, y, &target); 457 | range_from_addresses(&v->range, &anchor, &target); 458 | return true; 459 | } else if(state & Button3Mask) { 460 | scroll_y -= rely; 461 | ssize_t move = scroll_y / v->line_height; 462 | scroll_y %= (int)v->line_height; 463 | return view_move_start(v, move); 464 | } 465 | 466 | return false; 467 | } 468 | 469 | bool 470 | view_mouse_release(view_t *v, unsigned int btn, int x, int y) 471 | { 472 | if(btn == Button1) { 473 | if(!selecting) { 474 | return false; 475 | } 476 | address_t target; 477 | view_xy_to_address(v, x, y, &target); 478 | int cmp = range_from_addresses(&v->range, &anchor, &target); 479 | if(cmp != 0) { 480 | v->selbar_wrap.visible = true; 481 | v->selbar_wrap.line = target.line; 482 | if(cmp < 0) { 483 | v->selbar_wrap.line++; 484 | view_move_start(v, 1); 485 | } 486 | } 487 | selecting = false; 488 | return true; 489 | } 490 | return false; 491 | } 492 | 493 | #if 0 494 | 495 | int 496 | main(int argc, char *argv[]) 497 | { 498 | setlocale(LC_CTYPE, ""); 499 | signal(SIGPIPE, SIG_IGN); 500 | sigaction(SIGCHLD, &(struct sigaction) { 501 | .sa_sigaction = sigchld, 502 | .sa_flags = SA_SIGINFO | SA_NOCLDSTOP 503 | }, 0); 504 | 505 | file_insert_line(&file, 0, "", 0); 506 | 507 | if(argc > 1) { 508 | file_read(argv[1], &file); 509 | } 510 | 511 | window_init(); 512 | 513 | FT_Library ftlib; 514 | FT_Init_FreeType(&ftlib); 515 | DIEIF(!FcInit()); 516 | 517 | fontset_t fontset = {0}; 518 | DIEIF( fontset_init(&fontset, FcNameParse((FcChar8*)"DroidSans")) ); 519 | cairo_font_face_t *face = cairo_dt_face_create(&fontset); 520 | 521 | cairo_set_font_face(g.win.buf, face); 522 | cairo_font_face_destroy(face); 523 | cairo_set_font_size(g.win.buf, 15.0); 524 | 525 | view_resize(); 526 | 527 | /* 528 | glyphs_t gl = {0}; 529 | for(size_t i = 0; i < file.nmemb; i++) { 530 | text_to_glyphs(&file.data[i], &gl); 531 | } 532 | return 0; 533 | */ 534 | 535 | cairo_save(g.win.buf); 536 | double s = 0.625; 537 | cairo_matrix_t mat; 538 | cairo_get_font_matrix(g.win.buf, &mat); 539 | cairo_set_font_size(g.win.buf, mat.xx * s); 540 | 541 | char labels[] = "Cut\nCopy\nPaste\nDelete\nFind\n./Open\nExec\nurxvt\ngrep std | grep \"<.*>\" -o\n+\n...\n"; 542 | char *lbl = labels; 543 | for(char *next; (next = strchr(lbl, '\n')) != NULL; lbl = next) { 544 | next++; 545 | ARR_EXTEND(&selbar.buttons, 1); 546 | button_t *btn = &selbar.buttons.data[selbar.buttons.nmemb - 1]; 547 | btn->label.data = lbl; 548 | btn->label.nmemb = next - lbl; 549 | btn->label.amemb = btn->label.nmemb; 550 | memset(&btn->glyphs, 0, sizeof btn->glyphs); 551 | 552 | text_to_glyphs(&btn->label, &btn->glyphs); 553 | btn->label.data[btn->label.nmemb - 1] = '\0'; 554 | } 555 | cairo_restore(g.win.buf); 556 | 557 | run(); 558 | 559 | fontset_free(&fontset); 560 | FcFini(); 561 | FT_Done_FreeType(ftlib); 562 | 563 | window_deinit(); 564 | 565 | file_free(&file); 566 | 567 | for(size_t i = 0; i < g.view.nmemb; i++) { 568 | free(g.view.lines[i].data); 569 | free(g.view.lines[i].glyph_to_offset); 570 | free(g.view.lines[i].offset_to_glyph); 571 | } 572 | 573 | free(g.view.lines); 574 | 575 | return 0; 576 | } 577 | 578 | #endif 579 | -------------------------------------------------------------------------------- /view.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | cairo_glyph_t *data; 3 | int nmemb; 4 | size_t *glyph_to_offset; 5 | int *offset_to_glyph; 6 | } glyphs_t; 7 | 8 | typedef struct { 9 | string_t label; 10 | glyphs_t glyphs; 11 | } button_t; 12 | 13 | typedef struct { 14 | ARRAY(button_t) buttons; 15 | } toolbar_t; 16 | 17 | typedef struct { 18 | size_t line; 19 | bool visible; 20 | toolbar_t bar; 21 | } toolbar_wrap_t; 22 | 23 | typedef struct { 24 | int width; 25 | int height; 26 | cairo_font_extents_t extents; 27 | double line_height; 28 | double left_margin; 29 | cairo_scaled_font_t *font; 30 | 31 | range_t range; 32 | int last_x; 33 | ssize_t start; 34 | glyphs_t *lines; 35 | size_t nmemb; 36 | toolbar_wrap_t selbar_wrap; 37 | } view_t; 38 | 39 | void glyphs_from_text(glyphs_t *gl, cairo_scaled_font_t *font, string_t *line); 40 | 41 | bool toolbar_click(toolbar_t *bar, view_t *v, int x); 42 | 43 | void view_hide_toolbar(view_t *v, toolbar_wrap_t *bar_wrap, size_t line); 44 | 45 | bool view_keybinds(view_t *v, KeySym keysym); 46 | bool view_keypress(view_t *v, KeySym keysym, char *buf, size_t len); 47 | bool view_mouse_motion(view_t *v, unsigned int state, int x, int y, int relx, int rely); 48 | bool view_mouse_press(view_t *v, unsigned int btn, int x, int y); 49 | bool view_mouse_release(view_t *v, unsigned int btn, int x, int y); 50 | 51 | void view_move_address_line(view_t *v, address_t *adr, int move); 52 | void view_move_address(view_t *v, address_t *adr, int move); 53 | 54 | bool view_move_start(view_t *v, ssize_t move); 55 | void view_set_start(view_t *v, size_t nr); 56 | 57 | glyphs_t *view_get_glyphs(view_t *v, size_t nr); 58 | ssize_t view_clamp_start(view_t *v, ssize_t nr); 59 | double view_address_to_x(view_t *v, address_t *adr); 60 | double view_line_to_y(view_t *v, size_t nr); 61 | size_t view_x_to_offset(view_t *v, size_t nr, int x); 62 | void view_xy_to_address(view_t *v, int x, int y, address_t *adr); 63 | ssize_t view_y_to_line(view_t *v, int y); 64 | 65 | void view_resize(view_t *v, int width, int height); 66 | void view_reshape(view_t *v); 67 | -------------------------------------------------------------------------------- /werf.c: -------------------------------------------------------------------------------- 1 | #include 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 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include "util.h" 23 | #include "array.h" 24 | 25 | #include "utf.h" 26 | #include "font.h" 27 | #include "edit.h" 28 | #include "view.h" 29 | #include "window.h" 30 | #include "pipe.h" 31 | #include "command.h" 32 | 33 | static control_t *g_control; 34 | 35 | static file_t file; 36 | static view_wrap_t view_wrap = { 37 | .view.range.file = &file 38 | }; 39 | static window_t win = { 40 | .width = 800, 41 | .height = 600, 42 | .view_wrap = &view_wrap 43 | }; 44 | 45 | /* 46 | typedef struct { 47 | size_t line_idx; 48 | size_t offset; 49 | double width; 50 | } vis_to_line_t; 51 | 52 | typedef struct { 53 | double width; 54 | size_t vis_first_idx; 55 | } line_to_vis_t; 56 | */ 57 | 58 | 59 | int 60 | selection_recv(control_t *control, void *usr, string_t *buf, size_t len) 61 | { 62 | (void)usr; 63 | if(!len) { 64 | return 0; 65 | } 66 | if(control->pipe.disregard) { 67 | buf->nmemb -= len; 68 | } 69 | return 0; 70 | } 71 | 72 | int 73 | control_recv(control_t *control, void *usr, string_t *buf, size_t len) 74 | { 75 | static const char disregard_str[] = "disregard"; 76 | static const char finish_str[] = "finish"; 77 | 78 | (void)usr; 79 | if(!len) { 80 | return 0; 81 | } 82 | 83 | size_t shift = 0; 84 | 85 | char *delim; 86 | char *scan_start = buf->data + buf->nmemb - len; 87 | char *line_start = buf->data; 88 | while( (delim = memchr(scan_start, '\n', len)) ) { 89 | size_t line_len = delim - line_start; 90 | 91 | if(is_str_eq(line_start, line_len, disregard_str, sizeof disregard_str - 1)) { 92 | control->pipe.disregard = true; 93 | } else if(is_str_eq(line_start, line_len, finish_str, sizeof finish_str - 1)) { 94 | control->pipe.finish = true; 95 | if(control->pipe.write_end) { 96 | control->pipe.done = true; 97 | } 98 | } else { 99 | fprintf(stderr, "unknown ctl command: '%.*s'\n", (int)line_len, line_start); 100 | } 101 | 102 | scan_start = delim + 1; 103 | line_start = scan_start; 104 | 105 | len -= line_len + 1; 106 | shift += line_len + 1; 107 | } 108 | ARR_FRAG_SHIFT(buf, 0, buf->nmemb, -shift); 109 | ARR_SHRINK(buf, shift); 110 | return 0; 111 | } 112 | 113 | typedef struct { 114 | range_t rng; 115 | char buf[BUFSIZ * 2]; 116 | } selection_send_work_t; 117 | 118 | int 119 | selection_send(control_t *control, void *usr, string_t *buf, size_t len) 120 | { 121 | selection_send_work_t *work = usr; 122 | if(!len) { 123 | control->pipe.write_end = true; 124 | if(control->pipe.finish) { 125 | control->pipe.done = true; 126 | } 127 | return 0; 128 | } 129 | buf->nmemb += range_copy(&work->rng, buf->data + buf->nmemb, buf->amemb - buf->nmemb); 130 | return 0; 131 | } 132 | 133 | int 134 | builtin_command(char *cmd) 135 | { 136 | if(!strcmp("Delete", cmd)) { 137 | command_delete(&win.view_wrap->view); 138 | return 1; 139 | } else if(!strcmp("Read", cmd)) { 140 | return 1; 141 | } else if(!strcmp("Write", cmd)) { 142 | return 1; 143 | } else if(!strcmp("Undo", cmd)) { 144 | command_undo(&win.view_wrap->view); 145 | return 1; 146 | } else if(!strcmp("Redo", cmd)) { 147 | command_redo(&win.view_wrap->view); 148 | return 1; 149 | } else if(!strcmp("+", cmd)) { 150 | return 1; 151 | } else if(!strcmp("...", cmd)) { 152 | return 1; 153 | } 154 | 155 | return 0; 156 | } 157 | 158 | int 159 | handle_command(char *cmd) 160 | { 161 | if(builtin_command(cmd)) { 162 | return 1; 163 | } 164 | 165 | control_t control = {0}; 166 | g_control = &control; 167 | 168 | selection_send_work_t selection_send_work = { 169 | .rng = win.view_wrap->view.range 170 | }; 171 | 172 | struct { 173 | struct { 174 | pipe_t selection; 175 | pipe_t control; 176 | } r; 177 | struct { 178 | pipe_t selection; 179 | } w; 180 | } pipes = { 181 | .r.selection.handler = selection_recv, 182 | .r.control = { 183 | .child.name = "werf_control_W", 184 | .handler = control_recv 185 | }, 186 | .w.selection = { 187 | .handler = selection_send, 188 | .usr = &selection_send_work, 189 | .buf.data = selection_send_work.buf, 190 | .buf.amemb = sizeof selection_send_work.buf 191 | } 192 | }; 193 | 194 | pipe_t *pipes_all = (pipe_t*)&pipes; 195 | pipe_t *pipes_r = (pipe_t*)&pipes.r; 196 | pipe_t *pipes_w = (pipe_t*)&pipes.w; 197 | 198 | size_t num_r = sizeof pipes.r / sizeof pipes_r[0]; 199 | size_t num_w = sizeof pipes.w / sizeof pipes_w[0]; 200 | 201 | if(pipe_init(pipes_r, num_r, 0) < 0 || 202 | pipe_init(pipes_w, num_w, 1) < 0) { 203 | return -1; 204 | } 205 | if( (control.child.pid = fork()) < 0 ) { 206 | goto out_err; 207 | } 208 | 209 | if(control.child.pid == 0) { 210 | char *argv[] = {"sh", "-c", cmd, (char*)0}; 211 | if(dup2(pipes.w.selection.child.fd, STDIN_FILENO) < 0 || 212 | dup2(pipes.r.selection.child.fd, STDOUT_FILENO) < 0) { 213 | die("dup2 failed: %s\n", strerror(errno)); 214 | } 215 | pipe_cmd_exec(pipes_all, num_r + num_w, argv); 216 | die("pipe_cmd_exec failed: %s\n", strerror(errno)); 217 | } 218 | 219 | for(size_t i = 0; i < num_r + num_w; i++) { 220 | close(pipes_all[i].child.fd); 221 | if(fcntl(pipes_all[i].fd, F_SETFL, O_NONBLOCK) < 0) { 222 | goto out_err; 223 | } 224 | } 225 | 226 | fd_set rfd; 227 | fd_set wfd; 228 | 229 | while(pipe_select(&control, &rfd, &wfd, pipes_r, num_r, pipes_w, num_w)) { 230 | for(size_t i = 0; i < num_r; i++) { 231 | if(pipes_r[i].fd >= 0 && FD_ISSET(pipes_r[i].fd, &rfd)) { 232 | pipe_recv(&pipes_r[i], &control); 233 | break; 234 | } 235 | } 236 | for(size_t i = 0; i < num_w; i++) { 237 | if(pipes_w[i].fd >= 0 && FD_ISSET(pipes_w[i].fd, &wfd)) { 238 | pipe_send(&pipes_w[i], &control); 239 | break; 240 | } 241 | } 242 | } 243 | 244 | for(size_t i = 0; i < num_r; i++) { 245 | while(pipes_r[i].fd >= 0) { 246 | pipe_recv(&pipes_r[i], &control); 247 | } 248 | } 249 | 250 | for(size_t i = 0; i < num_w; i++) { 251 | if(pipes_w[i].fd >= 0) { 252 | close(pipes_w[i].fd); 253 | } 254 | } 255 | 256 | if(!control.child.exited) { 257 | waitpid(control.child.pid, 0, 0); 258 | } 259 | g_control = 0; 260 | 261 | if(!control.pipe.disregard) { 262 | range_push(&win.view_wrap->view.range, pipes.r.selection.buf.data, 263 | pipes.r.selection.buf.nmemb, OP_Replace); 264 | } 265 | for(size_t i = 0; i < num_r; i++) { 266 | ARR_FREE(&pipes_r[i].buf); 267 | } 268 | 269 | if(control.pipe.disregard) { 270 | // FIXME: we may still want to hide toolbar 271 | return 0; 272 | } 273 | return 1; 274 | 275 | out_err: 276 | g_control = 0; 277 | for(size_t i = 0; i < num_r + num_w; i++) { 278 | if(pipes_all[i].fd >= 0) { 279 | close(pipes_all[i].fd); 280 | } 281 | } 282 | // FIXME: what about this error? 283 | return -1; 284 | } 285 | 286 | void 287 | file_read(file_t *f, char *fname) 288 | { 289 | int fd = open(fname, O_RDONLY); 290 | DIEIF(fd < 0); 291 | 292 | range_read(&(range_t){.file = f}, fd); 293 | close(fd); 294 | 295 | f->dirty = true; 296 | 297 | printf("file lines: %zu\n", f->content.nmemb); 298 | } 299 | 300 | static void 301 | sigchld(int sig, siginfo_t *inf, void *ctx) 302 | { 303 | (void)sig; 304 | (void)ctx; 305 | 306 | if(g_control && !g_control->error && !g_control->child.exited && 307 | g_control->child.pid == inf->si_pid) { 308 | g_control->child.exited = true; 309 | g_control->child.status = inf->si_status; 310 | if(inf->si_status) { 311 | g_control->error = true; 312 | g_control->pipe.done = true; 313 | } 314 | g_control = 0; 315 | } 316 | } 317 | 318 | int 319 | main(int argc, char *argv[]) 320 | { 321 | setlocale(LC_CTYPE, ""); 322 | signal(SIGPIPE, SIG_IGN); 323 | sigaction(SIGCHLD, &(struct sigaction) { 324 | .sa_sigaction = sigchld, 325 | .sa_flags = SA_SIGINFO | SA_NOCLDSTOP 326 | }, 0); 327 | 328 | file_insert_line(win.view_wrap->view.range.file, 0, "", 0); 329 | if(argc > 1) { 330 | file_read(&file, argv[1]); 331 | } 332 | 333 | window_init(&win); 334 | 335 | FT_Library ftlib; 336 | FT_Init_FreeType(&ftlib); 337 | DIEIF(!FcInit()); 338 | 339 | fontset_t fontset = {0}; 340 | DIEIF( fontset_init(&fontset, FcNameParse((FcChar8*)"DroidSans")) ); 341 | cairo_font_face_t *font = font_cairo_font_face_create(&fontset); 342 | 343 | cairo_set_font_face(win.cr, font); 344 | //cairo_font_face_destroy(font); 345 | cairo_set_font_size(win.cr, 15.0); 346 | 347 | cairo_save(win.cr); 348 | double s = 0.625; 349 | cairo_matrix_t mat; 350 | cairo_get_font_matrix(win.cr, &mat); 351 | cairo_set_font_size(win.cr, mat.xx * s); 352 | 353 | char labels[] = "Cut\nCopy\nPaste\nDelete\nFind\n./Open\nExec\nurxvt\ngrep std | grep \"<.*>\" -o\n+\n...\n"; 354 | char *lbl = labels; 355 | for(char *next; (next = strchr(lbl, '\n')) != NULL; lbl = next) { 356 | next++; 357 | toolbar_t *bar = &win.view_wrap->view.selbar_wrap.bar; 358 | ARR_EXTEND(&bar->buttons, 1); 359 | button_t *btn = &bar->buttons.data[bar->buttons.nmemb - 1]; 360 | btn->label.data = lbl; 361 | btn->label.nmemb = next - lbl; 362 | btn->label.amemb = btn->label.nmemb; 363 | memset(&btn->glyphs, 0, sizeof btn->glyphs); 364 | 365 | glyphs_from_text(&btn->glyphs, cairo_get_scaled_font(win.cr), 366 | &btn->label); 367 | btn->label.data[btn->label.nmemb - 1] = '\0'; 368 | } 369 | cairo_restore(win.cr); 370 | 371 | cairo_font_extents(win.cr, &win.view_wrap->view.extents); 372 | if(!win.view_wrap->view.font) { 373 | win.view_wrap->view.font = cairo_get_scaled_font(win.cr); 374 | } 375 | view_resize(&win.view_wrap->view, win.width, win.height); 376 | window_run(&win); 377 | 378 | fontset_free(&fontset); 379 | FcFini(); 380 | FT_Done_FreeType(ftlib); 381 | 382 | window_deinit(&win); 383 | 384 | file_free(&file); 385 | 386 | for(size_t i = 0; i < win.view_wrap->view.nmemb; i++) { 387 | free(win.view_wrap->view.lines[i].data); 388 | free(win.view_wrap->view.lines[i].glyph_to_offset); 389 | free(win.view_wrap->view.lines[i].offset_to_glyph); 390 | } 391 | 392 | free(win.view_wrap->view.lines); 393 | 394 | return 0; 395 | } 396 | -------------------------------------------------------------------------------- /window.c: -------------------------------------------------------------------------------- 1 | #include 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 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include "util.h" 23 | #include "array.h" 24 | 25 | #include "edit.h" 26 | #include "view.h" 27 | #include "draw.h" 28 | #include "window.h" 29 | 30 | void 31 | window_redraw(window_t *win) 32 | { 33 | XFillRectangle(win->display, win->pixmap, win->gfxctx, 34 | 0, 0, win->width, win->height); 35 | 36 | cairo_identity_matrix(win->cr); 37 | cairo_set_source_rgba(win->cr, 0, 0, 0, 1); 38 | 39 | draw_view(win->cr, &win->view_wrap->view); 40 | 41 | XCopyArea(win->display, win->pixmap, win->window, win->gfxctx, 42 | 0, 0, win->width, win->height, 0, 0); 43 | } 44 | 45 | static bool 46 | window_resize(window_t *win, XEvent *ev) 47 | { 48 | if(win->width == ev->xconfigure.width && win->height == ev->xconfigure.height) { 49 | return false; 50 | } 51 | win->width = ev->xconfigure.width; 52 | win->height = ev->xconfigure.height; 53 | 54 | XFreePixmap(win->display, win->pixmap); 55 | win->pixmap = XCreatePixmap(win->display, win->window, win->width, win->height, 56 | DefaultDepth(win->display, win->screen)); 57 | 58 | cairo_xlib_surface_set_drawable(cairo_get_target(win->cr), 59 | win->pixmap, win->width, win->height); 60 | 61 | cairo_font_extents(win->cr, &win->view_wrap->view.extents); 62 | if(!win->view_wrap->view.font) { 63 | win->view_wrap->view.font = cairo_get_scaled_font(win->cr); 64 | } 65 | view_resize(&win->view_wrap->view, win->width, win->height); 66 | return true; 67 | } 68 | 69 | static void 70 | window_init_input_methods(window_t *win) 71 | { 72 | char *locale_mods[] = { 73 | "", 74 | "@im=local", 75 | "@im=", 76 | }; 77 | size_t i = 0; 78 | 79 | do { 80 | XSetLocaleModifiers(locale_mods[i++]); 81 | win->xim = XOpenIM(win->display, NULL, NULL, NULL); 82 | } while(win->xim == NULL && i < LEN(locale_mods)); 83 | 84 | DIEIF(win->xim == NULL); 85 | 86 | win->xic = XCreateIC(win->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 87 | XNClientWindow, win->window, XNFocusWindow, win->window, NULL); 88 | DIEIF(win->xic == NULL); 89 | } 90 | 91 | void 92 | window_init(window_t *win) 93 | { 94 | win->display = XOpenDisplay(NULL); 95 | DIEIF(win->display == NULL); 96 | 97 | Window parent = XRootWindow(win->display, win->screen); 98 | win->screen = XDefaultScreen(win->display); 99 | win->window = XCreateSimpleWindow(win->display, parent, 100 | 0, 0, win->width, win->height, 0, 0, 101 | WhitePixel(win->display, win->screen)); 102 | 103 | win->gfxctx = XCreateGC(win->display, parent, GCGraphicsExposures, 104 | &(XGCValues){.graphics_exposures = False}); 105 | XSetForeground(win->display, win->gfxctx, WhitePixel(win->display, win->screen)); 106 | 107 | window_init_input_methods(win); 108 | 109 | XSelectInput(win->display, win->window, StructureNotifyMask | ExposureMask | 110 | KeyPressMask | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask); 111 | XMapWindow(win->display, win->window); 112 | 113 | XSetWMProperties(win->display, win->window, NULL, NULL, NULL, 0, NULL, 114 | &(XWMHints){.flags = InputHint, .input = 1}, 115 | &(XClassHint){"Werf", "Werf"}); 116 | XSync(win->display, False); 117 | 118 | for(XEvent ev;;) { 119 | XNextEvent(win->display, &ev); 120 | if(XFilterEvent(&ev, None)) 121 | continue; 122 | if(ev.type == ConfigureNotify) { 123 | win->width = ev.xconfigure.width; 124 | win->height = ev.xconfigure.height; 125 | } else if(ev.type == MapNotify) { 126 | break; 127 | } 128 | } 129 | 130 | win->pixmap = XCreatePixmap(win->display, win->window, win->width, win->height, 131 | DefaultDepth(win->display, win->screen)); 132 | cairo_surface_t *bufsurf = cairo_xlib_surface_create(win->display, win->pixmap, 133 | DefaultVisual(win->display, win->screen), win->width, win->height); 134 | win->cr = cairo_create(bufsurf); 135 | 136 | win->run = true; 137 | } 138 | 139 | void 140 | window_deinit(window_t *win) 141 | { 142 | cairo_destroy(win->cr); 143 | 144 | XCloseDisplay(win->display); 145 | win->display = NULL; 146 | } 147 | 148 | static bool 149 | window_keypress(window_t *win, XEvent *ev) 150 | { 151 | KeySym keysym; 152 | char buf[32]; 153 | Status status; 154 | XKeyEvent *e = &ev->xkey; 155 | int len = Xutf8LookupString(win->xic, e, buf, sizeof buf, &keysym, &status); 156 | 157 | if(keysym == XK_Escape) { 158 | win->run = false; 159 | return true; 160 | } 161 | 162 | return view_keypress(&win->view_wrap->view, keysym, buf, len > 0 ? len : 0); 163 | } 164 | 165 | static bool 166 | window_mouse_press(window_t *win, XEvent *ev) 167 | { 168 | bool handled; 169 | XButtonEvent *e = &ev->xbutton; 170 | int x = e->x - win->view_wrap->x; 171 | int y = e->y - win->view_wrap->y; 172 | 173 | handled = view_mouse_press(&win->view_wrap->view, e->button, x, y); 174 | 175 | win->prevx = e->x; 176 | win->prevy = e->y; 177 | return handled; 178 | } 179 | 180 | static bool 181 | window_mouse_motion(window_t *win, XEvent *ev) 182 | { 183 | bool handled; 184 | XMotionEvent *e = &ev->xmotion; 185 | int x = e->x - win->view_wrap->x; 186 | int y = e->y - win->view_wrap->y; 187 | int relx = e->x - win->prevx; 188 | int rely = e->y - win->prevy; 189 | 190 | handled = view_mouse_motion(&win->view_wrap->view, e->state, x, y, relx, rely); 191 | 192 | win->prevx = e->x; 193 | win->prevy = e->y; 194 | return handled; 195 | } 196 | 197 | static bool 198 | window_mouse_release(window_t *win, XEvent *ev) 199 | { 200 | XButtonEvent *e = &ev->xbutton; 201 | int x = e->x - win->view_wrap->x; 202 | int y = e->y - win->view_wrap->y; 203 | return view_mouse_release(&win->view_wrap->view, e->button, x, y); 204 | } 205 | 206 | void 207 | window_run(window_t *win) 208 | { 209 | fd_set rfd; 210 | int xfd = XConnectionNumber(win->display); 211 | struct timespec now, prev; 212 | struct timespec drawtime = {.tv_nsec = 0}; 213 | struct timespec *tv = &drawtime; 214 | bool draw_request = false; 215 | 216 | clock_gettime(CLOCK_MONOTONIC, &prev); 217 | XEvent ev; 218 | while(win->run) { 219 | FD_ZERO(&rfd); 220 | FD_SET(xfd, &rfd); 221 | DIEIF(pselect(xfd+1, &rfd, NULL, NULL, tv, NULL) < 0 && errno != EINTR); 222 | 223 | while(XPending(win->display)) { 224 | bool handled = false; 225 | XNextEvent(win->display, &ev); 226 | switch(ev.type) { 227 | case DestroyNotify: 228 | win->run = false; 229 | return; 230 | case KeyPress: 231 | handled = window_keypress(win, &ev); 232 | break; 233 | case ButtonPress: 234 | handled = window_mouse_press(win, &ev); 235 | break; 236 | case ButtonRelease: 237 | handled = window_mouse_release(win, &ev); 238 | break; 239 | case MotionNotify: 240 | handled = window_mouse_motion(win, &ev); 241 | break; 242 | case ConfigureNotify: 243 | handled = window_resize(win, &ev); 244 | break; 245 | case Expose: 246 | handled = true; 247 | break; 248 | default: 249 | break; 250 | } 251 | if(handled) { 252 | draw_request = true; 253 | } 254 | } 255 | 256 | if(!draw_request) { 257 | continue; 258 | } 259 | clock_gettime(CLOCK_MONOTONIC, &now); 260 | unsigned int diff = ((now.tv_sec - prev.tv_sec) * 1000 + 261 | (now.tv_nsec - prev.tv_nsec) / 1E6); 262 | if(diff < 1000 / 60) { 263 | drawtime.tv_nsec = diff * 1E6; 264 | tv = &drawtime; 265 | continue; 266 | } 267 | 268 | draw_request = false; 269 | window_redraw(win); 270 | XFlush(win->display); 271 | clock_gettime(CLOCK_MONOTONIC, &prev); 272 | tv = NULL; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /window.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | int x; 3 | int y; 4 | view_t view; 5 | } view_wrap_t; 6 | 7 | typedef struct { 8 | int width; 9 | int height; 10 | Display *display; 11 | int screen; 12 | GC gfxctx; 13 | Window window; 14 | Drawable pixmap; 15 | cairo_t *cr; 16 | XIM xim; 17 | XIC xic; 18 | view_wrap_t *view_wrap; 19 | bool run; 20 | 21 | int prevx; 22 | int prevy; 23 | } window_t; 24 | 25 | void window_init(window_t *win); 26 | void window_deinit(window_t *win); 27 | void window_run(window_t *win); 28 | void window_redraw(window_t *win); 29 | --------------------------------------------------------------------------------