1
2 /*
3 * glspi_run.c - This file is part of the Lua scripting plugin for the Geany IDE
4 * See the file "geanylua.c" for copyright information.
5 */
6
7 #define NEED_FAIL_ARG_TYPE
8 #include "glspi.h"
9
10
11 static KeyfileAssignFunc glspi_kfile_assign=NULL;
12
13
14 /*
15 If a script gets caught in a tight loop and the timeout expires,
16 and the user confirms they want to keep waiting, for some reason
17 the normal methods for repainting the window don't work in the
18 editor window, which makes it appear as if the dialog is still
19 active. So we need to tell scintilla to paint over the spot
20 where the dialog was.
21 */
repaint_scintilla(void)22 static void repaint_scintilla(void)
23 {
24 GeanyDocument* doc=document_get_current();
25 if ( doc && doc->is_valid ) {
26 gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(doc->editor->sci)), NULL, TRUE);
27 gdk_window_process_updates(gtk_widget_get_window(GTK_WIDGET(doc->editor->sci)), TRUE);
28 }
29 }
30
31
32
33 /* Internal yes-or-no question box (not used by scripts) */
glspi_show_question(const gchar * title,const gchar * question,gboolean default_result)34 static gboolean glspi_show_question(const gchar*title, const gchar*question, gboolean default_result)
35 {
36 GtkWidget *dialog, *yes_btn, *no_btn;
37 GtkResponseType dv, rv;
38 dv=default_result?GTK_RESPONSE_YES:GTK_RESPONSE_NO;
39 dialog=gtk_message_dialog_new(GTK_WINDOW(main_widgets->window),
40 GTK_DIALOG_DESTROY_WITH_PARENT|GTK_DIALOG_MODAL,
41 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", title);
42 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", question);
43 yes_btn=gtk_dialog_add_button(GTK_DIALOG(dialog),
44 GTK_STOCK_YES, GTK_RESPONSE_YES);
45 no_btn=gtk_dialog_add_button(GTK_DIALOG(dialog),
46 GTK_STOCK_NO, GTK_RESPONSE_NO);
47 gtk_widget_grab_default(dv==GTK_RESPONSE_YES?yes_btn:no_btn);
48 gtk_window_set_title(GTK_WINDOW(dialog), DEFAULT_BANNER);
49 rv=gtk_dialog_run(GTK_DIALOG(dialog));
50 gtk_widget_destroy(dialog);
51 if ((rv!=GTK_RESPONSE_YES)&&(rv!=GTK_RESPONSE_NO)) {rv=dv;}
52 repaint_scintilla();
53 return GTK_RESPONSE_YES==rv;
54 }
55
56
glspi_goto_error(const gchar * fn,gint line)57 static gboolean glspi_goto_error(const gchar *fn, gint line)
58 {
59 GeanyDocument *doc=document_open_file(fn, FALSE, NULL, NULL);
60 if (doc) {
61 if (doc->is_valid) {
62 ScintillaObject*sci=doc->editor->sci;
63 if (sci) {
64 gint pos=sci_get_position_from_line(sci,line-1);
65 sci_set_current_position(sci,pos,TRUE);
66 return TRUE;
67 }
68 }
69 }
70 return FALSE;
71 }
72
73
74
75 /*
76 Display a message box showing any script error...
77 Depending on the type of error, Lua will sometimes prepend the filename
78 to the message. If need_name is TRUE then we assume that Lua didn't add
79 the filename, so we prepend it ourself. If need_name is FALSE, then the
80 error message likely contains a filename *and* a line number, so we
81 give the user an option to automatically open the file and scroll to
82 the offending line.
83 */
glspi_script_error(const gchar * script_file,const gchar * msg,gboolean need_name,gint line)84 static void glspi_script_error(const gchar *script_file, const gchar *msg, gboolean need_name, gint line)
85 {
86 GtkWidget *dialog;
87 if (need_name) {
88 dialog=gtk_message_dialog_new(GTK_WINDOW(main_widgets->window),
89 GTK_DIALOG_DESTROY_WITH_PARENT|GTK_DIALOG_MODAL,
90 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Lua script error:"));
91 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
92 "%s:\n%s", script_file, msg);
93 } else {
94 GtkWidget *open_btn;
95 dialog=gtk_message_dialog_new(GTK_WINDOW(main_widgets->window),
96 GTK_DIALOG_DESTROY_WITH_PARENT|GTK_DIALOG_MODAL,
97 GTK_MESSAGE_ERROR, GTK_BUTTONS_NONE, _("Lua script error:"));
98 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", msg);
99 gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
100 open_btn=gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT);
101 gtk_widget_grab_default(open_btn);
102 }
103 gtk_window_set_title(GTK_WINDOW(dialog), DEFAULT_BANNER);
104 if ( (gtk_dialog_run(GTK_DIALOG(dialog))==GTK_RESPONSE_ACCEPT) && !need_name) {
105 glspi_goto_error(script_file, line);
106 }
107 gtk_widget_destroy(dialog);
108 }
109
110
111
112
113
114 typedef struct _StateInfo {
115 lua_State *state;
116 GString *source;
117 gint line;
118 GTimer*timer;
119 gint counter;
120 gdouble remaining;
121 gdouble max;
122 gboolean optimized;
123 } StateInfo;
124
125 static GSList *state_list=NULL;
126
127
find_state(lua_State * L)128 static StateInfo*find_state(lua_State *L)
129 {
130 GSList*p=state_list;
131 for (p=state_list; p; p=p->next) {
132 if ( p->data && ((StateInfo*)p->data)->state==L ) { return p->data; }
133 }
134 return NULL;
135 }
136
137
glspi_get_error_info(lua_State * L,gint * line)138 static gchar *glspi_get_error_info(lua_State* L, gint *line)
139 {
140 StateInfo*si=find_state(L);
141 if (si) {
142 *line=si->line;
143 if (si->source->str && *(si->source->str) ) {
144 return g_strdup(si->source->str);
145 }
146 } else { *line=-1; }
147 return NULL;
148 }
149
150
151
glspi_timeout(lua_State * L)152 static gint glspi_timeout(lua_State* L)
153 {
154 if (( lua_gettop(L) > 0 ) && lua_isnumber(L,1)) {
155 gint n=lua_tonumber(L,1);
156 if (n>=0) {
157 StateInfo*si=find_state(L);
158 if (si) {
159 si->max=n;
160 si->remaining=n;
161 }
162 } else { return FAIL_UNSIGNED_ARG(1); }
163 } else { return FAIL_NUMERIC_ARG(1); }
164 return 0;
165 }
166
167
168
glspi_yield(lua_State * L)169 static gint glspi_yield(lua_State* L)
170 {
171 while (gtk_events_pending()) { gtk_main_iteration(); }
172 return 0;
173 }
174
175
glspi_optimize(lua_State * L)176 static gint glspi_optimize(lua_State* L)
177 {
178 StateInfo*si=find_state(L);
179 if (si) { si->optimized=TRUE; }
180 return 0;
181 }
182
183
184 /* Lua debug hook callback */
debug_hook(lua_State * L,lua_Debug * ar)185 static void debug_hook(lua_State *L, lua_Debug *ar)
186 {
187 StateInfo*si=find_state(L);
188 if (si && !si->optimized) {
189 if (lua_getinfo(L,"Sl",ar)) {
190 if (ar->source && (ar->source[0]=='@') && strcmp(si->source->str, ar->source+1)) {
191 g_string_assign(si->source, ar->source+1);
192 }
193 si->line=ar->currentline;
194 }
195 if (si->timer) {
196 if (si->timer && si->max && (g_timer_elapsed(si->timer,NULL)>si->remaining)) {
197 if ( glspi_show_question(_("Script timeout"), _(
198 "A Lua script seems to be taking excessive time to complete.\n"
199 "Do you want to continue waiting?"
200 ), FALSE) )
201 {
202 si->remaining=si->max;
203 g_timer_start(si->timer);
204 } else
205 {
206 lua_pushstring(L, _("Script timeout exceeded."));
207 lua_error(L);
208 }
209 }
210 }
211 if (si->counter > 100000) {
212 gdk_window_invalidate_rect(gtk_widget_get_window(main_widgets->window), NULL, TRUE);
213 gdk_window_process_updates(gtk_widget_get_window(main_widgets->window), TRUE);
214 si->counter=0;
215 } else si->counter++;
216 }
217 }
218
219
220
221 /*
222 Pause the run timer, while dialogs are displayed. Note that we
223 purposely add 1/10 of a second to our elapsed time here.
224 That should not even be noticeable for most scripts, but
225 it helps us time-out faster for dialogs caught in a loop.
226 */
227
glspi_pause_timer(gboolean pause,gpointer user_data)228 static void glspi_pause_timer(gboolean pause, gpointer user_data)
229 {
230 StateInfo*si=find_state((lua_State*)user_data);
231 if (si && si->timer) {
232 if (pause) {
233 si->remaining -= g_timer_elapsed(si->timer,NULL) + 0.10;
234 if ( si->remaining < 0 ) si->remaining = 0;
235 g_timer_stop(si->timer);
236 } else {
237 g_timer_start(si->timer);
238 }
239 }
240 }
241
242
243
244
glspi_state_new(void)245 static lua_State *glspi_state_new(void)
246 {
247 lua_State *L = luaL_newstate();
248 StateInfo*si=g_new0(StateInfo,1);
249 luaL_openlibs(L);
250 si->state=L;
251 si->timer=g_timer_new();
252 si->max=DEFAULT_MAX_EXEC_TIME;
253 si->remaining=DEFAULT_MAX_EXEC_TIME;
254 si->source=g_string_new("");
255 si->line=-1;
256 si->counter=0;
257 state_list=g_slist_append(state_list,si);
258 lua_sethook(L,debug_hook,LUA_MASKLINE,1);
259 return L;
260 }
261
262
glspi_state_done(lua_State * L)263 static void glspi_state_done(lua_State *L)
264 {
265 StateInfo*si=find_state(L);
266 if (si) {
267 if (si->timer) {
268 g_timer_destroy(si->timer);
269 si->timer=NULL;
270 }
271 if (si->source) {
272 g_string_free(si->source, TRUE);
273 }
274 state_list=g_slist_remove(state_list,si);
275 g_free(si);
276 }
277 lua_close(L);
278 }
279
280
281
282 static const struct luaL_reg glspi_timer_funcs[] = {
283 {"timeout", glspi_timeout},
284 {"yield", glspi_yield},
285 {"optimize", glspi_optimize},
286 {NULL,NULL}
287 };
288
289
290
291
292
293 /* Catch and report script errors */
glspi_traceback(lua_State * L)294 static gint glspi_traceback(lua_State *L)
295 {
296 lua_getfield(L, LUA_GLOBALSINDEX, "debug");
297 if (!lua_istable(L, -1)) {
298 lua_pop(L, 1);
299 return 1;
300 }
301 lua_getfield(L, -1, "traceback");
302 if (!lua_isfunction(L, -1)) {
303 lua_pop(L, 2);
304 return 1;
305 }
306 lua_pushvalue(L, 1);
307 lua_pushinteger(L, 2);
308 lua_call(L, 2, 1);
309
310 return 1;
311 }
312
313 /*
314 The set_*_token functions assign default values for module-level variables
315 */
316
set_string_token(lua_State * L,const gchar * name,const gchar * value)317 static void set_string_token(lua_State *L, const gchar*name, const gchar*value)
318 {
319 lua_getglobal(L, LUA_MODULE_NAME);
320 if (lua_istable(L, -1)) {
321 lua_pushstring(L,name);
322 lua_pushstring(L,value);
323 lua_settable(L, -3);
324 } else {
325 g_printerr("*** %s: Failed to set value for %s\n", PLUGIN_NAME, name);
326 }
327 }
328
329
330
set_numeric_token(lua_State * L,const gchar * name,gint value)331 static void set_numeric_token(lua_State *L, const gchar*name, gint value)
332 {
333 lua_getglobal(L, LUA_MODULE_NAME);
334 if (lua_istable(L, -1)) {
335 lua_pushstring(L,name);
336 push_number(L,value);
337 lua_settable(L, -3);
338 } else {
339 g_printerr("*** %s: Failed to set value for %s\n", PLUGIN_NAME, name);
340 }
341 }
342
343
344
set_boolean_token(lua_State * L,const gchar * name,gboolean value)345 static void set_boolean_token(lua_State *L, const gchar*name, gboolean value)
346 {
347 lua_getglobal(L, LUA_MODULE_NAME);
348 if (lua_istable(L, -1)) {
349 lua_pushstring(L,name);
350 lua_pushboolean(L,value);
351 lua_settable(L, -3);
352 } else {
353 g_printerr("*** %s: Failed to set value for %s\n", PLUGIN_NAME, name);
354 }
355 }
356
357
358
set_keyfile_token(lua_State * L,const gchar * name,GKeyFile * value)359 static void set_keyfile_token(lua_State *L, const gchar*name, GKeyFile* value)
360 {
361 if (!value) {return;}
362 lua_getglobal(L, LUA_MODULE_NAME);
363 if (lua_istable(L, -1)) {
364 lua_pushstring(L,name);
365 glspi_kfile_assign(L, value);
366 lua_settable(L, -3);
367 } else {
368 g_printerr("*** %s: Failed to set value for %s\n", PLUGIN_NAME, name);
369 }
370 }
371
372
373
show_error(lua_State * L,const gchar * script_file)374 static void show_error(lua_State *L, const gchar *script_file)
375 {
376 gint line=-1;
377 gchar *fn = glspi_get_error_info(L, &line);
378 if (!lua_isnil(L, -1)) {
379 const gchar *msg;
380 msg = lua_tostring(L, -1);
381 if (msg == NULL) {
382 msg = _("(error object is not a string)");
383 }
384 glspi_script_error(fn?fn:script_file, msg, FALSE, line);
385 lua_pop(L, 1);
386 } else {
387 glspi_script_error(fn?fn:script_file, _("Unknown Error inside script."), FALSE, line);
388 }
389 if (fn) g_free(fn);
390 }
391
392
393
glspi_init_module(lua_State * L,const gchar * script_file,gint caller,GKeyFile * proj,const gchar * script_dir)394 static gint glspi_init_module(lua_State *L, const gchar *script_file, gint caller, GKeyFile*proj, const gchar*script_dir)
395 {
396 luaL_openlib(L, LUA_MODULE_NAME, glspi_timer_funcs, 0);
397 glspi_init_sci_funcs(L);
398 glspi_init_doc_funcs(L);
399 glspi_init_mnu_funcs(L);
400 glspi_init_dlg_funcs(L, glspi_pause_timer);
401 glspi_init_app_funcs(L,script_dir);
402 set_string_token(L,tokenWordChars,GEANY_WORDCHARS);
403 set_string_token(L,tokenBanner,DEFAULT_BANNER);
404 set_string_token(L,tokenDirSep, G_DIR_SEPARATOR_S);
405 set_boolean_token(L,tokenRectSel,FALSE);
406 set_numeric_token(L,tokenCaller, caller);
407 glspi_init_gsdlg_module(L,glspi_pause_timer, geany_data?GTK_WINDOW(main_widgets->window):NULL);
408 glspi_init_kfile_module(L,&glspi_kfile_assign);
409 set_keyfile_token(L,tokenProject, proj);
410 set_string_token(L,tokenScript,script_file);
411 return 0;
412 }
413
414
415
416 /*
417 Function to load this module into the standalone lua interpreter.
418 The only reason you would ever want to do this is to re-generate
419 the "keywords.list" file from the command line.
420 See the file "util/keywords.lua" for more info.
421 */
422 PLUGIN_EXPORT
luaopen_libgeanylua(lua_State * L)423 gint luaopen_libgeanylua(lua_State *L)
424 {
425 return glspi_init_module(L, "", 0, NULL, NULL);
426 }
427
428
429
430 /* Load and run the script */
glspi_run_script(const gchar * script_file,gint caller,GKeyFile * proj,const gchar * script_dir)431 void glspi_run_script(const gchar *script_file, gint caller, GKeyFile*proj, const gchar *script_dir)
432 {
433 gint status;
434 lua_State *L = glspi_state_new();
435 glspi_init_module(L, script_file, caller,proj,script_dir);
436 #if 0
437 while (gtk_events_pending()) { gtk_main_iteration(); }
438 #endif
439 status = luaL_loadfile(L, script_file);
440 switch (status) {
441 case 0: {
442 gint base = lua_gettop(L); /* function index */
443 lua_pushcfunction(L, glspi_traceback); /* push traceback function */
444 lua_insert(L, base); /* put it under chunk and args */
445 status = lua_pcall(L, 0, 0, base);
446 lua_remove(L, base); /* remove traceback function */
447 if (0 == status) {
448 status = lua_pcall(L, 0, 0, 0);
449 } else {
450 lua_gc(L, LUA_GCCOLLECT, 0); /* force garbage collection if error */
451 show_error(L, script_file);
452 }
453 break;
454 }
455 case LUA_ERRSYNTAX:
456 show_error(L, script_file);
457 break;
458 case LUA_ERRMEM:
459 glspi_script_error(script_file, _("Out of memory."), TRUE, -1);
460 break;
461 case LUA_ERRFILE:
462 glspi_script_error(script_file, _("Failed to open script file."), TRUE, -1);
463 break;
464 default:
465 glspi_script_error(script_file, _("Unknown error while loading script file."), TRUE, -1);
466 }
467 glspi_state_done(L);
468 }
469
470