diff --git a/xed/xed-io-error-info-bar.c b/xed/xed-io-error-info-bar.c index 7c298e74..17d12351 100644 --- a/xed/xed-io-error-info-bar.c +++ b/xed/xed-io-error-info-bar.c @@ -1136,3 +1136,61 @@ xed_invalid_character_info_bar_new (GFile *location) return info_bar; } + +GtkWidget * +xed_binary_file_warning_info_bar_new (GFile *location, + const gchar *content_type) +{ + GtkWidget *info_bar; + gchar *full_formatted_uri; + gchar *temp_uri_for_display; + gchar *uri_for_display; + gchar *primary_text; + gchar *secondary_text; + + g_return_val_if_fail (G_IS_FILE (location), NULL); + + full_formatted_uri = g_file_get_parse_name (location); + temp_uri_for_display = xed_utils_str_middle_truncate (full_formatted_uri, MAX_URI_IN_DIALOG_LENGTH); + g_free (full_formatted_uri); + + uri_for_display = g_markup_printf_escaped ("%s", temp_uri_for_display); + g_free (temp_uri_for_display); + + info_bar = gtk_info_bar_new (); + + gtk_info_bar_add_button (GTK_INFO_BAR (info_bar), + _("Open Any_way"), + GTK_RESPONSE_YES); + gtk_info_bar_add_button (GTK_INFO_BAR (info_bar), + _("D_on't Open"), + GTK_RESPONSE_CANCEL); + gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING); + + primary_text = g_strdup_printf (_("The file %s appears to be a binary file."), uri_for_display); + g_free (uri_for_display); + + if (content_type != NULL) + { + gchar *desc = g_content_type_get_description (content_type); + + secondary_text = g_strdup_printf ( + _("The file type is \"%s\". Opening binary files in a text editor " + "can cause the application to become unresponsive."), + desc); + g_free (desc); + } + else + { + secondary_text = g_strdup ( + _("Opening binary files in a text editor can cause the application " + "to become unresponsive.")); + } + + set_info_bar_text_and_icon (info_bar, "xsi-dialog-warning-symbolic", + primary_text, secondary_text); + g_free (primary_text); + g_free (secondary_text); + + return info_bar; +} diff --git a/xed/xed-io-error-info-bar.h b/xed/xed-io-error-info-bar.h index 3bf7ea49..8299091b 100644 --- a/xed/xed-io-error-info-bar.h +++ b/xed/xed-io-error-info-bar.h @@ -64,6 +64,9 @@ GtkWidget *xed_externally_modified_info_bar_new (GFile *location, GtkWidget *xed_invalid_character_info_bar_new (GFile *location); +GtkWidget *xed_binary_file_warning_info_bar_new (GFile *location, + const gchar *content_type); + G_END_DECLS #endif /* __XED_IO_ERROR_INFO_BAR_H__ */ diff --git a/xed/xed-tab.c b/xed/xed-tab.c index 4ee03a8d..dea6b129 100644 --- a/xed/xed-tab.c +++ b/xed/xed-tab.c @@ -83,6 +83,9 @@ struct _XedTabPrivate /*tmp data for loading */ guint user_requested_encoding : 1; + + /* Set after user confirms opening a binary file */ + guint binary_check_done : 1; }; typedef struct _SaverData SaverData; @@ -1961,6 +1964,106 @@ load (XedTab *tab, tab); } +/* Check if a file appears to be binary by reading the first chunk and + * looking for NUL bytes, which are almost never present in text files. + * Also checks the content type via GIO. + * Returns the content type string (caller must free) or NULL if text. + */ +static gchar * +check_file_is_binary (GFile *location) +{ + GFileInfo *info; + const gchar *content_type; + GFileInputStream *stream; + guchar buf[8192]; + gssize bytes_read; + gssize i; + + info = g_file_query_info (location, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (info != NULL) + { + content_type = g_file_info_get_content_type (info); + + if (content_type != NULL && + !g_content_type_is_unknown (content_type) && + !g_content_type_is_a (content_type, "text/plain") && + !g_content_type_equals (content_type, "application/xml") && + !g_content_type_is_a (content_type, "application/xml") && + !g_content_type_equals (content_type, "application/json") && + !g_content_type_equals (content_type, "application/javascript") && + !g_content_type_equals (content_type, "image/svg+xml")) + { + gchar *ret = g_strdup (content_type); + g_object_unref (info); + return ret; + } + + g_object_unref (info); + } + + /* Fallback: read the first 8KB and check for NUL bytes */ + stream = g_file_read (location, NULL, NULL); + + if (stream == NULL) + { + return NULL; + } + + bytes_read = g_input_stream_read (G_INPUT_STREAM (stream), + buf, + sizeof (buf), + NULL, + NULL); + g_object_unref (stream); + + if (bytes_read <= 0) + { + return NULL; + } + + for (i = 0; i < bytes_read; i++) + { + if (buf[i] == '\0') + { + return g_strdup ("application/octet-stream"); + } + } + + return NULL; +} + +static void +binary_file_warning_info_bar_response (GtkWidget *info_bar, + gint response_id, + XedTab *tab) +{ + GFile *location; + GtkSourceFile *file; + XedDocument *doc; + + doc = xed_tab_get_document (tab); + file = xed_document_get_file (doc); + location = gtk_source_file_get_location (file); + + set_info_bar (tab, NULL); + + if (response_id == GTK_RESPONSE_YES) + { + /* User chose "Open Anyway" — skip binary check and load */ + tab->priv->binary_check_done = TRUE; + _xed_tab_load (tab, location, NULL, 0, FALSE); + } + else + { + remove_tab (tab); + } +} + void _xed_tab_load (XedTab *tab, GFile *location, @@ -1975,6 +2078,33 @@ _xed_tab_load (XedTab *tab, g_return_if_fail (G_IS_FILE (location)); g_return_if_fail (tab->priv->state == XED_TAB_STATE_NORMAL); + /* Check for binary files before loading to prevent UI freeze. + * Binary files without line breaks cause Pango text shaping to + * block the main thread indefinitely (see issue #491, #591). + */ + if (!tab->priv->binary_check_done && !create) + { + gchar *binary_type = check_file_is_binary (location); + + if (binary_type != NULL) + { + GtkWidget *info_bar; + + info_bar = xed_binary_file_warning_info_bar_new (location, binary_type); + g_free (binary_type); + + g_signal_connect (info_bar, "response", + G_CALLBACK (binary_file_warning_info_bar_response), tab); + + set_info_bar (tab, info_bar); + gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_CANCEL); + gtk_widget_show (info_bar); + return; + } + } + + tab->priv->binary_check_done = FALSE; + xed_tab_set_state (tab, XED_TAB_STATE_LOADING); doc = xed_tab_get_document (tab); diff --git a/xed/xed-window.c b/xed/xed-window.c index 5c9356ca..87c989ed 100644 --- a/xed/xed-window.c +++ b/xed/xed-window.c @@ -1791,18 +1791,30 @@ update_cursor_position_statusbar (GtkTextBuffer *buffer, tab_size = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW(view)); - while (!gtk_text_iter_equal (&start, &iter)) + /* For very long lines (e.g. binary files opened as text), the + * character-by-character iteration below becomes extremely slow + * and can freeze the UI. Use the offset directly if the line is + * too long for the O(n) scan to be practical. + */ + if (gtk_text_iter_get_chars_in_line (&start) > 10000) { - /* FIXME: Are we Unicode compliant here? */ - if (gtk_text_iter_get_char (&start) == '\t') - { - col += (tab_size - (col % tab_size)); - } - else + col = gtk_text_iter_get_line_offset (&iter); + } + else + { + while (!gtk_text_iter_equal (&start, &iter)) { - ++col; + /* FIXME: Are we Unicode compliant here? */ + if (gtk_text_iter_get_char (&start) == '\t') + { + col += (tab_size - (col % tab_size)); + } + else + { + ++col; + } + gtk_text_iter_forward_char (&start); } - gtk_text_iter_forward_char (&start); } xed_statusbar_set_cursor_position (XED_STATUSBAR(window->priv->statusbar), row + 1, col + 1);