Commit 9dd622ad authored by Adam Dingle's avatar Adam Dingle Committed by Johannes Schmid

Reviewed by: Johannes Schmid <jhs@gnome.org>

2008-12-18  Adam Dingle  <adam@medovina.org>

  Reviewed by: Johannes Schmid <jhs@gnome.org>
   
  * plugins/document-manager/anjuta-document-manager.ui:
  * plugins/document-manager/plugin.c:
  Fixed #564987 – Save All, Close All should appear in Documents menu.
  Added an icon to the Save All menu item.

  * plugins/document-manager/plugin.c:
  Fixed #565002 – Redo shortcut should be Ctrl+Shift+Z
  
  * plugins/document-manager/plugin.c:
  Fixed #565016 – Previous History and Next History should have shortcuts

  * plugins/search/search-replace_backend.c
    (file_buffer_line_from_pos), (file_match_line_from_pos),
  (extra_match), (get_next_regex_match), (match_info),
  (get_next_ascii_match), (normalize), (normal_advance),
  (get_next_utf8_match), (str_is_ascii), (get_next_match):
  * plugins/search/search-replace_backend.h: 	
  Rewrote the UTF-8 search implementation to be faster and more robust.
  When a search string is ASCII (which is virtually always the case),
  we now bypass UTF-8 search altogether; this speeds up searching enormously.
  Fixed bugs that sometimes caused incorrect lines to appear in the find output.
  This fixes #563585 (Find in Files is extremely slow).

svn path=/trunk/; revision=4476
parent 31828da3
2008-12-18 Adam Dingle <adam@medovina.org>
Reviewed by: Johannes Schmid <jhs@gnome.org>
* plugins/document-manager/anjuta-document-manager.ui:
* plugins/document-manager/plugin.c:
Fixed #564987 – Save All, Close All should appear in Documents menu.
Added an icon to the Save All menu item.
* plugins/document-manager/plugin.c:
Fixed #565002 – Redo shortcut should be Ctrl+Shift+Z
* plugins/document-manager/plugin.c:
Fixed #565016 – Previous History and Next History should have shortcuts
* plugins/search/search-replace_backend.c
(file_buffer_line_from_pos), (file_match_line_from_pos),
(extra_match), (get_next_regex_match), (match_info),
(get_next_ascii_match), (normalize), (normal_advance),
(get_next_utf8_match), (str_is_ascii), (get_next_match):
* plugins/search/search-replace_backend.h:
Rewrote the UTF-8 search implementation to be faster and more robust.
When a search string is ASCII (which is virtually always the case),
we now bypass UTF-8 search altogether; this speeds up searching enormously.
Fixed bugs that sometimes caused incorrect lines to appear in the find output.
This fixes #563585 (Find in Files is extremely slow).
2008-12-20 Massimo Cora' <mcora@svn.gnome.org>
* plugins/symbol-db/symbol-db-engine-priv.h:
......@@ -5,9 +5,7 @@
<placeholder name="PlaceholderFileMenus">
<menuitem name="Save" action="ActionFileSave" />
<menuitem name="SaveAs" action="ActionFileSaveAs" />
<menuitem name="SaveAll" action="ActionFileSaveAll" />
<menuitem name="Close" action="ActionFileClose" />
<menuitem name="CloseAll" action="ActionFileCloseAll" />
<menuitem name="Reload" action="ActionFileReload" />
<separator name="separator4"/>
</placeholder>
......@@ -104,6 +102,9 @@
</menu>
<placeholder name="PlaceHolderDocumentsMenus">
<menu name="Documents" action="ActionMenuDocuments">
<menuitem name="SaveAll" action="ActionFileSaveAll" />
<menuitem name="CloseAll" action="ActionFileCloseAll" />
<separator />
<menuitem name="PreviousDocument" action="ActionDocumentsPrevious" />
<menuitem name="NextDocument" action="ActionDocumentsNext" />
<separator />
......
......@@ -143,7 +143,7 @@ static GtkActionEntry actions_file[] = {
{ "ActionFileSaveAs", GTK_STOCK_SAVE_AS, N_("Save _As..."), "<shift><control>s",
N_("Save the current file with a different name"),
G_CALLBACK (on_save_as_activate)},
{ "ActionFileSaveAll", NULL, N_("Save A_ll"), NULL,
{ "ActionFileSaveAll", GTK_STOCK_SAVE, N_("Save A_ll"), "<shift><control>l",
N_("Save all currently open files, except new files"),
G_CALLBACK (on_save_all_activate)},
{ "ActionFileClose", GTK_STOCK_CLOSE, N_("_Close File"), "<control>w",
......@@ -249,10 +249,10 @@ static GtkActionEntry actions_navigation[] = {
"<control><alt>e", N_("Go to the end of the current block"),
G_CALLBACK (on_goto_block_end1_activate)},
{ "ActionEditGotoHistoryPrev", ANJUTA_STOCK_HISTORY_PREV, N_("Previous _History"),
NULL, N_("Goto previous history"),
"<alt>Left", N_("Goto previous history"),
G_CALLBACK (on_prev_history)},
{ "ActionEditGotoHistoryNext", ANJUTA_STOCK_HISTORY_NEXT, N_("Next Histor_y"),
NULL, N_("Goto next history"),
"<alt>Right", N_("Goto next history"),
G_CALLBACK (on_next_history)}
};
......@@ -284,7 +284,7 @@ static GtkActionEntry actions_edit[] = {
{ "ActionEditUndo", GTK_STOCK_UNDO, N_("U_ndo"), "<control>z",
N_("Undo the last action"),
G_CALLBACK (on_editor_command_undo_activate)},
{ "ActionEditRedo", GTK_STOCK_REDO, N_("_Redo"), "<control>r",
{ "ActionEditRedo", GTK_STOCK_REDO, N_("_Redo"), "<shift><control>z",
N_("Redo the last undone action"),
G_CALLBACK (on_editor_command_redo_activate)},
{ "ActionEditCut", GTK_STOCK_CUT, N_("C_ut"), "<control>x",
......
......@@ -230,7 +230,7 @@ file_buffer_new_from_path (const char *path, const char *buf, int len, int pos)
{
const AnjutaEncoding *encoding_used = NULL;
gchar* converted_text;
guint converted_len;
gsize converted_len;
converted_text = anjuta_convert_to_utf8 (fb->buf,
fb->len,
&encoding_used,
......@@ -297,9 +297,10 @@ file_buffer_line_from_pos(FileBuffer *fb, int pos)
g_return_val_if_fail(fb && pos >= 0, 1);
if (FB_FILE == fb->type)
{
gchar* p = g_utf8_offset_to_pointer(fb->buf, pos);
for (tmp = fb->lines; tmp; tmp = g_list_next(tmp))
{
if (pos < GPOINTER_TO_INT(tmp->data))
if (p - fb->buf < GPOINTER_TO_INT(tmp->data))
return lineno;
++ lineno;
}
......@@ -321,11 +322,13 @@ gchar *
file_match_line_from_pos(FileBuffer *fb, int pos)
{
gint length=1;
int start;
gint i;
g_return_val_if_fail(fb && pos >= 0, NULL);
for (i= pos+1; ((fb->buf[i] != '\n') && (fb->buf[i] != '\0')); i++, length++);
for (i= pos-1; (fb->buf[i] != '\n') && (i >= 0); i--, length++);
start = g_utf8_offset_to_pointer(fb->buf, pos) - fb->buf;
for (i= start+1; ((fb->buf[i] != '\n') && (fb->buf[i] != '\0')); i++, length++);
for (i= start-1; (fb->buf[i] != '\n') && (i >= 0); i--, length++);
return g_strndup (fb->buf + i + 1, length);
}
......@@ -403,27 +406,29 @@ isawordchar (gunichar c)
}
static gboolean
extra_match (FileBuffer *fb, gchar* begin, gchar* end, SearchExpression *s)
extra_match (gboolean at_start, gchar* begin, gchar* end, SearchExpression *s)
{
gunichar b, e;
b = g_utf8_get_char (g_utf8_prev_char (begin));
e = g_utf8_get_char (end);
if (g_unichar_ismark(e))
return FALSE; /* matched character continues past match end */
if (s->whole_line)
if ((fb->pos == 0 || b == '\n' || b == '\r') &&
if ((at_start || b == '\n' || b == '\r') &&
(e == '\0' || e == '\n' || e == '\r'))
return TRUE;
else
return FALSE;
else if (s->whole_word)
if ((fb->pos ==0 || !isawordchar(b)) &&
if ((at_start || !isawordchar(b)) &&
(e=='\0' || !isawordchar(e)))
return TRUE;
else
return FALSE;
else if (s->word_start)
if (fb->pos ==0 || !isawordchar(b))
if (at_start || !isawordchar(b))
return TRUE;
else
return FALSE;
......@@ -431,201 +436,230 @@ extra_match (FileBuffer *fb, gchar* begin, gchar* end, SearchExpression *s)
return TRUE;
}
/* Returns the next match in the passed buffer. The search expression should
be pre-compiled. The returned pointer should be freed with match_info_free()
when no longer required. */
MatchInfo *
get_next_match(FileBuffer *fb, SearchDirection direction, SearchExpression *s)
static MatchInfo *
get_next_regex_match(FileBuffer *fb, SearchDirection direction, SearchExpression *s)
{
MatchInfo *mi = NULL;
g_return_val_if_fail(fb && s, NULL);
GMatchInfo* match_info;
if (s->regex)
if (s->regex_info == NULL)
{
GMatchInfo* match_info;
if (s->regex_info == NULL)
GError* error = NULL;
GRegexCompileFlags compile_flags = 0;
GRegexMatchFlags match_flags = 0;
match_flags |= G_REGEX_MATCH_NOTEMPTY;
if (!s->match_case)
{
GError* error = NULL;
GRegexCompileFlags compile_flags = 0;
GRegexMatchFlags match_flags = 0;
match_flags |= G_REGEX_MATCH_NOTEMPTY;
if (!s->match_case)
{
compile_flags |= G_REGEX_CASELESS;
}
if (!s->greedy)
{
compile_flags |= G_REGEX_UNGREEDY;
}
s->regex_info = g_regex_new (s->search_str, compile_flags,
match_flags, &error);
if (error)
{
anjuta_util_dialog_error (NULL, error->message);
g_error_free(error);
s->regex_info = NULL;
return NULL;
}
compile_flags |= G_REGEX_CASELESS;
}
if (!s->greedy)
{
compile_flags |= G_REGEX_UNGREEDY;
}
s->regex_info = g_regex_new (s->search_str, compile_flags,
match_flags, &error);
if (error)
{
anjuta_util_dialog_error (NULL, error->message);
g_error_free(error);
s->regex_info = NULL;
return NULL;
}
}
g_regex_match_full (s->regex_info, fb->buf, fb->len,
g_utf8_offset_to_pointer (fb->buf, fb->pos) - fb->buf,
G_REGEX_MATCH_NOTEMPTY, &match_info, NULL);
if (g_match_info_matches (match_info))
g_regex_match_full (s->regex_info, fb->buf, fb->len,
g_utf8_offset_to_pointer (fb->buf, fb->pos) - fb->buf,
G_REGEX_MATCH_NOTEMPTY, &match_info, NULL);
if (g_match_info_matches (match_info))
{
gint start;
gint end;
int i;
mi = g_new0(MatchInfo, 1);
if (g_match_info_fetch_pos (match_info, 0, &start, &end))
{
gint start;
gint end;
int i;
mi = g_new0(MatchInfo, 1);
if (g_match_info_fetch_pos (match_info, 0, &start, &end))
{
DEBUG_PRINT ("Regex: %d %d", start, end);
mi->pos = g_utf8_pointer_to_offset (fb->buf, fb->buf + start);
mi->len = g_utf8_pointer_to_offset (fb->buf, fb->buf + end) - mi->pos;
mi->line = file_buffer_line_from_pos(fb, mi->pos);
}
for (i = 1; i < g_match_info_get_match_count(match_info); i++) /* Captured subexpressions */
DEBUG_PRINT ("Regex: %d %d", start, end);
mi->pos = g_utf8_pointer_to_offset (fb->buf, fb->buf + start);
mi->len = g_utf8_pointer_to_offset (fb->buf, fb->buf + end) - mi->pos;
mi->line = file_buffer_line_from_pos(fb, mi->pos);
}
for (i = 1; i < g_match_info_get_match_count(match_info); i++) /* Captured subexpressions */
{
MatchSubStr *ms;
ms = g_new0(MatchSubStr, 1);
if (g_match_info_fetch_pos (match_info, i, &start, &end))
{
MatchSubStr *ms;
ms = g_new0(MatchSubStr, 1);
if (g_match_info_fetch_pos (match_info, i, &start, &end))
{
ms->start = g_utf8_pointer_to_offset (fb->buf, fb->buf + start);
ms->len = g_utf8_pointer_to_offset (fb->buf, fb->buf + end) - ms->start;
}
mi->subs = g_list_prepend(mi->subs, ms);
ms->start = g_utf8_pointer_to_offset (fb->buf, fb->buf + start);
ms->len = g_utf8_pointer_to_offset (fb->buf, fb->buf + end) - ms->start;
}
mi->subs = g_list_reverse(mi->subs);
fb->pos = g_utf8_pointer_to_offset (fb->buf, fb->buf + end);
mi->subs = g_list_prepend(mi->subs, ms);
}
mi->subs = g_list_reverse(mi->subs);
fb->pos = g_utf8_pointer_to_offset (fb->buf, fb->buf + end);
}
return mi;
}
static MatchInfo *match_info(FileBuffer *fb, gchar* match, gchar* match_end,
SearchDirection direction)
{
MatchInfo *mi = g_new0 (MatchInfo, 1);
mi->pos = g_utf8_pointer_to_offset(fb->buf, match);
mi->len = g_utf8_pointer_to_offset(match, match_end);
mi->line = file_buffer_line_from_pos (fb, mi->pos);
if (direction == SD_BACKWARD)
fb->pos = mi->pos;
else
fb->pos = mi->pos + mi->len;
return mi;
}
/* Get the next match, given that the search string is ASCII (as will nearly
* always be the case). In this case we don't need to perform any UTF-8
* normalization, since any bytes with values 0-127 in a UTF-8 string always
* actually represent ASCII characters. */
static MatchInfo *
get_next_ascii_match(FileBuffer *fb, SearchDirection direction, SearchExpression *s)
{
int len = strlen(s->search_str);
gint (*compare)(const gchar *, const gchar *, gsize) =
s->match_case ? strncmp : g_ascii_strncasecmp;
gchar *p = g_utf8_offset_to_pointer (fb->buf, fb->pos);
if (direction == SD_BACKWARD)
{
for (p -= len; p >= fb->buf; --p)
if (!compare(p, s->search_str, len) &&
extra_match(p == fb->buf, p, p + len, s))
return match_info(fb, p, p + len, direction);
}
else
{
/* Simple string search - this needs to be performance-tuned */
gboolean found;
gint match_len;
for (; *p ; ++p)
if (!compare(p, s->search_str, len) &&
extra_match(p == fb->buf, p, p + len, s))
return match_info(fb, p, p + len, direction);
}
return NULL;
}
match_len = g_utf8_strlen (s->search_str, -1);
if (match_len == 0)
return NULL;
/* Normalize a UTF-8 string, optionally folding case. Returns NULL if invalid. */
static gchar* normalize(gchar *s, int len_bytes, gboolean match_case) {
gchar *c, *n;
if (match_case)
return g_utf8_normalize(s, len_bytes, G_NORMALIZE_NFD);
c = g_utf8_casefold(s, len_bytes);
n = g_utf8_normalize(c, -1, G_NORMALIZE_NFD);
g_free(c);
return n;
}
found = FALSE;
if (SD_BACKWARD == direction)
/* Given a UTF-8 string s, advance past a prefix whose normalized length in
* bytes is [normal_bytes]. */
static gchar* normal_advance(gchar *s, int normal_bytes, gboolean match_case)
{
while (*s && normal_bytes > 0)
{
gchar *next = g_utf8_next_char(s);
gchar *n = normalize(s, next - s, match_case);
normal_bytes -= strlen(n);
g_free(n);
s = next;
}
return s;
}
static MatchInfo *
get_next_utf8_match(FileBuffer *fb, SearchDirection direction, SearchExpression *s)
{
gchar* search_key = normalize(s->search_str, -1, s->match_case);
gchar* current = g_utf8_offset_to_pointer (fb->buf, fb->pos);
gchar* search_buf;
gchar* p = NULL;
MatchInfo *mi = NULL;
int keylen;
if (!search_key)
return NULL;
keylen = strlen(search_key);
if (direction == SD_BACKWARD)
{
search_buf = normalize(fb->buf, current - fb->buf, s->match_case);
if (search_buf)
{
/* Backward matching. */
if (!s->match_case)
p = search_buf + strlen(search_buf);
while ((p = g_strrstr_len(search_buf, p - search_buf, search_key)) != NULL)
{
gchar* current = g_utf8_offset_to_pointer (fb->buf, fb->pos);
gint len = g_utf8_strlen (s->search_str, -1);
gchar* search_caseless = g_utf8_casefold (s->search_str, len);
for (; fb->pos >= len; --fb->pos)
{
gchar* current_caseless = g_utf8_casefold (current, len);
if (g_utf8_collate (current_caseless, search_caseless) == 0 &&
extra_match (fb, current, current + strlen (search_caseless),
s))
{
found = TRUE;
g_free (current_caseless);
break;
}
else
current = g_utf8_prev_char (current);
}
g_free (search_caseless);
}
else
{
gchar* current = g_utf8_offset_to_pointer (fb->buf, fb->pos);
gint len = g_utf8_strlen (s->search_str, -1);
gchar* search_key = g_utf8_collate_key (s->search_str, len);
for (; fb->pos >= len; --fb->pos)
{
gchar* current_key = g_utf8_collate_key (current, len);
if (g_str_equal (current_key, search_key) &&
extra_match (fb, current, current + strlen (s->search_str),
s))
{
found = TRUE;
g_free (current_key);
break;
}
else
current = g_utf8_prev_char (current);
}
g_free (search_key);
if (extra_match (p == search_buf, p, p + keylen, s))
break;
p = p + keylen - 1;
}
}
else
}
else
{ /* forward search */
search_buf = normalize(current, -1, s->match_case);
if (search_buf)
{
/* Forward match */
if (!s->match_case)
{
gchar* current = g_utf8_offset_to_pointer (fb->buf, fb->pos);
gint len = g_utf8_strlen (s->search_str, -1);
gchar* search_caseless = g_utf8_casefold (s->search_str, len);
gint buf_len = g_utf8_strlen (fb->buf, fb->len);
for (; fb->pos < buf_len; ++fb->pos)
{
gchar* current_caseless = g_utf8_casefold (current, len);
if (g_utf8_collate (current_caseless, search_caseless) == 0 &&
extra_match (fb, current, current + strlen (search_caseless),
s))
{
found = TRUE;
g_free (current_caseless);
break;
}
else
current = g_utf8_next_char (current);
}
g_free (search_caseless);
}
else
p = search_buf;
while ((p = strstr(p, search_key)) != NULL)
{
gchar* current = g_utf8_offset_to_pointer (fb->buf, fb->pos);
gint len = g_utf8_strlen (s->search_str, -1);
gint buf_len = g_utf8_strlen (fb->buf, fb->len);
gchar* search_key = g_utf8_collate_key (s->search_str, len);
for (; fb->pos < buf_len; ++fb->pos)
{
gchar* current_key = g_utf8_collate_key (current, len);
if (g_str_equal (current_key, search_key) &&
extra_match (fb, current, current + strlen (s->search_str),
s))
{
found = TRUE;
g_free (current_key);
break;
}
else
{
g_free (current_key);
current = g_utf8_next_char (current);
}
}
g_free (search_key);
if (extra_match (fb->pos == 0 && p == search_buf, p, p + keylen, s))
break;
++p;
}
}
if (found)
{
mi = g_new0 (MatchInfo, 1); //better to abort than silently fail to report match ?
mi->pos = fb->pos;
mi->len = match_len;
mi->line = file_buffer_line_from_pos (fb, fb->pos);
if (direction == SD_BACKWARD)
fb->pos -= match_len;
else
fb->pos += match_len;
}
}
g_free(search_key);
if (p)
{
/* We've found a match in the normalized buffer. Now find the
* corresponding position in the source buffer. */
gchar* match = normal_advance(
direction == SD_BACKWARD ? fb->buf : current,
p - search_buf, s->match_case);
gchar* match_end = normal_advance(match, keylen, s->match_case);
mi = match_info(fb, match, match_end, direction);
}
g_free(search_buf);
return mi;
}
static gboolean str_is_ascii(gchar* s)
{
for (; *s; ++s)
if (!isascii(*s))
return FALSE;
return TRUE;
}
/* Returns the next match in the passed buffer. If direction is SD_BACKWARD,
finds the previous match whose end falls before the current position.
The search expression should be pre-compiled. The returned pointer should
be freed with match_info_free() when no longer required. */
MatchInfo *
get_next_match(FileBuffer *fb, SearchDirection direction, SearchExpression *s)
{
g_return_val_if_fail(fb && s, NULL);
if (s->regex)
return get_next_regex_match(fb, direction, s);
return str_is_ascii(s->search_str) ?
get_next_ascii_match(fb, direction, s) :
get_next_utf8_match(fb, direction, s);
}
/* Create list of search entries */
GList *
create_search_entries (Search *s)
......
......@@ -142,16 +142,24 @@ typedef enum _FileBufferType
typedef struct _FileBuffer
{
FileBufferType type;
/* The following are valid only for files loaded from disk */
gchar *name; /* Name of the file */
gchar *path; /* Full path to the file */
gchar *uri; /* URI to the file */
gchar *buf; /* Contents of the file */
gchar *buf; /* Contents of the file, null-terminated */
gint len; /* Length of the buffer */
gint pos; /* Current position */
gint endpos; /* Restrict action upto this position */
/* Current position: a count of UTF-8 characters, *not* bytes. */
gint pos;
gint line; /* Current line */
GList *lines; /* List of integers specifying line start positions */
GList *lines; /* List of integers specifying line start positions in bytes */
gchar *canonical; /* buffer text converted to canonical utf-8 form */
gchar *canonical_p; /* current pointer into canonical text */
/* The following are valid only for files corresponding to a TextEditor */
IAnjutaEditor *te;
} FileBuffer;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment