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