Make code more readable by renaming poorly named macros NZV and NVL
[geany-mirror.git] / src / msgwindow.c
blobf423267b143632b7308cd9949264e760a70593fc
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 #include "geany.h"
32 #include "support.h"
33 #include "prefs.h"
34 #include "callbacks.h"
35 #include "ui_utils.h"
36 #include "utils.h"
37 #include "document.h"
38 #include "filetypes.h"
39 #include "build.h"
40 #include "main.h"
41 #include "vte.h"
42 #include "navqueue.h"
43 #include "editor.h"
44 #include "msgwindow.h"
45 #include "keybindings.h"
47 #include <string.h>
48 #include <stdlib.h>
49 #include <time.h>
51 #include <gdk/gdkkeysyms.h>
54 /* used for parse_file_line */
55 typedef struct
57 const gchar *string; /* line data */
58 const gchar *pattern; /* pattern to split the error message into some fields */
59 guint min_fields; /* used to detect errors after parsing */
60 guint line_idx; /* idx of the field where the line is */
61 gint file_idx; /* idx of the field where the filename is or -1 */
63 ParseData;
65 MessageWindow msgwindow;
68 static void prepare_msg_tree_view(void);
69 static void prepare_status_tree_view(void);
70 static void prepare_compiler_tree_view(void);
71 static GtkWidget *create_message_popup_menu(gint type);
72 static gboolean on_msgwin_button_press_event(GtkWidget *widget, GdkEventButton *event,
73 gpointer user_data);
74 static void on_scribble_populate(GtkTextView *textview, GtkMenu *arg1, gpointer user_data);
77 void msgwin_show_hide_tabs(void)
79 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_status), interface_prefs.msgwin_status_visible);
80 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_compiler), interface_prefs.msgwin_compiler_visible);
81 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_msg), interface_prefs.msgwin_messages_visible);
82 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.scribble), interface_prefs.msgwin_scribble_visible);
86 /** Sets the Messages path for opening any parsed filenames without absolute path
87 * from message lines.
88 * @param messages_dir The directory. **/
89 void msgwin_set_messages_dir(const gchar *messages_dir)
91 g_free(msgwindow.messages_dir);
92 msgwindow.messages_dir = g_strdup(messages_dir);
96 void msgwin_init(void)
98 msgwindow.notebook = ui_lookup_widget(main_widgets.window, "notebook_info");
99 msgwindow.tree_status = ui_lookup_widget(main_widgets.window, "treeview3");
100 msgwindow.tree_msg = ui_lookup_widget(main_widgets.window, "treeview4");
101 msgwindow.tree_compiler = ui_lookup_widget(main_widgets.window, "treeview5");
102 msgwindow.scribble = ui_lookup_widget(main_widgets.window, "textview_scribble");
103 msgwindow.messages_dir = NULL;
105 prepare_status_tree_view();
106 prepare_msg_tree_view();
107 prepare_compiler_tree_view();
108 msgwindow.popup_status_menu = create_message_popup_menu(MSG_STATUS);
109 msgwindow.popup_msg_menu = create_message_popup_menu(MSG_MESSAGE);
110 msgwindow.popup_compiler_menu = create_message_popup_menu(MSG_COMPILER);
112 ui_widget_modify_font_from_string(msgwindow.scribble, interface_prefs.msgwin_font);
113 g_signal_connect(msgwindow.scribble, "populate-popup", G_CALLBACK(on_scribble_populate), NULL);
117 void msgwin_finalize(void)
119 g_free(msgwindow.messages_dir);
123 static gboolean on_msgwin_key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
125 gboolean enter_or_return = ui_is_keyval_enter_or_return(event->keyval);
127 if (enter_or_return || event->keyval == GDK_space)
129 switch (GPOINTER_TO_INT(data))
131 case MSG_COMPILER:
132 { /* key press in the compiler treeview */
133 msgwin_goto_compiler_file_line(enter_or_return);
134 break;
136 case MSG_MESSAGE:
137 { /* key press in the message treeview (results of 'Find usage') */
138 msgwin_goto_messages_file_line(enter_or_return);
139 break;
143 return FALSE;
147 /* does some preparing things to the status message list widget */
148 static void prepare_status_tree_view(void)
150 GtkCellRenderer *renderer;
151 GtkTreeViewColumn *column;
153 msgwindow.store_status = gtk_list_store_new(1, G_TYPE_STRING);
154 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_status), GTK_TREE_MODEL(msgwindow.store_status));
155 g_object_unref(msgwindow.store_status);
157 renderer = gtk_cell_renderer_text_new();
158 column = gtk_tree_view_column_new_with_attributes(_("Status messages"), renderer, "text", 0, NULL);
159 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_status), column);
161 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_status), FALSE);
163 ui_widget_modify_font_from_string(msgwindow.tree_status, interface_prefs.msgwin_font);
165 g_signal_connect(msgwindow.tree_status, "button-press-event",
166 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_STATUS));
170 /* does some preparing things to the message list widget
171 * (currently used for showing results of 'Find usage') */
172 static void prepare_msg_tree_view(void)
174 GtkCellRenderer *renderer;
175 GtkTreeViewColumn *column;
176 GtkTreeSelection *selection;
178 /* line, doc, fg, str */
179 msgwindow.store_msg = gtk_list_store_new(4, G_TYPE_INT, G_TYPE_POINTER,
180 GDK_TYPE_COLOR, G_TYPE_STRING);
181 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_msg), GTK_TREE_MODEL(msgwindow.store_msg));
182 g_object_unref(msgwindow.store_msg);
184 renderer = gtk_cell_renderer_text_new();
185 column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
186 "foreground-gdk", 2, "text", 3, NULL);
187 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_msg), column);
189 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_msg), FALSE);
191 ui_widget_modify_font_from_string(msgwindow.tree_msg, interface_prefs.msgwin_font);
193 /* use button-release-event so the selection has changed
194 * (connect_after button-press-event doesn't work) */
195 g_signal_connect(msgwindow.tree_msg, "button-release-event",
196 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_MESSAGE));
197 /* for double-clicking only, after the first release */
198 g_signal_connect(msgwindow.tree_msg, "button-press-event",
199 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_MESSAGE));
200 g_signal_connect(msgwindow.tree_msg, "key-press-event",
201 G_CALLBACK(on_msgwin_key_press_event), GINT_TO_POINTER(MSG_MESSAGE));
203 /* selection handling */
204 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_msg));
205 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
206 /*g_signal_connect(selection, "changed",G_CALLBACK(on_msg_tree_selection_changed), NULL);*/
210 /* does some preparing things to the compiler list widget */
211 static void prepare_compiler_tree_view(void)
213 GtkCellRenderer *renderer;
214 GtkTreeViewColumn *column;
215 GtkTreeSelection *selection;
217 msgwindow.store_compiler = gtk_list_store_new(2, GDK_TYPE_COLOR, G_TYPE_STRING);
218 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_compiler), GTK_TREE_MODEL(msgwindow.store_compiler));
219 g_object_unref(msgwindow.store_compiler);
221 renderer = gtk_cell_renderer_text_new();
222 column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "foreground-gdk", 0, "text", 1, NULL);
223 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_compiler), column);
225 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_compiler), FALSE);
227 ui_widget_modify_font_from_string(msgwindow.tree_compiler, interface_prefs.msgwin_font);
229 /* use button-release-event so the selection has changed
230 * (connect_after button-press-event doesn't work) */
231 g_signal_connect(msgwindow.tree_compiler, "button-release-event",
232 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_COMPILER));
233 /* for double-clicking only, after the first release */
234 g_signal_connect(msgwindow.tree_compiler, "button-press-event",
235 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_COMPILER));
236 g_signal_connect(msgwindow.tree_compiler, "key-press-event",
237 G_CALLBACK(on_msgwin_key_press_event), GINT_TO_POINTER(MSG_COMPILER));
239 /* selection handling */
240 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_compiler));
241 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
242 /*g_signal_connect(selection, "changed", G_CALLBACK(on_msg_tree_selection_changed), NULL);*/
246 static const GdkColor color_error = {0, 65535, 0, 0};
248 static const GdkColor *get_color(gint msg_color)
250 static const GdkColor dark_red = {0, 65535 / 2, 0, 0};
251 static const GdkColor blue = {0, 0, 0, 0xD000}; /* not too bright ;-) */
253 switch (msg_color)
255 case COLOR_RED: return &color_error;
256 case COLOR_DARK_RED: return &dark_red;
257 case COLOR_BLUE: return &blue;
258 default: return NULL;
264 * Adds a new message in the compiler tab treeview in the messages window.
266 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
267 * @param format @c printf()-style format string.
268 * @param ... Arguments for the @c format string.
270 void msgwin_compiler_add(gint msg_color, const gchar *format, ...)
272 gchar *string;
273 va_list args;
275 va_start(args, format);
276 string = g_strdup_vprintf(format, args);
277 va_end(args);
278 msgwin_compiler_add_string(msg_color, string);
279 g_free(string);
283 void msgwin_compiler_add_string(gint msg_color, const gchar *msg)
285 GtkTreeIter iter;
286 GtkTreePath *path;
287 const GdkColor *color = get_color(msg_color);
288 gchar *utf8_msg;
290 if (! g_utf8_validate(msg, -1, NULL))
291 utf8_msg = utils_get_utf8_from_locale(msg);
292 else
293 utf8_msg = (gchar *) msg;
295 gtk_list_store_append(msgwindow.store_compiler, &iter);
296 gtk_list_store_set(msgwindow.store_compiler, &iter, 0, color, 1, utf8_msg, -1);
298 if (ui_prefs.msgwindow_visible && interface_prefs.compiler_tab_autoscroll)
300 path = gtk_tree_model_get_path(
301 gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_compiler)), &iter);
302 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(msgwindow.tree_compiler), path, NULL, TRUE, 0.5, 0.5);
303 gtk_tree_path_free(path);
306 /* calling build_menu_update for every build message would be overkill, TODO really should call it once when all done */
307 gtk_widget_set_sensitive(build_get_menu_items(-1)->menu_item[GBG_FIXED][GBF_NEXT_ERROR], TRUE);
308 gtk_widget_set_sensitive(build_get_menu_items(-1)->menu_item[GBG_FIXED][GBF_PREV_ERROR], TRUE);
310 if (utf8_msg != msg)
311 g_free(utf8_msg);
315 void msgwin_show_hide(gboolean show)
317 ui_prefs.msgwindow_visible = show;
318 ignore_callback = TRUE;
319 gtk_check_menu_item_set_active(
320 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_show_messages_window1")),
321 show);
322 ignore_callback = FALSE;
323 ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "scrolledwindow1"), show);
324 /* set the input focus back to the editor */
325 keybindings_send_command(GEANY_KEY_GROUP_FOCUS, GEANY_KEYS_FOCUS_EDITOR);
330 * Adds a new message in the messages tab treeview in the messages window.
331 * If @a line and @a doc are set, clicking on this line jumps into the file which is specified
332 * by @a doc into the line specified with @a line.
334 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
335 * @param line The document's line where the message belongs to. Set to @c -1 to ignore.
336 * @param doc The document. Set to @c NULL to ignore.
337 * @param format @c printf()-style format string.
338 * @param ... Arguments for the @c format string.
340 * @since 0.15
342 void msgwin_msg_add(gint msg_color, gint line, GeanyDocument *doc, const gchar *format, ...)
344 gchar *string;
345 va_list args;
347 va_start(args, format);
348 string = g_strdup_vprintf(format, args);
349 va_end(args);
351 msgwin_msg_add_string(msg_color, line, doc, string);
352 g_free(string);
356 /* adds string to the msg treeview */
357 void msgwin_msg_add_string(gint msg_color, gint line, GeanyDocument *doc, const gchar *string)
359 GtkTreeIter iter;
360 const GdkColor *color = get_color(msg_color);
361 gchar *tmp;
362 gsize len;
363 gchar *utf8_msg;
365 if (! ui_prefs.msgwindow_visible)
366 msgwin_show_hide(TRUE);
368 /* work around a strange problem when adding very long lines(greater than 4000 bytes)
369 * cut the string to a maximum of 1024 bytes and discard the rest */
370 /* TODO: find the real cause for the display problem / if it is GtkTreeView file a bug report */
371 len = strlen(string);
372 if (len > 1024)
373 tmp = g_strndup(string, 1024);
374 else
375 tmp = g_strdup(string);
377 if (! g_utf8_validate(tmp, -1, NULL))
378 utf8_msg = utils_get_utf8_from_locale(tmp);
379 else
380 utf8_msg = tmp;
382 gtk_list_store_append(msgwindow.store_msg, &iter);
383 gtk_list_store_set(msgwindow.store_msg, &iter, 0, line, 1, doc, 2, color, 3, utf8_msg, -1);
385 g_free(tmp);
386 if (utf8_msg != tmp)
387 g_free(utf8_msg);
392 * Logs a status message *without* setting the status bar.
393 * (Use ui_set_statusbar() to display text on the statusbar)
395 * @param format @c printf()-style format string.
396 * @param ... Arguments for the @c format string.
398 void msgwin_status_add(const gchar *format, ...)
400 GtkTreeIter iter;
401 gchar *string;
402 gchar *statusmsg, *time_str;
403 va_list args;
405 va_start(args, format);
406 string = g_strdup_vprintf(format, args);
407 va_end(args);
409 /* add a timestamp to status messages */
410 time_str = utils_get_current_time_string();
411 statusmsg = g_strconcat(time_str, ": ", string, NULL);
412 g_free(time_str);
413 g_free(string);
415 /* add message to Status window */
416 gtk_list_store_append(msgwindow.store_status, &iter);
417 gtk_list_store_set(msgwindow.store_status, &iter, 0, statusmsg, -1);
418 g_free(statusmsg);
420 if (G_LIKELY(main_status.main_window_realized))
422 GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_status)), &iter);
424 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(msgwindow.tree_status), path, NULL, FALSE, 0.0, 0.0);
425 if (prefs.switch_to_status)
426 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_STATUS);
427 gtk_tree_path_free(path);
432 static void
433 on_message_treeview_clear_activate(GtkMenuItem *menuitem, gpointer user_data)
435 gint tabnum = GPOINTER_TO_INT(user_data);
437 msgwin_clear_tab(tabnum);
441 static void
442 on_compiler_treeview_copy_activate(GtkMenuItem *menuitem, gpointer user_data)
444 GtkWidget *tv = NULL;
445 GtkTreeSelection *selection;
446 GtkTreeModel *model;
447 GtkTreeIter iter;
448 gint str_idx = 1;
450 switch (GPOINTER_TO_INT(user_data))
452 case MSG_STATUS:
453 tv = msgwindow.tree_status;
454 str_idx = 0;
455 break;
457 case MSG_COMPILER:
458 tv = msgwindow.tree_compiler;
459 break;
461 case MSG_MESSAGE:
462 tv = msgwindow.tree_msg;
463 str_idx = 3;
464 break;
466 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
468 if (gtk_tree_selection_get_selected(selection, &model, &iter))
470 gchar *string;
472 gtk_tree_model_get(model, &iter, str_idx, &string, -1);
473 if (!EMPTY(string))
475 gtk_clipboard_set_text(gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
476 string, -1);
478 g_free(string);
483 static void on_compiler_treeview_copy_all_activate(GtkMenuItem *menuitem, gpointer user_data)
485 GtkListStore *store = msgwindow.store_compiler;
486 GtkTreeIter iter;
487 GString *str = g_string_new("");
488 gint str_idx = 1;
489 gboolean valid;
491 switch (GPOINTER_TO_INT(user_data))
493 case MSG_STATUS:
494 store = msgwindow.store_status;
495 str_idx = 0;
496 break;
498 case MSG_COMPILER:
499 /* default values */
500 break;
502 case MSG_MESSAGE:
503 store = msgwindow.store_msg;
504 str_idx = 3;
505 break;
508 /* walk through the list and copy every line into a string */
509 valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
510 while (valid)
512 gchar *line;
514 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, str_idx, &line, -1);
515 if (!EMPTY(line))
517 g_string_append(str, line);
518 g_string_append_c(str, '\n');
520 g_free(line);
522 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
525 /* copy the string into the clipboard */
526 if (str->len > 0)
528 gtk_clipboard_set_text(
529 gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
530 str->str,
531 str->len);
533 g_string_free(str, TRUE);
537 static void
538 on_hide_message_window(GtkMenuItem *menuitem, gpointer user_data)
540 msgwin_show_hide(FALSE);
544 static GtkWidget *create_message_popup_menu(gint type)
546 GtkWidget *message_popup_menu, *clear, *copy, *copy_all, *image;
548 message_popup_menu = gtk_menu_new();
550 clear = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLEAR, NULL);
551 gtk_widget_show(clear);
552 gtk_container_add(GTK_CONTAINER(message_popup_menu), clear);
553 g_signal_connect(clear, "activate",
554 G_CALLBACK(on_message_treeview_clear_activate), GINT_TO_POINTER(type));
556 copy = gtk_image_menu_item_new_with_mnemonic(_("C_opy"));
557 gtk_widget_show(copy);
558 gtk_container_add(GTK_CONTAINER(message_popup_menu), copy);
559 image = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
560 gtk_widget_show(image);
561 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(copy), image);
562 g_signal_connect(copy, "activate",
563 G_CALLBACK(on_compiler_treeview_copy_activate), GINT_TO_POINTER(type));
565 copy_all = gtk_image_menu_item_new_with_mnemonic(_("Copy _All"));
566 gtk_widget_show(copy_all);
567 gtk_container_add(GTK_CONTAINER(message_popup_menu), copy_all);
568 image = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
569 gtk_widget_show(image);
570 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(copy_all), image);
571 g_signal_connect(copy_all, "activate",
572 G_CALLBACK(on_compiler_treeview_copy_all_activate), GINT_TO_POINTER(type));
574 msgwin_menu_add_common_items(GTK_MENU(message_popup_menu));
576 return message_popup_menu;
580 static void on_scribble_populate(GtkTextView *textview, GtkMenu *arg1, gpointer user_data)
582 msgwin_menu_add_common_items(arg1);
586 /* Menu items that should be on all message window popup menus */
587 void msgwin_menu_add_common_items(GtkMenu *menu)
589 GtkWidget *item;
591 item = gtk_separator_menu_item_new();
592 gtk_widget_show(item);
593 gtk_container_add(GTK_CONTAINER(menu), item);
595 item = gtk_menu_item_new_with_mnemonic(_("_Hide Message Window"));
596 gtk_widget_show(item);
597 gtk_container_add(GTK_CONTAINER(menu), item);
598 g_signal_connect(item, "activate", G_CALLBACK(on_hide_message_window), NULL);
602 /* look back up from the current path and find the directory we came from */
603 static gboolean
604 find_prev_build_dir(GtkTreePath *cur, GtkTreeModel *model, gchar **prefix)
606 GtkTreeIter iter;
607 *prefix = NULL;
609 while (gtk_tree_path_prev(cur))
611 if (gtk_tree_model_get_iter(model, &iter, cur))
613 gchar *string;
614 gtk_tree_model_get(model, &iter, 1, &string, -1);
615 if (string != NULL && build_parse_make_dir(string, prefix))
617 g_free(string);
618 return TRUE;
620 g_free(string);
624 return FALSE;
628 static gboolean goto_compiler_file_line(const gchar *filename, gint line, gboolean focus_editor)
630 if (!filename || line <= -1)
631 return FALSE;
633 /* If the path doesn't exist, try the current document.
634 * This happens when we receive build messages in the wrong order - after the
635 * 'Leaving directory' messages */
636 if (!g_file_test(filename, G_FILE_TEST_EXISTS))
638 gchar *cur_dir = utils_get_current_file_dir_utf8();
639 gchar *name;
641 if (cur_dir)
643 /* we let the user know we couldn't find the parsed filename from the message window */
644 SETPTR(cur_dir, utils_get_locale_from_utf8(cur_dir));
645 name = g_path_get_basename(filename);
646 SETPTR(name, g_build_path(G_DIR_SEPARATOR_S, cur_dir, name, NULL));
647 g_free(cur_dir);
649 if (g_file_test(name, G_FILE_TEST_EXISTS))
651 ui_set_statusbar(FALSE, _("Could not find file '%s' - trying the current document path."),
652 filename);
653 filename = name;
655 else
656 g_free(name);
661 gchar *utf8_filename = utils_get_utf8_from_locale(filename);
662 GeanyDocument *doc = document_find_by_filename(utf8_filename);
663 GeanyDocument *old_doc = document_get_current();
665 g_free(utf8_filename);
667 if (doc == NULL) /* file not already open */
668 doc = document_open_file(filename, FALSE, NULL, NULL);
670 if (doc != NULL)
672 gboolean ret;
674 if (! doc->changed && editor_prefs.use_indicators) /* if modified, line may be wrong */
675 editor_indicator_set_on_line(doc->editor, GEANY_INDICATOR_ERROR, line - 1);
677 ret = navqueue_goto_line(old_doc, doc, line);
678 if (ret && focus_editor)
679 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
681 return ret;
684 return FALSE;
688 gboolean msgwin_goto_compiler_file_line(gboolean focus_editor)
690 GtkTreeIter iter;
691 GtkTreeModel *model;
692 GtkTreeSelection *selection;
693 gchar *string;
694 GdkColor *color;
696 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_compiler));
697 if (gtk_tree_selection_get_selected(selection, &model, &iter))
699 /* if the item is not coloured red, it's not an error line */
700 gtk_tree_model_get(model, &iter, 0, &color, -1);
701 if (color == NULL || ! gdk_color_equal(color, &color_error))
703 if (color != NULL)
704 gdk_color_free(color);
705 return FALSE;
707 gdk_color_free(color);
709 gtk_tree_model_get(model, &iter, 1, &string, -1);
710 if (string != NULL)
712 gint line;
713 gchar *filename, *dir;
714 GtkTreePath *path;
715 gboolean ret;
717 path = gtk_tree_model_get_path(model, &iter);
718 find_prev_build_dir(path, model, &dir);
719 gtk_tree_path_free(path);
720 msgwin_parse_compiler_error_line(string, dir, &filename, &line);
721 g_free(string);
722 g_free(dir);
724 ret = goto_compiler_file_line(filename, line, focus_editor);
725 g_free(filename);
726 return ret;
729 return FALSE;
733 static void make_absolute(gchar **filename, const gchar *dir)
735 guint skip_dot_slash = 0; /* number of characters to skip at the beginning of the filename */
737 if (*filename == NULL)
738 return;
740 /* skip some characters at the beginning of the filename, at the moment only "./"
741 * can be extended if other "trash" is known */
742 if (strncmp(*filename, "./", 2) == 0)
743 skip_dot_slash = 2;
745 /* add directory */
746 if (! utils_is_absolute_path(*filename))
747 SETPTR(*filename, g_build_filename(dir, *filename + skip_dot_slash, NULL));
751 /* try to parse the file and line number where the error occured described in line
752 * and when something useful is found, it stores the line number in *line and the
753 * relevant file with the error in *filename.
754 * *line will be -1 if no error was found in string.
755 * *filename must be freed unless it is NULL. */
756 static void parse_file_line(ParseData *data, gchar **filename, gint *line)
758 gchar *end = NULL;
759 gchar **fields;
761 *filename = NULL;
762 *line = -1;
764 g_return_if_fail(data->string != NULL);
766 fields = g_strsplit_set(data->string, data->pattern, data->min_fields);
768 /* parse the line */
769 if (g_strv_length(fields) < data->min_fields)
771 g_strfreev(fields);
772 return;
775 *line = strtol(fields[data->line_idx], &end, 10);
777 /* if the line could not be read, line is 0 and an error occurred, so we leave */
778 if (fields[data->line_idx] == end)
780 g_strfreev(fields);
781 return;
784 /* let's stop here if there is no filename in the error message */
785 if (data->file_idx == -1)
787 /* we have no filename in the error message, so take the current one and hope it's correct */
788 GeanyDocument *doc = document_get_current();
789 if (doc != NULL)
790 *filename = g_strdup(doc->file_name);
791 g_strfreev(fields);
792 return;
795 *filename = g_strdup(fields[data->file_idx]);
796 g_strfreev(fields);
800 static void parse_compiler_error_line(const gchar *string,
801 gchar **filename, gint *line)
803 ParseData data = {NULL, NULL, 0, 0, 0};
805 data.string = string;
807 switch (build_info.file_type_id)
809 case GEANY_FILETYPES_PHP:
811 /* Parse error: parse error, unexpected T_CASE in brace_bug.php on line 3
812 * Parse error: syntax error, unexpected T_LNUMBER, expecting T_FUNCTION in bob.php on line 16 */
813 gchar *tmp = strstr(string, " in ");
815 if (tmp != NULL)
817 data.string = tmp;
818 data.pattern = " ";
819 data.min_fields = 6;
820 data.line_idx = 5;
821 data.file_idx = 2;
823 else
825 data.pattern = " ";
826 data.min_fields = 11;
827 data.line_idx = 10;
828 data.file_idx = 7;
830 break;
832 case GEANY_FILETYPES_PERL:
834 /* syntax error at test.pl line 7, near "{ */
835 data.pattern = " ";
836 data.min_fields = 6;
837 data.line_idx = 5;
838 data.file_idx = 3;
839 break;
841 /* the error output of python and tcl equals */
842 case GEANY_FILETYPES_TCL:
843 case GEANY_FILETYPES_PYTHON:
845 /* File "HyperArch.py", line 37, in ?
846 * (file "clrdial.tcl" line 12)
847 * */
848 if (strstr(string, " line ") != NULL)
850 /* Tcl and old Python format (<= Python 2.5) */
851 data.pattern = " \"";
852 data.min_fields = 6;
853 data.line_idx = 5;
854 data.file_idx = 2;
856 else
858 /* SyntaxError: ('invalid syntax', ('sender.py', 149, 20, ' ...'))
859 * (used since Python 2.6) */
860 data.pattern = ",'";
861 data.min_fields = 8;
862 data.line_idx = 6;
863 data.file_idx = 4;
865 break;
867 case GEANY_FILETYPES_BASIC:
868 case GEANY_FILETYPES_PASCAL:
869 case GEANY_FILETYPES_CS:
871 /* getdrive.bas(52) error 18: Syntax error in '? GetAllDrives'
872 * bandit.pas(149,3) Fatal: Syntax error, ";" expected but "ELSE" found */
873 data.pattern = "(";
874 data.min_fields = 2;
875 data.line_idx = 1;
876 data.file_idx = 0;
877 break;
879 case GEANY_FILETYPES_D:
881 /* GNU D compiler front-end, gdc
882 * gantry.d:18: variable gantry.main.c reference to auto class must be auto
883 * warning - gantry.d:20: statement is not reachable
884 * Digital Mars dmd compiler
885 * warning - pi.d(118): implicit conversion of expression (digit) of type int ...
886 * gantry.d(18): variable gantry.main.c reference to auto class must be auto */
887 if (strncmp(string, "warning - ", 10) == 0)
889 data.pattern = " (:";
890 data.min_fields = 4;
891 data.line_idx = 3;
892 data.file_idx = 2;
894 else
896 data.pattern = "(:";
897 data.min_fields = 2;
898 data.line_idx = 1;
899 data.file_idx = 0;
901 break;
903 case GEANY_FILETYPES_FERITE:
905 /* Error: Parse Error: on line 5 in "/tmp/hello.fe"
906 * Error: Compile Error: on line 24, in /test/class.fe */
907 if (strncmp(string, "Error: Compile Error", 20) == 0)
909 data.pattern = " ";
910 data.min_fields = 8;
911 data.line_idx = 5;
912 data.file_idx = 7;
914 else
916 data.pattern = " \"";
917 data.min_fields = 10;
918 data.line_idx = 5;
919 data.file_idx = 8;
921 break;
923 case GEANY_FILETYPES_HTML:
925 /* line 78 column 7 - Warning: <table> missing '>' for end of tag */
926 data.pattern = " ";
927 data.min_fields = 4;
928 data.line_idx = 1;
929 data.file_idx = -1;
930 break;
932 /* All GNU gcc-like error messages */
933 case GEANY_FILETYPES_C:
934 case GEANY_FILETYPES_CPP:
935 case GEANY_FILETYPES_RUBY:
936 case GEANY_FILETYPES_JAVA:
937 /* only gcc is supported, I don't know any other C(++) compilers and their error messages
938 * empty.h:4: Warnung: type defaults to `int' in declaration of `foo'
939 * empty.c:21: error: conflicting types for `foo'
940 * Only parse file and line, so that linker errors will also work (with -g) */
941 case GEANY_FILETYPES_F77:
942 case GEANY_FILETYPES_FORTRAN:
943 case GEANY_FILETYPES_LATEX:
944 /* ./kommtechnik_2b.tex:18: Emergency stop. */
945 case GEANY_FILETYPES_MAKE: /* Assume makefile is building with gcc */
946 case GEANY_FILETYPES_NONE:
947 default: /* The default is a GNU gcc type error */
949 if (build_info.file_type_id == GEANY_FILETYPES_JAVA &&
950 strncmp(string, "[javac]", 7) == 0)
952 /* Java Apache Ant.
953 * [javac] <Full Path to File + extension>:<line n°>: <error> */
954 data.pattern = " :";
955 data.min_fields = 4;
956 data.line_idx = 2;
957 data.file_idx = 1;
958 break;
960 /* don't accidently find libtool versions x:y:x and think it is a file name */
961 if (strstr(string, "libtool --mode=link") == NULL)
963 data.pattern = ":";
964 data.min_fields = 3;
965 data.line_idx = 1;
966 data.file_idx = 0;
967 break;
972 if (data.pattern != NULL)
973 parse_file_line(&data, filename, line);
977 /* try to parse the file and line number where the error occured described in string
978 * and when something useful is found, it stores the line number in *line and the
979 * relevant file with the error in *filename.
980 * *line will be -1 if no error was found in string.
981 * *filename must be freed unless it is NULL. */
982 void msgwin_parse_compiler_error_line(const gchar *string, const gchar *dir,
983 gchar **filename, gint *line)
985 GeanyFiletype *ft;
986 gchar *trimmed_string;
988 *filename = NULL;
989 *line = -1;
991 if (G_UNLIKELY(string == NULL))
992 return;
994 if (dir == NULL)
995 dir = build_info.dir;
996 g_return_if_fail(dir != NULL);
998 trimmed_string = g_strdup(string);
999 g_strchug(trimmed_string); /* remove possible leading whitespace */
1001 ft = filetypes[build_info.file_type_id];
1003 /* try parsing with a custom regex */
1004 if (!filetypes_parse_error_message(ft, trimmed_string, filename, line))
1006 /* fallback to default old-style parsing */
1007 parse_compiler_error_line(trimmed_string, filename, line);
1009 make_absolute(filename, dir);
1010 g_free(trimmed_string);
1014 /* Tries to parse strings of the file:line style, allowing line field to be missing
1015 * * filename is filled with the filename, should be freed
1016 * * line is filled with the line number or -1 */
1017 static void msgwin_parse_generic_line(const gchar *string, gchar **filename, gint *line)
1019 gchar **fields;
1020 gboolean incertain = TRUE; /* whether we're reasonably certain of the result */
1022 *filename = NULL;
1023 *line = -1;
1025 fields = g_strsplit(string, ":", 2);
1026 /* extract the filename */
1027 if (fields[0] != NULL)
1029 *filename = g_strdup(fields[0]);
1030 if (msgwindow.messages_dir != NULL)
1031 make_absolute(filename, msgwindow.messages_dir);
1033 /* now the line */
1034 if (fields[1] != NULL)
1036 gchar *end;
1038 *line = strtol(fields[1], &end, 10);
1039 if (end == fields[1])
1040 *line = -1;
1041 else if (*end == ':' || g_ascii_isspace(*end))
1042 { /* if we have a blank or a separator right after the number, assume we really got a
1043 * filename (it's a grep-like syntax) */
1044 incertain = FALSE;
1048 /* if we aren't sure we got a supposedly correct filename, check it */
1049 if (incertain && ! g_file_test(*filename, G_FILE_TEST_EXISTS))
1051 SETPTR(*filename, NULL);
1052 *line = -1;
1055 g_strfreev(fields);
1059 gboolean msgwin_goto_messages_file_line(gboolean focus_editor)
1061 GtkTreeIter iter;
1062 GtkTreeModel *model;
1063 GtkTreeSelection *selection;
1064 gboolean ret = FALSE;
1066 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_msg));
1067 if (gtk_tree_selection_get_selected(selection, &model, &iter))
1069 gint line;
1070 gchar *string;
1071 GeanyDocument *doc;
1072 GeanyDocument *old_doc = document_get_current();
1074 gtk_tree_model_get(model, &iter, 0, &line, 1, &doc, 3, &string, -1);
1075 /* doc may have been closed, so check doc->index: */
1076 if (line >= 0 && DOC_VALID(doc))
1078 ret = navqueue_goto_line(old_doc, doc, line);
1079 if (ret && focus_editor)
1080 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1082 else if (line < 0 && string != NULL)
1084 gchar *filename;
1086 /* try with a file:line parsing */
1087 msgwin_parse_generic_line(string, &filename, &line);
1088 if (filename != NULL)
1090 /* use document_open_file to find an already open file, or open it in place */
1091 doc = document_open_file(filename, FALSE, NULL, NULL);
1092 if (doc != NULL)
1094 ret = (line < 0) ? TRUE : navqueue_goto_line(old_doc, doc, line);
1095 if (ret && focus_editor)
1096 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1099 g_free(filename);
1101 g_free(string);
1103 return ret;
1107 static gboolean on_msgwin_button_press_event(GtkWidget *widget, GdkEventButton *event,
1108 gpointer user_data)
1110 /* user_data might be NULL, GPOINTER_TO_INT returns 0 if called with NULL */
1111 gboolean double_click = event->type == GDK_2BUTTON_PRESS;
1113 if (event->button == 1 && (event->type == GDK_BUTTON_RELEASE || double_click))
1115 switch (GPOINTER_TO_INT(user_data))
1117 case MSG_COMPILER:
1118 { /* mouse click in the compiler treeview */
1119 msgwin_goto_compiler_file_line(double_click);
1120 break;
1122 case MSG_MESSAGE:
1123 { /* mouse click in the message treeview (results of 'Find usage') */
1124 msgwin_goto_messages_file_line(double_click);
1125 break;
1128 return double_click; /* TRUE prevents message window re-focusing */
1131 if (event->button == 3)
1132 { /* popupmenu to hide or clear the active treeview */
1133 switch (GPOINTER_TO_INT(user_data))
1135 case MSG_STATUS:
1137 gtk_menu_popup(GTK_MENU(msgwindow.popup_status_menu), NULL, NULL, NULL, NULL,
1138 event->button, event->time);
1139 break;
1141 case MSG_MESSAGE:
1143 gtk_menu_popup(GTK_MENU(msgwindow.popup_msg_menu), NULL, NULL, NULL, NULL,
1144 event->button, event->time);
1145 break;
1147 case MSG_COMPILER:
1149 gtk_menu_popup(GTK_MENU(msgwindow.popup_compiler_menu), NULL, NULL, NULL, NULL,
1150 event->button, event->time);
1151 break;
1155 return FALSE;
1160 * Switches to the given notebook tab of the messages window and shows the messages window
1161 * if it was previously hidden and @a show is set to @c TRUE.
1163 * @param tabnum An index of a tab in the messages window. Valid values are all elements of
1164 * #MessageWindowTabNum.
1165 * @param show Whether to show the messages window at all if it was hidden before.
1167 * @since 0.15
1169 void msgwin_switch_tab(gint tabnum, gboolean show)
1171 GtkWidget *widget = NULL; /* widget to focus */
1173 switch (tabnum)
1175 case MSG_SCRATCH: widget = msgwindow.scribble; break;
1176 case MSG_COMPILER: widget = msgwindow.tree_compiler; break;
1177 case MSG_STATUS: widget = msgwindow.tree_status; break;
1178 case MSG_MESSAGE: widget = msgwindow.tree_msg; break;
1179 #ifdef HAVE_VTE
1180 case MSG_VTE: widget = (vte_info.have_vte) ? vc->vte : NULL; break;
1181 #endif
1182 default: break;
1185 /* the msgwin must be visible before we switch to the VTE page so that
1186 * the font settings are applied on realization */
1187 if (show)
1188 msgwin_show_hide(TRUE);
1189 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), tabnum);
1190 if (show && widget)
1191 gtk_widget_grab_focus(widget);
1196 * Removes all messages from a tab specified by @a tabnum in the messages window.
1198 * @param tabnum An index of a tab in the messages window which should be cleared.
1199 * Valid values are @c MSG_STATUS, @c MSG_COMPILER and @c MSG_MESSAGE.
1201 * @since 0.15
1203 void msgwin_clear_tab(gint tabnum)
1205 GtkListStore *store = NULL;
1207 switch (tabnum)
1209 case MSG_MESSAGE:
1210 store = msgwindow.store_msg;
1211 break;
1213 case MSG_COMPILER:
1214 gtk_list_store_clear(msgwindow.store_compiler);
1215 build_menu_update(NULL); /* update next error items */
1216 return;
1218 case MSG_STATUS: store = msgwindow.store_status; break;
1219 default: return;
1221 if (store == NULL)
1222 return;
1223 gtk_list_store_clear(store);