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.
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.
34 #include "msgwindow.h"
38 #include "callbacks.h"
39 #include "filetypes.h"
40 #include "keybindings.h"
53 #include <gdk/gdkkeysyms.h>
56 /* used for parse_file_line */
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 */
67 MessageWindow msgwindow
;
80 COMPILER_COL_COLOR
= 0,
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
,
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.
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)
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
);
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
];
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
))
193 { /* key press in the compiler treeview */
194 msgwin_goto_compiler_file_line(enter_or_return
);
198 { /* key press in the message treeview (results of 'Find usage') */
199 msgwin_goto_messages_file_line(enter_or_return
);
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
)
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()
331 void msgwin_compiler_add(gint msg_color
, const gchar
*format
, ...)
336 va_start(args
, format
);
337 string
= g_strdup_vprintf(format
, args
);
339 msgwin_compiler_add_string(msg_color
, 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)
354 void msgwin_compiler_add_string(gint msg_color
, const gchar
*msg
)
357 const GdkColor
*color
= get_color(msg_color
);
360 if (! g_utf8_validate(msg
, -1, NULL
))
361 utf8_msg
= utils_get_utf8_from_locale(msg
);
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
);
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")),
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()
418 void msgwin_msg_add(gint msg_color
, gint line
, GeanyDocument
*doc
, const gchar
*format
, ...)
423 va_start(args
, format
);
424 string
= g_strdup_vprintf(format
, args
);
427 msgwin_msg_add_string(msg_color
, line
, doc
, 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)
448 void msgwin_msg_add_string(gint msg_color
, gint line
, GeanyDocument
*doc
, const gchar
*string
)
451 const GdkColor
*color
= get_color(msg_color
);
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
);
464 tmp
= g_strndup(string
, 1024);
466 tmp
= g_strdup(string
);
468 if (! g_utf8_validate(tmp
, -1, NULL
))
469 utf8_msg
= utils_get_utf8_from_locale(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);
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)
496 void msgwin_status_add_string(const gchar
*string
)
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
);
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);
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()
535 void msgwin_status_add(const gchar
*format
, ...)
540 va_start(args
, format
);
541 string
= g_strdup_vprintf(format
, args
);
544 msgwin_status_add_string(string
);
550 on_message_treeview_clear_activate(GtkMenuItem
*menuitem
, gpointer user_data
)
552 gint tabnum
= GPOINTER_TO_INT(user_data
);
554 msgwin_clear_tab(tabnum
);
559 on_compiler_treeview_copy_activate(GtkMenuItem
*menuitem
, gpointer user_data
)
561 GtkWidget
*tv
= NULL
;
562 GtkTreeSelection
*selection
;
565 gint str_idx
= COMPILER_COL_STRING
;
567 switch (GPOINTER_TO_INT(user_data
))
570 tv
= msgwindow
.tree_status
;
575 tv
= msgwindow
.tree_compiler
;
579 tv
= msgwindow
.tree_msg
;
580 str_idx
= MSG_COL_STRING
;
583 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
585 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
))
589 gtk_tree_model_get(model
, &iter
, str_idx
, &string
, -1);
592 gtk_clipboard_set_text(gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE
)),
600 static void on_compiler_treeview_copy_all_activate(GtkMenuItem
*menuitem
, gpointer user_data
)
602 GtkListStore
*store
= msgwindow
.store_compiler
;
604 GString
*str
= g_string_new("");
605 gint str_idx
= COMPILER_COL_STRING
;
608 switch (GPOINTER_TO_INT(user_data
))
611 store
= msgwindow
.store_status
;
620 store
= msgwindow
.store_msg
;
621 str_idx
= MSG_COL_STRING
;
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
);
631 gtk_tree_model_get(GTK_TREE_MODEL(store
), &iter
, str_idx
, &line
, -1);
634 g_string_append(str
, line
);
635 g_string_append_c(str
, '\n');
639 valid
= gtk_tree_model_iter_next(GTK_TREE_MODEL(store
), &iter
);
642 /* copy the string into the clipboard */
645 gtk_clipboard_set_text(
646 gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", FALSE
)),
650 g_string_free(str
, TRUE
);
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
)
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 */
721 find_prev_build_dir(GtkTreePath
*cur
, GtkTreeModel
*model
, gchar
**prefix
)
726 while (gtk_tree_path_prev(cur
))
728 if (gtk_tree_model_get_iter(model
, &iter
, cur
))
731 gtk_tree_model_get(model
, &iter
, COMPILER_COL_STRING
, &string
, -1);
732 if (string
!= NULL
&& build_parse_make_dir(string
, prefix
))
745 static gboolean
goto_compiler_file_line(const gchar
*fname
, gint line
, gboolean focus_editor
)
747 gboolean ret
= FALSE
;
750 if (!fname
|| line
<= -1)
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();
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
));
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."),
775 SETPTR(filename
, 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
);
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
));
811 gboolean
msgwin_goto_compiler_file_line(gboolean focus_editor
)
815 GtkTreeSelection
*selection
;
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
))
827 gdk_color_free(color
);
830 gdk_color_free(color
);
832 gtk_tree_model_get(model
, &iter
, COMPILER_COL_STRING
, &string
, -1);
836 gchar
*filename
, *dir
;
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
);
847 ret
= goto_compiler_file_line(filename
, line
, focus_editor
);
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
)
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)
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
)
887 g_return_if_fail(data
->string
!= NULL
);
889 fields
= g_strsplit_set(data
->string
, data
->pattern
, data
->min_fields
);
892 if (g_strv_length(fields
) < data
->min_fields
)
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
)
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();
913 *filename
= g_strdup(doc
->file_name
);
918 *filename
= g_strdup(fields
[data
->file_idx
]);
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 ");
949 data
.min_fields
= 11;
955 case GEANY_FILETYPES_PERL
:
957 /* syntax error at test.pl line 7, near "{ */
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)
971 if (strstr(string
, " line ") != NULL
)
973 /* Tcl and old Python format (<= Python 2.5) */
974 data
.pattern
= " \"";
981 /* SyntaxError: ('invalid syntax', ('sender.py', 149, 20, ' ...'))
982 * (used since Python 2.6) */
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 */
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;
1019 data
.pattern
= "(:";
1020 data
.min_fields
= 2;
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)
1033 data
.min_fields
= 8;
1039 data
.pattern
= " \"";
1040 data
.min_fields
= 10;
1046 case GEANY_FILETYPES_HTML
:
1048 /* line 78 column 7 - Warning: <table> missing '>' for end of tag */
1050 data
.min_fields
= 4;
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)
1076 * [javac] <Full Path to File + extension>:<line n°>: <error> */
1077 data
.pattern
= " :";
1078 data
.min_fields
= 4;
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
)
1087 data
.min_fields
= 3;
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
)
1109 gchar
*trimmed_string
, *utf8_dir
;
1114 if (G_UNLIKELY(string
== NULL
))
1118 utf8_dir
= utils_get_utf8_from_locale(build_info
.dir
);
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
);
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
)
1146 gboolean incertain
= TRUE
; /* whether we're reasonably certain of the result */
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
);
1160 if (fields
[1] != NULL
)
1164 *line
= strtol(fields
[1], &end
, 10);
1165 if (end
== fields
[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) */
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
);
1185 gboolean
msgwin_goto_messages_file_line(gboolean focus_editor
)
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
))
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
);
1209 ui_set_statusbar(FALSE
, _("The document has been closed."));
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
)
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
);
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
));
1244 static gboolean
on_msgwin_button_press_event(GtkWidget
*widget
, GdkEventButton
*event
,
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
))
1255 { /* mouse click in the compiler treeview */
1256 msgwin_goto_compiler_file_line(double_click
);
1260 { /* mouse click in the message treeview (results of 'Find usage') */
1261 msgwin_goto_messages_file_line(double_click
);
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
))
1274 gtk_menu_popup(GTK_MENU(msgwindow
.popup_status_menu
), NULL
, NULL
, NULL
, NULL
,
1275 event
->button
, event
->time
);
1280 gtk_menu_popup(GTK_MENU(msgwindow
.popup_msg_menu
), NULL
, NULL
, NULL
, NULL
,
1281 event
->button
, event
->time
);
1286 gtk_menu_popup(GTK_MENU(msgwindow
.popup_compiler_menu
), NULL
, NULL
, NULL
, NULL
,
1287 event
->button
, event
->time
);
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.
1308 void msgwin_switch_tab(gint tabnum
, gboolean show
)
1310 GtkWidget
*widget
= NULL
; /* widget to focus */
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;
1319 case MSG_VTE
: widget
= (vte_info
.have_vte
) ? vc
->vte
: NULL
; break;
1324 /* the msgwin must be visible before we switch to the VTE page so that
1325 * the font settings are applied on realization */
1327 msgwin_show_hide(TRUE
);
1328 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow
.notebook
), tabnum
);
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.
1343 void msgwin_clear_tab(gint tabnum
)
1345 GtkListStore
*store
= NULL
;
1350 store
= msgwindow
.store_msg
;
1354 gtk_list_store_clear(msgwindow
.store_compiler
);
1355 build_menu_update(NULL
); /* update next error items */
1358 case MSG_STATUS
: store
= msgwindow
.store_status
; break;
1363 gtk_list_store_clear(store
);