Add explicit build commands to README
[vis.git] / vis-lua.c
blob5b44ea1ad710e6e5d65d7c8be3f6361472359ba0
1 #include <stdarg.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <sys/types.h>
5 #include <pwd.h>
7 #include "vis-lua.h"
8 #include "vis-core.h"
9 #include "text-motions.h"
11 #if !CONFIG_LUA
13 void vis_lua_start(Vis *vis) { }
14 void vis_lua_quit(Vis *vis) { }
15 void vis_lua_file_open(Vis *vis, File *file) { }
16 void vis_lua_file_save(Vis *vis, File *file) { }
17 void vis_lua_file_close(Vis *vis, File *file) { }
18 void vis_lua_win_open(Vis *vis, Win *win) { }
19 void vis_lua_win_close(Vis *vis, Win *win) { }
20 bool vis_theme_load(Vis *vis, const char *name) { return true; }
22 #else
24 #if 0
25 static void stack_dump_entry(lua_State *L, int i) {
26 int t = lua_type(L, i);
27 switch (t) {
28 case LUA_TNIL:
29 printf("nil");
30 break;
31 case LUA_TBOOLEAN:
32 printf(lua_toboolean(L, i) ? "true" : "false");
33 break;
34 case LUA_TLIGHTUSERDATA:
35 printf("lightuserdata(%p)", lua_touserdata(L, i));
36 break;
37 case LUA_TNUMBER:
38 printf("%g", lua_tonumber(L, i));
39 break;
40 case LUA_TSTRING:
41 printf("`%s'", lua_tostring(L, i));
42 break;
43 case LUA_TTABLE:
44 printf("table[");
45 lua_pushnil(L); /* first key */
46 while (lua_next(L, i > 0 ? i : i - 1)) {
47 stack_dump_entry(L, -2);
48 printf("=");
49 stack_dump_entry(L, -1);
50 printf(",");
51 lua_pop(L, 1); /* remove value, keep key */
53 printf("]");
54 break;
55 case LUA_TUSERDATA:
56 printf("userdata(%p)", lua_touserdata(L, i));
57 break;
58 default: /* other values */
59 printf("%s", lua_typename(L, t));
60 break;
64 static void stack_dump(lua_State *L, const char *format, ...) {
65 va_list ap;
66 va_start(ap, format);
67 vprintf(format, ap);
68 va_end(ap);
69 int top = lua_gettop(L);
70 for (int i = 1; i <= top; i++) {
71 printf("%d: ", i);
72 stack_dump_entry(L, i);
73 printf("\n");
75 printf("\n\n");
76 fflush(stdout);
79 #endif
81 static void *obj_new(lua_State *L, size_t size, const char *type) {
82 void *obj = lua_newuserdata(L, size);
83 luaL_getmetatable(L, type);
84 lua_setmetatable(L, -2);
85 lua_newtable(L);
86 lua_setuservalue(L, -2);
87 return obj;
90 /* returns registry["vis.objects"][addr] if it is of correct type */
91 static void *obj_ref_get(lua_State *L, void *addr, const char *type) {
92 lua_getfield(L, LUA_REGISTRYINDEX, "vis.objects");
93 lua_pushlightuserdata(L, addr);
94 lua_gettable(L, -2);
95 lua_remove(L, -2);
96 if (lua_isnil(L, -1)) {
97 lua_pop(L, 1);
98 return NULL;
100 return luaL_checkudata(L, -1, type);
103 /* expects a userdatum at the top of the stack and sets
105 * registry["vis.objects"][addr] = userdata
107 static void obj_ref_set(lua_State *L, void *addr) {
108 lua_getfield(L, LUA_REGISTRYINDEX, "vis.objects");
109 lua_pushlightuserdata(L, addr);
110 lua_pushvalue(L, -3);
111 lua_settable(L, -3);
112 lua_pop(L, 1);
115 /* invalidates an object reference
117 * registry["vis.objects"][addr] = nil
119 static void obj_ref_free(lua_State *L, void *addr) {
120 lua_pushnil(L);
121 obj_ref_set(L, addr);
124 /* creates a new object reference of given type if it does not
125 * already exist in the registry */
126 static void *obj_ref_new(lua_State *L, void *addr, const char *type) {
127 if (!addr)
128 return NULL;
129 void **handle = (void**)obj_ref_get(L, addr, type);
130 if (!handle) {
131 handle = obj_new(L, sizeof(addr), type);
132 obj_ref_set(L, addr);
133 *handle = addr;
135 return *handle;
138 /* retrieve object stored in reference at stack location `idx' */
139 static void *obj_ref_check_get(lua_State *L, int idx, const char *type) {
140 void **addr = luaL_checkudata(L, idx, type);
141 if (!obj_ref_get(L, *addr, type))
142 return NULL;
143 return *addr;
146 /* (type) check validity of object reference at stack location `idx' */
147 static void *obj_ref_check(lua_State *L, int idx, const char *type) {
148 void *obj = obj_ref_check_get(L, idx, type);
149 if (obj)
150 lua_pop(L, 1);
151 return obj;
154 static int index_common(lua_State *L) {
155 lua_getmetatable(L, 1);
156 lua_pushvalue(L, 2);
157 lua_gettable(L, -2);
158 if (lua_isnil(L, -1)) {
159 lua_getuservalue(L, 1);
160 lua_pushvalue(L, 2);
161 lua_gettable(L, -2);
163 return 1;
166 static int newindex_common(lua_State *L) {
167 lua_getuservalue(L, 1);
168 lua_pushvalue(L, 2);
169 lua_pushvalue(L, 3);
170 lua_settable(L, -3);
171 return 0;
174 static int windows_iter(lua_State *L);
176 static int windows(lua_State *L) {
177 Vis *vis = lua_touserdata(L, lua_upvalueindex(1));
178 Win **handle = lua_newuserdata(L, sizeof *handle);
179 *handle = vis->windows;
180 lua_pushcclosure(L, windows_iter, 1);
181 return 1;
184 static int windows_iter(lua_State *L) {
185 Win **handle = lua_touserdata(L, lua_upvalueindex(1));
186 if (!*handle)
187 return 0;
188 Win *win = obj_ref_new(L, *handle, "vis.window");
189 if (!win)
190 return 0;
191 *handle = win->next;
192 return 1;
195 static int files_iter(lua_State *L);
197 static int files(lua_State *L) {
198 Vis *vis = lua_touserdata(L, lua_upvalueindex(1));
199 File **handle = lua_newuserdata(L, sizeof *handle);
200 *handle = vis->files;
201 lua_pushcclosure(L, files_iter, 1);
202 return 1;
205 static int files_iter(lua_State *L) {
206 File **handle = lua_touserdata(L, lua_upvalueindex(1));
207 if (!*handle)
208 return 0;
209 File *file = obj_ref_new(L, *handle, "vis.file");
210 if (!file)
211 return 0;
212 *handle = file->next;
213 return 1;
216 static int command(lua_State *L) {
217 Vis *vis = lua_touserdata(L, lua_upvalueindex(1));
218 const char *cmd = luaL_checkstring(L, 1);
219 bool ret = vis_cmd(vis, cmd);
220 lua_pushboolean(L, ret);
221 return 1;
224 static int info(lua_State *L) {
225 Vis *vis = lua_touserdata(L, lua_upvalueindex(1));
226 const char *msg = luaL_checkstring(L, 1);
227 vis_info_show(vis, "%s", msg);
228 return 0;
231 static int open(lua_State *L) {
232 Vis *vis = lua_touserdata(L, lua_upvalueindex(1));
233 const char *name = luaL_checkstring(L, 1);
234 File *file = NULL;
235 if (vis_window_new(vis, name))
236 file = obj_ref_new(L, vis->win->file, "vis.file");
237 if (!file)
238 lua_pushnil(L);
239 return 1;
242 static const struct luaL_Reg vis_lua[] = {
243 { "files", files },
244 { "windows", windows },
245 { "command", command },
246 { "info", info },
247 { "open", open },
248 { NULL, NULL },
251 static int window_index(lua_State *L) {
252 Win *win = obj_ref_check(L, 1, "vis.window");
253 if (!win) {
254 lua_pushnil(L);
255 return 1;
258 if (lua_isstring(L, 2)) {
259 const char *key = lua_tostring(L, 2);
260 if (strcmp(key, "file") == 0) {
261 obj_ref_new(L, win->file, "vis.file");
262 return 1;
265 if (strcmp(key, "cursor") == 0) {
266 obj_ref_new(L, win->view, "vis.window.cursor");
267 return 1;
271 return index_common(L);
274 static int window_newindex(lua_State *L) {
275 Win *win = obj_ref_check(L, 1, "vis.window");
276 if (!win)
277 return 0;
278 return newindex_common(L);
281 static const struct luaL_Reg window_funcs[] = {
282 { "__index", window_index },
283 { "__newindex", window_newindex },
284 { NULL, NULL },
287 static int window_cursor_index(lua_State *L) {
288 View *view = obj_ref_check(L, 1, "vis.window.cursor");
289 if (!view) {
290 lua_pushnil(L);
291 return 1;
294 if (lua_isstring(L, 2)) {
295 const char *key = lua_tostring(L, 2);
296 if (strcmp(key, "pos") == 0) {
297 lua_pushunsigned(L, view_cursor_get(view));
298 return 1;
301 if (strcmp(key, "line") == 0) {
302 lua_pushunsigned(L, view_cursor_getpos(view).line);
303 return 1;
306 if (strcmp(key, "col") == 0) {
307 lua_pushunsigned(L, view_cursor_getpos(view).col);
308 return 1;
312 return index_common(L);
315 static int window_cursor_newindex(lua_State *L) {
316 View *view = obj_ref_check(L, 1, "vis.window.cursor");
317 if (!view)
318 return 0;
319 if (lua_isstring(L, 2)) {
320 const char *key = lua_tostring(L, 2);
321 if (strcmp(key, "pos") == 0) {
322 size_t pos = luaL_checkunsigned(L, 3);
323 view_cursor_to(view, pos);
324 return 0;
327 return newindex_common(L);
330 static const struct luaL_Reg window_cursor_funcs[] = {
331 { "__index", window_cursor_index },
332 { "__newindex", window_cursor_newindex },
333 { NULL, NULL },
336 static int file_index(lua_State *L) {
337 File *file = obj_ref_check(L, 1, "vis.file");
338 if (!file) {
339 lua_pushnil(L);
340 return 1;
343 if (lua_isstring(L, 2)) {
344 const char *key = lua_tostring(L, 2);
345 if (strcmp(key, "name") == 0) {
346 lua_pushstring(L, file->name);
347 return 1;
349 if (strcmp(key, "lines") == 0) {
350 obj_ref_new(L, file->text, "vis.file.text");
351 return 1;
355 return index_common(L);
358 static int file_newindex(lua_State *L) {
359 File *file = obj_ref_check(L, 1, "vis.file");
360 if (!file)
361 return 0;
362 return newindex_common(L);
365 static int file_insert(lua_State *L) {
366 File *file = obj_ref_check(L, 1, "vis.file");
367 if (file) {
368 size_t pos = luaL_checkunsigned(L, 2);
369 size_t len;
370 luaL_checkstring(L, 3);
371 const char *data = lua_tolstring(L, 3, &len);
372 bool ret = text_insert(file->text, pos, data, len);
373 lua_pushboolean(L, ret);
374 } else {
375 lua_pushboolean(L, false);
377 return 1;
380 static int file_delete(lua_State *L) {
381 File *file = obj_ref_check(L, 1, "vis.file");
382 if (file) {
383 size_t pos = luaL_checkunsigned(L, 2);
384 size_t len = luaL_checkunsigned(L, 3);
385 bool ret = text_delete(file->text, pos, len);
386 lua_pushboolean(L, ret);
387 } else {
388 lua_pushboolean(L, false);
390 return 1;
393 static int file_lines_iterator_it(lua_State *L);
395 static int file_lines_iterator(lua_State *L) {
396 /* need to check second parameter first, because obj_ref_check_get
397 * modifies the stack */
398 size_t line = luaL_optunsigned(L, 2, 1);
399 File *file = obj_ref_check_get(L, 1, "vis.file");
400 size_t *pos = lua_newuserdata(L, sizeof *pos);
401 *pos = text_pos_by_lineno(file->text, line);
402 lua_pushcclosure(L, file_lines_iterator_it, 2);
403 return 1;
406 static int file_lines_iterator_it(lua_State *L) {
407 File *file = *(File**)lua_touserdata(L, lua_upvalueindex(1));
408 size_t *start = lua_touserdata(L, lua_upvalueindex(2));
409 if (*start == text_size(file->text))
410 return 0;
411 size_t end = text_line_end(file->text, *start);
412 size_t len = end - *start;
413 char *buf = malloc(len);
414 if (!buf && len)
415 return 0;
416 len = text_bytes_get(file->text, *start, len, buf);
417 lua_pushlstring(L, buf, len);
418 free(buf);
419 *start = text_line_next(file->text, end);
420 return 1;
423 static const struct luaL_Reg file_funcs[] = {
424 { "__index", file_index },
425 { "__newindex", file_newindex },
426 { "insert", file_insert },
427 { "delete", file_delete },
428 { "lines_iterator", file_lines_iterator },
429 { NULL, NULL },
432 static int file_lines_index(lua_State *L) {
433 Text *txt = obj_ref_check(L, 1, "vis.file.text");
434 if (!txt)
435 goto err;
436 size_t line = luaL_checkunsigned(L, 2);
437 size_t start = text_pos_by_lineno(txt, line);
438 size_t end = text_line_end(txt, start);
439 if (start != EPOS && end != EPOS) {
440 size_t size = end - start;
441 char *data = malloc(size);
442 if (!data && size)
443 goto err;
444 size = text_bytes_get(txt, start, size, data);
445 lua_pushlstring(L, data, size);
446 free(data);
447 return 1;
449 err:
450 lua_pushnil(L);
451 return 1;
454 static int file_lines_newindex(lua_State *L) {
455 Text *txt = obj_ref_check(L, 1, "vis.file.text");
456 if (!txt)
457 return 0;
458 size_t line = luaL_checkunsigned(L, 2);
459 size_t size;
460 const char *data = luaL_checklstring(L, 3, &size);
461 if (line == 0) {
462 text_insert(txt, 0, data, size);
463 text_insert_newline(txt, size);
464 return 0;
466 size_t start = text_pos_by_lineno(txt, line);
467 size_t end = text_line_end(txt, start);
468 if (start != EPOS && end != EPOS) {
469 text_delete(txt, start, end - start);
470 text_insert(txt, start, data, size);
471 if (text_size(txt) == start + size)
472 text_insert_newline(txt, text_size(txt));
474 return 0;
477 static int file_lines_len(lua_State *L) {
478 Text *txt = obj_ref_check(L, 1, "vis.file.text");
479 size_t lines = 0;
480 if (txt) {
481 char lastchar;
482 size_t size = text_size(txt);
483 if (size > 0)
484 lines = text_lineno_by_pos(txt, size);
485 if (lines > 1 && text_byte_get(txt, size-1, &lastchar) && lastchar == '\n')
486 lines--;
488 lua_pushunsigned(L, lines);
489 return 1;
492 static const struct luaL_Reg file_lines_funcs[] = {
493 { "__index", file_lines_index },
494 { "__newindex", file_lines_newindex },
495 { "__len", file_lines_len },
496 { NULL, NULL },
499 static void vis_lua_event(Vis *vis, const char *name) {
500 lua_State *L = vis->lua;
501 lua_getglobal(L, "vis");
502 lua_getfield(L, -1, "events");
503 if (lua_istable(L, -1)) {
504 lua_getfield(L, -1, name);
506 lua_remove(L, -2);
509 void vis_lua_start(Vis *vis) {
510 lua_State *L = luaL_newstate();
511 if (!L)
512 return;
513 vis->lua = L;
514 luaL_openlibs(L);
517 /* extends lua's package.path with:
518 * - $VIS_PATH/{,lexers}
519 * - $XDG_CONFIG_HOME/vis/{,lexers} (defaulting to $HOME/.config/vis/{,lexers})
520 * - /usr/local/share/vis/{,lexers}
521 * - /usr/share/vis/{,lexers}
522 * - package.path (standard lua search path)
524 int paths = 3;
525 lua_getglobal(L, "package");
527 const char *vis_path = getenv("VIS_PATH");
528 if (vis_path) {
529 lua_pushstring(L, vis_path);
530 lua_pushstring(L, "/?.lua;");
531 lua_pushstring(L, vis_path);
532 lua_pushstring(L, "/lexers/?.lua;");
533 lua_concat(L, 4);
534 paths++;
537 /* try to get users home directory */
538 const char *home = getenv("HOME");
539 if (!home || !*home) {
540 struct passwd *pw = getpwuid(getuid());
541 if (pw)
542 home = pw->pw_dir;
545 const char *xdg_config = getenv("XDG_CONFIG_HOME");
546 if (xdg_config) {
547 lua_pushstring(L, xdg_config);
548 lua_pushstring(L, "/vis/?.lua;");
549 lua_pushstring(L, xdg_config);
550 lua_pushstring(L, "/vis/lexers/?.lua;");
551 lua_concat(L, 4);
552 paths++;
553 } else if (home && *home) {
554 lua_pushstring(L, home);
555 lua_pushstring(L, "/.config/vis/?.lua;");
556 lua_pushstring(L, home);
557 lua_pushstring(L, "/.config/vis/lexers/?.lua;");
558 lua_concat(L, 4);
559 paths++;
562 lua_pushstring(L, "/usr/local/share/vis/?.lua;/usr/local/share/vis/lexers/?.lua;");
563 lua_pushstring(L, "/usr/share/vis/?.lua;/usr/share/vis/lexers/?.lua;");
564 lua_getfield(L, -paths, "path");
565 lua_concat(L, paths);
566 lua_setfield(L, -2, "path");
567 lua_pop(L, 1); /* package */
569 /* table in registry to track lifetimes of C objects */
570 lua_newtable(L);
571 lua_setfield(L, LUA_REGISTRYINDEX, "vis.objects");
572 /* metatable used to type check user data */
573 luaL_newmetatable(L, "vis.file");
574 luaL_setfuncs(L, file_funcs, 0);
575 luaL_newmetatable(L, "vis.file.text");
576 luaL_setfuncs(L, file_lines_funcs, 0);
577 luaL_newmetatable(L, "vis.window");
578 luaL_setfuncs(L, window_funcs, 0);
579 luaL_newmetatable(L, "vis.window.cursor");
580 luaL_setfuncs(L, window_cursor_funcs, 0);
581 /* vis module table with up value as the C pointer */
582 luaL_newlibtable(L, vis_lua);
583 lua_pushlightuserdata(L, vis);
584 luaL_setfuncs(L, vis_lua, 1);
585 lua_setglobal(L, "vis");
587 lua_getglobal(L, "require");
588 lua_pushstring(L, "visrc");
589 lua_pcall(L, 1, 0, 0);
590 vis_lua_event(vis, "start");
591 if (lua_isfunction(L, -1))
592 lua_pcall(L, 0, 0, 0);
593 lua_pop(L, 1);
596 void vis_lua_quit(Vis *vis) {
597 lua_State *L = vis->lua;
598 if (!L)
599 return;
600 vis_lua_event(vis, "quit");
601 if (lua_isfunction(L, -1))
602 lua_pcall(L, 0, 0, 0);
603 lua_pop(L, 1);
604 lua_close(L);
607 void vis_lua_file_open(Vis *vis, File *file) {
611 void vis_lua_file_save(Vis *vis, File *file) {
615 void vis_lua_file_close(Vis *vis, File *file) {
616 lua_State *L = vis->lua;
617 vis_lua_event(vis, "file_close");
618 if (lua_isfunction(L, -1)) {
619 obj_ref_new(L, file, "vis.file");
620 lua_pcall(L, 1, 0, 0);
622 obj_ref_free(L, file->text);
623 obj_ref_free(L, file);
624 lua_pop(L, 1);
627 void vis_lua_win_open(Vis *vis, Win *win) {
628 lua_State *L = vis->lua;
629 vis_lua_event(vis, "win_open");
630 if (lua_isfunction(L, -1)) {
631 obj_ref_new(L, win, "vis.window");
632 lua_pcall(L, 1, 0, 0);
634 lua_pop(L, 1);
637 void vis_lua_win_close(Vis *vis, Win *win) {
638 lua_State *L = vis->lua;
639 vis_lua_event(vis, "win_close");
640 if (lua_isfunction(L, -1)) {
641 obj_ref_new(L, win, "vis.window");
642 lua_pcall(L, 1, 0, 0);
644 obj_ref_free(L, win->view);
645 obj_ref_free(L, win);
646 lua_pop(L, 1);
649 bool vis_theme_load(Vis *vis, const char *name) {
650 lua_State *L = vis->lua;
651 if (!L)
652 return false;
653 /* package.loaded['themes/'..name] = nil
654 * require 'themes/'..name */
655 lua_pushstring(L, "themes/");
656 lua_pushstring(L, name);
657 lua_concat(L, 2);
658 lua_getglobal(L, "package");
659 lua_getfield(L, -1, "loaded");
660 lua_pushvalue(L, -3);
661 lua_pushnil(L);
662 lua_settable(L, -3);
663 lua_pop(L, 2);
664 lua_getglobal(L, "require");
665 lua_pushvalue(L, -2);
666 if (lua_pcall(L, 1, 0, 0))
667 return false;
668 for (Win *win = vis->windows; win; win = win->next)
669 view_syntax_set(win->view, view_syntax_get(win->view));
670 return true;
673 #endif