1 /*
2  *  utils.c
3  *
4  *  Copyright 2012 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 
24 #include <ctype.h>
25 #include <errno.h>
26 #include <limits.h>
27 #include <stdarg.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 
34 #include "common.h"
35 
36 
37 /* The maximum length of an expression for evaluating the value.
38    (Including the string terminator '\0') */
39 #define SCOPE_MAX_EVALUATE_EXPR_LENGTH 256
40 
41 
42 #ifdef G_OS_UNIX
43 #include <fcntl.h>
44 
show_errno(const char * prefix)45 void show_errno(const char *prefix)
46 {
47 	show_error(_("%s: %s."), prefix, g_strerror(errno));
48 }
49 #else  /* G_OS_UNIX */
50 #include <windows.h>
51 
show_errno(const char * prefix)52 void show_errno(const char *prefix)
53 {
54 	gchar *error = g_win32_error_message(GetLastError());
55 	show_error(_("%s: %s"), prefix, error);
56 	g_free(error);
57 }
58 #endif  /* G_OS_UNIX */
59 
utils_set_nonblock(GPollFD * fd)60 gboolean utils_set_nonblock(GPollFD *fd)
61 {
62 #ifdef G_OS_UNIX
63 	int state = fcntl(fd->fd, F_GETFL);
64 	return state != -1 && fcntl(fd->fd, F_SETFL, state | O_NONBLOCK) != -1;
65 #else  /* G_OS_UNIX */
66 	HANDLE h = (HANDLE) _get_osfhandle(fd->fd);
67 	DWORD state;
68 
69 	if (h != INVALID_HANDLE_VALUE &&
70 		GetNamedPipeHandleState(h, &state, NULL, NULL, NULL, NULL, 0))
71 	{
72 			state |= PIPE_NOWAIT;
73 			if (SetNamedPipeHandleState(h, &state, NULL, NULL))
74 				return TRUE;
75 	}
76 
77 	return FALSE;
78 #endif  /* G_OS_UNIX */
79 }
80 
utils_handle_button_press(GtkWidget * widget,GdkEventButton * event)81 void utils_handle_button_press(GtkWidget *widget, GdkEventButton *event)
82 {
83 	/* from sidebar.c */
84 	GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS(widget);
85 
86 	if (widget_class->button_press_event)
87 		widget_class->button_press_event(widget, event);
88 }
89 
utils_handle_button_release(GtkWidget * widget,GdkEventButton * event)90 void utils_handle_button_release(GtkWidget *widget, GdkEventButton *event)
91 {
92 	GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS(widget);
93 
94 	if (widget_class->button_release_event)
95 		widget_class->button_release_event(widget, event);
96 }
97 
utils_check_path(const gchar * pathname,gboolean file,int mode)98 gboolean utils_check_path(const gchar *pathname, gboolean file, int mode)
99 {
100 	if (*pathname)
101 	{
102 		char *path = utils_get_locale_from_utf8(pathname);
103 		struct stat buf;
104 		gboolean result = FALSE;
105 
106 		if (stat(path, &buf) == 0)
107 		{
108 			if ((!S_ISDIR(buf.st_mode)) == file)
109 				result = access(path, mode) == 0;
110 			else
111 				errno = file ? EISDIR : ENOTDIR;
112 		}
113 
114 		g_free(path);
115 		return result;
116 	}
117 
118 	return TRUE;
119 }
120 
utils_skip_spaces(const gchar * text)121 const gchar *utils_skip_spaces(const gchar *text)
122 {
123 	while (isspace(*text))
124 		text++;
125 	return text;
126 }
127 
utils_strchrepl(char * text,char c,char rep)128 void utils_strchrepl(char *text, char c, char rep)
129 {
130 	char *p = text;
131 
132 	while (*text)
133 	{
134 		if (*text == c)
135 		{
136 			if (rep)
137 				*text = rep;
138 		}
139 		else if (!rep)
140 			*p++ = *text;
141 
142 		text++;
143 	}
144 
145 	if (!rep)
146 		*p = '\0';
147 }
148 
store_find(ScpTreeStore * store,GtkTreeIter * iter,guint column,const char * key)149 gboolean store_find(ScpTreeStore *store, GtkTreeIter *iter, guint column, const char *key)
150 {
151 	if (scp_tree_store_get_column_type(store, column) == G_TYPE_STRING)
152 		return scp_tree_store_search(store, FALSE, FALSE, iter, NULL, column, key);
153 
154 	return scp_tree_store_search(store, FALSE, FALSE, iter, NULL, column, atoi(key));
155 }
156 
store_foreach(ScpTreeStore * store,GFunc each_func,gpointer gdata)157 void store_foreach(ScpTreeStore *store, GFunc each_func, gpointer gdata)
158 {
159 	GtkTreeIter iter;
160 	gboolean valid = scp_tree_store_get_iter_first(store, &iter);
161 
162 	while (valid)
163 	{
164 		each_func(&iter, gdata);
165 		valid = scp_tree_store_iter_next(store, &iter);
166 	}
167 }
168 
store_save(ScpTreeStore * store,GKeyFile * config,const gchar * prefix,gboolean (* save_func)(GKeyFile * config,const char * section,GtkTreeIter * iter))169 void store_save(ScpTreeStore *store, GKeyFile *config, const gchar *prefix,
170 	gboolean (*save_func)(GKeyFile *config, const char *section, GtkTreeIter *iter))
171 {
172 	guint i = 0;
173 	GtkTreeIter iter;
174 	gboolean valid = scp_tree_store_get_iter_first(store, &iter);
175 
176 	while (valid)
177 	{
178 		char *section = g_strdup_printf("%s_%d", prefix, i);
179 
180 		i += save_func(config, section, &iter);
181 		valid = scp_tree_store_iter_next(store, &iter);
182 		g_free(section);
183 	}
184 
185 	do
186 	{
187 		char *section = g_strdup_printf("%s_%d", prefix, i++);
188 		valid = g_key_file_remove_group(config, section, NULL);
189 		g_free(section);
190 	} while (valid);
191 }
192 
store_gint_compare(ScpTreeStore * store,GtkTreeIter * a,GtkTreeIter * b,gpointer gdata)193 gint store_gint_compare(ScpTreeStore *store, GtkTreeIter *a, GtkTreeIter *b, gpointer gdata)
194 {
195 	const gchar *s1, *s2;
196 
197 	scp_tree_store_get(store, a, GPOINTER_TO_INT(gdata), &s1, -1);
198 	scp_tree_store_get(store, b, GPOINTER_TO_INT(gdata), &s2, -1);
199 	return utils_atoi0(s1) - utils_atoi0(s2);
200 }
201 
store_seek_compare(ScpTreeStore * store,GtkTreeIter * a,GtkTreeIter * b,G_GNUC_UNUSED gpointer gdata)202 gint store_seek_compare(ScpTreeStore *store, GtkTreeIter *a, GtkTreeIter *b,
203 	G_GNUC_UNUSED gpointer gdata)
204 {
205 	gint result = scp_tree_store_compare_func(store, a, b, GINT_TO_POINTER(COLUMN_FILE));
206 
207 	if (!result)
208 	{
209 		gint i1, i2;
210 
211 		scp_tree_store_get(store, a, COLUMN_LINE, &i1, -1);
212 		scp_tree_store_get(store, b, COLUMN_LINE, &i2, -1);
213 		result = i1 - i2;
214 	}
215 
216 	return result;
217 }
218 
utils_load(GKeyFile * config,const gchar * prefix,gboolean (* load_func)(GKeyFile * config,const char * section))219 void utils_load(GKeyFile *config, const gchar *prefix,
220 	gboolean (*load_func)(GKeyFile *config, const char *section))
221 {
222 	guint i = 0;
223 	gboolean valid;
224 
225 	do
226 	{
227 		char *section = g_strdup_printf("%s_%d", prefix, i++);
228 		valid = FALSE;
229 
230 		if (g_key_file_has_group(config, section))
231 		{
232 			if (load_func(config, section))
233 				valid = TRUE;
234 			else
235 				msgwin_status_add(_("Scope: error reading [%s]."), section);
236 		}
237 
238 		g_free(section);
239 
240 	} while (valid);
241 }
242 
utils_stash_group_free(StashGroup * group)243 void utils_stash_group_free(StashGroup *group)
244 {
245 	stash_group_free_settings(group);
246 	stash_group_free(group);
247 }
248 
249 const char *const SCOPE_OPEN = "scope_open";
250 const char *const SCOPE_LOCK = "scope_lock";
251 gint utils_sci_marker_first;
252 
utils_seek(const char * file,gint line,gboolean focus,SeekerType seeker)253 void utils_seek(const char *file, gint line, gboolean focus, SeekerType seeker)
254 {
255 	GeanyDocument *doc = NULL;
256 
257 	if (file)
258 	{
259 		GeanyDocument *old_doc = document_get_current();
260 		ScintillaObject *sci;
261 
262 		doc = document_find_by_real_path(file);
263 
264 		if (doc)
265 		{
266 			sci = doc->editor->sci;
267 			gtk_notebook_set_current_page(GTK_NOTEBOOK(geany->main_widgets->notebook),
268 				document_get_notebook_page(doc));
269 
270 			if (seeker == SK_EXEC_MARK)
271 				sci_set_marker_at_line(sci, line - 1, MARKER_EXECUTE);
272 		}
273 		else if (g_file_test(file, G_FILE_TEST_EXISTS) &&
274 			(doc = document_open_file(file, FALSE, NULL, NULL)) != NULL)
275 		{
276 			sci = doc->editor->sci;
277 			if (seeker == SK_EXECUTE || seeker == SK_EXEC_MARK)
278 				g_object_set_data(G_OBJECT(sci), SCOPE_OPEN, utils_seek);
279 		}
280 
281 		if (doc)
282 		{
283 			if (line)
284 			{
285 				if (seeker == SK_DEFAULT && pref_seek_with_navqueue)
286 					navqueue_goto_line(old_doc, doc, line);
287 				else
288 				{
289 					scintilla_send_message(sci, SCI_SETYCARETPOLICY,
290 						pref_sci_caret_policy, pref_sci_caret_slop);
291 					sci_goto_line(sci, line - 1, TRUE);
292 					scintilla_send_message(sci, SCI_SETYCARETPOLICY, CARET_EVEN, 0);
293 				}
294 			}
295 
296 			if (focus)
297 				gtk_widget_grab_focus(GTK_WIDGET(sci));
298 		}
299 	}
300 
301 	if (!doc && (seeker == SK_EXECUTE || seeker == SK_EXEC_MARK))
302 		dc_error("thread %s at %s:%d", thread_id, file, line + 1);
303 }
304 
utils_mark(const char * file,gint line,gboolean mark,gint marker)305 void utils_mark(const char *file, gint line, gboolean mark, gint marker)
306 {
307 	if (line)
308 	{
309 		GeanyDocument *doc = document_find_by_real_path(file);
310 
311 		if (doc)
312 		{
313 			if (mark)
314 				sci_set_marker_at_line(doc->editor->sci, line - 1, marker);
315 			else
316 				sci_delete_marker_at_line(doc->editor->sci, line - 1, marker);
317 		}
318 	}
319 }
320 
utils_source_filetype(GeanyFiletype * ft)321 gboolean utils_source_filetype(GeanyFiletype *ft)
322 {
323 	if (ft)
324 	{
325 		static const GeanyFiletypeID ft_id[] = { GEANY_FILETYPES_C, GEANY_FILETYPES_CPP,
326 			GEANY_FILETYPES_D, GEANY_FILETYPES_OBJECTIVEC, GEANY_FILETYPES_FORTRAN,
327 			GEANY_FILETYPES_F77, GEANY_FILETYPES_JAVA, /* GEANY_FILETYPES_OPENCL_C, */
328 			GEANY_FILETYPES_PASCAL, /* GEANY_FILETYPES_S, */ GEANY_FILETYPES_ASM,
329 			/* GEANY_FILETYPES_MODULA_2, */ GEANY_FILETYPES_ADA,  };
330 
331 		guint i;
332 
333 		for (i = 0; i < sizeof(ft_id) / sizeof(ft_id[0]); i++)
334 			if (ft_id[i] == ft->id)
335 				return TRUE;
336 	}
337 
338 	return FALSE;
339 }
340 
utils_source_document(GeanyDocument * doc)341 gboolean utils_source_document(GeanyDocument *doc)
342 {
343 	return doc->real_path && utils_source_filetype(doc->file_type);
344 }
345 
346 enum { GCS_CURRENT_LINE = 7 };  /* from highlighting.c */
347 
line_mark_unmark(GeanyDocument * doc,gboolean lock)348 static void line_mark_unmark(GeanyDocument *doc, gboolean lock)
349 {
350 	if (pref_unmark_current_line)
351 	{
352 		scintilla_send_message(doc->editor->sci, SCI_SETCARETLINEVISIBLE, lock ? FALSE :
353 			highlighting_get_style(GEANY_FILETYPES_NONE, GCS_CURRENT_LINE)->bold, 0);
354 	}
355 }
356 
357 static GtkCheckMenuItem *set_file_readonly1;
358 
doc_lock_unlock(GeanyDocument * doc,gboolean lock)359 static void doc_lock_unlock(GeanyDocument *doc, gboolean lock)
360 {
361 	if (set_file_readonly1 && doc == document_get_current())
362 	{
363 		/* to ensure correct state of Document -> [ ] Read Only */
364 		if (gtk_check_menu_item_get_active(set_file_readonly1) != lock)
365 			gtk_check_menu_item_set_active(set_file_readonly1, lock);
366 	}
367 	else
368 	{
369 		scintilla_send_message(doc->editor->sci, SCI_SETREADONLY, lock, 0);
370 		doc->readonly = lock;
371 		document_set_text_changed(doc, doc->changed);  /* to redraw tab & update sidebar */
372 	}
373 }
374 
utils_lock(GeanyDocument * doc)375 void utils_lock(GeanyDocument *doc)
376 {
377 	if (utils_source_document(doc))
378 	{
379 		if (!doc->readonly)
380 		{
381 			doc_lock_unlock(doc, TRUE);
382 			g_object_set_data(G_OBJECT(doc->editor->sci), SCOPE_LOCK, utils_lock);
383 		}
384 
385 		line_mark_unmark(doc, TRUE);
386 		tooltip_attach(doc->editor);
387 	}
388 }
389 
utils_unlock(GeanyDocument * doc)390 void utils_unlock(GeanyDocument *doc)
391 {
392 	if (utils_attrib(doc, SCOPE_LOCK))
393 	{
394 		doc_lock_unlock(doc, FALSE);
395 		g_object_steal_data(G_OBJECT(doc->editor->sci), SCOPE_LOCK);
396 	}
397 
398 	line_mark_unmark(doc, FALSE);
399 	tooltip_remove(doc->editor);
400 }
401 
utils_lock_unlock(GeanyDocument * doc,gboolean lock)402 void utils_lock_unlock(GeanyDocument *doc, gboolean lock)
403 {
404 	if (lock)
405 		utils_lock(doc);
406 	else
407 		utils_unlock(doc);
408 }
409 
utils_lock_all(gboolean lock)410 void utils_lock_all(gboolean lock)
411 {
412 	guint i;
413 
414 	foreach_document(i)
415 		utils_lock_unlock(documents[i], lock);
416 }
417 
utils_move_mark(ScintillaObject * sci,gint line,gint start,gint delta,gint marker)418 void utils_move_mark(ScintillaObject *sci, gint line, gint start, gint delta, gint marker)
419 {
420 	sci_delete_marker_at_line(sci, delta > 0 || start - delta <= line ? line + delta : start,
421 		marker);
422 	sci_set_marker_at_line(sci, line, marker);
423 }
424 
425 #define marker_delete_all(doc, marker) \
426 	scintilla_send_message((doc)->editor->sci, SCI_MARKERDELETEALL, (marker), 0)
427 
utils_remark(GeanyDocument * doc)428 void utils_remark(GeanyDocument *doc)
429 {
430 	if (doc)
431 	{
432 		if (debug_state() != DS_INACTIVE)
433 		{
434 			marker_delete_all(doc, MARKER_EXECUTE);
435 			threads_mark(doc);
436 		}
437 
438 		marker_delete_all(doc, MARKER_BREAKPT);
439 		marker_delete_all(doc, MARKER_BREAKPT + TRUE);
440 		breaks_mark(doc);
441 	}
442 }
443 
utils_parse_sci_color(const gchar * string)444 guint utils_parse_sci_color(const gchar *string)
445 {
446 #if !GTK_CHECK_VERSION(3, 14, 0)
447 	GdkColor color;
448 
449 	gdk_color_parse(string, &color);
450 	return ((color.blue >> 8) << 16) + (color.green & 0xFF00) + (color.red >> 8);
451 #else
452 	GdkRGBA color;
453 	guint blue, green, red;
454 
455 	gdk_rgba_parse(&color, string);
456 	blue = color.blue * 0xFF;
457 	green = color.green * 0xFF;
458 	red = color.red * 0xFF;
459 	return (blue << 16) + (green << 8) + red;
460 #endif
461 }
462 
utils_key_file_write_to_file(GKeyFile * config,const char * configfile)463 gboolean utils_key_file_write_to_file(GKeyFile *config, const char *configfile)
464 {
465 	gchar *data = g_key_file_to_data(config, NULL, NULL);
466 	gint error = utils_write_file(configfile, data);
467 
468 	g_free(data);
469 	if (error)
470 		msgwin_status_add(_("Scope: %s: %s."), configfile, g_strerror(error));
471 
472 	return !error;
473 }
474 
utils_key_file_get_string(GKeyFile * config,const char * section,const char * key)475 gchar *utils_key_file_get_string(GKeyFile *config, const char *section, const char *key)
476 {
477 	gchar *string = utils_get_setting_string(config, section, key, NULL);
478 
479 	if (!validate_column(string, TRUE))
480 	{
481 		g_free(string);
482 		string = NULL;
483 	}
484 
485 	return string;
486 }
487 
utils_get_utf8_basename(const char * file)488 gchar *utils_get_utf8_basename(const char *file)
489 {
490 	gchar *utf8 = utils_get_utf8_from_locale(file);
491 	gchar *base = g_path_get_basename(utf8);
492 	g_free(utf8);
493 	return base;
494 }
495 
utils_7bit_text_to_locale(const char * text,char * locale)496 static void utils_7bit_text_to_locale(const char *text, char *locale)
497 {
498 	while (*text)
499 	{
500 		if (*text == '\\' && (guint) (text[1] - '0') <= 3 &&
501 			(guint) (text[2] - '0') <= 7 && (guint) (text[3] - '0') <= 7)
502 		{
503 			unsigned char c = (text[1] - '0') * 64 + (text[2] - '0') * 8 + text[3] - '0';
504 
505 			if (isgraph(c))
506 			{
507 				*locale++ = c;
508 				text += 4;
509 				continue;
510 			}
511 		}
512 
513 		*locale++ = *text++;
514 	}
515 
516 	*locale = '\0';
517 }
518 
utils_7bit_to_locale(char * text)519 char *utils_7bit_to_locale(char *text)
520 {
521 	if (text)
522 		utils_7bit_text_to_locale(text, text);
523 
524 	return text;
525 }
526 
utils_get_locale_from_7bit(const char * text)527 char *utils_get_locale_from_7bit(const char *text)
528 {
529 	char *locale;
530 
531 	if (text)
532 	{
533 		locale = g_malloc(strlen(text) + 1);
534 		utils_7bit_text_to_locale(text, locale);
535 	}
536 	else
537 		locale = NULL;
538 
539 	return locale;
540 }
541 
utils_get_locale_from_display(const gchar * display,gint hb_mode)542 char *utils_get_locale_from_display(const gchar *display, gint hb_mode)
543 {
544 	return opt_hb_mode(hb_mode) == HB_LOCALE ? utils_get_locale_from_utf8(display) :
545 		g_strdup(display);
546 }
547 
utils_get_display_from_7bit(const char * text,gint hb_mode)548 gchar *utils_get_display_from_7bit(const char *text, gint hb_mode)
549 {
550 	gchar *display;
551 
552 	if (opt_hb_mode(hb_mode) == HB_7BIT)
553 		display = g_strdup(text);
554 	else
555 	{
556 		char *locale = utils_get_locale_from_7bit(text);
557 		display = utils_get_display_from_locale(locale, hb_mode);
558 		g_free(locale);
559 	}
560 
561 	return display;
562 }
563 
utils_get_display_from_locale(const char * locale,gint hb_mode)564 gchar *utils_get_display_from_locale(const char *locale, gint hb_mode)
565 {
566 	return opt_hb_mode(hb_mode) == HB_LOCALE ? utils_get_utf8_from_locale(locale) :
567 		g_strdup(locale);
568 }
569 
utils_verify_selection(gchar * text)570 gchar *utils_verify_selection(gchar *text)
571 {
572 	if (text)
573 	{
574 		gchar *s;
575 
576 		for (s = text; (s = strchr(s, '=')) != NULL; s++)
577 		{
578 			if (s[1] == '=')
579 				s++;
580 			else if (s < text + 2 || !strchr("<>", s[-1]) || s[-1] == s[-2])
581 			{
582 				g_free(text);
583 				return NULL;
584 			}
585 		}
586 	}
587 
588 	return text;
589 }
590 
utils_get_default_selection(void)591 gchar *utils_get_default_selection(void)
592 {
593 	GeanyDocument *doc = document_get_current();
594 	gchar *text = NULL;
595 
596 	if (doc && utils_source_document(doc))
597 		text = editor_get_default_selection(doc->editor, TRUE, NULL);
598 
599 	return utils_verify_selection(text);
600 }
601 
on_insert_text(GtkEditable * editable,gchar * new_text,gint new_text_length,G_GNUC_UNUSED gint * position,gpointer gdata)602 static void on_insert_text(GtkEditable *editable, gchar *new_text, gint new_text_length,
603 	G_GNUC_UNUSED gint *position, gpointer gdata)
604 {
605 	gboolean valid = TRUE;
606 
607 	if (new_text_length == -1)
608 		new_text_length = (gint) strlen(new_text);
609 
610 	if (GPOINTER_TO_INT(gdata) == VALIDATOR_VARFRAME)
611 	{
612 		gchar *s = gtk_editable_get_chars(editable, 0, 1);
613 
614 		if (*s == '\0' && new_text_length == 1 && (*new_text == '*' || *new_text == '@'))
615 			new_text_length = 0;
616 		else if (*s == '*' || *s == '@')
617 			valid = !new_text_length;
618 
619 		g_free(s);
620 	}
621 
622 	while (new_text_length-- > 0 && valid)
623 	{
624 		switch (GPOINTER_TO_INT(gdata))
625 		{
626 			case VALIDATOR_NUMERIC : valid = isdigit(*new_text); break;
627 			case VALIDATOR_NOSPACE : valid = !isspace(*new_text); break;
628 			case VALIDATOR_VARFRAME :
629 			{
630 				valid = isxdigit(*new_text) || tolower(*new_text) == 'x';
631 				break;
632 			}
633 			default : valid = FALSE;
634 		}
635 		new_text++;
636 	}
637 
638 	if (!valid)
639 		g_signal_stop_emission_by_name(editable, "insert-text");
640 }
641 
utils_matches_frame(const char * token)642 gboolean utils_matches_frame(const char *token)
643 {
644 	size_t len = *token - '0' + 1;
645 
646 	return thread_id && len == strlen(thread_id) && strlen(++token) > len &&
647 		!memcmp(token, thread_id, len) && !g_strcmp0(token + len, frame_id);
648 }
649 
validator_attach(GtkEditable * editable,gint validator)650 void validator_attach(GtkEditable *editable, gint validator)
651 {
652 	g_signal_connect(editable, "insert-text", G_CALLBACK(on_insert_text),
653 		GINT_TO_POINTER(validator));
654 }
655 
validate_string(gchar * text)656 static gchar *validate_string(gchar *text)
657 {
658 	char *s = text + strlen(text);
659 
660 	while (--s >= text && isspace(*s));
661 	s[1] = '\0';
662 	return *text ? text : NULL;
663 }
664 
validate_number(gchar * text)665 static gchar *validate_number(gchar *text)
666 {
667 	char *s;
668 
669 	if (*text == '+') text++;
670 	while (*text == '0') text++;
671 
672 	for (s = text; isdigit(*s); s++);
673 	*s = '\0';
674 	return *text && (s - text < 10 ||
675 		(s - text == 10 && strcmp(text, "2147483648") < 0)) ? text : NULL;
676 }
677 
validate_column(gchar * text,gboolean string)678 gchar *validate_column(gchar *text, gboolean string)
679 {
680 	if (text)
681 	{
682 		const gchar *s = utils_skip_spaces(text);
683 		memmove(text, s, strlen(s) + 1);
684 		return string ? validate_string(text) : validate_number(text);
685 	}
686 
687 	return NULL;
688 }
689 
on_dialog_response(GtkDialog * dialog,gint response,G_GNUC_UNUSED gpointer gdata)690 static void on_dialog_response(GtkDialog *dialog, gint response, G_GNUC_UNUSED gpointer gdata)
691 {
692 	if (response)
693 		gtk_widget_hide(GTK_WIDGET(dialog));
694 }
695 
dialog_connect(const char * name)696 GtkWidget *dialog_connect(const char *name)
697 {
698 	GtkWidget *dialog = get_widget(name);
699 
700 	gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(geany->main_widgets->window));
701 	g_signal_connect(dialog, "response", G_CALLBACK(on_dialog_response), NULL);
702 	return dialog;
703 }
704 
utils_text_buffer_get_text(GtkTextBuffer * text,gint maxlen)705 gchar *utils_text_buffer_get_text(GtkTextBuffer *text, gint maxlen)
706 {
707 	GtkTextIter start, end;
708 
709 	gtk_text_buffer_get_start_iter(text, &start);
710 	gtk_text_buffer_get_iter_at_offset(text, &end, maxlen);
711 	return gtk_text_buffer_get_text(text, &start, &end, FALSE);
712 }
713 
on_widget_key_press(G_GNUC_UNUSED GtkWidget * widget,GdkEventKey * event,GtkWidget * button)714 static gboolean on_widget_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
715 	GtkWidget *button)
716 {
717 	if (!ui_is_keyval_enter_or_return(event->keyval))
718 		return FALSE;
719 
720 	if (gtk_widget_get_sensitive(button))
721 		gtk_button_clicked(GTK_BUTTON(button));
722 
723 	return TRUE;
724 }
725 
utils_enter_to_clicked(GtkWidget * widget,GtkWidget * button)726 void utils_enter_to_clicked(GtkWidget *widget, GtkWidget *button)
727 {
728 	g_signal_connect(widget, "key-press-event", G_CALLBACK(on_widget_key_press), button);
729 }
730 
utils_tree_set_cursor(GtkTreeSelection * selection,GtkTreeIter * iter,gdouble alignment)731 void utils_tree_set_cursor(GtkTreeSelection *selection, GtkTreeIter *iter, gdouble alignment)
732 {
733 	GtkTreeView *tree = gtk_tree_selection_get_tree_view(selection);
734 	GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(tree), iter);
735 
736 	if (alignment >= 0)
737 		gtk_tree_view_scroll_to_cell(tree, path, NULL, TRUE, alignment, 0);
738 
739 	gtk_tree_view_set_cursor(tree, path, NULL, FALSE);
740 	gtk_tree_path_free(path);
741 }
742 
utils_init(void)743 void utils_init(void)
744 {
745 	set_file_readonly1 = GTK_CHECK_MENU_ITEM(ui_lookup_widget(geany->main_widgets->window,
746 		"set_file_readonly1"));
747 }
748 
utils_finalize(void)749 void utils_finalize(void)
750 {
751 	guint i = 0;
752 	DebugState state = debug_state();
753 
754 	foreach_document(i)
755 	{
756 		g_object_steal_data(G_OBJECT(documents[i]->editor->sci), SCOPE_OPEN);
757 
758 		if (state != DS_INACTIVE)
759 			utils_unlock(documents[i]);
760 	}
761 }
762 
763 /* checks whether @p c is an ASCII character (e.g. < 0x80) */
764 #define IS_ASCII(c) (((unsigned char)(c)) < 0x80)
765 
766 /* This function is doing the parsing for 'utils_read_evaluate_expr()'.
767    It was mainly separated from it to support easy unit testing. */
utils_evaluate_expr_from_string(gchar * chunk,guint peek_index)768 gchar *utils_evaluate_expr_from_string(gchar *chunk, guint peek_index)
769 {
770 	static const gchar *keep_prefix_list[] =
771 		{ "sizeof", NULL };
772 	static gchar expr[SCOPE_MAX_EVALUATE_EXPR_LENGTH];
773 	static const gchar *wchars = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
774 	static const gchar *wspace = " \t";
775 	gint startword, endword, temp_pos, round_brackets;
776 	gint pos, left_bracket, cmp;
777 	gboolean stop, oper, whitespace, brackets_exist;
778 	gchar *expr_copy;
779 
780 	g_return_val_if_fail(chunk != NULL, NULL);
781 
782 	startword = peek_index;
783 	endword = peek_index;
784 	expr[0] = '\0';
785 
786 	if (chunk[startword] == ' ')
787 	{
788 		return NULL;
789 	}
790 
791 	/* Find the left boundary of the expression. If the current sign
792 	   is a '*' or a '&' then we already are at the left boundary. */
793 	round_brackets = 0;
794 	if (chunk[startword] != '*' && chunk[startword] != '&')
795 	{
796 		stop = FALSE;
797 		oper = FALSE;
798 		whitespace = FALSE;
799 		while (startword > 0 && stop == FALSE)
800 		{
801 			/* the checks for "c < 0" are to allow any Unicode character which should make the code
802 			 * a little bit more Unicode safe, anyway, this allows also any Unicode punctuation */
803 			if (strchr(wchars, chunk[startword]) || ! IS_ASCII(chunk[startword]))
804 			{
805 				if (whitespace == TRUE && oper == FALSE)
806 				{
807 					startword++;
808 					stop = TRUE;
809 				}
810 				else
811 				{
812 					startword--;
813 					oper = FALSE;
814 					whitespace = FALSE;
815 				}
816 			}
817 			else
818 			{
819 				switch (chunk[startword])
820 				{
821 					case ' ':
822 					case '\t':
823 						startword--;
824 						whitespace = TRUE;
825 					break;
826 
827 					case '(':
828 						round_brackets++;
829 						startword--;
830 					break;
831 
832 					case ')':
833 						round_brackets--;
834 						startword--;
835 					break;
836 
837 					case '[':
838 					case ']':
839 						startword--;
840 					break;
841 
842 					case '.':
843 						/* Stop if there are no more signs before the current position,
844 						   or if we already have seen an operator, or the sign before
845 						   the operator is not a valid variable-name-char or whitespace. */
846 						if (startword == 0 || oper == TRUE ||
847 							(strchr(wchars, chunk[startword - 1]) == NULL &&
848 							 strchr(wspace, chunk[startword - 1]) == NULL &&
849 							 chunk[startword - 1] != ']'))
850 						{
851 							stop = TRUE;
852 							break;
853 						}
854 						oper = TRUE;
855 						startword--;
856 					break;
857 
858 					case '>':
859 						/* Stop if there are no more signs before the current position,
860 						   or if we already have seen an operator, or the sign before
861 						   the current sign is not a '-' (we expect a "->" here) */
862 						if (startword < 2 || oper == TRUE || chunk[startword - 1] != '-')
863 						{
864 							stop = TRUE;
865 							break;
866 						}
867 						oper = TRUE;
868 						startword -= 2;
869 					break;
870 
871 					case ':':
872 						/* Stop if there are no more signs before the current position,
873 						   or if we already have seen an operator, or the sign before
874 						   the current sign is not a ':' (we expect a "::" here) */
875 						if (startword < 2 || oper == TRUE || chunk[startword - 1] != ':')
876 						{
877 							stop = TRUE;
878 							break;
879 						}
880 						oper = TRUE;
881 						startword -= 2;
882 					break;
883 
884 					default:
885 						/* Not a valid variable-name-char or whitespace or operator. */
886 						stop = TRUE;
887 						if (whitespace == TRUE)
888 						{
889 							startword += 2;
890 						}
891 					break;
892 				}
893 			}
894 		}
895 		if (chunk[startword] == '*' || chunk[startword] == '&')
896 		{
897 			startword++;
898 		}
899 	}
900 
901 	/* Find the right boundary of the expression. */
902 	stop = FALSE;
903 	while (chunk[endword] != 0 && stop == FALSE)
904 	{
905 		/* the checks for "c < 0" are to allow any Unicode character which should make the code
906 		 * a little bit more Unicode safe, anyway, this allows also any Unicode punctuation */
907 		if (strchr(wchars, chunk[endword]) || ! IS_ASCII(chunk[endword]))
908 		{
909 			endword++;
910 		}
911 		else
912 		{
913 			switch (chunk[endword])
914 			{
915 				case ')':
916 					round_brackets--;
917 					if (round_brackets < 1)
918 					{
919 						/* We stop here in any case. 0 would be the normal case.
920 						   If the value is below zero something went wrong in the
921 						   loop above. Maybe this is just not a valid expression. */
922 						stop = TRUE;
923 					}
924 				break;
925 
926 				case ']':
927 				case '*':
928 				case '&':
929 					endword++;
930 				break;
931 
932 				case ' ':
933 				case '\t':
934 					/* We should usually stop here. But we need to continue
935 					   if the whitespace is followed by an array index "[...]"! */
936 					temp_pos = endword + 1;
937 					while (chunk[temp_pos] != 0 &&
938 						   (chunk[temp_pos] == ' ' || chunk[temp_pos] == '\t'))
939 					{
940 						temp_pos++;
941 					}
942 					if (chunk[temp_pos] == '[' && chunk[temp_pos+1] != ']')
943 					{
944 						endword = temp_pos + 1;
945 					}
946 					else
947 					{
948 						stop = TRUE;
949 					}
950 				break;
951 
952 				default:
953 					endword--;
954 					stop = TRUE;
955 				break;
956 			}
957 		}
958 	}
959 
960 	/* Skip leading whitespace. */
961 	while ((chunk[startword] == ' ' || chunk[startword] == '\t') &&
962 		   startword < endword)
963 	{
964 		startword++;
965 	}
966 
967 	/* Skip trailing whitespace. */
968 	while (endword > 0 &&
969 		   (chunk[endword] == ' ' || chunk[endword] == '\t') &&
970 		   startword < endword)
971 	{
972 		endword--;
973 	}
974 
975 	/* Validate/ensure balanced round brackets. */
976 	round_brackets = 0;
977 	left_bracket = 0;
978 	stop = FALSE;
979 	brackets_exist = FALSE;
980 	for (pos = startword ; pos <= endword && stop == FALSE ; pos++)
981 	{
982 		switch (chunk[pos])
983 		{
984 			case '(':
985 				round_brackets++;
986 				brackets_exist = TRUE;
987 				left_bracket = pos;
988 			break;
989 
990 			case ')':
991 				round_brackets--;
992 				if (round_brackets == 0)
993 				{
994 					endword = pos;
995 					stop = TRUE;
996 				}
997 				else if (round_brackets < 0)
998 				{
999 					endword = pos - 1;
1000 					stop = TRUE;
1001 				}
1002 			break;
1003 
1004 			case ' ':
1005 			case '\t':
1006 			break;
1007 
1008 			default:
1009 				temp_pos = pos;
1010 			break;
1011 		}
1012 	}
1013 
1014 	/* If brackets exist and the left-most sign is not a bracket itself,
1015 	   then it could be a function name or an operator (sizeof). We
1016 	   want to exclude the function name but include an operator name. */
1017 	if (brackets_exist == TRUE && chunk[startword] != '(')
1018 	{
1019 		pos = 0;
1020 		oper = FALSE;
1021 		while (keep_prefix_list[pos] != NULL)
1022 		{
1023 			cmp = strncmp(keep_prefix_list[pos], &(chunk[startword]),
1024 				strlen(keep_prefix_list[pos]));
1025 			if (cmp == 0)
1026 			{
1027 				/* Match with allowed operator/prefix. Keep it. */
1028 				oper = TRUE;
1029 				break;
1030 			}
1031 			pos++;
1032 		}
1033 
1034 		/* Did we find a wanted prefix? */
1035 		if (oper == FALSE)
1036 		{
1037 			/* No! Move startword back to position of left-most valid
1038 			   round bracket. */
1039 			startword = left_bracket;
1040 		}
1041 	}
1042 
1043 	/* Skip useless surrounding brackets if present. */
1044 	if (startword < endword)
1045 	{
1046 		guint skipped = 0;
1047 
1048 		while (chunk[startword] == '(' && startword < endword)
1049 		{
1050 			skipped++;
1051 			startword++;
1052 		}
1053 		while (chunk[endword] == ')' && skipped > 0)
1054 		{
1055 			skipped--;
1056 			endword--;
1057 		}
1058 	}
1059 
1060 	expr_copy = NULL;
1061 	if (startword < endword)
1062 	{
1063 		if (chunk[endword] != '\0')
1064 		{
1065 			chunk[endword+1] = '\0';
1066 		}
1067 
1068 		/* ensure null terminated */
1069 		g_strlcpy(expr, &(chunk[startword]), sizeof(expr));
1070 		expr_copy = g_strdup(expr);
1071 	}
1072 
1073 	return expr_copy;
1074 }
1075 
1076 /* Reads the expression for evaluate at the given cursor position and writes
1077  * it into the given buffer. The buffer will be NULL terminated in any case,
1078  * even when the word is truncated because wordlen is too small.
1079  * Position can be -1, then the current position is used. */
utils_read_evaluate_expr(GeanyEditor * editor,gint peek_pos)1080 gchar *utils_read_evaluate_expr(GeanyEditor *editor, gint peek_pos)
1081 {
1082 	gint line, line_start;
1083 	gchar *chunk, *expr;
1084 	ScintillaObject *sci;
1085 
1086 	g_return_val_if_fail(editor != NULL, NULL);
1087 	sci = editor->sci;
1088 
1089 	if (peek_pos == -1)
1090 	{
1091 		peek_pos = sci_get_current_position(sci);
1092 	}
1093 
1094 	line = sci_get_line_from_position(sci, peek_pos);
1095 	line_start = sci_get_position_from_line(sci, line);
1096 	chunk = sci_get_line(sci, line);
1097 
1098 	/* Now that we got the content, let's parse it. */
1099 	expr = utils_evaluate_expr_from_string (chunk, peek_pos - line_start);
1100 
1101 	g_free(chunk);
1102 
1103 	return expr;
1104 }
1105