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