parent be7fe1a3
......@@ -491,6 +491,18 @@ close_input_stream_ready_cb (GInputStream *stream,
return;
}
/* Check if we needed some fallback char, if so, check if there was
a previous error and if not set a fallback used error */
if ((gedit_document_output_stream_get_num_fallbacks (GEDIT_DOCUMENT_OUTPUT_STREAM (async->loader->priv->output)) != 0) &&
async->loader->priv->error == NULL)
{
g_set_error_literal (&async->loader->priv->error,
GEDIT_DOCUMENT_ERROR,
GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK,
"There was a conversion error and it was "
"needed to use a fallback char");
}
loader_load_completed_or_failed (async->loader, async);
}
......@@ -621,18 +633,7 @@ async_read_cb (GInputStream *stream,
loader->priv->auto_detected_newline_type =
gedit_document_output_stream_detect_newline_type (GEDIT_DOCUMENT_OUTPUT_STREAM (loader->priv->output));
/* Check if we needed some fallback char, if so, check if there was
a previous error and if not set a fallback used error */
/* FIXME Uncomment this when we want to manage conversion fallback */
/*if ((gedit_document_output_stream_get_num_fallbacks (GEDIT_DOCUMENT_OUTPUT_STREAM (loader->priv->output)) != 0) &&
loader->priv->error == NULL)
{
g_set_error_literal (&loader->priv->error,
GEDIT_DOCUMENT_ERROR,
GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK,
"There was a conversion error and it was "
"needed to use a fallback char");
}*/
write_complete (async);
......
......@@ -57,6 +57,9 @@ struct _GeditDocumentOutputStreamPrivate
GSList *encodings;
GSList *current_encoding;
gint error_offset;
guint n_fallback_errors;
guint is_utf8 : 1;
guint use_first : 1;
......@@ -221,6 +224,8 @@ gedit_document_output_stream_init (GeditDocumentOutputStream *stream)
stream->priv->encodings = NULL;
stream->priv->current_encoding = NULL;
stream->priv->error_offset = -1;
stream->priv->is_initialized = FALSE;
stream->priv->is_closed = FALSE;
stream->priv->is_utf8 = FALSE;
......@@ -244,7 +249,10 @@ get_encoding (GeditDocumentOutputStream *stream)
return (const GeditEncoding *)stream->priv->current_encoding->data;
}
return NULL;
stream->priv->use_first = TRUE;
stream->priv->current_encoding = stream->priv->encodings;
return (const GeditEncoding *)stream->priv->current_encoding->data;
}
static gboolean
......@@ -497,77 +505,135 @@ gedit_document_output_stream_get_guessed (GeditDocumentOutputStream *stream)
guint
gedit_document_output_stream_get_num_fallbacks (GeditDocumentOutputStream *stream)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT_OUTPUT_STREAM (stream), FALSE);
g_return_val_if_fail (GEDIT_IS_DOCUMENT_OUTPUT_STREAM (stream), 0);
return stream->priv->n_fallback_errors;
}
if (stream->priv->charset_conv == NULL)
static void
apply_error_tag (GeditDocumentOutputStream *stream)
{
GtkTextIter start;
if (stream->priv->error_offset == -1)
{
return FALSE;
return;
}
return g_charset_converter_get_num_fallbacks (stream->priv->charset_conv) != 0;
gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (stream->priv->doc),
&start, stream->priv->error_offset);
_gedit_document_apply_error_style (stream->priv->doc,
&start, &stream->priv->pos);
stream->priv->error_offset = -1;
}
static gboolean
static void
insert_fallback (GeditDocumentOutputStream *stream,
const gchar *buffer)
{
guint8 out[4];
guint8 v;
const gchar hex[] = "0123456789ABCDEF";
/* if we are here is because we are pointing to an invalid char
* so we substitute it by an hex value */
v = *(guint8 *)buffer;
out[0] = '\\';
out[1] = hex[(v & 0xf0) >> 4];
out[2] = hex[(v & 0x0f) >> 0];
out[3] = '\0';
gtk_text_buffer_insert (GTK_TEXT_BUFFER (stream->priv->doc),
&stream->priv->pos, (const gchar *)out, 3);
stream->priv->n_fallback_errors++;
}
static void
validate_and_insert (GeditDocumentOutputStream *stream,
const gchar *buffer,
gsize count)
{
const gchar *end;
gsize nvalid;
gboolean valid;
GtkTextBuffer *text_buffer;
GtkTextIter *iter;
gsize len;
text_buffer = GTK_TEXT_BUFFER (stream->priv->doc);
iter = &stream->priv->pos;
len = count;
/* validate */
valid = g_utf8_validate (buffer, len, &end);
nvalid = end - buffer;
if (!valid)
while (len != 0)
{
gsize remainder;
const gchar *end;
gboolean valid;
gsize nvalid;
remainder = len - nvalid;
/* validate */
valid = g_utf8_validate (buffer, len, &end);
nvalid = end - buffer;
if ((remainder < MAX_UNICHAR_LEN) &&
(g_utf8_get_char_validated (buffer + nvalid, remainder) == (gunichar)-2))
/* Note: this is a workaround for a 'bug' in GtkTextBuffer where
inserting first a \r and then in a second insert, a \n,
will result in two lines being added instead of a single
one */
if (valid)
{
stream->priv->buffer = g_strndup (end, remainder);
stream->priv->buflen = remainder;
len -= remainder;
gchar *ptr;
ptr = g_utf8_find_prev_char (buffer, buffer + len);
if (ptr && *ptr == '\r' && ptr - buffer == len - 1)
{
stream->priv->buffer = g_new (gchar, 1);
stream->priv->buffer[0] = '\r';
stream->priv->buflen = 1;
/* Decrease also the len so in the check
nvalid == len we get out of this method */
--nvalid;
--len;
}
}
else
/* if we've got any valid char we must tag the invalid chars */
if (nvalid > 0)
{
return FALSE;
apply_error_tag (stream);
}
}
else
{
gchar *ptr;
/* Note: this is a workaround for a 'bug' in GtkTextBuffer where
inserting first a \r and then in a second insert, a \n,
will result in two lines being added instead of a single
one */
gtk_text_buffer_insert (text_buffer, iter, buffer, nvalid);
ptr = g_utf8_find_prev_char (buffer, buffer + len);
/* If we inserted all return */
if (nvalid == len)
{
break;
}
buffer += nvalid;
len = len - nvalid;
if (ptr && *ptr == '\r' && ptr - buffer == len - 1)
if ((len < MAX_UNICHAR_LEN) &&
(g_utf8_get_char_validated (buffer, len) == (gunichar)-2))
{
stream->priv->buffer = g_new (gchar, 1);
stream->priv->buffer[0] = '\r';
stream->priv->buflen = 1;
stream->priv->buffer = g_strndup (end, len);
stream->priv->buflen = len;
--len;
break;
}
}
gtk_text_buffer_insert (GTK_TEXT_BUFFER (stream->priv->doc),
&stream->priv->pos,
buffer,
len);
/* we need the start of the chunk of invalid chars */
if (stream->priv->error_offset == -1)
{
stream->priv->error_offset = gtk_text_iter_get_offset (&stream->priv->pos);
}
return TRUE;
insert_fallback (stream, buffer);
buffer++;
len--;
}
}
/* If the last char is a newline, remove it from the buffer (otherwise
......@@ -775,21 +841,7 @@ gedit_document_output_stream_write (GOutputStream *stream,
freetext = TRUE;
}
if (!validate_and_insert (ostream, text, len))
{
/* TODO: we could escape invalid text and tag it in red
* and make the doc readonly.
*/
g_set_error (error, G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
_("Invalid UTF-8 sequence in input"));
if (freetext)
{
g_free (text);
}
return -1;
}
validate_and_insert (ostream, text, len);
if (freetext)
{
......@@ -813,12 +865,33 @@ gedit_document_output_stream_flush (GOutputStream *stream,
return TRUE;
}
if (ostream->priv->buflen == 0)
if (ostream->priv->buflen > 0 && *ostream->priv->buffer != '\r')
{
return TRUE;
/* If we reached here is because the last insertion was a half
correct char, which has to be inserted as fallback */
gchar *text;
if (ostream->priv->error_offset == -1)
{
ostream->priv->error_offset = gtk_text_iter_get_offset (&ostream->priv->pos);
}
text = ostream->priv->buffer;
while (ostream->priv->buflen != 0)
{
insert_fallback (ostream, text);
text++;
ostream->priv->buflen--;
}
g_free (ostream->priv->buffer);
ostream->priv->buffer = NULL;
}
else if (ostream->priv->buflen == 1 && *ostream->priv->buffer == '\r')
{
/* The previous chars can be invalid */
apply_error_tag (ostream);
/* See special case above, flush this */
gtk_text_buffer_insert (GTK_TEXT_BUFFER (ostream->priv->doc),
&ostream->priv->pos,
......@@ -828,19 +901,11 @@ gedit_document_output_stream_flush (GOutputStream *stream,
g_free (ostream->priv->buffer);
ostream->priv->buffer = NULL;
ostream->priv->buflen = 0;
return TRUE;
}
else
{
/* Conversion error */
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
_("Incomplete UTF-8 sequence in input"));
return FALSE;
}
apply_error_tag (ostream);
return TRUE;
}
static gboolean
......
......@@ -138,15 +138,17 @@ struct _GeditDocumentPrivate
GeditTextRegion *to_search_region;
GtkTextTag *found_tag;
GtkTextTag *error_tag;
/* Mount operation factory */
GeditMountOperationFactory mount_operation_factory;
gpointer mount_operation_userdata;
gint readonly : 1;
gint last_save_was_manually : 1;
gint language_set_by_user : 1;
gint stop_cursor_moved_emission : 1;
gint dispose_has_run : 1;
guint readonly : 1;
guint last_save_was_manually : 1;
guint language_set_by_user : 1;
guint stop_cursor_moved_emission : 1;
guint dispose_has_run : 1;
};
enum {
......@@ -1336,15 +1338,15 @@ set_readonly (GeditDocument *doc,
}
/**
* gedit_document_set_readonly:
* _gedit_document_set_readonly:
* @doc: a #GeditDocument
* @readonly: %TRUE to se the document as read-only
* @readonly: %TRUE to set the document as read-only
*
* If @readonly is %TRUE sets @doc as read-only.
*/
void
_gedit_document_set_readonly (GeditDocument *doc,
gboolean readonly)
gboolean readonly)
{
gedit_debug (DEBUG_DOCUMENT);
......@@ -1704,6 +1706,29 @@ gedit_document_load_cancel (GeditDocument *doc)
return gedit_document_loader_cancel (doc->priv->loader);
}
static gboolean
has_invalid_chars (GeditDocument *doc)
{
GtkTextBuffer *buffer;
GtkTextIter start;
g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), FALSE);
gedit_debug (DEBUG_DOCUMENT);
buffer = GTK_TEXT_BUFFER (doc);
gtk_text_buffer_get_start_iter (buffer, &start);
if (gtk_text_iter_begins_tag (&start, doc->priv->error_tag) ||
gtk_text_iter_forward_to_tag_toggle (&start, doc->priv->error_tag))
{
return TRUE;
}
return FALSE;
}
static void
document_saver_saving (GeditDocumentSaver *saver,
gboolean completed,
......@@ -1790,25 +1815,42 @@ gedit_document_save_real (GeditDocument *doc,
{
g_return_if_fail (doc->priv->saver == NULL);
/* create a saver, it will be destroyed once saving is complete */
doc->priv->saver = gedit_document_saver_new (doc,
location,
encoding,
newline_type,
compression_type,
flags);
g_signal_connect (doc->priv->saver,
"saving",
G_CALLBACK (document_saver_saving),
doc);
if (!(flags & GEDIT_DOCUMENT_SAVE_IGNORE_INVALID_CHARS) && has_invalid_chars (doc))
{
GError *error = NULL;
doc->priv->requested_encoding = encoding;
doc->priv->newline_type = newline_type;
doc->priv->compression_type = compression_type;
g_set_error_literal (&error,
GEDIT_DOCUMENT_ERROR,
GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK,
"The document contains invalid characters");
g_signal_emit (doc,
document_signals[SAVED],
0,
error);
}
else
{
/* create a saver, it will be destroyed once saving is complete */
doc->priv->saver = gedit_document_saver_new (doc,
location,
encoding,
newline_type,
compression_type,
flags);
g_signal_connect (doc->priv->saver,
"saving",
G_CALLBACK (document_saver_saving),
doc);
doc->priv->requested_encoding = encoding;
doc->priv->newline_type = newline_type;
doc->priv->compression_type = compression_type;
gedit_document_saver_save (doc->priv->saver,
&doc->priv->mtime);
gedit_document_saver_save (doc->priv->saver,
&doc->priv->mtime);
}
}
/**
......@@ -1855,10 +1897,20 @@ gedit_document_save_as (GeditDocument *doc,
GeditDocumentCompressionType compression_type,
GeditDocumentSaveFlags flags)
{
GError *error = NULL;
g_return_if_fail (GEDIT_IS_DOCUMENT (doc));
g_return_if_fail (G_IS_FILE (location));
g_return_if_fail (encoding != NULL);
if (has_invalid_chars (doc))
{
g_set_error_literal (&error,
GEDIT_DOCUMENT_ERROR,
GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK,
"The document contains invalid chars");
}
/* priv->mtime refers to the the old location (if any). Thus, it should be
* ignored when saving as. */
g_signal_emit (doc,
......@@ -1868,10 +1920,11 @@ gedit_document_save_as (GeditDocument *doc,
encoding,
newline_type,
compression_type,
flags | GEDIT_DOCUMENT_SAVE_IGNORE_MTIME);
flags | GEDIT_DOCUMENT_SAVE_IGNORE_MTIME,
error);
}
gboolean
gboolean
gedit_document_is_untouched (GeditDocument *doc)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), TRUE);
......@@ -3100,4 +3153,48 @@ gedit_document_set_metadata (GeditDocument *doc,
}
#endif
static void
sync_error_tag (GeditDocument *doc,
GParamSpec *pspec,
gpointer data)
{
sync_tag_style (doc, doc->priv->error_tag, "def:error");
}
void
_gedit_document_apply_error_style (GeditDocument *doc,
GtkTextIter *start,
GtkTextIter *end)
{
GtkTextBuffer *buffer;
gedit_debug (DEBUG_DOCUMENT);
buffer = GTK_TEXT_BUFFER (doc);
if (doc->priv->error_tag == NULL)
{
doc->priv->error_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (doc),
"invalid-char-style",
NULL);
sync_error_tag (doc, NULL, NULL);
g_signal_connect (doc,
"notify::style-scheme",
G_CALLBACK (sync_error_tag),
NULL);
}
/* make sure the 'error' tag has the priority over
* syntax highlighting tags */
text_tag_set_highest_priority (doc->priv->error_tag,
GTK_TEXT_BUFFER (doc));
gtk_text_buffer_apply_tag (buffer,
doc->priv->error_tag,
start,
end);
}
/* ex:set ts=8 noet: */
......@@ -109,7 +109,8 @@ typedef enum
{
GEDIT_DOCUMENT_SAVE_IGNORE_MTIME = 1 << 0,
GEDIT_DOCUMENT_SAVE_IGNORE_BACKUP = 1 << 1,
GEDIT_DOCUMENT_SAVE_PRESERVE_BACKUP = 1 << 2
GEDIT_DOCUMENT_SAVE_PRESERVE_BACKUP = 1 << 2,
GEDIT_DOCUMENT_SAVE_IGNORE_INVALID_CHARS= 1 << 3
} GeditDocumentSaveFlags;
/* Private structure type */
......@@ -323,6 +324,11 @@ void _gedit_document_set_readonly (GeditDocument *doc,
glong _gedit_document_get_seconds_since_last_save_or_load
(GeditDocument *doc);
void _gedit_document_apply_error_style
(GeditDocument *doc,
GtkTextIter *start,
GtkTextIter *end);
/* Note: this is a sync stat: use only on local files */
gboolean _gedit_document_check_externally_modified
(GeditDocument *doc);
......
......@@ -505,23 +505,19 @@ create_conversion_error_info_bar (const gchar *primary_text,
different from other main menu access keys (Open, Edit, View...) */
_("Edit Any_way"),
GTK_RESPONSE_YES);
gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
/* Translators: the access key chosen for this string should be
different from other main menu access keys (Open, Edit, View...) */
_("D_on't Edit"),
GTK_RESPONSE_NO);
gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar),
GTK_MESSAGE_WARNING);
}
else
{
gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
GTK_STOCK_CANCEL,
GTK_RESPONSE_CANCEL);
gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar),
GTK_MESSAGE_ERROR);
}
gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
GTK_STOCK_CANCEL,
GTK_RESPONSE_CANCEL);
hbox_content = gtk_hbox_new (FALSE, 8);
image = gtk_image_new_from_stock ("gtk-dialog-error", GTK_ICON_SIZE_DIALOG);
......@@ -631,8 +627,8 @@ gedit_io_loading_error_info_bar_new (GFile *location,
error_message = g_strdup_printf (_("There was a problem opening the file %s."),
uri_for_display);
message_details = g_strconcat (_("The file you opened has some invalid characters. "
"If you continue editing this file you could make this "
"document useless."), "\n",
"If you continue editing this file you could corrupt this "
"document."), "\n",
_("You can also choose another character encoding and try again."),
NULL);
edit_anyway = TRUE;
......@@ -1225,4 +1221,92 @@ gedit_externally_modified_info_bar_new (GFile *location,
return info_bar;
}
GtkWidget *
gedit_invalid_character_info_bar_new (GFile *location)
{
GtkWidget *info_bar;
GtkWidget *hbox_content;
GtkWidget *image;
GtkWidget *vbox;
GtkWidget *primary_label;
GtkWidget *secondary_label;
gchar *primary_markup;
gchar *secondary_markup;
gchar *primary_text;
gchar *full_formatted_uri;
gchar *uri_for_display;
gchar *temp_uri_for_display;
const gchar *secondary_text;
g_return_val_if_fail (G_IS_FILE (location), NULL);
full_formatted_uri = g_file_get_parse_name (location);
/* Truncate the URI so it doesn't get insanely wide. Note that even
* though the dialog uses wrapped text, if the URI doesn't contain
* white space then the text-wrapping code is too stupid to wrap it.
*/
temp_uri_for_display = gedit_utils_str_middle_truncate (full_formatted_uri,
MAX_URI_IN_DIALOG_LENGTH);
g_free (full_formatted_uri);
uri_for_display = g_markup_printf_escaped ("<i>%s</i>", temp_uri_for_display);
g_free (temp_uri_for_display);
info_bar = gtk_info_bar_new ();
info_bar_add_stock_button_with_text (GTK_INFO_BAR (info_bar),
_("S_ave Anyway"),
GTK_STOCK_SAVE,
GTK_RESPONSE_YES);
gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
_("D_on't Save"),
GTK_RESPONSE_CANCEL);
gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar),
GTK_MESSAGE_WARNING);
hbox_content = gtk_hbox_new (FALSE, 8);
image = gtk_image_new_from_stock ("gtk-dialog-warning", GTK_ICON_SIZE_DIALOG);
gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0);
gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0);
vbox = gtk_vbox_new (FALSE, 6);
gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0);
primary_text = g_strdup_printf (_("Some invalid chars have been detected while saving %s"),
uri_for_display);
g_free (uri_for_display);
primary_markup = g_strdup_printf ("<b>%s</b>", primary_text);
g_free (primary_text);
primary_label = gtk_label_new (primary_markup);
g_free (primary_markup);
gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0);
gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5);
gtk_widget_set_can_focus (primary_label, TRUE);
gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
secondary_text = _("If you continue saving this file you can corrupt the document. "
" Save anyway?");
secondary_markup = g_strdup_printf ("<small>%s</small>",
secondary_text);
secondary_label = gtk_label_new (secondary_markup);
g_free (secondary_markup);
gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0);
gtk_widget_set_can_focus (secondary_label, TRUE);
gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE);
gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
gtk_widget_show_all (hbox_content);
set_contents (info_bar, hbox_content);
return info_bar;
}
/* ex:set ts=8 noet: */
......@@ -63,6 +63,8 @@ GtkWidget *gedit_unrecoverable_saving_error_info_bar_new (GFile *
GtkWidget *gedit_externally_modified_info_bar_new (GFile *location,
gboolean document_modified);
GtkWidget *gedit_invalid_character_info_bar_new (GFile *location);
G_END_DECLS
#endif /* __GEDIT_IO_ERROR_INFO_BAR_H__ */
......
......@@ -597,11 +597,9 @@ io_loading_error_info_bar_response (GtkWidget *info_bar,
break;
case GTK_RESPONSE_YES:
/* This means that we want to edit the document anyway */
set_info_bar (tab, NULL);
_gedit_document_set_readonly (doc, FALSE);
break;
case GTK_RESPONSE_NO:
/* We don't want to edit the document just show it */
tab->priv->not_editable = FALSE;
gtk_text_view_set_editable (GTK_TEXT_VIEW (view),
TRUE);
set_info_bar (tab, NULL);
break;
default:
......@@ -1067,11 +1065,13 @@ document_loaded (GeditDocument *document,
{
GtkWidget *emsg;
_gedit_document_set_readonly (document, TRUE);
/* Set the tab as not editable as we have an error, the
user can decide to make it editable again */
tab->priv->not_editable = TRUE;
emsg = gedit_io_loading_error_info_bar_new (location,
tab->priv->tmp_encoding,
error);
tab->priv->tmp_encoding,
error);
set_info_bar (tab, emsg);
......@@ -1208,8 +1208,8 @@ end_saving (GeditTab *tab)
static void