Version bump.
[geany-mirror.git] / src / msgwindow.c
blob302c82a904249ecc5159a5280ee98b80b4037ec9
1 /*
2 * msgwindow.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2010 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2010 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
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 * $Id$
25 * Message window functions (status, compiler, messages windows).
26 * Also compiler error message parsing and grep file and line parsing.
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 void msgwin_parse_grep_line(const gchar *string, gchar **filename, gint *line);
73 static gboolean on_msgwin_button_press_event(GtkWidget *widget, GdkEventButton *event,
74 gpointer user_data);
75 static void on_scribble_populate(GtkTextView *textview, GtkMenu *arg1, gpointer user_data);
78 void msgwin_show_hide_tabs(void)
80 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_status), interface_prefs.msgwin_status_visible);
81 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_compiler), interface_prefs.msgwin_compiler_visible);
82 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_msg), interface_prefs.msgwin_messages_visible);
83 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.scribble), interface_prefs.msgwin_scribble_visible);
87 void msgwin_init(void)
89 msgwindow.notebook = ui_lookup_widget(main_widgets.window, "notebook_info");
90 msgwindow.tree_status = ui_lookup_widget(main_widgets.window, "treeview3");
91 msgwindow.tree_msg = ui_lookup_widget(main_widgets.window, "treeview4");
92 msgwindow.tree_compiler = ui_lookup_widget(main_widgets.window, "treeview5");
93 msgwindow.scribble = ui_lookup_widget(main_widgets.window, "textview_scribble");
94 msgwindow.find_in_files_dir = NULL;
96 prepare_status_tree_view();
97 prepare_msg_tree_view();
98 prepare_compiler_tree_view();
99 msgwindow.popup_status_menu = create_message_popup_menu(MSG_STATUS);
100 msgwindow.popup_msg_menu = create_message_popup_menu(MSG_MESSAGE);
101 msgwindow.popup_compiler_menu = create_message_popup_menu(MSG_COMPILER);
103 ui_widget_modify_font_from_string(msgwindow.scribble, interface_prefs.msgwin_font);
104 g_signal_connect(msgwindow.scribble, "populate-popup", G_CALLBACK(on_scribble_populate), NULL);
108 void msgwin_finalize(void)
110 g_free(msgwindow.find_in_files_dir);
114 static gboolean on_msgwin_key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
116 if (ui_is_keyval_enter_or_return(event->keyval) || event->keyval == GDK_space)
118 switch (GPOINTER_TO_INT(data))
120 case MSG_COMPILER:
121 { /* single click in the compiler treeview */
122 msgwin_goto_compiler_file_line(event->keyval);
123 break;
125 case MSG_MESSAGE:
126 { /* single click in the message treeview (results of 'Find usage') */
127 msgwin_goto_messages_file_line(event->keyval);
128 break;
132 return FALSE;
136 /* does some preparing things to the status message list widget */
137 static void prepare_status_tree_view(void)
139 GtkCellRenderer *renderer;
140 GtkTreeViewColumn *column;
142 msgwindow.store_status = gtk_list_store_new(1, G_TYPE_STRING);
143 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_status), GTK_TREE_MODEL(msgwindow.store_status));
144 g_object_unref(msgwindow.store_status);
146 renderer = gtk_cell_renderer_text_new();
147 column = gtk_tree_view_column_new_with_attributes(_("Status messages"), renderer, "text", 0, NULL);
148 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_status), column);
150 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_status), FALSE);
152 ui_widget_modify_font_from_string(msgwindow.tree_status, interface_prefs.msgwin_font);
154 g_signal_connect(msgwindow.tree_status, "button-press-event",
155 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_STATUS));
159 /* does some preparing things to the message list widget
160 * (currently used for showing results of 'Find usage') */
161 static void prepare_msg_tree_view(void)
163 GtkCellRenderer *renderer;
164 GtkTreeViewColumn *column;
165 GtkTreeSelection *selection;
167 /* line, doc, fg, str */
168 msgwindow.store_msg = gtk_list_store_new(4, G_TYPE_INT, G_TYPE_POINTER,
169 GDK_TYPE_COLOR, G_TYPE_STRING);
170 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_msg), GTK_TREE_MODEL(msgwindow.store_msg));
171 g_object_unref(msgwindow.store_msg);
173 renderer = gtk_cell_renderer_text_new();
174 column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
175 "foreground-gdk", 2, "text", 3, NULL);
176 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_msg), column);
178 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_msg), FALSE);
180 ui_widget_modify_font_from_string(msgwindow.tree_msg, interface_prefs.msgwin_font);
182 /* use button-release-event so the selection has changed
183 * (connect_after button-press-event doesn't work) */
184 g_signal_connect(msgwindow.tree_msg, "button-release-event",
185 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_MESSAGE));
186 g_signal_connect(msgwindow.tree_msg, "key-press-event",
187 G_CALLBACK(on_msgwin_key_press_event), GINT_TO_POINTER(MSG_MESSAGE));
189 /* selection handling */
190 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_msg));
191 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
192 /*g_signal_connect(selection, "changed",G_CALLBACK(on_msg_tree_selection_changed), NULL);*/
196 /* does some preparing things to the compiler list widget */
197 static void prepare_compiler_tree_view(void)
199 GtkCellRenderer *renderer;
200 GtkTreeViewColumn *column;
201 GtkTreeSelection *selection;
203 msgwindow.store_compiler = gtk_list_store_new(2, GDK_TYPE_COLOR, G_TYPE_STRING);
204 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_compiler), GTK_TREE_MODEL(msgwindow.store_compiler));
205 g_object_unref(msgwindow.store_compiler);
207 renderer = gtk_cell_renderer_text_new();
208 column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "foreground-gdk", 0, "text", 1, NULL);
209 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_compiler), column);
211 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_compiler), FALSE);
213 ui_widget_modify_font_from_string(msgwindow.tree_compiler, interface_prefs.msgwin_font);
215 /* use button-release-event so the selection has changed
216 * (connect_after button-press-event doesn't work) */
217 g_signal_connect(msgwindow.tree_compiler, "button-release-event",
218 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_COMPILER));
219 g_signal_connect(msgwindow.tree_compiler, "key-press-event",
220 G_CALLBACK(on_msgwin_key_press_event), GINT_TO_POINTER(MSG_COMPILER));
222 /* selection handling */
223 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_compiler));
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 static const GdkColor color_error = {0, 65535, 0, 0};
231 static const GdkColor *get_color(gint msg_color)
233 static const GdkColor dark_red = {0, 65535 / 2, 0, 0};
234 static const GdkColor blue = {0, 0, 0, 0xD000}; /* not too bright ;-) */
236 switch (msg_color)
238 case COLOR_RED: return &color_error;
239 case COLOR_DARK_RED: return &dark_red;
240 case COLOR_BLUE: return &blue;
241 default: return NULL;
247 * Adds a new message in the compiler tab treeview in the messages window.
249 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
250 * @param format @c printf()-style format string.
251 * @param ... Arguments for the @c format string.
253 void msgwin_compiler_add(gint msg_color, const gchar *format, ...)
255 gchar *string;
256 va_list args;
258 va_start(args, format);
259 string = g_strdup_vprintf(format, args);
260 va_end(args);
261 msgwin_compiler_add_string(msg_color, string);
262 g_free(string);
266 void msgwin_compiler_add_string(gint msg_color, const gchar *msg)
268 GtkTreeIter iter;
269 GtkTreePath *path;
270 const GdkColor *color = get_color(msg_color);
271 gchar *utf8_msg;
273 if (! g_utf8_validate(msg, -1, NULL))
274 utf8_msg = utils_get_utf8_from_locale(msg);
275 else
276 utf8_msg = (gchar *) msg;
278 gtk_list_store_append(msgwindow.store_compiler, &iter);
279 gtk_list_store_set(msgwindow.store_compiler, &iter, 0, color, 1, utf8_msg, -1);
281 if (ui_prefs.msgwindow_visible && interface_prefs.compiler_tab_autoscroll)
283 path = gtk_tree_model_get_path(
284 gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_compiler)), &iter);
285 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(msgwindow.tree_compiler), path, NULL, TRUE, 0.5, 0.5);
286 gtk_tree_path_free(path);
289 /* calling build_menu_update for every build message would be overkill, TODO really should call it once when all done */
290 gtk_widget_set_sensitive(build_get_menu_items(-1)->menu_item[GBG_FIXED][GBF_NEXT_ERROR], TRUE);
291 gtk_widget_set_sensitive(build_get_menu_items(-1)->menu_item[GBG_FIXED][GBF_PREV_ERROR], TRUE);
293 if (utf8_msg != msg)
294 g_free(utf8_msg);
298 void msgwin_show_hide(gboolean show)
300 ui_prefs.msgwindow_visible = show;
301 ignore_callback = TRUE;
302 gtk_check_menu_item_set_active(
303 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_show_messages_window1")),
304 show);
305 ignore_callback = FALSE;
306 ui_widget_show_hide(ui_lookup_widget(main_widgets.window, "scrolledwindow1"), show);
307 /* set the input focus back to the editor */
308 keybindings_send_command(GEANY_KEY_GROUP_FOCUS, GEANY_KEYS_FOCUS_EDITOR);
313 * Adds a new message in the messages tab treeview in the messages window.
314 * If @a line and @a doc are set, clicking on this line jumps into the file which is specified
315 * by @a doc into the line specified with @a line.
317 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
318 * @param line The document's line where the message belongs to. Set to @c -1 to ignore.
319 * @param doc The document. Set to @c NULL to ignore.
320 * @param format @c printf()-style format string.
321 * @param ... Arguments for the @c format string.
323 * @since 0.15
325 void msgwin_msg_add(gint msg_color, gint line, GeanyDocument *doc, const gchar *format, ...)
327 gchar *string;
328 va_list args;
330 va_start(args, format);
331 string = g_strdup_vprintf(format, args);
332 va_end(args);
334 msgwin_msg_add_string(msg_color, line, doc, string);
335 g_free(string);
339 /* adds string to the msg treeview */
340 void msgwin_msg_add_string(gint msg_color, gint line, GeanyDocument *doc, const gchar *string)
342 GtkTreeIter iter;
343 const GdkColor *color = get_color(msg_color);
344 gchar *tmp;
345 gsize len;
346 gchar *utf8_msg;
348 if (! ui_prefs.msgwindow_visible)
349 msgwin_show_hide(TRUE);
351 /* work around a strange problem when adding very long lines(greater than 4000 bytes)
352 * cut the string to a maximum of 1024 bytes and discard the rest */
353 /* TODO: find the real cause for the display problem / if it is GtkTreeView file a bug report */
354 len = strlen(string);
355 if (len > 1024)
356 tmp = g_strndup(string, 1024);
357 else
358 tmp = g_strdup(string);
360 if (! g_utf8_validate(tmp, -1, NULL))
361 utf8_msg = utils_get_utf8_from_locale(tmp);
362 else
363 utf8_msg = tmp;
365 gtk_list_store_append(msgwindow.store_msg, &iter);
366 gtk_list_store_set(msgwindow.store_msg, &iter, 0, line, 1, doc, 2, color, 3, utf8_msg, -1);
368 g_free(tmp);
369 if (utf8_msg != tmp)
370 g_free(utf8_msg);
375 * Logs a status message *without* setting the status bar.
376 * (Use ui_set_statusbar() to display text on the statusbar)
378 * @param format @c printf()-style format string.
379 * @param ... Arguments for the @c format string.
381 void msgwin_status_add(const gchar *format, ...)
383 GtkTreeIter iter;
384 gchar *string;
385 gchar *statusmsg, *time_str;
386 va_list args;
388 va_start(args, format);
389 string = g_strdup_vprintf(format, args);
390 va_end(args);
392 /* add a timestamp to status messages */
393 time_str = utils_get_current_time_string();
394 statusmsg = g_strconcat(time_str, ": ", string, NULL);
395 g_free(time_str);
396 g_free(string);
398 /* add message to Status window */
399 gtk_list_store_append(msgwindow.store_status, &iter);
400 gtk_list_store_set(msgwindow.store_status, &iter, 0, statusmsg, -1);
401 g_free(statusmsg);
403 if (G_LIKELY(main_status.main_window_realized))
405 GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_status)), &iter);
407 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(msgwindow.tree_status), path, NULL, FALSE, 0.0, 0.0);
408 if (prefs.switch_to_status)
409 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_STATUS);
410 gtk_tree_path_free(path);
415 static void
416 on_message_treeview_clear_activate (GtkMenuItem *menuitem,
417 gpointer user_data)
419 gint tabnum = GPOINTER_TO_INT(user_data);
421 msgwin_clear_tab(tabnum);
425 static void
426 on_compiler_treeview_copy_activate (GtkMenuItem *menuitem,
427 gpointer user_data)
429 GtkWidget *tv = NULL;
430 GtkTreeSelection *selection;
431 GtkTreeModel *model;
432 GtkTreeIter iter;
433 gint str_idx = 1;
435 switch (GPOINTER_TO_INT(user_data))
437 case MSG_STATUS:
438 tv = msgwindow.tree_status;
439 str_idx = 0;
440 break;
442 case MSG_COMPILER:
443 tv = msgwindow.tree_compiler;
444 break;
446 case MSG_MESSAGE:
447 tv = msgwindow.tree_msg;
448 str_idx = 3;
449 break;
451 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
453 if (gtk_tree_selection_get_selected(selection, &model, &iter))
455 gchar *string;
457 gtk_tree_model_get(model, &iter, str_idx, &string, -1);
458 if (NZV(string))
460 gtk_clipboard_set_text(gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
461 string, -1);
463 g_free(string);
468 static void on_compiler_treeview_copy_all_activate(GtkMenuItem *menuitem, gpointer user_data)
470 GtkListStore *store = msgwindow.store_compiler;
471 GtkTreeIter iter;
472 GString *str = g_string_new("");
473 gint str_idx = 1;
474 gboolean valid;
476 switch (GPOINTER_TO_INT(user_data))
478 case MSG_STATUS:
479 store = msgwindow.store_status;
480 str_idx = 0;
481 break;
483 case MSG_COMPILER:
484 /* default values */
485 break;
487 case MSG_MESSAGE:
488 store = msgwindow.store_msg;
489 str_idx = 3;
490 break;
493 /* walk through the list and copy every line into a string */
494 valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
495 while (valid)
497 gchar *line;
499 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, str_idx, &line, -1);
500 if (NZV(line))
502 g_string_append(str, line);
503 g_string_append_c(str, '\n');
505 g_free(line);
507 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
510 /* copy the string into the clipboard */
511 if (str->len > 0)
513 gtk_clipboard_set_text(
514 gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
515 str->str,
516 str->len);
518 g_string_free(str, TRUE);
522 static void
523 on_hide_message_window (GtkMenuItem *menuitem,
524 gpointer user_data)
526 msgwin_show_hide(FALSE);
530 static GtkWidget *create_message_popup_menu(gint type)
532 GtkWidget *message_popup_menu, *clear, *copy, *copy_all, *image;
534 message_popup_menu = gtk_menu_new();
536 clear = gtk_image_menu_item_new_from_stock("gtk-clear", NULL);
537 gtk_widget_show(clear);
538 gtk_container_add(GTK_CONTAINER(message_popup_menu), clear);
539 g_signal_connect(clear, "activate",
540 G_CALLBACK(on_message_treeview_clear_activate), GINT_TO_POINTER(type));
542 copy = gtk_image_menu_item_new_with_mnemonic(_("C_opy"));
543 gtk_widget_show(copy);
544 gtk_container_add(GTK_CONTAINER(message_popup_menu), copy);
545 image = gtk_image_new_from_stock("gtk-copy", GTK_ICON_SIZE_MENU);
546 gtk_widget_show(image);
547 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(copy), image);
548 g_signal_connect(copy, "activate",
549 G_CALLBACK(on_compiler_treeview_copy_activate), GINT_TO_POINTER(type));
551 copy_all = gtk_image_menu_item_new_with_mnemonic(_("Copy _All"));
552 gtk_widget_show(copy_all);
553 gtk_container_add(GTK_CONTAINER(message_popup_menu), copy_all);
554 image = gtk_image_new_from_stock("gtk-copy", GTK_ICON_SIZE_MENU);
555 gtk_widget_show(image);
556 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(copy_all), image);
557 g_signal_connect(copy_all, "activate",
558 G_CALLBACK(on_compiler_treeview_copy_all_activate), GINT_TO_POINTER(type));
560 msgwin_menu_add_common_items(GTK_MENU(message_popup_menu));
562 return message_popup_menu;
566 static void on_scribble_populate(GtkTextView *textview, GtkMenu *arg1, gpointer user_data)
568 msgwin_menu_add_common_items(arg1);
572 /* Menu items that should be on all message window popup menus */
573 void msgwin_menu_add_common_items(GtkMenu *menu)
575 GtkWidget *item;
577 item = gtk_separator_menu_item_new();
578 gtk_widget_show(item);
579 gtk_container_add(GTK_CONTAINER(menu), item);
581 item = gtk_menu_item_new_with_mnemonic(_("_Hide Message Window"));
582 gtk_widget_show(item);
583 gtk_container_add(GTK_CONTAINER(menu), item);
584 g_signal_connect(item, "activate", G_CALLBACK(on_hide_message_window), NULL);
588 /* look back up from the current path and find the directory we came from */
589 static gboolean
590 find_prev_build_dir(GtkTreePath *cur, GtkTreeModel *model, gchar **prefix)
592 GtkTreeIter iter;
593 *prefix = NULL;
595 while (gtk_tree_path_prev(cur))
597 if (gtk_tree_model_get_iter(model, &iter, cur))
599 gchar *string;
600 gtk_tree_model_get(model, &iter, 1, &string, -1);
601 if (string != NULL && build_parse_make_dir(string, prefix))
603 g_free(string);
604 return TRUE;
606 g_free(string);
610 return FALSE;
614 static gboolean goto_compiler_file_line(const gchar *filename, gint line, guint keyval)
616 if (!filename || line <= -1)
617 return FALSE;
619 /* If the path doesn't exist, try the current document.
620 * This happens when we receive build messages in the wrong order - after the
621 * 'Leaving directory' messages */
622 if (!g_file_test(filename, G_FILE_TEST_EXISTS))
624 gchar *cur_dir = utils_get_current_file_dir_utf8();
625 gchar *name;
627 if (cur_dir)
629 /* we let the user know we couldn't find the parsed filename from the message window */
630 setptr(cur_dir, utils_get_locale_from_utf8(cur_dir));
631 name = g_path_get_basename(filename);
632 setptr(name, g_build_path(G_DIR_SEPARATOR_S, cur_dir, name, NULL));
633 g_free(cur_dir);
635 if (g_file_test(name, G_FILE_TEST_EXISTS))
637 ui_set_statusbar(FALSE, _("Could not find file '%s' - trying the current document path."),
638 filename);
639 filename = name;
641 else
642 g_free(name);
647 gchar *utf8_filename = utils_get_utf8_from_locale(filename);
648 GeanyDocument *doc = document_find_by_filename(utf8_filename);
649 GeanyDocument *old_doc = document_get_current();
651 g_free(utf8_filename);
653 if (doc == NULL) /* file not already open */
654 doc = document_open_file(filename, FALSE, NULL, NULL);
656 if (doc != NULL)
658 gboolean ret;
660 if (! doc->changed && editor_prefs.use_indicators) /* if modified, line may be wrong */
661 editor_indicator_set_on_line(doc->editor, GEANY_INDICATOR_ERROR, line - 1);
663 ret = navqueue_goto_line(old_doc, doc, line);
664 if (ret && ui_is_keyval_enter_or_return(keyval))
665 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
667 return ret;
670 return FALSE;
674 gboolean msgwin_goto_compiler_file_line(guint keyval)
676 GtkTreeIter iter;
677 GtkTreeModel *model;
678 GtkTreeSelection *selection;
679 gchar *string;
680 GdkColor *color;
682 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_compiler));
683 if (gtk_tree_selection_get_selected(selection, &model, &iter))
685 /* if the item is not coloured red, it's not an error line */
686 gtk_tree_model_get(model, &iter, 0, &color, -1);
687 if (color == NULL || ! gdk_color_equal(color, &color_error))
689 if (color != NULL)
690 gdk_color_free(color);
691 return FALSE;
693 gdk_color_free(color);
695 gtk_tree_model_get(model, &iter, 1, &string, -1);
696 if (string != NULL)
698 gint line;
699 gchar *filename, *dir;
700 GtkTreePath *path;
701 gboolean ret;
703 path = gtk_tree_model_get_path(model, &iter);
704 find_prev_build_dir(path, model, &dir);
705 gtk_tree_path_free(path);
706 msgwin_parse_compiler_error_line(string, dir, &filename, &line);
707 g_free(string);
708 g_free(dir);
710 ret = goto_compiler_file_line(filename, line, keyval);
711 g_free(filename);
712 return ret;
715 return FALSE;
719 static void make_absolute(gchar **filename, const gchar *dir)
721 guint skip_dot_slash = 0; /* number of characters to skip at the beginning of the filename */
723 if (*filename == NULL)
724 return;
726 /* skip some characters at the beginning of the filename, at the moment only "./"
727 * can be extended if other "trash" is known */
728 if (strncmp(*filename, "./", 2) == 0)
729 skip_dot_slash = 2;
731 /* add directory */
732 if (! utils_is_absolute_path(*filename))
733 setptr(*filename, g_strconcat(dir, G_DIR_SEPARATOR_S,
734 *filename + skip_dot_slash, NULL));
738 /* try to parse the file and line number where the error occured described in line
739 * and when something useful is found, it stores the line number in *line and the
740 * relevant file with the error in *filename.
741 * *line will be -1 if no error was found in string.
742 * *filename must be freed unless it is NULL. */
743 static void parse_file_line(ParseData *data, gchar **filename, gint *line)
745 gchar *end = NULL;
746 gchar **fields;
748 *filename = NULL;
749 *line = -1;
751 g_return_if_fail(data->string != NULL);
753 fields = g_strsplit_set(data->string, data->pattern, data->min_fields);
755 /* parse the line */
756 if (g_strv_length(fields) < data->min_fields)
758 g_strfreev(fields);
759 return;
762 *line = strtol(fields[data->line_idx], &end, 10);
764 /* if the line could not be read, line is 0 and an error occurred, so we leave */
765 if (fields[data->line_idx] == end)
767 g_strfreev(fields);
768 return;
771 /* let's stop here if there is no filename in the error message */
772 if (data->file_idx == -1)
774 /* we have no filename in the error message, so take the current one and hope it's correct */
775 GeanyDocument *doc = document_get_current();
776 if (doc != NULL)
777 *filename = g_strdup(doc->file_name);
778 g_strfreev(fields);
779 return;
782 *filename = g_strdup(fields[data->file_idx]);
783 g_strfreev(fields);
787 static void parse_compiler_error_line(const gchar *string,
788 gchar **filename, gint *line)
790 ParseData data = {NULL, NULL, 0, 0, 0};
792 data.string = string;
794 switch (build_info.file_type_id)
796 case GEANY_FILETYPES_PHP:
798 /* Parse error: parse error, unexpected T_CASE in brace_bug.php on line 3
799 * Parse error: syntax error, unexpected T_LNUMBER, expecting T_FUNCTION in bob.php on line 16 */
800 gchar *tmp = strstr(string, " in ");
802 if (tmp != NULL)
804 data.string = tmp;
805 data.pattern = " ";
806 data.min_fields = 6;
807 data.line_idx = 5;
808 data.file_idx = 2;
810 else
812 data.pattern = " ";
813 data.min_fields = 11;
814 data.line_idx = 10;
815 data.file_idx = 7;
817 break;
819 case GEANY_FILETYPES_PERL:
821 /* syntax error at test.pl line 7, near "{ */
822 data.pattern = " ";
823 data.min_fields = 6;
824 data.line_idx = 5;
825 data.file_idx = 3;
826 break;
828 /* the error output of python and tcl equals */
829 case GEANY_FILETYPES_TCL:
830 case GEANY_FILETYPES_PYTHON:
832 /* File "HyperArch.py", line 37, in ?
833 * (file "clrdial.tcl" line 12) */
834 data.pattern = " \"";
835 data.min_fields = 6;
836 data.line_idx = 5;
837 data.file_idx = 2;
838 break;
840 case GEANY_FILETYPES_BASIC:
841 case GEANY_FILETYPES_PASCAL:
842 case GEANY_FILETYPES_CS:
844 /* getdrive.bas(52) error 18: Syntax error in '? GetAllDrives'
845 * bandit.pas(149,3) Fatal: Syntax error, ";" expected but "ELSE" found */
846 data.pattern = "(";
847 data.min_fields = 2;
848 data.line_idx = 1;
849 data.file_idx = 0;
850 break;
852 case GEANY_FILETYPES_D:
854 /* GNU D compiler front-end, gdc
855 * gantry.d:18: variable gantry.main.c reference to auto class must be auto
856 * warning - gantry.d:20: statement is not reachable
857 * Digital Mars dmd compiler
858 * warning - pi.d(118): implicit conversion of expression (digit) of type int ...
859 * gantry.d(18): variable gantry.main.c reference to auto class must be auto */
860 if (strncmp(string, "warning - ", 10) == 0)
862 data.pattern = " (:";
863 data.min_fields = 4;
864 data.line_idx = 3;
865 data.file_idx = 2;
867 else
869 data.pattern = "(:";
870 data.min_fields = 2;
871 data.line_idx = 1;
872 data.file_idx = 0;
874 break;
876 case GEANY_FILETYPES_FERITE:
878 /* Error: Parse Error: on line 5 in "/tmp/hello.fe"
879 * Error: Compile Error: on line 24, in /test/class.fe */
880 if (strncmp(string, "Error: Compile Error", 20) == 0)
882 data.pattern = " ";
883 data.min_fields = 8;
884 data.line_idx = 5;
885 data.file_idx = 7;
887 else
889 data.pattern = " \"";
890 data.min_fields = 10;
891 data.line_idx = 5;
892 data.file_idx = 8;
894 break;
896 case GEANY_FILETYPES_HTML:
898 /* line 78 column 7 - Warning: <table> missing '>' for end of tag */
899 data.pattern = " ";
900 data.min_fields = 4;
901 data.line_idx = 1;
902 data.file_idx = -1;
903 break;
905 /* All GNU gcc-like error messages */
906 case GEANY_FILETYPES_C:
907 case GEANY_FILETYPES_CPP:
908 case GEANY_FILETYPES_RUBY:
909 case GEANY_FILETYPES_JAVA:
910 /* only gcc is supported, I don't know any other C(++) compilers and their error messages
911 * empty.h:4: Warnung: type defaults to `int' in declaration of `foo'
912 * empty.c:21: error: conflicting types for `foo'
913 * Only parse file and line, so that linker errors will also work (with -g) */
914 case GEANY_FILETYPES_F77:
915 case GEANY_FILETYPES_FORTRAN:
916 case GEANY_FILETYPES_LATEX:
917 /* ./kommtechnik_2b.tex:18: Emergency stop. */
918 case GEANY_FILETYPES_MAKE: /* Assume makefile is building with gcc */
919 case GEANY_FILETYPES_NONE:
920 default: /* The default is a GNU gcc type error */
922 if (build_info.file_type_id == GEANY_FILETYPES_JAVA &&
923 strncmp(string, "[javac]", 7) == 0)
925 /* Java Apache Ant.
926 * [javac] <Full Path to File + extension>:<line n°>: <error> */
927 data.pattern = " :";
928 data.min_fields = 4;
929 data.line_idx = 2;
930 data.file_idx = 1;
931 break;
933 /* don't accidently find libtool versions x:y:x and think it is a file name */
934 if (strstr(string, "libtool --mode=link") == NULL)
936 data.pattern = ":";
937 data.min_fields = 3;
938 data.line_idx = 1;
939 data.file_idx = 0;
940 break;
945 if (data.pattern != NULL)
946 parse_file_line(&data, filename, line);
950 /* try to parse the file and line number where the error occured described in string
951 * and when something useful is found, it stores the line number in *line and the
952 * relevant file with the error in *filename.
953 * *line will be -1 if no error was found in string.
954 * *filename must be freed unless it is NULL. */
955 void msgwin_parse_compiler_error_line(const gchar *string, const gchar *dir,
956 gchar **filename, gint *line)
958 GeanyFiletype *ft;
959 gchar *trimmed_string;
961 *filename = NULL;
962 *line = -1;
964 if (G_UNLIKELY(string == NULL))
965 return;
967 if (dir == NULL)
968 dir = build_info.dir;
969 g_return_if_fail(dir != NULL);
971 trimmed_string = g_strdup(string);
972 g_strchug(trimmed_string); /* remove possible leading whitespace */
974 ft = filetypes[build_info.file_type_id];
976 /* try parsing with a custom regex */
977 if (!filetypes_parse_error_message(ft, trimmed_string, filename, line))
979 /* fallback to default old-style parsing */
980 parse_compiler_error_line(trimmed_string, filename, line);
982 make_absolute(filename, dir);
983 g_free(trimmed_string);
987 gboolean msgwin_goto_messages_file_line(guint keyval)
989 GtkTreeIter iter;
990 GtkTreeModel *model;
991 GtkTreeSelection *selection;
992 gboolean ret = FALSE;
994 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_msg));
995 if (gtk_tree_selection_get_selected(selection, &model, &iter))
997 gint line;
998 gchar *string;
999 GeanyDocument *doc;
1000 GeanyDocument *old_doc = document_get_current();
1002 gtk_tree_model_get(model, &iter, 0, &line, 1, &doc, 3, &string, -1);
1003 /* doc may have been closed, so check doc->index: */
1004 if (line >= 0 && DOC_VALID(doc))
1006 ret = navqueue_goto_line(old_doc, doc, line);
1007 if (ret && ui_is_keyval_enter_or_return(keyval))
1008 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1010 else if (line < 0 && string != NULL)
1012 gchar *filename;
1013 msgwin_parse_grep_line(string, &filename, &line);
1014 if (filename != NULL && line > -1)
1016 /* use document_open_file to find an already open file, or open it in place */
1017 doc = document_open_file(filename, FALSE, NULL, NULL);
1018 if (doc != NULL)
1020 ret = navqueue_goto_line(old_doc, doc, line);
1021 if (ret && ui_is_keyval_enter_or_return(keyval))
1022 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1025 g_free(filename);
1027 g_free(string);
1029 return ret;
1033 /* Try to parse the file and line number for string and when something useful is
1034 * found, store the line number in *line and the relevant file with the error in
1035 * *filename.
1036 * *line will be -1 if no error was found in string.
1037 * *filename must be freed unless NULL. */
1038 static void msgwin_parse_grep_line(const gchar *string, gchar **filename, gint *line)
1040 ParseData data;
1042 *filename = NULL;
1043 *line = -1;
1045 if (string == NULL || msgwindow.find_in_files_dir == NULL)
1046 return;
1048 /* conflict:3:conflicting types for `foo' */
1049 data.string = string;
1050 data.pattern = ":";
1051 data.min_fields = 3;
1052 data.line_idx = 1;
1053 data.file_idx = 0;
1055 parse_file_line(&data, filename, line);
1056 make_absolute(filename, msgwindow.find_in_files_dir);
1060 static gboolean on_msgwin_button_press_event(GtkWidget *widget, GdkEventButton *event,
1061 gpointer user_data)
1063 /* user_data might be NULL, GPOINTER_TO_INT returns 0 if called with NULL */
1065 if (event->button == 1)
1067 switch (GPOINTER_TO_INT(user_data))
1069 case MSG_COMPILER:
1070 { /* single click in the compiler treeview */
1071 msgwin_goto_compiler_file_line(0);
1072 break;
1074 case MSG_MESSAGE:
1075 { /* single click in the message treeview (results of 'Find usage') */
1076 msgwin_goto_messages_file_line(0);
1077 break;
1082 if (event->button == 3)
1083 { /* popupmenu to hide or clear the active treeview */
1084 switch (GPOINTER_TO_INT(user_data))
1086 case MSG_STATUS:
1088 gtk_menu_popup(GTK_MENU(msgwindow.popup_status_menu), NULL, NULL, NULL, NULL,
1089 event->button, event->time);
1090 break;
1092 case MSG_MESSAGE:
1094 gtk_menu_popup(GTK_MENU(msgwindow.popup_msg_menu), NULL, NULL, NULL, NULL,
1095 event->button, event->time);
1096 break;
1098 case MSG_COMPILER:
1100 gtk_menu_popup(GTK_MENU(msgwindow.popup_compiler_menu), NULL, NULL, NULL, NULL,
1101 event->button, event->time);
1102 break;
1106 return FALSE;
1111 * Switches to the given notebook tab of the messages window and shows the messages window
1112 * if it was previously hidden and @a show is set to @c TRUE.
1114 * @param tabnum An index of a tab in the messages window. Valid values are all elements of
1115 * #MessageWindowTabNum.
1116 * @param show Whether to show the messages window at all if it was hidden before.
1118 * @since 0.15
1120 void msgwin_switch_tab(gint tabnum, gboolean show)
1122 GtkWidget *widget = NULL; /* widget to focus */
1124 switch (tabnum)
1126 case MSG_SCRATCH: widget = msgwindow.scribble; break;
1127 case MSG_COMPILER: widget = msgwindow.tree_compiler; break;
1128 case MSG_STATUS: widget = msgwindow.tree_status; break;
1129 case MSG_MESSAGE: widget = msgwindow.tree_msg; break;
1130 #ifdef HAVE_VTE
1131 case MSG_VTE: widget = (vte_info.have_vte) ? vc->vte : NULL; break;
1132 #endif
1133 default: break;
1136 /* the msgwin must be visible before we switch to the VTE page so that
1137 * the font settings are applied on realization */
1138 if (show)
1139 msgwin_show_hide(TRUE);
1140 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), tabnum);
1141 if (show && widget)
1142 gtk_widget_grab_focus(widget);
1147 * Removes all messages from a tab specified by @a tabnum in the messages window.
1149 * @param tabnum An index of a tab in the messages window which should be cleared.
1150 * Valid values are @c MSG_STATUS, @c MSG_COMPILER and @c MSG_MESSAGE.
1152 * @since 0.15
1154 void msgwin_clear_tab(gint tabnum)
1156 GtkListStore *store = NULL;
1158 switch (tabnum)
1160 case MSG_MESSAGE:
1161 store = msgwindow.store_msg;
1162 break;
1164 case MSG_COMPILER:
1165 gtk_list_store_clear(msgwindow.store_compiler);
1166 build_menu_update(NULL); /* update next error items */
1167 return;
1169 case MSG_STATUS: store = msgwindow.store_status; break;
1170 default: return;
1172 if (store == NULL)
1173 return;
1174 gtk_list_store_clear(store);