diff options
author | Christian Hergert <chergert@redhat.com> | 2023-03-07 15:26:30 -0800 |
---|---|---|
committer | Christian Hergert <chergert@redhat.com> | 2023-03-07 15:26:30 -0800 |
commit | 738183fa605780700e06113142d5f93cc64ed430 (patch) | |
tree | 4c8bd983958a60da98c4f996def6c336f537f86a | |
parent | 3601367a62b89fa39c38100d34aba6a51133eb8c (diff) | |
download | gtksourceview-738183fa605780700e06113142d5f93cc64ed430.tar.gz |
vim: avoid small deletions in filter command
Removing text and then inserting it turns out to be really expensive when
there are lots of signal handlers. Instead do what we do elsewhere and
remove all of the text only to re-insert it/indent the line one by one.
This fixes some issues in Builder where reformatting with = on large
buffers can bring things to a hault.
-rw-r--r-- | gtksourceview/vim/gtksourcevim.c | 136 |
1 files changed, 116 insertions, 20 deletions
diff --git a/gtksourceview/vim/gtksourcevim.c b/gtksourceview/vim/gtksourcevim.c index a2d2a19d..a8d5c239 100644 --- a/gtksourceview/vim/gtksourcevim.c +++ b/gtksourceview/vim/gtksourcevim.c @@ -89,6 +89,67 @@ gtk_source_vim_real_format (GtkSourceVim *self, return FALSE; } +typedef struct _LineReader +{ + const char *contents; + gsize length; + gssize pos; +} LineReader; + +static void +line_reader_init (LineReader *reader, + const char *contents, + gssize length) +{ + if (length < 0) + length = strlen (contents); + + if (contents != NULL) + { + reader->contents = contents; + reader->length = length; + reader->pos = 0; + } + else + { + reader->contents = NULL; + reader->length = 0; + reader->pos = 0; + } +} + +static const char * +line_reader_next (LineReader *reader, + gsize *length) +{ + const char *ret = NULL; + + if ((reader->contents == NULL) || (reader->pos >= reader->length)) + { + *length = 0; + return NULL; + } + + ret = &reader->contents [reader->pos]; + + for (; reader->pos < reader->length; reader->pos++) + { + if (reader->contents [reader->pos] == '\n') + { + *length = &reader->contents [reader->pos] - ret; + /* Ingore the \r in \r\n if provided */ + if (*length > 0 && reader->pos > 0 && reader->contents [reader->pos - 1] == '\r') + (*length)--; + reader->pos++; + return ret; + } + } + + *length = &reader->contents [reader->pos] - ret; + + return ret; +} + static gboolean gtk_source_vim_real_filter (GtkSourceVim *self, GtkTextIter *begin, @@ -100,6 +161,11 @@ gtk_source_vim_real_filter (GtkSourceVim *self, GtkTextMark *begin_mark; GtkTextMark *end_mark; GtkTextIter iter; + LineReader reader; + const char *line; + char *text; + gsize line_len; + guint count = 0; g_assert (GTK_SOURCE_IS_VIM (self)); g_assert (begin != NULL); @@ -114,39 +180,67 @@ gtk_source_vim_real_filter (GtkSourceVim *self, return FALSE; } + gtk_text_iter_order (begin, end); + + /* Remove trialing \n which might have happened from linewise */ + if (gtk_text_iter_starts_line (end) && + gtk_text_iter_get_line (begin) != gtk_text_iter_get_line (end)) + { + gtk_text_iter_backward_char (end); + } + + if (!gtk_text_iter_starts_line (begin)) + { + gtk_text_iter_set_line_offset (begin, 0); + } + + if (!gtk_text_iter_ends_line (end)) + { + gtk_text_iter_forward_to_line_end (end); + } + + if (gtk_text_iter_equal (begin, end)) + { + return FALSE; + } + /* Create temporary marks for bounds checking */ begin_mark = gtk_text_buffer_create_mark (buffer, NULL, begin, TRUE); end_mark = gtk_text_buffer_create_mark (buffer, NULL, end, FALSE); - /* Start at beginning of first line */ - gtk_text_buffer_get_iter_at_mark (buffer, &iter, begin_mark); - gtk_text_iter_set_line_offset (&iter, 0); + /* Remove all text, as if we try to do this within the buffer it has + * the chance to really hammer applications which process events on + * buffer changes. + */ + text = gtk_text_iter_get_slice (begin, end); + gtk_text_buffer_delete (buffer, begin, end); - /* Remove prefix space from each line */ - while (compare_position (&iter, end_mark) < 0) + iter = *begin; + line_reader_init (&reader, text, -1); + while ((line = line_reader_next (&reader, &line_len))) { - GtkTextIter end_of_space = iter; - guint line; + char *stripped = g_strstrip (g_strndup (line, line_len)); + guint offset; - while (!gtk_text_iter_ends_line (&end_of_space) && - g_unichar_isspace (gtk_text_iter_get_char (&end_of_space))) + if (count > 0) { - gtk_text_iter_forward_char (&end_of_space); + gtk_text_buffer_insert (buffer, &iter, "\n", -1); } - if (!gtk_text_iter_equal (&iter, &end_of_space)) + offset = gtk_text_iter_get_offset (&iter); + gtk_text_buffer_insert (buffer, &iter, stripped, -1); + gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset); + + gtk_source_indenter_indent (indenter, view, &iter); + + if (!gtk_text_iter_ends_line (&iter)) { - gtk_text_buffer_delete (buffer, &iter, &end_of_space); + gtk_text_iter_forward_to_line_end (&iter); } - /* The thought here to get_iter_at_line() instead of forward_line() - * is that it is a bit more resilient against how much the indenter - * changes outside of the direct indentation point. It ensures that - * we take the next line after we were on and then processes it too. - */ - line = gtk_text_iter_get_line (&iter); - gtk_source_indenter_indent (indenter, view, &iter); - gtk_text_buffer_get_iter_at_line (buffer, &iter, line + 1); + count++; + + g_free (stripped); } /* Revalidate iter positions */ @@ -157,6 +251,8 @@ gtk_source_vim_real_filter (GtkSourceVim *self, gtk_text_buffer_delete_mark (buffer, begin_mark); gtk_text_buffer_delete_mark (buffer, end_mark); + g_free (text); + return TRUE; } |