c++: Fix handling of the `final` contextual keyword
[geany-mirror.git] / src / msgwindow.c
blob4e580b8b31fa92d1c18d835211e67c1f68f7a765
1 /*
2 * msgwindow.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 /**
23 * @file msgwindow.h
24 * Message window functions (status, compiler, messages windows).
25 * Also compiler error message parsing and grep file and line parsing.
27 * @see GeanyMainWidgets::message_window_notebook to append a new notebook page.
28 **/
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
34 #include "msgwindow.h"
36 #include "build.h"
37 #include "document.h"
38 #include "callbacks.h"
39 #include "filetypes.h"
40 #include "keybindings.h"
41 #include "main.h"
42 #include "navqueue.h"
43 #include "prefs.h"
44 #include "support.h"
45 #include "ui_utils.h"
46 #include "utils.h"
47 #include "vte.h"
49 #include <string.h>
50 #include <stdlib.h>
51 #include <time.h>
53 #include <gdk/gdkkeysyms.h>
56 /* used for parse_file_line */
57 typedef struct
59 const gchar *string; /* line data */
60 const gchar *pattern; /* pattern to split the error message into some fields */
61 guint min_fields; /* used to detect errors after parsing */
62 guint line_idx; /* idx of the field where the line is */
63 gint file_idx; /* idx of the field where the filename is or -1 */
65 ParseData;
67 MessageWindow msgwindow;
69 enum
71 MSG_COL_LINE = 0,
72 MSG_COL_DOC_ID,
73 MSG_COL_COLOR,
74 MSG_COL_STRING,
75 MSG_COL_COUNT
78 enum
80 COMPILER_COL_COLOR = 0,
81 COMPILER_COL_STRING,
82 COMPILER_COL_COUNT
86 static void prepare_msg_tree_view(void);
87 static void prepare_status_tree_view(void);
88 static void prepare_compiler_tree_view(void);
89 static GtkWidget *create_message_popup_menu(gint type);
90 static gboolean on_msgwin_button_press_event(GtkWidget *widget, GdkEventButton *event,
91 gpointer user_data);
92 static void on_scribble_populate(GtkTextView *textview, GtkMenu *arg1, gpointer user_data);
95 void msgwin_show_hide_tabs(void)
97 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_status), interface_prefs.msgwin_status_visible);
98 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_compiler), interface_prefs.msgwin_compiler_visible);
99 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_msg), interface_prefs.msgwin_messages_visible);
100 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.scribble), interface_prefs.msgwin_scribble_visible);
104 /** Sets the Messages path for opening any parsed filenames without absolute path
105 * from message lines.
106 * @param messages_dir The directory. **/
107 GEANY_API_SYMBOL
108 void msgwin_set_messages_dir(const gchar *messages_dir)
110 g_free(msgwindow.messages_dir);
111 msgwindow.messages_dir = g_strdup(messages_dir);
115 void msgwin_init(void)
117 msgwindow.notebook = ui_lookup_widget(main_widgets.window, "notebook_info");
118 msgwindow.tree_status = ui_lookup_widget(main_widgets.window, "treeview3");
119 msgwindow.tree_msg = ui_lookup_widget(main_widgets.window, "treeview4");
120 msgwindow.tree_compiler = ui_lookup_widget(main_widgets.window, "treeview5");
121 msgwindow.scribble = ui_lookup_widget(main_widgets.window, "textview_scribble");
122 msgwindow.messages_dir = NULL;
124 prepare_status_tree_view();
125 prepare_msg_tree_view();
126 prepare_compiler_tree_view();
127 msgwindow.popup_status_menu = create_message_popup_menu(MSG_STATUS);
128 msgwindow.popup_msg_menu = create_message_popup_menu(MSG_MESSAGE);
129 msgwindow.popup_compiler_menu = create_message_popup_menu(MSG_COMPILER);
131 ui_widget_modify_font_from_string(msgwindow.scribble, interface_prefs.msgwin_font);
132 g_signal_connect(msgwindow.scribble, "populate-popup", G_CALLBACK(on_scribble_populate), NULL);
136 void msgwin_finalize(void)
138 g_free(msgwindow.messages_dir);
142 static gboolean on_msgwin_key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
144 gboolean enter_or_return = ui_is_keyval_enter_or_return(event->keyval);
146 if (enter_or_return || event->keyval == GDK_space)
148 switch (GPOINTER_TO_INT(data))
150 case MSG_COMPILER:
151 { /* key press in the compiler treeview */
152 msgwin_goto_compiler_file_line(enter_or_return);
153 break;
155 case MSG_MESSAGE:
156 { /* key press in the message treeview (results of 'Find usage') */
157 msgwin_goto_messages_file_line(enter_or_return);
158 break;
162 return FALSE;
166 /* does some preparing things to the status message list widget */
167 static void prepare_status_tree_view(void)
169 GtkCellRenderer *renderer;
170 GtkTreeViewColumn *column;
172 msgwindow.store_status = gtk_list_store_new(1, G_TYPE_STRING);
173 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_status), GTK_TREE_MODEL(msgwindow.store_status));
174 g_object_unref(msgwindow.store_status);
176 renderer = gtk_cell_renderer_text_new();
177 column = gtk_tree_view_column_new_with_attributes(_("Status messages"), renderer, "text", 0, NULL);
178 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_status), column);
180 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_status), FALSE);
182 ui_widget_modify_font_from_string(msgwindow.tree_status, interface_prefs.msgwin_font);
184 g_signal_connect(msgwindow.tree_status, "button-press-event",
185 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_STATUS));
189 /* does some preparing things to the message list widget
190 * (currently used for showing results of 'Find usage') */
191 static void prepare_msg_tree_view(void)
193 GtkCellRenderer *renderer;
194 GtkTreeViewColumn *column;
195 GtkTreeSelection *selection;
197 /* line, doc id, fg, str */
198 msgwindow.store_msg = gtk_list_store_new(MSG_COL_COUNT, G_TYPE_INT, G_TYPE_UINT,
199 GDK_TYPE_COLOR, G_TYPE_STRING);
200 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_msg), GTK_TREE_MODEL(msgwindow.store_msg));
201 g_object_unref(msgwindow.store_msg);
203 renderer = gtk_cell_renderer_text_new();
204 column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
205 "foreground-gdk", MSG_COL_COLOR, "text", MSG_COL_STRING, NULL);
206 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_msg), column);
208 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_msg), FALSE);
210 ui_widget_modify_font_from_string(msgwindow.tree_msg, interface_prefs.msgwin_font);
212 /* use button-release-event so the selection has changed
213 * (connect_after button-press-event doesn't work) */
214 g_signal_connect(msgwindow.tree_msg, "button-release-event",
215 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_MESSAGE));
216 /* for double-clicking only, after the first release */
217 g_signal_connect(msgwindow.tree_msg, "button-press-event",
218 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_MESSAGE));
219 g_signal_connect(msgwindow.tree_msg, "key-press-event",
220 G_CALLBACK(on_msgwin_key_press_event), GINT_TO_POINTER(MSG_MESSAGE));
222 /* selection handling */
223 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_msg));
224 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
225 /*g_signal_connect(selection, "changed",G_CALLBACK(on_msg_tree_selection_changed), NULL);*/
229 /* does some preparing things to the compiler list widget */
230 static void prepare_compiler_tree_view(void)
232 GtkCellRenderer *renderer;
233 GtkTreeViewColumn *column;
234 GtkTreeSelection *selection;
236 msgwindow.store_compiler = gtk_list_store_new(COMPILER_COL_COUNT, GDK_TYPE_COLOR, G_TYPE_STRING);
237 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_compiler), GTK_TREE_MODEL(msgwindow.store_compiler));
238 g_object_unref(msgwindow.store_compiler);
240 renderer = gtk_cell_renderer_text_new();
241 column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
242 "foreground-gdk", COMPILER_COL_COLOR, "text", COMPILER_COL_STRING, NULL);
243 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_compiler), column);
245 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_compiler), FALSE);
247 ui_widget_modify_font_from_string(msgwindow.tree_compiler, interface_prefs.msgwin_font);
249 /* use button-release-event so the selection has changed
250 * (connect_after button-press-event doesn't work) */
251 g_signal_connect(msgwindow.tree_compiler, "button-release-event",
252 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_COMPILER));
253 /* for double-clicking only, after the first release */
254 g_signal_connect(msgwindow.tree_compiler, "button-press-event",
255 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_COMPILER));
256 g_signal_connect(msgwindow.tree_compiler, "key-press-event",
257 G_CALLBACK(on_msgwin_key_press_event), GINT_TO_POINTER(MSG_COMPILER));
259 /* selection handling */
260 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_compiler));
261 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
262 /*g_signal_connect(selection, "changed", G_CALLBACK(on_msg_tree_selection_changed), NULL);*/
266 static const GdkColor color_error = {0, 65535, 0, 0};
268 static const GdkColor *get_color(gint msg_color)
270 static const GdkColor dark_red = {0, 65535 / 2, 0, 0};
271 static const GdkColor blue = {0, 0, 0, 0xD000}; /* not too bright ;-) */
273 switch (msg_color)
275 case COLOR_RED: return &color_error;
276 case COLOR_DARK_RED: return &dark_red;
277 case COLOR_BLUE: return &blue;
278 default: return NULL;
284 * Adds a new message in the compiler tab treeview in the messages window.
286 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
287 * @param format @c printf()-style format string.
288 * @param ... Arguments for the @c format string.
290 GEANY_API_SYMBOL
291 void msgwin_compiler_add(gint msg_color, const gchar *format, ...)
293 gchar *string;
294 va_list args;
296 va_start(args, format);
297 string = g_strdup_vprintf(format, args);
298 va_end(args);
299 msgwin_compiler_add_string(msg_color, string);
300 g_free(string);
304 void msgwin_compiler_add_string(gint msg_color, const gchar *msg)
306 GtkTreeIter iter;
307 GtkTreePath *path;
308 const GdkColor *color = get_color(msg_color);
309 gchar *utf8_msg;
311 if (! g_utf8_validate(msg, -1, NULL))
312 utf8_msg = utils_get_utf8_from_locale(msg);
313 else
314 utf8_msg = (gchar *) msg;
316 gtk_list_store_append(msgwindow.store_compiler, &iter);
317 gtk_list_store_set(msgwindow.store_compiler, &iter,
318 COMPILER_COL_COLOR, color, COMPILER_COL_STRING, utf8_msg, -1);
320 if (ui_prefs.msgwindow_visible && interface_prefs.compiler_tab_autoscroll)
322 path = gtk_tree_model_get_path(
323 gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_compiler)), &iter);
324 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(msgwindow.tree_compiler), path, NULL, TRUE, 0.5, 0.5);
325 gtk_tree_path_free(path);
328 /* calling build_menu_update for every build message would be overkill, TODO really should call it once when all done */
329 gtk_widget_set_sensitive(build_get_menu_items(-1)->menu_item[GBG_FIXED][GBF_NEXT_ERROR], TRUE);
330 gtk_widget_set_sensitive(build_get_menu_items(-1)->menu_item[GBG_FIXED][GBF_PREV_ERROR], TRUE);
332 if (utf8_msg != msg)
333 g_free(utf8_msg);
337 void msgwin_show_hide(gboolean show)
339 ui_prefs.msgwindow_visible = show;
340 ignore_callback = TRUE;
341 gtk_check_menu_item_set_active(
342 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_show_messages_window1")),
343 show);
344 ignore_callback = FALSE;
345 ui_widget_show_hide(main_widgets.message_window_notebook, show);
346 /* set the input focus back to the editor */
347 keybindings_send_command(GEANY_KEY_GROUP_FOCUS, GEANY_KEYS_FOCUS_EDITOR);
352 * Adds a new message in the messages tab treeview in the messages window.
353 * If @a line and @a doc are set, clicking on this line jumps into the file which is specified
354 * by @a doc into the line specified with @a line.
356 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
357 * @param line The document's line where the message belongs to. Set to @c -1 to ignore.
358 * @param doc The document. Set to @c NULL to ignore.
359 * @param format @c printf()-style format string.
360 * @param ... Arguments for the @c format string.
362 * @since 0.15
364 GEANY_API_SYMBOL
365 void msgwin_msg_add(gint msg_color, gint line, GeanyDocument *doc, const gchar *format, ...)
367 gchar *string;
368 va_list args;
370 va_start(args, format);
371 string = g_strdup_vprintf(format, args);
372 va_end(args);
374 msgwin_msg_add_string(msg_color, line, doc, string);
375 g_free(string);
379 /* adds string to the msg treeview */
380 void msgwin_msg_add_string(gint msg_color, gint line, GeanyDocument *doc, const gchar *string)
382 GtkTreeIter iter;
383 const GdkColor *color = get_color(msg_color);
384 gchar *tmp;
385 gsize len;
386 gchar *utf8_msg;
388 if (! ui_prefs.msgwindow_visible)
389 msgwin_show_hide(TRUE);
391 /* work around a strange problem when adding very long lines(greater than 4000 bytes)
392 * cut the string to a maximum of 1024 bytes and discard the rest */
393 /* TODO: find the real cause for the display problem / if it is GtkTreeView file a bug report */
394 len = strlen(string);
395 if (len > 1024)
396 tmp = g_strndup(string, 1024);
397 else
398 tmp = g_strdup(string);
400 if (! g_utf8_validate(tmp, -1, NULL))
401 utf8_msg = utils_get_utf8_from_locale(tmp);
402 else
403 utf8_msg = tmp;
405 gtk_list_store_append(msgwindow.store_msg, &iter);
406 gtk_list_store_set(msgwindow.store_msg, &iter,
407 MSG_COL_LINE, line, MSG_COL_DOC_ID, doc ? doc->id : 0, MSG_COL_COLOR,
408 color, MSG_COL_STRING, utf8_msg, -1);
410 g_free(tmp);
411 if (utf8_msg != tmp)
412 g_free(utf8_msg);
417 * Logs a status message *without* setting the status bar.
418 * (Use ui_set_statusbar() to display text on the statusbar)
420 * @param format @c printf()-style format string.
421 * @param ... Arguments for the @c format string.
423 GEANY_API_SYMBOL
424 void msgwin_status_add(const gchar *format, ...)
426 GtkTreeIter iter;
427 gchar *string;
428 gchar *statusmsg, *time_str;
429 va_list args;
431 va_start(args, format);
432 string = g_strdup_vprintf(format, args);
433 va_end(args);
435 /* add a timestamp to status messages */
436 time_str = utils_get_current_time_string();
437 statusmsg = g_strconcat(time_str, ": ", string, NULL);
438 g_free(time_str);
439 g_free(string);
441 /* add message to Status window */
442 gtk_list_store_append(msgwindow.store_status, &iter);
443 gtk_list_store_set(msgwindow.store_status, &iter, 0, statusmsg, -1);
444 g_free(statusmsg);
446 if (G_LIKELY(main_status.main_window_realized))
448 GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_status)), &iter);
450 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(msgwindow.tree_status), path, NULL, FALSE, 0.0, 0.0);
451 if (prefs.switch_to_status)
452 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_STATUS);
453 gtk_tree_path_free(path);
458 static void
459 on_message_treeview_clear_activate(GtkMenuItem *menuitem, gpointer user_data)
461 gint tabnum = GPOINTER_TO_INT(user_data);
463 msgwin_clear_tab(tabnum);
467 static void
468 on_compiler_treeview_copy_activate(GtkMenuItem *menuitem, gpointer user_data)
470 GtkWidget *tv = NULL;
471 GtkTreeSelection *selection;
472 GtkTreeModel *model;
473 GtkTreeIter iter;
474 gint str_idx = COMPILER_COL_STRING;
476 switch (GPOINTER_TO_INT(user_data))
478 case MSG_STATUS:
479 tv = msgwindow.tree_status;
480 str_idx = 0;
481 break;
483 case MSG_COMPILER:
484 tv = msgwindow.tree_compiler;
485 break;
487 case MSG_MESSAGE:
488 tv = msgwindow.tree_msg;
489 str_idx = MSG_COL_STRING;
490 break;
492 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
494 if (gtk_tree_selection_get_selected(selection, &model, &iter))
496 gchar *string;
498 gtk_tree_model_get(model, &iter, str_idx, &string, -1);
499 if (!EMPTY(string))
501 gtk_clipboard_set_text(gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
502 string, -1);
504 g_free(string);
509 static void on_compiler_treeview_copy_all_activate(GtkMenuItem *menuitem, gpointer user_data)
511 GtkListStore *store = msgwindow.store_compiler;
512 GtkTreeIter iter;
513 GString *str = g_string_new("");
514 gint str_idx = COMPILER_COL_STRING;
515 gboolean valid;
517 switch (GPOINTER_TO_INT(user_data))
519 case MSG_STATUS:
520 store = msgwindow.store_status;
521 str_idx = 0;
522 break;
524 case MSG_COMPILER:
525 /* default values */
526 break;
528 case MSG_MESSAGE:
529 store = msgwindow.store_msg;
530 str_idx = MSG_COL_STRING;
531 break;
534 /* walk through the list and copy every line into a string */
535 valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
536 while (valid)
538 gchar *line;
540 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, str_idx, &line, -1);
541 if (!EMPTY(line))
543 g_string_append(str, line);
544 g_string_append_c(str, '\n');
546 g_free(line);
548 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
551 /* copy the string into the clipboard */
552 if (str->len > 0)
554 gtk_clipboard_set_text(
555 gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
556 str->str,
557 str->len);
559 g_string_free(str, TRUE);
563 static void
564 on_hide_message_window(GtkMenuItem *menuitem, gpointer user_data)
566 msgwin_show_hide(FALSE);
570 static GtkWidget *create_message_popup_menu(gint type)
572 GtkWidget *message_popup_menu, *clear, *copy, *copy_all, *image;
574 message_popup_menu = gtk_menu_new();
576 clear = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLEAR, NULL);
577 gtk_widget_show(clear);
578 gtk_container_add(GTK_CONTAINER(message_popup_menu), clear);
579 g_signal_connect(clear, "activate",
580 G_CALLBACK(on_message_treeview_clear_activate), GINT_TO_POINTER(type));
582 copy = gtk_image_menu_item_new_with_mnemonic(_("C_opy"));
583 gtk_widget_show(copy);
584 gtk_container_add(GTK_CONTAINER(message_popup_menu), copy);
585 image = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
586 gtk_widget_show(image);
587 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(copy), image);
588 g_signal_connect(copy, "activate",
589 G_CALLBACK(on_compiler_treeview_copy_activate), GINT_TO_POINTER(type));
591 copy_all = gtk_image_menu_item_new_with_mnemonic(_("Copy _All"));
592 gtk_widget_show(copy_all);
593 gtk_container_add(GTK_CONTAINER(message_popup_menu), copy_all);
594 image = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
595 gtk_widget_show(image);
596 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(copy_all), image);
597 g_signal_connect(copy_all, "activate",
598 G_CALLBACK(on_compiler_treeview_copy_all_activate), GINT_TO_POINTER(type));
600 msgwin_menu_add_common_items(GTK_MENU(message_popup_menu));
602 return message_popup_menu;
606 static void on_scribble_populate(GtkTextView *textview, GtkMenu *arg1, gpointer user_data)
608 msgwin_menu_add_common_items(arg1);
612 /* Menu items that should be on all message window popup menus */
613 void msgwin_menu_add_common_items(GtkMenu *menu)
615 GtkWidget *item;
617 item = gtk_separator_menu_item_new();
618 gtk_widget_show(item);
619 gtk_container_add(GTK_CONTAINER(menu), item);
621 item = gtk_menu_item_new_with_mnemonic(_("_Hide Message Window"));
622 gtk_widget_show(item);
623 gtk_container_add(GTK_CONTAINER(menu), item);
624 g_signal_connect(item, "activate", G_CALLBACK(on_hide_message_window), NULL);
628 /* look back up from the current path and find the directory we came from */
629 static gboolean
630 find_prev_build_dir(GtkTreePath *cur, GtkTreeModel *model, gchar **prefix)
632 GtkTreeIter iter;
633 *prefix = NULL;
635 while (gtk_tree_path_prev(cur))
637 if (gtk_tree_model_get_iter(model, &iter, cur))
639 gchar *string;
640 gtk_tree_model_get(model, &iter, COMPILER_COL_STRING, &string, -1);
641 if (string != NULL && build_parse_make_dir(string, prefix))
643 g_free(string);
644 return TRUE;
646 g_free(string);
650 return FALSE;
654 static gboolean goto_compiler_file_line(const gchar *filename, gint line, gboolean focus_editor)
656 if (!filename || line <= -1)
657 return FALSE;
659 /* If the path doesn't exist, try the current document.
660 * This happens when we receive build messages in the wrong order - after the
661 * 'Leaving directory' messages */
662 if (!g_file_test(filename, G_FILE_TEST_EXISTS))
664 gchar *cur_dir = utils_get_current_file_dir_utf8();
665 gchar *name;
667 if (cur_dir)
669 /* we let the user know we couldn't find the parsed filename from the message window */
670 SETPTR(cur_dir, utils_get_locale_from_utf8(cur_dir));
671 name = g_path_get_basename(filename);
672 SETPTR(name, g_build_path(G_DIR_SEPARATOR_S, cur_dir, name, NULL));
673 g_free(cur_dir);
675 if (g_file_test(name, G_FILE_TEST_EXISTS))
677 ui_set_statusbar(FALSE, _("Could not find file '%s' - trying the current document path."),
678 filename);
679 filename = name;
681 else
682 g_free(name);
687 gchar *utf8_filename = utils_get_utf8_from_locale(filename);
688 GeanyDocument *doc = document_find_by_filename(utf8_filename);
689 GeanyDocument *old_doc = document_get_current();
691 g_free(utf8_filename);
693 if (doc == NULL) /* file not already open */
694 doc = document_open_file(filename, FALSE, NULL, NULL);
696 if (doc != NULL)
698 gboolean ret;
700 if (! doc->changed && editor_prefs.use_indicators) /* if modified, line may be wrong */
701 editor_indicator_set_on_line(doc->editor, GEANY_INDICATOR_ERROR, line - 1);
703 ret = navqueue_goto_line(old_doc, doc, line);
704 if (ret && focus_editor)
705 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
707 return ret;
710 return FALSE;
714 gboolean msgwin_goto_compiler_file_line(gboolean focus_editor)
716 GtkTreeIter iter;
717 GtkTreeModel *model;
718 GtkTreeSelection *selection;
719 gchar *string;
720 GdkColor *color;
722 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_compiler));
723 if (gtk_tree_selection_get_selected(selection, &model, &iter))
725 /* if the item is not coloured red, it's not an error line */
726 gtk_tree_model_get(model, &iter, COMPILER_COL_COLOR, &color, -1);
727 if (color == NULL || ! gdk_color_equal(color, &color_error))
729 if (color != NULL)
730 gdk_color_free(color);
731 return FALSE;
733 gdk_color_free(color);
735 gtk_tree_model_get(model, &iter, COMPILER_COL_STRING, &string, -1);
736 if (string != NULL)
738 gint line;
739 gchar *filename, *dir;
740 GtkTreePath *path;
741 gboolean ret;
743 path = gtk_tree_model_get_path(model, &iter);
744 find_prev_build_dir(path, model, &dir);
745 gtk_tree_path_free(path);
746 msgwin_parse_compiler_error_line(string, dir, &filename, &line);
747 g_free(string);
748 g_free(dir);
750 ret = goto_compiler_file_line(filename, line, focus_editor);
751 g_free(filename);
752 return ret;
755 return FALSE;
759 static void make_absolute(gchar **filename, const gchar *dir)
761 guint skip_dot_slash = 0; /* number of characters to skip at the beginning of the filename */
763 if (*filename == NULL)
764 return;
766 /* skip some characters at the beginning of the filename, at the moment only "./"
767 * can be extended if other "trash" is known */
768 if (strncmp(*filename, "./", 2) == 0)
769 skip_dot_slash = 2;
771 /* add directory */
772 if (! utils_is_absolute_path(*filename))
773 SETPTR(*filename, g_build_filename(dir, *filename + skip_dot_slash, NULL));
777 /* try to parse the file and line number where the error occurred described in line
778 * and when something useful is found, it stores the line number in *line and the
779 * relevant file with the error in *filename.
780 * *line will be -1 if no error was found in string.
781 * *filename must be freed unless it is NULL. */
782 static void parse_file_line(ParseData *data, gchar **filename, gint *line)
784 gchar *end = NULL;
785 gchar **fields;
787 *filename = NULL;
788 *line = -1;
790 g_return_if_fail(data->string != NULL);
792 fields = g_strsplit_set(data->string, data->pattern, data->min_fields);
794 /* parse the line */
795 if (g_strv_length(fields) < data->min_fields)
797 g_strfreev(fields);
798 return;
801 *line = strtol(fields[data->line_idx], &end, 10);
803 /* if the line could not be read, line is 0 and an error occurred, so we leave */
804 if (fields[data->line_idx] == end)
806 g_strfreev(fields);
807 return;
810 /* let's stop here if there is no filename in the error message */
811 if (data->file_idx == -1)
813 /* we have no filename in the error message, so take the current one and hope it's correct */
814 GeanyDocument *doc = document_get_current();
815 if (doc != NULL)
816 *filename = g_strdup(doc->file_name);
817 g_strfreev(fields);
818 return;
821 *filename = g_strdup(fields[data->file_idx]);
822 g_strfreev(fields);
826 static void parse_compiler_error_line(const gchar *string,
827 gchar **filename, gint *line)
829 ParseData data = {NULL, NULL, 0, 0, 0};
831 data.string = string;
833 switch (build_info.file_type_id)
835 case GEANY_FILETYPES_PHP:
837 /* Parse error: parse error, unexpected T_CASE in brace_bug.php on line 3
838 * Parse error: syntax error, unexpected T_LNUMBER, expecting T_FUNCTION in bob.php on line 16 */
839 gchar *tmp = strstr(string, " in ");
841 if (tmp != NULL)
843 data.string = tmp;
844 data.pattern = " ";
845 data.min_fields = 6;
846 data.line_idx = 5;
847 data.file_idx = 2;
849 else
851 data.pattern = " ";
852 data.min_fields = 11;
853 data.line_idx = 10;
854 data.file_idx = 7;
856 break;
858 case GEANY_FILETYPES_PERL:
860 /* syntax error at test.pl line 7, near "{ */
861 data.pattern = " ";
862 data.min_fields = 6;
863 data.line_idx = 5;
864 data.file_idx = 3;
865 break;
867 /* the error output of python and tcl equals */
868 case GEANY_FILETYPES_TCL:
869 case GEANY_FILETYPES_PYTHON:
871 /* File "HyperArch.py", line 37, in ?
872 * (file "clrdial.tcl" line 12)
873 * */
874 if (strstr(string, " line ") != NULL)
876 /* Tcl and old Python format (<= Python 2.5) */
877 data.pattern = " \"";
878 data.min_fields = 6;
879 data.line_idx = 5;
880 data.file_idx = 2;
882 else
884 /* SyntaxError: ('invalid syntax', ('sender.py', 149, 20, ' ...'))
885 * (used since Python 2.6) */
886 data.pattern = ",'";
887 data.min_fields = 8;
888 data.line_idx = 6;
889 data.file_idx = 4;
891 break;
893 case GEANY_FILETYPES_BASIC:
894 case GEANY_FILETYPES_PASCAL:
895 case GEANY_FILETYPES_CS:
897 /* getdrive.bas(52) error 18: Syntax error in '? GetAllDrives'
898 * bandit.pas(149,3) Fatal: Syntax error, ";" expected but "ELSE" found */
899 data.pattern = "(";
900 data.min_fields = 2;
901 data.line_idx = 1;
902 data.file_idx = 0;
903 break;
905 case GEANY_FILETYPES_D:
907 /* GNU D compiler front-end, gdc
908 * gantry.d:18: variable gantry.main.c reference to auto class must be auto
909 * warning - gantry.d:20: statement is not reachable
910 * Digital Mars dmd compiler
911 * warning - pi.d(118): implicit conversion of expression (digit) of type int ...
912 * gantry.d(18): variable gantry.main.c reference to auto class must be auto */
913 if (strncmp(string, "warning - ", 10) == 0)
915 data.pattern = " (:";
916 data.min_fields = 4;
917 data.line_idx = 3;
918 data.file_idx = 2;
920 else
922 data.pattern = "(:";
923 data.min_fields = 2;
924 data.line_idx = 1;
925 data.file_idx = 0;
927 break;
929 case GEANY_FILETYPES_FERITE:
931 /* Error: Parse Error: on line 5 in "/tmp/hello.fe"
932 * Error: Compile Error: on line 24, in /test/class.fe */
933 if (strncmp(string, "Error: Compile Error", 20) == 0)
935 data.pattern = " ";
936 data.min_fields = 8;
937 data.line_idx = 5;
938 data.file_idx = 7;
940 else
942 data.pattern = " \"";
943 data.min_fields = 10;
944 data.line_idx = 5;
945 data.file_idx = 8;
947 break;
949 case GEANY_FILETYPES_HTML:
951 /* line 78 column 7 - Warning: <table> missing '>' for end of tag */
952 data.pattern = " ";
953 data.min_fields = 4;
954 data.line_idx = 1;
955 data.file_idx = -1;
956 break;
958 /* All GNU gcc-like error messages */
959 case GEANY_FILETYPES_C:
960 case GEANY_FILETYPES_CPP:
961 case GEANY_FILETYPES_RUBY:
962 case GEANY_FILETYPES_JAVA:
963 /* only gcc is supported, I don't know any other C(++) compilers and their error messages
964 * empty.h:4: Warnung: type defaults to `int' in declaration of `foo'
965 * empty.c:21: error: conflicting types for `foo'
966 * Only parse file and line, so that linker errors will also work (with -g) */
967 case GEANY_FILETYPES_F77:
968 case GEANY_FILETYPES_FORTRAN:
969 case GEANY_FILETYPES_LATEX:
970 /* ./kommtechnik_2b.tex:18: Emergency stop. */
971 case GEANY_FILETYPES_MAKE: /* Assume makefile is building with gcc */
972 case GEANY_FILETYPES_NONE:
973 default: /* The default is a GNU gcc type error */
975 if (build_info.file_type_id == GEANY_FILETYPES_JAVA &&
976 strncmp(string, "[javac]", 7) == 0)
978 /* Java Apache Ant.
979 * [javac] <Full Path to File + extension>:<line n°>: <error> */
980 data.pattern = " :";
981 data.min_fields = 4;
982 data.line_idx = 2;
983 data.file_idx = 1;
984 break;
986 /* don't accidentally find libtool versions x:y:x and think it is a file name */
987 if (strstr(string, "libtool --mode=link") == NULL)
989 data.pattern = ":";
990 data.min_fields = 3;
991 data.line_idx = 1;
992 data.file_idx = 0;
993 break;
998 if (data.pattern != NULL)
999 parse_file_line(&data, filename, line);
1003 /* try to parse the file and line number where the error occurred described in string
1004 * and when something useful is found, it stores the line number in *line and the
1005 * relevant file with the error in *filename.
1006 * *line will be -1 if no error was found in string.
1007 * *filename must be freed unless it is NULL. */
1008 void msgwin_parse_compiler_error_line(const gchar *string, const gchar *dir,
1009 gchar **filename, gint *line)
1011 GeanyFiletype *ft;
1012 gchar *trimmed_string;
1014 *filename = NULL;
1015 *line = -1;
1017 if (G_UNLIKELY(string == NULL))
1018 return;
1020 if (dir == NULL)
1021 dir = build_info.dir;
1022 g_return_if_fail(dir != NULL);
1024 trimmed_string = g_strdup(string);
1025 g_strchug(trimmed_string); /* remove possible leading whitespace */
1027 ft = filetypes[build_info.file_type_id];
1029 /* try parsing with a custom regex */
1030 if (!filetypes_parse_error_message(ft, trimmed_string, filename, line))
1032 /* fallback to default old-style parsing */
1033 parse_compiler_error_line(trimmed_string, filename, line);
1035 make_absolute(filename, dir);
1036 g_free(trimmed_string);
1040 /* Tries to parse strings of the file:line style, allowing line field to be missing
1041 * * filename is filled with the filename, should be freed
1042 * * line is filled with the line number or -1 */
1043 static void msgwin_parse_generic_line(const gchar *string, gchar **filename, gint *line)
1045 gchar **fields;
1046 gboolean incertain = TRUE; /* whether we're reasonably certain of the result */
1048 *filename = NULL;
1049 *line = -1;
1051 fields = g_strsplit(string, ":", 2);
1052 /* extract the filename */
1053 if (fields[0] != NULL)
1055 *filename = g_strdup(fields[0]);
1056 if (msgwindow.messages_dir != NULL)
1057 make_absolute(filename, msgwindow.messages_dir);
1059 /* now the line */
1060 if (fields[1] != NULL)
1062 gchar *end;
1064 *line = strtol(fields[1], &end, 10);
1065 if (end == fields[1])
1066 *line = -1;
1067 else if (*end == ':' || g_ascii_isspace(*end))
1068 { /* if we have a blank or a separator right after the number, assume we really got a
1069 * filename (it's a grep-like syntax) */
1070 incertain = FALSE;
1074 /* if we aren't sure we got a supposedly correct filename, check it */
1075 if (incertain && ! g_file_test(*filename, G_FILE_TEST_EXISTS))
1077 SETPTR(*filename, NULL);
1078 *line = -1;
1081 g_strfreev(fields);
1085 gboolean msgwin_goto_messages_file_line(gboolean focus_editor)
1087 GtkTreeIter iter;
1088 GtkTreeModel *model;
1089 GtkTreeSelection *selection;
1090 gboolean ret = FALSE;
1092 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_msg));
1093 if (gtk_tree_selection_get_selected(selection, &model, &iter))
1095 gint line;
1096 guint id;
1097 gchar *string;
1098 GeanyDocument *doc;
1099 GeanyDocument *old_doc = document_get_current();
1101 gtk_tree_model_get(model, &iter,
1102 MSG_COL_LINE, &line, MSG_COL_DOC_ID, &id, MSG_COL_STRING, &string, -1);
1103 if (line >= 0 && id > 0)
1105 /* check doc is still open */
1106 doc = document_find_by_id(id);
1107 if (!doc)
1109 ui_set_statusbar(FALSE, _("The document has been closed."));
1110 utils_beep();
1112 else
1114 ret = navqueue_goto_line(old_doc, doc, line);
1115 if (ret && focus_editor)
1116 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1119 else if (line < 0 && string != NULL)
1121 gchar *filename;
1123 /* try with a file:line parsing */
1124 msgwin_parse_generic_line(string, &filename, &line);
1125 if (filename != NULL)
1127 /* use document_open_file to find an already open file, or open it in place */
1128 doc = document_open_file(filename, FALSE, NULL, NULL);
1129 if (doc != NULL)
1131 ret = (line < 0) ? TRUE : navqueue_goto_line(old_doc, doc, line);
1132 if (ret && focus_editor)
1133 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1136 g_free(filename);
1138 g_free(string);
1140 return ret;
1144 static gboolean on_msgwin_button_press_event(GtkWidget *widget, GdkEventButton *event,
1145 gpointer user_data)
1147 /* user_data might be NULL, GPOINTER_TO_INT returns 0 if called with NULL */
1148 gboolean double_click = event->type == GDK_2BUTTON_PRESS;
1150 if (event->button == 1 && (event->type == GDK_BUTTON_RELEASE || double_click))
1152 switch (GPOINTER_TO_INT(user_data))
1154 case MSG_COMPILER:
1155 { /* mouse click in the compiler treeview */
1156 msgwin_goto_compiler_file_line(double_click);
1157 break;
1159 case MSG_MESSAGE:
1160 { /* mouse click in the message treeview (results of 'Find usage') */
1161 msgwin_goto_messages_file_line(double_click);
1162 break;
1165 return double_click; /* TRUE prevents message window re-focusing */
1168 if (event->button == 3)
1169 { /* popupmenu to hide or clear the active treeview */
1170 switch (GPOINTER_TO_INT(user_data))
1172 case MSG_STATUS:
1174 gtk_menu_popup(GTK_MENU(msgwindow.popup_status_menu), NULL, NULL, NULL, NULL,
1175 event->button, event->time);
1176 break;
1178 case MSG_MESSAGE:
1180 gtk_menu_popup(GTK_MENU(msgwindow.popup_msg_menu), NULL, NULL, NULL, NULL,
1181 event->button, event->time);
1182 break;
1184 case MSG_COMPILER:
1186 gtk_menu_popup(GTK_MENU(msgwindow.popup_compiler_menu), NULL, NULL, NULL, NULL,
1187 event->button, event->time);
1188 break;
1192 return FALSE;
1197 * Switches to the given notebook tab of the messages window and shows the messages window
1198 * if it was previously hidden and @a show is set to @c TRUE.
1200 * @param tabnum An index of a tab in the messages window. Valid values are all elements of
1201 * #MessageWindowTabNum.
1202 * @param show Whether to show the messages window at all if it was hidden before.
1204 * @since 0.15
1206 GEANY_API_SYMBOL
1207 void msgwin_switch_tab(gint tabnum, gboolean show)
1209 GtkWidget *widget = NULL; /* widget to focus */
1211 switch (tabnum)
1213 case MSG_SCRATCH: widget = msgwindow.scribble; break;
1214 case MSG_COMPILER: widget = msgwindow.tree_compiler; break;
1215 case MSG_STATUS: widget = msgwindow.tree_status; break;
1216 case MSG_MESSAGE: widget = msgwindow.tree_msg; break;
1217 #ifdef HAVE_VTE
1218 case MSG_VTE: widget = (vte_info.have_vte) ? vc->vte : NULL; break;
1219 #endif
1220 default: break;
1223 /* the msgwin must be visible before we switch to the VTE page so that
1224 * the font settings are applied on realization */
1225 if (show)
1226 msgwin_show_hide(TRUE);
1227 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), tabnum);
1228 if (show && widget)
1229 gtk_widget_grab_focus(widget);
1234 * Removes all messages from a tab specified by @a tabnum in the messages window.
1236 * @param tabnum An index of a tab in the messages window which should be cleared.
1237 * Valid values are @c MSG_STATUS, @c MSG_COMPILER and @c MSG_MESSAGE.
1239 * @since 0.15
1241 GEANY_API_SYMBOL
1242 void msgwin_clear_tab(gint tabnum)
1244 GtkListStore *store = NULL;
1246 switch (tabnum)
1248 case MSG_MESSAGE:
1249 store = msgwindow.store_msg;
1250 break;
1252 case MSG_COMPILER:
1253 gtk_list_store_clear(msgwindow.store_compiler);
1254 build_menu_update(NULL); /* update next error items */
1255 return;
1257 case MSG_STATUS: store = msgwindow.store_status; break;
1258 default: return;
1260 if (store == NULL)
1261 return;
1262 gtk_list_store_clear(store);