tsex.c - sex - libtermbox based text editor
(HTM) git clone git://z3bra.org/sex
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
---
tsex.c (4948B)
---
1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <sys/mman.h>
4 #include <sys/queue.h>
5 #include <termbox.h>
6 #include <unistd.h>
7 #include <string.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <fcntl.h>
11 #include <err.h>
12
13 #include "arg.h"
14 #include "sex.h"
15
16 #define TAB_WIDTH 8
17
18 typedef enum { UP, DOWN, RIGHT, LEFT } direction;
19
20 struct line_s {
21 char *p;
22 size_t len;
23 size_t number;
24 TAILQ_ENTRY(line_s) entries;
25 };
26
27 struct file_s {
28 char *path;
29 char *map;
30 char *p;
31 size_t size;
32 int d;
33 struct line_s *ln;
34 };
35
36 struct coord_s {
37 int x;
38 int y;
39 };
40
41 struct ui_s {
42 struct line_s *top;
43 struct line_s *cur;
44 int width;
45 int height;
46 struct coord_s pos;
47 };
48
49 static struct file_s f;
50 TAILQ_HEAD(line_s_head, line_s) head;
51
52 void
53 cleanup(void)
54 {
55 munmap(f.map, f.size);
56 close(f.d);
57 tb_shutdown();
58 }
59
60 void
61 init_termbox(void)
62 {
63 int r;
64
65 r = tb_init();
66 if (r)
67 err(1, "tb_init()");
68 }
69
70 void
71 open_file(char *path)
72 {
73 size_t i, last_eol;
74 int d, ln = 1;
75 struct stat s;
76 struct line_s *tmp;
77
78 f.path = path;
79
80 d = open(f.path, O_RDWR);
81 if (d < 0)
82 err(1, "open()");
83
84 if (fstat(d, &s))
85 err(1, "fstat()");
86
87 f.size = (size_t)s.st_size;
88
89 f.map = mmap(NULL, f.size, PROT_READ | PROT_WRITE, MAP_PRIVATE, d, 0);
90 if (f.map == MAP_FAILED)
91 err(1, "mmap()");
92
93 f.p = f.map;
94
95 TAILQ_INIT(&head);
96
97 for (i = last_eol = 0; i < f.size; ++i) {
98 if (*(f.map + i) == '\n') {
99 tmp = malloc(sizeof(*tmp));
100 if (tmp == NULL)
101 err(1, "malloc()");
102
103 tmp->p = f.map + last_eol;
104 tmp->len = i - last_eol + 1;
105 tmp->number = ln++;
106 last_eol = i + 1;
107 TAILQ_INSERT_TAIL(&head, tmp, entries);
108 }
109 }
110 }
111
112 ssize_t
113 ui_expand_tab(int x, int y, uint8_t tw)
114 {
115 uint8_t i, tabsize = 0;
116
117 tabsize = tw - (x % tw);
118 for (i=x; i<(x + tabsize); i++)
119 tb_change_cell(i, y, ' ', TB_DEFAULT, TB_DEFAULT);
120
121 return tabsize;
122 }
123
124 int
125 ui_print(char *str, int x, int y, int len) {
126 size_t n = 0;
127 uint32_t uni;
128
129 while (*str) {
130 switch(*str) {
131 case '\t':
132 x += ui_expand_tab(x, y, TAB_WIDTH);
133 str++;
134 break;
135 default:
136 str += tb_utf8_char_to_unicode(&uni, str);
137 tb_change_cell(x, y, uni, TB_DEFAULT, TB_DEFAULT);
138 x++;
139 }
140
141 if (++n == len || x >= tb_width())
142 break;
143 }
144 return x - 1;
145 }
146
147 void
148 ui_redraw(struct ui_s *ui) {
149 /* redraw screen */
150 struct line_s *tmp;
151 int row, j;
152 size_t s = 0;
153
154 tmp = ui->top;
155
156 tb_clear();
157 tb_present();
158
159 for (row = 0; row < tb_height() - 1; row++) {
160 if (tmp && tmp->p)
161 ui_print(tmp->p, 0, row, tmp->len);
162 else {
163 ui_print("~", 0, row, 1);
164 continue;
165 }
166
167 tmp = TAILQ_NEXT(tmp, entries);
168 }
169
170 tb_present();
171 }
172
173 void
174 ui_init(struct ui_s *ui)
175 {
176 ui->width = tb_width();
177 ui->height = tb_height();
178 ui->top = TAILQ_FIRST(&head);
179 ui->pos.x = 0;
180 ui->pos.y = 0;
181 ui->cur = ui->top;
182 }
183
184 void
185 scroll(struct ui_s *window, direction dir)
186 {
187 switch (dir) {
188 case UP:
189 if (window->pos.y == 0) {
190 if (window->top == TAILQ_FIRST(&head))
191 break;
192 window->top = TAILQ_PREV(window->top, line_s_head,
193 entries);
194 } else {
195 tb_set_cursor(window->pos.x, --window->pos.y);
196 break;
197 }
198 window->cur = TAILQ_PREV(window->cur, line_s_head, entries);
199 break;
200 case DOWN:
201 if (window->pos.y == tb_height() - 2) {
202 if (window->top == TAILQ_LAST(&head, line_s_head))
203 break;
204 window->top = TAILQ_NEXT(window->top, entries);
205 } else {
206 if (window->cur == TAILQ_LAST(&head, line_s_head))
207 break;
208 tb_set_cursor(window->pos.x, ++window->pos.y);
209 break;
210 }
211 window->cur = TAILQ_NEXT(window->cur, entries);
212 break;
213 case RIGHT:
214 if (window->pos.x < window->cur->len - 1)
215 tb_set_cursor(++window->pos.x, window->pos.y);
216 break;
217 case LEFT:
218 if (window->pos.x > 0)
219 tb_set_cursor(--window->pos.x, window->pos.y);
220 break;
221 }
222 }
223
224 void
225 ui_edit(char *path)
226 {
227 int needs_redraw;
228 struct tb_event ev;
229 struct ui_s window;
230
231 open_file(path);
232
233 ui_init(&window);
234 tb_set_cursor(0,0);
235 ui_redraw(&window);
236
237 while (tb_poll_event(&ev)) {
238 switch (ev.type) {
239 case TB_EVENT_KEY:
240 /* handle keys */
241 switch (ev.key | ev.ch) {
242 case TB_KEY_CTRL_C:
243 return;
244 /* NOTREACHED */
245 case TB_KEY_CTRL_L:
246 needs_redraw = 1;
247 break;
248 case 'k':
249 case TB_KEY_ARROW_UP:
250 scroll(&window, UP);
251 needs_redraw = 1;
252 break;
253 case 'j':
254 case TB_KEY_ARROW_DOWN:
255 scroll(&window, DOWN);
256 needs_redraw = 1;
257 break;
258 case 'l':
259 case TB_KEY_ARROW_RIGHT:
260 scroll(&window, RIGHT);
261 needs_redraw = 1;
262 break;
263 case 'h':
264 case TB_KEY_ARROW_LEFT:
265 scroll(&window, LEFT);
266 needs_redraw = 1;
267 break;
268 }
269 break;
270 case TB_EVENT_RESIZE:
271 /* handle resizing */
272 needs_redraw = 1;
273 break;
274 }
275
276 if (needs_redraw) {
277 ui_redraw(&window);
278 needs_redraw = 0;
279 }
280 }
281 }
282
283
284 int
285 main(int argc, char **argv)
286 {
287 char *argv0; /* shadow */
288
289 ARGBEGIN {
290 case 'V':
291 /* print version */
292 printf("%s - %s\n", argv0, VERSION);
293 return 0;
294 break;
295 } ARGEND
296
297 init_termbox();
298 atexit(cleanup);
299
300 while (*argv)
301 ui_edit(*argv++);
302
303 return 0;
304 }