Fix escaping of infobar text against entity injection from filename
[geany-mirror.git] / src / msgwindow.c
blob338df3b6d0e062e11129262a6afc0e4365a75910
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 GdkColor color_error = {0, 0xFFFF, 0, 0};
87 static GdkColor color_context = {0, 0x7FFF, 0, 0};
88 static GdkColor color_message = {0, 0, 0, 0xD000};
91 static void prepare_msg_tree_view(void);
92 static void prepare_status_tree_view(void);
93 static void prepare_compiler_tree_view(void);
94 static GtkWidget *create_message_popup_menu(gint type);
95 static gboolean on_msgwin_button_press_event(GtkWidget *widget, GdkEventButton *event,
96 gpointer user_data);
97 static void on_scribble_populate(GtkTextView *textview, GtkMenu *arg1, gpointer user_data);
100 void msgwin_show_hide_tabs(void)
102 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_status), interface_prefs.msgwin_status_visible);
103 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_compiler), interface_prefs.msgwin_compiler_visible);
104 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.tree_msg), interface_prefs.msgwin_messages_visible);
105 ui_widget_show_hide(gtk_widget_get_parent(msgwindow.scribble), interface_prefs.msgwin_scribble_visible);
110 * Sets the Messages path for opening any parsed filenames without absolute path from message lines.
112 * @param messages_dir The directory.
114 GEANY_API_SYMBOL
115 void msgwin_set_messages_dir(const gchar *messages_dir)
117 g_free(msgwindow.messages_dir);
118 msgwindow.messages_dir = g_strdup(messages_dir);
122 static void load_color(const gchar *color_name, GdkColor *color)
124 #if GTK_CHECK_VERSION(3, 0, 0)
125 GdkRGBA rgba_color;
126 GtkWidgetPath *path = gtk_widget_path_new();
127 GtkStyleContext *ctx = gtk_style_context_new();
129 gtk_widget_path_append_type(path, GTK_TYPE_WINDOW);
130 gtk_widget_path_iter_set_name(path, -1, color_name);
131 gtk_style_context_set_screen(ctx, gdk_screen_get_default());
132 gtk_style_context_set_path(ctx, path);
133 gtk_style_context_get_color(ctx, gtk_style_context_get_state(ctx), &rgba_color);
135 color->red = 0xffff * rgba_color.red;
136 color->green = 0xffff * rgba_color.green;
137 color->blue = 0xffff * rgba_color.blue;
139 gtk_widget_path_unref(path);
140 g_object_unref(ctx);
141 #else
142 gchar *path = g_strconcat("*.", color_name, NULL);
144 GtkSettings *settings = gtk_settings_get_default();
145 GtkStyle *style = gtk_rc_get_style_by_paths(settings, path, NULL, GTK_TYPE_WIDGET);
146 *color = style->fg[GTK_STATE_NORMAL];
148 g_free(path);
149 #endif
153 void msgwin_init(void)
155 msgwindow.notebook = ui_lookup_widget(main_widgets.window, "notebook_info");
156 msgwindow.tree_status = ui_lookup_widget(main_widgets.window, "treeview3");
157 msgwindow.tree_msg = ui_lookup_widget(main_widgets.window, "treeview4");
158 msgwindow.tree_compiler = ui_lookup_widget(main_widgets.window, "treeview5");
159 msgwindow.scribble = ui_lookup_widget(main_widgets.window, "textview_scribble");
160 msgwindow.messages_dir = NULL;
162 prepare_status_tree_view();
163 prepare_msg_tree_view();
164 prepare_compiler_tree_view();
165 msgwindow.popup_status_menu = create_message_popup_menu(MSG_STATUS);
166 msgwindow.popup_msg_menu = create_message_popup_menu(MSG_MESSAGE);
167 msgwindow.popup_compiler_menu = create_message_popup_menu(MSG_COMPILER);
169 ui_widget_modify_font_from_string(msgwindow.scribble, interface_prefs.msgwin_font);
170 g_signal_connect(msgwindow.scribble, "populate-popup", G_CALLBACK(on_scribble_populate), NULL);
172 load_color("geany-compiler-error", &color_error);
173 load_color("geany-compiler-context", &color_context);
174 load_color("geany-compiler-message", &color_message);
178 void msgwin_finalize(void)
180 g_free(msgwindow.messages_dir);
184 static gboolean on_msgwin_key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
186 gboolean enter_or_return = ui_is_keyval_enter_or_return(event->keyval);
188 if (enter_or_return || event->keyval == GDK_space)
190 switch (GPOINTER_TO_INT(data))
192 case MSG_COMPILER:
193 { /* key press in the compiler treeview */
194 msgwin_goto_compiler_file_line(enter_or_return);
195 break;
197 case MSG_MESSAGE:
198 { /* key press in the message treeview (results of 'Find usage') */
199 msgwin_goto_messages_file_line(enter_or_return);
200 break;
204 return FALSE;
208 /* does some preparing things to the status message list widget */
209 static void prepare_status_tree_view(void)
211 GtkCellRenderer *renderer;
212 GtkTreeViewColumn *column;
214 msgwindow.store_status = gtk_list_store_new(1, G_TYPE_STRING);
215 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_status), GTK_TREE_MODEL(msgwindow.store_status));
216 g_object_unref(msgwindow.store_status);
218 renderer = gtk_cell_renderer_text_new();
219 column = gtk_tree_view_column_new_with_attributes(_("Status messages"), renderer, "text", 0, NULL);
220 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_status), column);
222 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_status), FALSE);
224 ui_widget_modify_font_from_string(msgwindow.tree_status, interface_prefs.msgwin_font);
226 g_signal_connect(msgwindow.tree_status, "button-press-event",
227 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_STATUS));
231 /* does some preparing things to the message list widget
232 * (currently used for showing results of 'Find usage') */
233 static void prepare_msg_tree_view(void)
235 GtkCellRenderer *renderer;
236 GtkTreeViewColumn *column;
237 GtkTreeSelection *selection;
239 /* line, doc id, fg, str */
240 msgwindow.store_msg = gtk_list_store_new(MSG_COL_COUNT, G_TYPE_INT, G_TYPE_UINT,
241 GDK_TYPE_COLOR, G_TYPE_STRING);
242 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_msg), GTK_TREE_MODEL(msgwindow.store_msg));
243 g_object_unref(msgwindow.store_msg);
245 renderer = gtk_cell_renderer_text_new();
246 column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
247 "foreground-gdk", MSG_COL_COLOR, "text", MSG_COL_STRING, NULL);
248 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_msg), column);
250 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_msg), FALSE);
252 ui_widget_modify_font_from_string(msgwindow.tree_msg, interface_prefs.msgwin_font);
254 /* use button-release-event so the selection has changed
255 * (connect_after button-press-event doesn't work) */
256 g_signal_connect(msgwindow.tree_msg, "button-release-event",
257 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_MESSAGE));
258 /* for double-clicking only, after the first release */
259 g_signal_connect(msgwindow.tree_msg, "button-press-event",
260 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_MESSAGE));
261 g_signal_connect(msgwindow.tree_msg, "key-press-event",
262 G_CALLBACK(on_msgwin_key_press_event), GINT_TO_POINTER(MSG_MESSAGE));
264 /* selection handling */
265 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_msg));
266 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
267 /*g_signal_connect(selection, "changed",G_CALLBACK(on_msg_tree_selection_changed), NULL);*/
271 /* does some preparing things to the compiler list widget */
272 static void prepare_compiler_tree_view(void)
274 GtkCellRenderer *renderer;
275 GtkTreeViewColumn *column;
276 GtkTreeSelection *selection;
278 msgwindow.store_compiler = gtk_list_store_new(COMPILER_COL_COUNT, GDK_TYPE_COLOR, G_TYPE_STRING);
279 gtk_tree_view_set_model(GTK_TREE_VIEW(msgwindow.tree_compiler), GTK_TREE_MODEL(msgwindow.store_compiler));
280 g_object_unref(msgwindow.store_compiler);
282 renderer = gtk_cell_renderer_text_new();
283 column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
284 "foreground-gdk", COMPILER_COL_COLOR, "text", COMPILER_COL_STRING, NULL);
285 gtk_tree_view_append_column(GTK_TREE_VIEW(msgwindow.tree_compiler), column);
287 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(msgwindow.tree_compiler), FALSE);
289 ui_widget_modify_font_from_string(msgwindow.tree_compiler, interface_prefs.msgwin_font);
291 /* use button-release-event so the selection has changed
292 * (connect_after button-press-event doesn't work) */
293 g_signal_connect(msgwindow.tree_compiler, "button-release-event",
294 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_COMPILER));
295 /* for double-clicking only, after the first release */
296 g_signal_connect(msgwindow.tree_compiler, "button-press-event",
297 G_CALLBACK(on_msgwin_button_press_event), GINT_TO_POINTER(MSG_COMPILER));
298 g_signal_connect(msgwindow.tree_compiler, "key-press-event",
299 G_CALLBACK(on_msgwin_key_press_event), GINT_TO_POINTER(MSG_COMPILER));
301 /* selection handling */
302 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_compiler));
303 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
304 /*g_signal_connect(selection, "changed", G_CALLBACK(on_msg_tree_selection_changed), NULL);*/
307 static const GdkColor *get_color(gint msg_color)
309 switch (msg_color)
311 case COLOR_RED: return &color_error;
312 case COLOR_DARK_RED: return &color_context;
313 case COLOR_BLUE: return &color_message;
314 default: return NULL;
320 * Adds a formatted message in the compiler tab treeview in the messages window.
322 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
323 * @param format @c printf()-style format string.
324 * @param ... Arguments for the @c format string.
326 * @see msgwin_compiler_add_string()
328 * @since 0.16
330 GEANY_API_SYMBOL
331 void msgwin_compiler_add(gint msg_color, const gchar *format, ...)
333 gchar *string;
334 va_list args;
336 va_start(args, format);
337 string = g_strdup_vprintf(format, args);
338 va_end(args);
339 msgwin_compiler_add_string(msg_color, string);
340 g_free(string);
344 * Adds a new message in the compiler tab treeview in the messages window.
346 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
347 * @param msg Compiler message to be added.
349 * @see msgwin_compiler_add()
351 * @since 1.34 (API 236)
353 GEANY_API_SYMBOL
354 void msgwin_compiler_add_string(gint msg_color, const gchar *msg)
356 GtkTreeIter iter;
357 const GdkColor *color = get_color(msg_color);
358 gchar *utf8_msg;
360 if (! g_utf8_validate(msg, -1, NULL))
361 utf8_msg = utils_get_utf8_from_locale(msg);
362 else
363 utf8_msg = (gchar *) msg;
365 gtk_list_store_append(msgwindow.store_compiler, &iter);
366 gtk_list_store_set(msgwindow.store_compiler, &iter,
367 COMPILER_COL_COLOR, color, COMPILER_COL_STRING, utf8_msg, -1);
369 if (ui_prefs.msgwindow_visible && interface_prefs.compiler_tab_autoscroll)
371 GtkTreePath *path = gtk_tree_model_get_path(
372 gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_compiler)), &iter);
374 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(msgwindow.tree_compiler), path, NULL, TRUE, 0.5, 0.5);
375 gtk_tree_path_free(path);
378 /* calling build_menu_update for every build message would be overkill, TODO really should call it once when all done */
379 gtk_widget_set_sensitive(build_get_menu_items(-1)->menu_item[GBG_FIXED][GBF_NEXT_ERROR], TRUE);
380 gtk_widget_set_sensitive(build_get_menu_items(-1)->menu_item[GBG_FIXED][GBF_PREV_ERROR], TRUE);
382 if (utf8_msg != msg)
383 g_free(utf8_msg);
387 void msgwin_show_hide(gboolean show)
389 ui_prefs.msgwindow_visible = show;
390 ignore_callback = TRUE;
391 gtk_check_menu_item_set_active(
392 GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_show_messages_window1")),
393 show);
394 ignore_callback = FALSE;
395 ui_widget_show_hide(main_widgets.message_window_notebook, show);
396 /* set the input focus back to the editor */
397 keybindings_send_command(GEANY_KEY_GROUP_FOCUS, GEANY_KEYS_FOCUS_EDITOR);
402 * Adds a formatted message in the messages tab treeview in the messages window.
404 * If @a line and @a doc are set, clicking on this line jumps into the file
405 * which is specified by @a doc into the line specified with @a line.
407 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
408 * @param line The document's line where the message belongs to. Set to @c -1 to ignore.
409 * @param doc @nullable The document. Set to @c NULL to ignore.
410 * @param format @c printf()-style format string.
411 * @param ... Arguments for the @c format string.
413 * @see msgwin_msg_add_string()
415 * @since 0.16
417 GEANY_API_SYMBOL
418 void msgwin_msg_add(gint msg_color, gint line, GeanyDocument *doc, const gchar *format, ...)
420 gchar *string;
421 va_list args;
423 va_start(args, format);
424 string = g_strdup_vprintf(format, args);
425 va_end(args);
427 msgwin_msg_add_string(msg_color, line, doc, string);
428 g_free(string);
433 * Adds a new message in the messages tab treeview in the messages window.
435 * If @a line and @a doc are set, clicking on this line jumps into the
436 * file which is specified by @a doc into the line specified with @a line.
438 * @param msg_color A color to be used for the text. It must be an element of #MsgColors.
439 * @param line The document's line where the message belongs to. Set to @c -1 to ignore.
440 * @param doc @nullable The document. Set to @c NULL to ignore.
441 * @param string Message to be added.
443 * @see msgwin_msg_add()
445 * @since 1.34 (API 236)
447 GEANY_API_SYMBOL
448 void msgwin_msg_add_string(gint msg_color, gint line, GeanyDocument *doc, const gchar *string)
450 GtkTreeIter iter;
451 const GdkColor *color = get_color(msg_color);
452 gchar *tmp;
453 gsize len;
454 gchar *utf8_msg;
456 if (! ui_prefs.msgwindow_visible)
457 msgwin_show_hide(TRUE);
459 /* work around a strange problem when adding very long lines(greater than 4000 bytes)
460 * cut the string to a maximum of 1024 bytes and discard the rest */
461 /* TODO: find the real cause for the display problem / if it is GtkTreeView file a bug report */
462 len = strlen(string);
463 if (len > 1024)
464 tmp = g_strndup(string, 1024);
465 else
466 tmp = g_strdup(string);
468 if (! g_utf8_validate(tmp, -1, NULL))
469 utf8_msg = utils_get_utf8_from_locale(tmp);
470 else
471 utf8_msg = tmp;
473 gtk_list_store_append(msgwindow.store_msg, &iter);
474 gtk_list_store_set(msgwindow.store_msg, &iter,
475 MSG_COL_LINE, line, MSG_COL_DOC_ID, doc ? doc->id : 0, MSG_COL_COLOR,
476 color, MSG_COL_STRING, utf8_msg, -1);
478 g_free(tmp);
479 if (utf8_msg != tmp)
480 g_free(utf8_msg);
485 * Logs a new status message *without* setting the status bar.
487 * Use @ref ui_set_statusbar() to display text on the statusbar.
489 * @param string Status message to be logged.
491 * @see msgwin_status_add()
493 * @since 1.34 (API 236)
495 GEANY_API_SYMBOL
496 void msgwin_status_add_string(const gchar *string)
498 GtkTreeIter iter;
499 gchar *statusmsg, *time_str;
501 /* add a timestamp to status messages */
502 time_str = utils_get_current_time_string();
503 statusmsg = g_strconcat(time_str, ": ", string, NULL);
504 g_free(time_str);
506 /* add message to Status window */
507 gtk_list_store_append(msgwindow.store_status, &iter);
508 gtk_list_store_set(msgwindow.store_status, &iter, 0, statusmsg, -1);
509 g_free(statusmsg);
511 if (G_LIKELY(main_status.main_window_realized))
513 GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(GTK_TREE_VIEW(msgwindow.tree_status)), &iter);
515 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(msgwindow.tree_status), path, NULL, FALSE, 0.0, 0.0);
516 if (prefs.switch_to_status)
517 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_STATUS);
518 gtk_tree_path_free(path);
523 * Logs a formatted status message *without* setting the status bar.
525 * Use @ref ui_set_statusbar() to display text on the statusbar.
527 * @param format @c printf()-style format string.
528 * @param ... Arguments for the @c format string.
530 * @see msgwin_status_add_string()
532 * @since 0.12
534 GEANY_API_SYMBOL
535 void msgwin_status_add(const gchar *format, ...)
537 gchar *string;
538 va_list args;
540 va_start(args, format);
541 string = g_strdup_vprintf(format, args);
542 va_end(args);
544 msgwin_status_add_string(string);
545 g_free(string);
549 static void
550 on_message_treeview_clear_activate(GtkMenuItem *menuitem, gpointer user_data)
552 gint tabnum = GPOINTER_TO_INT(user_data);
554 msgwin_clear_tab(tabnum);
558 static void
559 on_compiler_treeview_copy_activate(GtkMenuItem *menuitem, gpointer user_data)
561 GtkWidget *tv = NULL;
562 GtkTreeSelection *selection;
563 GtkTreeModel *model;
564 GtkTreeIter iter;
565 gint str_idx = COMPILER_COL_STRING;
567 switch (GPOINTER_TO_INT(user_data))
569 case MSG_STATUS:
570 tv = msgwindow.tree_status;
571 str_idx = 0;
572 break;
574 case MSG_COMPILER:
575 tv = msgwindow.tree_compiler;
576 break;
578 case MSG_MESSAGE:
579 tv = msgwindow.tree_msg;
580 str_idx = MSG_COL_STRING;
581 break;
583 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
585 if (gtk_tree_selection_get_selected(selection, &model, &iter))
587 gchar *string;
589 gtk_tree_model_get(model, &iter, str_idx, &string, -1);
590 if (!EMPTY(string))
592 gtk_clipboard_set_text(gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
593 string, -1);
595 g_free(string);
600 static void on_compiler_treeview_copy_all_activate(GtkMenuItem *menuitem, gpointer user_data)
602 GtkListStore *store = msgwindow.store_compiler;
603 GtkTreeIter iter;
604 GString *str = g_string_new("");
605 gint str_idx = COMPILER_COL_STRING;
606 gboolean valid;
608 switch (GPOINTER_TO_INT(user_data))
610 case MSG_STATUS:
611 store = msgwindow.store_status;
612 str_idx = 0;
613 break;
615 case MSG_COMPILER:
616 /* default values */
617 break;
619 case MSG_MESSAGE:
620 store = msgwindow.store_msg;
621 str_idx = MSG_COL_STRING;
622 break;
625 /* walk through the list and copy every line into a string */
626 valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
627 while (valid)
629 gchar *line;
631 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, str_idx, &line, -1);
632 if (!EMPTY(line))
634 g_string_append(str, line);
635 g_string_append_c(str, '\n');
637 g_free(line);
639 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
642 /* copy the string into the clipboard */
643 if (str->len > 0)
645 gtk_clipboard_set_text(
646 gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE)),
647 str->str,
648 str->len);
650 g_string_free(str, TRUE);
654 static void
655 on_hide_message_window(GtkMenuItem *menuitem, gpointer user_data)
657 msgwin_show_hide(FALSE);
661 static GtkWidget *create_message_popup_menu(gint type)
663 GtkWidget *message_popup_menu, *clear, *copy, *copy_all, *image;
665 message_popup_menu = gtk_menu_new();
667 clear = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLEAR, NULL);
668 gtk_widget_show(clear);
669 gtk_container_add(GTK_CONTAINER(message_popup_menu), clear);
670 g_signal_connect(clear, "activate",
671 G_CALLBACK(on_message_treeview_clear_activate), GINT_TO_POINTER(type));
673 copy = gtk_image_menu_item_new_with_mnemonic(_("C_opy"));
674 gtk_widget_show(copy);
675 gtk_container_add(GTK_CONTAINER(message_popup_menu), copy);
676 image = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
677 gtk_widget_show(image);
678 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(copy), image);
679 g_signal_connect(copy, "activate",
680 G_CALLBACK(on_compiler_treeview_copy_activate), GINT_TO_POINTER(type));
682 copy_all = gtk_image_menu_item_new_with_mnemonic(_("Copy _All"));
683 gtk_widget_show(copy_all);
684 gtk_container_add(GTK_CONTAINER(message_popup_menu), copy_all);
685 image = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
686 gtk_widget_show(image);
687 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(copy_all), image);
688 g_signal_connect(copy_all, "activate",
689 G_CALLBACK(on_compiler_treeview_copy_all_activate), GINT_TO_POINTER(type));
691 msgwin_menu_add_common_items(GTK_MENU(message_popup_menu));
693 return message_popup_menu;
697 static void on_scribble_populate(GtkTextView *textview, GtkMenu *arg1, gpointer user_data)
699 msgwin_menu_add_common_items(arg1);
703 /* Menu items that should be on all message window popup menus */
704 void msgwin_menu_add_common_items(GtkMenu *menu)
706 GtkWidget *item;
708 item = gtk_separator_menu_item_new();
709 gtk_widget_show(item);
710 gtk_container_add(GTK_CONTAINER(menu), item);
712 item = gtk_menu_item_new_with_mnemonic(_("_Hide Message Window"));
713 gtk_widget_show(item);
714 gtk_container_add(GTK_CONTAINER(menu), item);
715 g_signal_connect(item, "activate", G_CALLBACK(on_hide_message_window), NULL);
719 /* look back up from the current path and find the directory we came from */
720 static gboolean
721 find_prev_build_dir(GtkTreePath *cur, GtkTreeModel *model, gchar **prefix)
723 GtkTreeIter iter;
724 *prefix = NULL;
726 while (gtk_tree_path_prev(cur))
728 if (gtk_tree_model_get_iter(model, &iter, cur))
730 gchar *string;
731 gtk_tree_model_get(model, &iter, COMPILER_COL_STRING, &string, -1);
732 if (string != NULL && build_parse_make_dir(string, prefix))
734 g_free(string);
735 return TRUE;
737 g_free(string);
741 return FALSE;
745 static gboolean goto_compiler_file_line(const gchar *fname, gint line, gboolean focus_editor)
747 gboolean ret = FALSE;
748 gchar *filename;
750 if (!fname || line <= -1)
751 return FALSE;
753 filename = utils_get_locale_from_utf8(fname);
755 /* If the path doesn't exist, try the current document.
756 * This happens when we receive build messages in the wrong order - after the
757 * 'Leaving directory' messages */
758 if (!g_file_test(filename, G_FILE_TEST_EXISTS))
760 gchar *cur_dir = utils_get_current_file_dir_utf8();
761 gchar *name;
763 if (cur_dir)
765 /* we let the user know we couldn't find the parsed filename from the message window */
766 SETPTR(cur_dir, utils_get_locale_from_utf8(cur_dir));
767 name = g_path_get_basename(filename);
768 SETPTR(name, g_build_path(G_DIR_SEPARATOR_S, cur_dir, name, NULL));
769 g_free(cur_dir);
771 if (g_file_test(name, G_FILE_TEST_EXISTS))
773 ui_set_statusbar(FALSE, _("Could not find file '%s' - trying the current document path."),
774 fname);
775 SETPTR(filename, name);
777 else
778 g_free(name);
783 gchar *utf8_filename = utils_get_utf8_from_locale(filename);
784 GeanyDocument *doc = document_find_by_filename(utf8_filename);
785 GeanyDocument *old_doc = document_get_current();
787 g_free(utf8_filename);
789 if (doc == NULL) /* file not already open */
790 doc = document_open_file(filename, FALSE, NULL, NULL);
792 if (doc != NULL)
794 if (! doc->changed && editor_prefs.use_indicators) /* if modified, line may be wrong */
795 editor_indicator_set_on_line(doc->editor, GEANY_INDICATOR_ERROR, line - 1);
797 ret = navqueue_goto_line(old_doc, doc, line);
798 if (ret && focus_editor)
799 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
801 ret = TRUE;
805 g_free(filename);
807 return ret;
811 gboolean msgwin_goto_compiler_file_line(gboolean focus_editor)
813 GtkTreeIter iter;
814 GtkTreeModel *model;
815 GtkTreeSelection *selection;
816 gchar *string;
817 GdkColor *color;
819 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_compiler));
820 if (gtk_tree_selection_get_selected(selection, &model, &iter))
822 /* if the item is not coloured red, it's not an error line */
823 gtk_tree_model_get(model, &iter, COMPILER_COL_COLOR, &color, -1);
824 if (color == NULL || ! gdk_color_equal(color, &color_error))
826 if (color != NULL)
827 gdk_color_free(color);
828 return FALSE;
830 gdk_color_free(color);
832 gtk_tree_model_get(model, &iter, COMPILER_COL_STRING, &string, -1);
833 if (string != NULL)
835 gint line;
836 gchar *filename, *dir;
837 GtkTreePath *path;
838 gboolean ret;
840 path = gtk_tree_model_get_path(model, &iter);
841 find_prev_build_dir(path, model, &dir);
842 gtk_tree_path_free(path);
843 msgwin_parse_compiler_error_line(string, dir, &filename, &line);
844 g_free(string);
845 g_free(dir);
847 ret = goto_compiler_file_line(filename, line, focus_editor);
848 g_free(filename);
849 return ret;
852 return FALSE;
856 static void make_absolute(gchar **filename, const gchar *dir)
858 guint skip_dot_slash = 0; /* number of characters to skip at the beginning of the filename */
860 if (*filename == NULL)
861 return;
863 /* skip some characters at the beginning of the filename, at the moment only "./"
864 * can be extended if other "trash" is known */
865 if (strncmp(*filename, "./", 2) == 0)
866 skip_dot_slash = 2;
868 /* add directory */
869 if (! utils_is_absolute_path(*filename))
870 SETPTR(*filename, g_build_filename(dir, *filename + skip_dot_slash, NULL));
874 /* try to parse the file and line number where the error occurred described in line
875 * and when something useful is found, it stores the line number in *line and the
876 * relevant file with the error in *filename.
877 * *line will be -1 if no error was found in string.
878 * *filename must be freed unless it is NULL. */
879 static void parse_file_line(ParseData *data, gchar **filename, gint *line)
881 gchar *end = NULL;
882 gchar **fields;
884 *filename = NULL;
885 *line = -1;
887 g_return_if_fail(data->string != NULL);
889 fields = g_strsplit_set(data->string, data->pattern, data->min_fields);
891 /* parse the line */
892 if (g_strv_length(fields) < data->min_fields)
894 g_strfreev(fields);
895 return;
898 *line = strtol(fields[data->line_idx], &end, 10);
900 /* if the line could not be read, line is 0 and an error occurred, so we leave */
901 if (fields[data->line_idx] == end)
903 g_strfreev(fields);
904 return;
907 /* let's stop here if there is no filename in the error message */
908 if (data->file_idx == -1)
910 /* we have no filename in the error message, so take the current one and hope it's correct */
911 GeanyDocument *doc = document_get_current();
912 if (doc != NULL)
913 *filename = g_strdup(doc->file_name);
914 g_strfreev(fields);
915 return;
918 *filename = g_strdup(fields[data->file_idx]);
919 g_strfreev(fields);
923 static void parse_compiler_error_line(const gchar *string,
924 gchar **filename, gint *line)
926 ParseData data = {NULL, NULL, 0, 0, 0};
928 data.string = string;
930 switch (build_info.file_type_id)
932 case GEANY_FILETYPES_PHP:
934 /* Parse error: parse error, unexpected T_CASE in brace_bug.php on line 3
935 * Parse error: syntax error, unexpected T_LNUMBER, expecting T_FUNCTION in bob.php on line 16 */
936 gchar *tmp = strstr(string, " in ");
938 if (tmp != NULL)
940 data.string = tmp;
941 data.pattern = " ";
942 data.min_fields = 6;
943 data.line_idx = 5;
944 data.file_idx = 2;
946 else
948 data.pattern = " ";
949 data.min_fields = 11;
950 data.line_idx = 10;
951 data.file_idx = 7;
953 break;
955 case GEANY_FILETYPES_PERL:
957 /* syntax error at test.pl line 7, near "{ */
958 data.pattern = " ";
959 data.min_fields = 6;
960 data.line_idx = 5;
961 data.file_idx = 3;
962 break;
964 /* the error output of python and tcl equals */
965 case GEANY_FILETYPES_TCL:
966 case GEANY_FILETYPES_PYTHON:
968 /* File "HyperArch.py", line 37, in ?
969 * (file "clrdial.tcl" line 12)
970 * */
971 if (strstr(string, " line ") != NULL)
973 /* Tcl and old Python format (<= Python 2.5) */
974 data.pattern = " \"";
975 data.min_fields = 6;
976 data.line_idx = 5;
977 data.file_idx = 2;
979 else
981 /* SyntaxError: ('invalid syntax', ('sender.py', 149, 20, ' ...'))
982 * (used since Python 2.6) */
983 data.pattern = ",'";
984 data.min_fields = 8;
985 data.line_idx = 6;
986 data.file_idx = 4;
988 break;
990 case GEANY_FILETYPES_BASIC:
991 case GEANY_FILETYPES_PASCAL:
992 case GEANY_FILETYPES_CS:
994 /* getdrive.bas(52) error 18: Syntax error in '? GetAllDrives'
995 * bandit.pas(149,3) Fatal: Syntax error, ";" expected but "ELSE" found */
996 data.pattern = "(";
997 data.min_fields = 2;
998 data.line_idx = 1;
999 data.file_idx = 0;
1000 break;
1002 case GEANY_FILETYPES_D:
1004 /* GNU D compiler front-end, gdc
1005 * gantry.d:18: variable gantry.main.c reference to auto class must be auto
1006 * warning - gantry.d:20: statement is not reachable
1007 * Digital Mars dmd compiler
1008 * warning - pi.d(118): implicit conversion of expression (digit) of type int ...
1009 * gantry.d(18): variable gantry.main.c reference to auto class must be auto */
1010 if (strncmp(string, "warning - ", 10) == 0)
1012 data.pattern = " (:";
1013 data.min_fields = 4;
1014 data.line_idx = 3;
1015 data.file_idx = 2;
1017 else
1019 data.pattern = "(:";
1020 data.min_fields = 2;
1021 data.line_idx = 1;
1022 data.file_idx = 0;
1024 break;
1026 case GEANY_FILETYPES_FERITE:
1028 /* Error: Parse Error: on line 5 in "/tmp/hello.fe"
1029 * Error: Compile Error: on line 24, in /test/class.fe */
1030 if (strncmp(string, "Error: Compile Error", 20) == 0)
1032 data.pattern = " ";
1033 data.min_fields = 8;
1034 data.line_idx = 5;
1035 data.file_idx = 7;
1037 else
1039 data.pattern = " \"";
1040 data.min_fields = 10;
1041 data.line_idx = 5;
1042 data.file_idx = 8;
1044 break;
1046 case GEANY_FILETYPES_HTML:
1048 /* line 78 column 7 - Warning: <table> missing '>' for end of tag */
1049 data.pattern = " ";
1050 data.min_fields = 4;
1051 data.line_idx = 1;
1052 data.file_idx = -1;
1053 break;
1055 /* All GNU gcc-like error messages */
1056 case GEANY_FILETYPES_C:
1057 case GEANY_FILETYPES_CPP:
1058 case GEANY_FILETYPES_RUBY:
1059 case GEANY_FILETYPES_JAVA:
1060 /* only gcc is supported, I don't know any other C(++) compilers and their error messages
1061 * empty.h:4: Warnung: type defaults to `int' in declaration of `foo'
1062 * empty.c:21: error: conflicting types for `foo'
1063 * Only parse file and line, so that linker errors will also work (with -g) */
1064 case GEANY_FILETYPES_F77:
1065 case GEANY_FILETYPES_FORTRAN:
1066 case GEANY_FILETYPES_LATEX:
1067 /* ./kommtechnik_2b.tex:18: Emergency stop. */
1068 case GEANY_FILETYPES_MAKE: /* Assume makefile is building with gcc */
1069 case GEANY_FILETYPES_NONE:
1070 default: /* The default is a GNU gcc type error */
1072 if (build_info.file_type_id == GEANY_FILETYPES_JAVA &&
1073 strncmp(string, "[javac]", 7) == 0)
1075 /* Java Apache Ant.
1076 * [javac] <Full Path to File + extension>:<line n°>: <error> */
1077 data.pattern = " :";
1078 data.min_fields = 4;
1079 data.line_idx = 2;
1080 data.file_idx = 1;
1081 break;
1083 /* don't accidentally find libtool versions x:y:x and think it is a file name */
1084 if (strstr(string, "libtool --mode=link") == NULL)
1086 data.pattern = ":";
1087 data.min_fields = 3;
1088 data.line_idx = 1;
1089 data.file_idx = 0;
1090 break;
1095 if (data.pattern != NULL)
1096 parse_file_line(&data, filename, line);
1100 /* try to parse the file and line number where the error occurred described in string
1101 * and when something useful is found, it stores the line number in *line and the
1102 * relevant file with the error in *filename.
1103 * *line will be -1 if no error was found in string.
1104 * *filename must be freed unless it is NULL. */
1105 void msgwin_parse_compiler_error_line(const gchar *string, const gchar *dir,
1106 gchar **filename, gint *line)
1108 GeanyFiletype *ft;
1109 gchar *trimmed_string, *utf8_dir;
1111 *filename = NULL;
1112 *line = -1;
1114 if (G_UNLIKELY(string == NULL))
1115 return;
1117 if (dir == NULL)
1118 utf8_dir = utils_get_utf8_from_locale(build_info.dir);
1119 else
1120 utf8_dir = g_strdup(dir);
1121 g_return_if_fail(utf8_dir != NULL);
1123 trimmed_string = g_strdup(string);
1124 g_strchug(trimmed_string); /* remove possible leading whitespace */
1126 ft = filetypes[build_info.file_type_id];
1128 /* try parsing with a custom regex */
1129 if (!filetypes_parse_error_message(ft, trimmed_string, filename, line))
1131 /* fallback to default old-style parsing */
1132 parse_compiler_error_line(trimmed_string, filename, line);
1134 make_absolute(filename, utf8_dir);
1135 g_free(trimmed_string);
1136 g_free(utf8_dir);
1140 /* Tries to parse strings of the file:line style, allowing line field to be missing
1141 * * filename is filled with the filename, should be freed
1142 * * line is filled with the line number or -1 */
1143 static void msgwin_parse_generic_line(const gchar *string, gchar **filename, gint *line)
1145 gchar **fields;
1146 gboolean incertain = TRUE; /* whether we're reasonably certain of the result */
1148 *filename = NULL;
1149 *line = -1;
1151 fields = g_strsplit(string, ":", 2);
1152 /* extract the filename */
1153 if (fields[0] != NULL)
1155 *filename = utils_get_locale_from_utf8(fields[0]);
1156 if (msgwindow.messages_dir != NULL)
1157 make_absolute(filename, msgwindow.messages_dir);
1159 /* now the line */
1160 if (fields[1] != NULL)
1162 gchar *end;
1164 *line = strtol(fields[1], &end, 10);
1165 if (end == fields[1])
1166 *line = -1;
1167 else if (*end == ':' || g_ascii_isspace(*end))
1168 { /* if we have a blank or a separator right after the number, assume we really got a
1169 * filename (it's a grep-like syntax) */
1170 incertain = FALSE;
1174 /* if we aren't sure we got a supposedly correct filename, check it */
1175 if (incertain && ! g_file_test(*filename, G_FILE_TEST_EXISTS))
1177 SETPTR(*filename, NULL);
1178 *line = -1;
1181 g_strfreev(fields);
1185 gboolean msgwin_goto_messages_file_line(gboolean focus_editor)
1187 GtkTreeIter iter;
1188 GtkTreeModel *model;
1189 GtkTreeSelection *selection;
1190 gboolean ret = FALSE;
1192 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(msgwindow.tree_msg));
1193 if (gtk_tree_selection_get_selected(selection, &model, &iter))
1195 gint line;
1196 guint id;
1197 gchar *string;
1198 GeanyDocument *doc;
1199 GeanyDocument *old_doc = document_get_current();
1201 gtk_tree_model_get(model, &iter,
1202 MSG_COL_LINE, &line, MSG_COL_DOC_ID, &id, MSG_COL_STRING, &string, -1);
1203 if (line >= 0 && id > 0)
1205 /* check doc is still open */
1206 doc = document_find_by_id(id);
1207 if (!doc)
1209 ui_set_statusbar(FALSE, _("The document has been closed."));
1210 utils_beep();
1212 else
1214 ret = navqueue_goto_line(old_doc, doc, line);
1215 if (ret && focus_editor)
1216 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1219 else if (line < 0 && string != NULL)
1221 gchar *filename;
1223 /* try with a file:line parsing */
1224 msgwin_parse_generic_line(string, &filename, &line);
1225 if (filename != NULL)
1227 /* use document_open_file to find an already open file, or open it in place */
1228 doc = document_open_file(filename, FALSE, NULL, NULL);
1229 if (doc != NULL)
1231 ret = (line < 0) ? TRUE : navqueue_goto_line(old_doc, doc, line);
1232 if (ret && focus_editor)
1233 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
1236 g_free(filename);
1238 g_free(string);
1240 return ret;
1244 static gboolean on_msgwin_button_press_event(GtkWidget *widget, GdkEventButton *event,
1245 gpointer user_data)
1247 /* user_data might be NULL, GPOINTER_TO_INT returns 0 if called with NULL */
1248 gboolean double_click = event->type == GDK_2BUTTON_PRESS;
1250 if (event->button == 1 && (event->type == GDK_BUTTON_RELEASE || double_click))
1252 switch (GPOINTER_TO_INT(user_data))
1254 case MSG_COMPILER:
1255 { /* mouse click in the compiler treeview */
1256 msgwin_goto_compiler_file_line(double_click);
1257 break;
1259 case MSG_MESSAGE:
1260 { /* mouse click in the message treeview (results of 'Find usage') */
1261 msgwin_goto_messages_file_line(double_click);
1262 break;
1265 return double_click; /* TRUE prevents message window re-focusing */
1268 if (event->button == 3)
1269 { /* popupmenu to hide or clear the active treeview */
1270 switch (GPOINTER_TO_INT(user_data))
1272 case MSG_STATUS:
1274 gtk_menu_popup(GTK_MENU(msgwindow.popup_status_menu), NULL, NULL, NULL, NULL,
1275 event->button, event->time);
1276 break;
1278 case MSG_MESSAGE:
1280 gtk_menu_popup(GTK_MENU(msgwindow.popup_msg_menu), NULL, NULL, NULL, NULL,
1281 event->button, event->time);
1282 break;
1284 case MSG_COMPILER:
1286 gtk_menu_popup(GTK_MENU(msgwindow.popup_compiler_menu), NULL, NULL, NULL, NULL,
1287 event->button, event->time);
1288 break;
1292 return FALSE;
1297 * Switches to the given notebook tab of the messages window.
1299 * The messages window is shown if it was previously hidden and @a show is set to @c TRUE.
1301 * @param tabnum An index of a tab in the messages window. Valid values are
1302 * all elements of #MessageWindowTabNum.
1303 * @param show Whether to show the messages window at all if it was hidden before.
1305 * @since 0.15
1307 GEANY_API_SYMBOL
1308 void msgwin_switch_tab(gint tabnum, gboolean show)
1310 GtkWidget *widget = NULL; /* widget to focus */
1312 switch (tabnum)
1314 case MSG_SCRATCH: widget = msgwindow.scribble; break;
1315 case MSG_COMPILER: widget = msgwindow.tree_compiler; break;
1316 case MSG_STATUS: widget = msgwindow.tree_status; break;
1317 case MSG_MESSAGE: widget = msgwindow.tree_msg; break;
1318 #ifdef HAVE_VTE
1319 case MSG_VTE: widget = (vte_info.have_vte) ? vc->vte : NULL; break;
1320 #endif
1321 default: break;
1324 /* the msgwin must be visible before we switch to the VTE page so that
1325 * the font settings are applied on realization */
1326 if (show)
1327 msgwin_show_hide(TRUE);
1328 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), tabnum);
1329 if (show && widget)
1330 gtk_widget_grab_focus(widget);
1335 * Removes all messages from a tab specified by @a tabnum in the messages window.
1337 * @param tabnum An index of a tab in the messages window which should be cleared.
1338 * Valid values are @c MSG_STATUS, @c MSG_COMPILER and @c MSG_MESSAGE.
1340 * @since 0.15
1342 GEANY_API_SYMBOL
1343 void msgwin_clear_tab(gint tabnum)
1345 GtkListStore *store = NULL;
1347 switch (tabnum)
1349 case MSG_MESSAGE:
1350 store = msgwindow.store_msg;
1351 break;
1353 case MSG_COMPILER:
1354 gtk_list_store_clear(msgwindow.store_compiler);
1355 build_menu_update(NULL); /* update next error items */
1356 return;
1358 case MSG_STATUS: store = msgwindow.store_status; break;
1359 default: return;
1361 if (store == NULL)
1362 return;
1363 gtk_list_store_clear(store);