Branch: refs/heads/master Author: Colomban Wendling ban@herbesfolles.org Committer: Colomban Wendling ban@herbesfolles.org Date: Sat, 09 Aug 2014 23:51:48 UTC Commit: 593b9dd2c9345c381a8929c8aaabbba968b2e472 https://github.com/geany/geany/commit/593b9dd2c9345c381a8929c8aaabbba968b2e4...
Log Message: ----------- Merge branch 'single-line-regex'
Modified Paths: -------------- doc/geany.txt src/callbacks.c src/document.c src/keybindings.c src/search.c src/search.h
Modified: doc/geany.txt 35 lines changed, 35 insertions(+), 0 deletions(-) =================================================================== @@ -1231,6 +1231,10 @@ The syntax for the *Use regular expressions* option is shown in .. note:: *Use escape sequences* is implied for regular expressions.
+The *Use multi-line matching* option enables multi-line regular +expressions instead of single-line ones. See `Regular expressions`_ for +more details on the differences between the two modes. + The *Use escape sequences* option will transform any escaped characters into their UTF-8 equivalent. For example, \t will be transformed into a tab character. Other recognized symbols are: \\, \n, \r, \uXXXX @@ -1451,10 +1455,17 @@ options`_). The syntax is Perl compatible. Basic syntax is described in the table below. For full details, see http://www.geany.org/manual/gtk/glib/glib-regex-syntax.html.
+By default regular expressions are matched on a line-by-line basis. +If you are interested in multi-line regular expressions, matched against +the whole buffer at once, see the section `Multi-line regular expressions`_ +below. + .. note:: 1. The *Use escape sequences* dialog option always applies for regular expressions. 2. Searching backwards with regular expressions is not supported. + 3. The *Use multi-line matching* dialog option to select single or + multi-line matching.
**In a regular expression, the following characters are interpreted:**
@@ -1531,6 +1542,30 @@ $ This matches the end of a line. distributed under the `License for Scintilla and SciTE`_.
+Multi-line regular expressions +`````````````````````````````` + +.. note:: + The *Use multi-line matching* dialog option enables multi-line + regular expressions. + +Multi-line regular expressions work just like single-line ones but a +match can span several lines. + +While the syntax is the same, a few practical differences applies: + +======= ============================================================ +. Matches any character but newlines. This behavior can be changed + to also match newlines using the (?s) option, see + http://www.geany.org/manual/gtk/glib/glib-regex-syntax.html#idp5671632 + +[^...] A negative range (see above) *will* match newlines if they are + not explicitly listed in that negative range. For example, range + [^a-z] will match newlines, while range [^a-z\r\n] won't. + While this is the expected behavior, it can lead to tricky + problems if one doesn't think about it when writing an expression. +======= ============================================================ +
View menu ---------
Modified: src/callbacks.c 6 lines changed, 3 insertions(+), 3 deletions(-) =================================================================== @@ -837,14 +837,14 @@ static void find_usage(gboolean in_session) if (sci_has_selection(doc->editor->sci)) { /* take selected text if there is a selection */ search_text = sci_get_selection_contents(doc->editor->sci); - flags = SCFIND_MATCHCASE; + flags = GEANY_FIND_MATCHCASE; } else { editor_find_current_word_sciwc(doc->editor, -1, editor_info.current_word, GEANY_MAX_WORD_LENGTH); search_text = g_strdup(editor_info.current_word); - flags = SCFIND_MATCHCASE | SCFIND_WHOLEWORD; + flags = GEANY_FIND_MATCHCASE | GEANY_FIND_WHOLEWORD; }
search_find_usage(search_text, search_text, flags, in_session); @@ -934,7 +934,7 @@ G_MODULE_EXPORT void on_find_next1_activate(GtkMenuItem *menuitem, gpointer user
G_MODULE_EXPORT void on_find_previous1_activate(GtkMenuItem *menuitem, gpointer user_data) { - if (search_data.flags & SCFIND_REGEXP) + if (search_data.flags & GEANY_FIND_REGEXP) /* Can't reverse search order for a regex (find next ignores search backwards) */ utils_beep(); else
Modified: src/document.c 6 lines changed, 3 insertions(+), 3 deletions(-) =================================================================== @@ -1491,7 +1491,7 @@ static void replace_header_filename(GeanyDocument *doc) ttf.chrg.cpMax = sci_get_position_from_line(doc->editor->sci, 4); ttf.lpstrText = filebase;
- if (search_find_text(doc->editor->sci, SCFIND_MATCHCASE | SCFIND_REGEXP, &ttf, NULL) != -1) + if (search_find_text(doc->editor->sci, GEANY_FIND_MATCHCASE | GEANY_FIND_REGEXP, &ttf, NULL) != -1) { sci_set_target_start(doc->editor->sci, ttf.chrgText.cpMin); sci_set_target_end(doc->editor->sci, ttf.chrgText.cpMax); @@ -2043,7 +2043,7 @@ gint document_find_text(GeanyDocument *doc, const gchar *text, const gchar *orig return -1;
/* Sci doesn't support searching backwards with a regex */ - if (flags & SCFIND_REGEXP) + if (flags & GEANY_FIND_REGEXP) search_backwards = FALSE;
if (!original_text) @@ -2124,7 +2124,7 @@ gint document_replace_text(GeanyDocument *doc, const gchar *find_text, const gch return -1;
/* Sci doesn't support searching backwards with a regex */ - if (flags & SCFIND_REGEXP) + if (flags & GEANY_FIND_REGEXP) search_backwards = FALSE;
if (!original_find_text)
Modified: src/keybindings.c 4 lines changed, 2 insertions(+), 2 deletions(-) =================================================================== @@ -1466,9 +1466,9 @@ static gboolean cb_func_search_action(guint key_id) text = get_current_word_or_sel(doc, TRUE);
if (sci_has_selection(sci)) - search_mark_all(doc, text, SCFIND_MATCHCASE); + search_mark_all(doc, text, GEANY_FIND_MATCHCASE); else - search_mark_all(doc, text, SCFIND_MATCHCASE | SCFIND_WHOLEWORD); + search_mark_all(doc, text, GEANY_FIND_MATCHCASE | GEANY_FIND_WHOLEWORD);
g_free(text); break;
Modified: src/search.c 151 lines changed, 104 insertions(+), 47 deletions(-) =================================================================== @@ -95,12 +95,14 @@ static struct gint fif_files_mode; gchar *fif_files; gboolean find_regexp; + gboolean find_regexp_multiline; gboolean find_escape_sequences; gboolean find_case_sensitive; gboolean find_match_whole_word; gboolean find_match_word_start; gboolean find_close_dialog; gboolean replace_regexp; + gboolean replace_regexp_multiline; gboolean replace_escape_sequences; gboolean replace_case_sensitive; gboolean replace_match_whole_word; @@ -237,6 +239,8 @@ static void init_prefs(void) configuration_add_pref_group(group, FALSE); stash_group_add_toggle_button(group, &settings.find_regexp, "find_regexp", FALSE, "check_regexp"); + stash_group_add_toggle_button(group, &settings.find_regexp_multiline, + "find_regexp_multiline", FALSE, "check_multiline"); stash_group_add_toggle_button(group, &settings.find_case_sensitive, "find_case_sensitive", FALSE, "check_case"); stash_group_add_toggle_button(group, &settings.find_escape_sequences, @@ -253,6 +257,8 @@ static void init_prefs(void) configuration_add_pref_group(group, FALSE); stash_group_add_toggle_button(group, &settings.replace_regexp, "replace_regexp", FALSE, "check_regexp"); + stash_group_add_toggle_button(group, &settings.replace_regexp_multiline, + "replace_regexp_multiline", FALSE, "check_multiline"); stash_group_add_toggle_button(group, &settings.replace_case_sensitive, "replace_case_sensitive", FALSE, "check_case"); stash_group_add_toggle_button(group, &settings.replace_escape_sequences, @@ -300,7 +306,7 @@ static void on_widget_toggled_set_insensitive( static GtkWidget *add_find_checkboxes(GtkDialog *dialog) { GtkWidget *checkbox1, *checkbox2, *check_regexp, *check_back, *checkbox5, - *checkbox7, *hbox, *fbox, *mbox; + *checkbox7, *check_multiline, *hbox, *fbox, *mbox;
check_regexp = gtk_check_button_new_with_mnemonic(_("_Use regular expressions")); ui_hookup_widget(dialog, check_regexp, "check_regexp"); @@ -310,33 +316,36 @@ static GtkWidget *add_find_checkboxes(GtkDialog *dialog) g_signal_connect(check_regexp, "toggled", G_CALLBACK(on_find_replace_checkbutton_toggled), dialog);
- if (dialog != GTK_DIALOG(find_dlg.dialog)) - { - check_back = gtk_check_button_new_with_mnemonic(_("Search _backwards")); - ui_hookup_widget(dialog, check_back, "check_back"); - gtk_button_set_focus_on_click(GTK_BUTTON(check_back), FALSE); - } - else - { /* align the two checkboxes at the top of the hbox */ - GtkSizeGroup *label_size; - check_back = gtk_label_new(NULL); - label_size = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL); - gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size), check_back); - gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size), check_regexp); - g_object_unref(label_size); - } checkbox7 = gtk_check_button_new_with_mnemonic(_("Use _escape sequences")); ui_hookup_widget(dialog, checkbox7, "check_escape"); gtk_button_set_focus_on_click(GTK_BUTTON(checkbox7), FALSE); gtk_widget_set_tooltip_text(checkbox7, - _("Replace \\, \t, \n, \r and \uXXXX (Unicode chararacters) with the " + _("Replace \\, \t, \n, \r and \uXXXX (Unicode characters) with the " "corresponding control characters"));
+ check_multiline = gtk_check_button_new_with_mnemonic(_("Use multi-_line matching")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_multiline), FALSE); + gtk_widget_set_sensitive(check_multiline, FALSE); + ui_hookup_widget(dialog, check_multiline, "check_multiline"); + gtk_button_set_focus_on_click(GTK_BUTTON(check_multiline), FALSE); + gtk_widget_set_tooltip_text(check_multiline, _("Perform regular expression " + "matching on the whole buffer at once rather than line by line, allowing " + "matches to span multiple lines. In this mode, newline characters are part " + "of the input and can be captured as normal characters by the pattern.")); + /* Search features */ fbox = gtk_vbox_new(FALSE, 0); - gtk_container_add(GTK_CONTAINER(fbox), check_regexp); - gtk_container_add(GTK_CONTAINER(fbox), checkbox7); - gtk_container_add(GTK_CONTAINER(fbox), check_back); + gtk_box_pack_start(GTK_BOX(fbox), check_regexp, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(fbox), check_multiline, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(fbox), checkbox7, FALSE, FALSE, 0); + + if (dialog != GTK_DIALOG(find_dlg.dialog)) + { + check_back = gtk_check_button_new_with_mnemonic(_("Search _backwards")); + ui_hookup_widget(dialog, check_back, "check_back"); + gtk_button_set_focus_on_click(GTK_BUTTON(check_back), FALSE); + gtk_container_add(GTK_CONTAINER(fbox), check_back); + }
checkbox1 = gtk_check_button_new_with_mnemonic(_("C_ase sensitive")); ui_hookup_widget(dialog, checkbox1, "check_case"); @@ -356,9 +365,9 @@ static GtkWidget *add_find_checkboxes(GtkDialog *dialog)
/* Matching options */ mbox = gtk_vbox_new(FALSE, 0); - gtk_container_add(GTK_CONTAINER(mbox), checkbox1); - gtk_container_add(GTK_CONTAINER(mbox), checkbox2); - gtk_container_add(GTK_CONTAINER(mbox), checkbox5); + gtk_box_pack_start(GTK_BOX(mbox), checkbox1, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(mbox), checkbox2, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(mbox), checkbox5, FALSE, FALSE, 0);
hbox = gtk_hbox_new(TRUE, 6); gtk_container_add(GTK_CONTAINER(hbox), fbox); @@ -1142,6 +1151,7 @@ on_find_replace_checkbutton_toggled(GtkToggleButton *togglebutton, gpointer user GtkWidget *check_word = ui_lookup_widget(dialog, "check_word"); GtkWidget *check_wordstart = ui_lookup_widget(dialog, "check_wordstart"); GtkWidget *check_escape = ui_lookup_widget(dialog, "check_escape"); + GtkWidget *check_multiline = ui_lookup_widget(dialog, "check_multiline"); gboolean replace = (dialog != find_dlg.dialog); const char *back_button[2] = { "btn_previous" , "check_back" };
@@ -1150,6 +1160,7 @@ on_find_replace_checkbutton_toggled(GtkToggleButton *togglebutton, gpointer user gtk_widget_set_sensitive(ui_lookup_widget(dialog, back_button[replace]), ! regex_set); gtk_widget_set_sensitive(check_word, ! regex_set); gtk_widget_set_sensitive(check_wordstart, ! regex_set); + gtk_widget_set_sensitive(check_multiline, regex_set); } }
@@ -1259,20 +1270,21 @@ static void on_find_entry_activate_backward(GtkEntry *entry, gpointer user_data) { /* can't search backwards with a regexp */ - if (search_data.flags & SCFIND_REGEXP) + if (search_data.flags & GEANY_FIND_REGEXP) utils_beep(); else on_find_dialog_response(NULL, GEANY_RESPONSE_FIND_PREVIOUS, user_data); }
-static gboolean int_search_flags(gint match_case, gint whole_word, gint regexp, gint word_start) +static gboolean int_search_flags(gint match_case, gint whole_word, gint regexp, gint multiline, gint word_start) { - return (match_case ? SCFIND_MATCHCASE : 0) | - (regexp ? SCFIND_REGEXP | SCFIND_POSIX : 0) | - (whole_word ? SCFIND_WHOLEWORD : 0) | + return (match_case ? GEANY_FIND_MATCHCASE : 0) | + (regexp ? GEANY_FIND_REGEXP : 0) | + (whole_word ? GEANY_FIND_WHOLEWORD : 0) | + (multiline ? GEANY_FIND_MULTILINE : 0) | /* SCFIND_WORDSTART overrides SCFIND_WHOLEWORD, but we want the opposite */ - (word_start && !whole_word ? SCFIND_WORDSTART : 0); + (word_start && !whole_word ? GEANY_FIND_WORDSTART : 0); }
@@ -1302,7 +1314,8 @@ on_find_dialog_response(GtkDialog *dialog, gint response, gpointer user_data) search_data.text = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(user_data))))); search_data.original_text = g_strdup(search_data.text); search_data.flags = int_search_flags(settings.find_case_sensitive, - settings.find_match_whole_word, settings.find_regexp, settings.find_match_word_start); + settings.find_match_whole_word, settings.find_regexp, settings.find_regexp_multiline, + settings.find_match_word_start);
if (EMPTY(search_data.text)) { @@ -1311,7 +1324,7 @@ on_find_dialog_response(GtkDialog *dialog, gint response, gpointer user_data) gtk_widget_grab_focus(find_dlg.entry); return; } - if (search_data.flags & SCFIND_REGEXP) + if (search_data.flags & GEANY_FIND_REGEXP) { GRegex *regex = compile_regex(search_data.text, search_data.flags); if (!regex) @@ -1443,16 +1456,16 @@ on_replace_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
search_flags_re = int_search_flags(settings.replace_case_sensitive, settings.replace_match_whole_word, settings.replace_regexp, - settings.replace_match_word_start); + settings.replace_regexp_multiline, settings.replace_match_word_start);
- if ((response != GEANY_RESPONSE_FIND) && (search_flags_re & SCFIND_MATCHCASE) + if ((response != GEANY_RESPONSE_FIND) && (search_flags_re & GEANY_FIND_MATCHCASE) && (strcmp(find, replace) == 0)) goto fail;
original_find = g_strdup(find); original_replace = g_strdup(replace);
- if (search_flags_re & SCFIND_REGEXP) + if (search_flags_re & GEANY_FIND_REGEXP) { GRegex *regex = compile_regex(find, search_flags_re); if (regex) @@ -1919,11 +1932,13 @@ static GRegex *compile_regex(const gchar *str, gint sflags) { GRegex *regex; GError *error = NULL; - gint rflags = G_REGEX_MULTILINE; + gint rflags = 0;
- if (~sflags & SCFIND_MATCHCASE) + if (sflags & GEANY_FIND_MULTILINE) + rflags |= G_REGEX_MULTILINE; + if (~sflags & GEANY_FIND_MATCHCASE) rflags |= G_REGEX_CASELESS; - if (sflags & (SCFIND_WHOLEWORD | SCFIND_WORDSTART)) + if (sflags & (GEANY_FIND_WHOLEWORD | GEANY_FIND_WORDSTART)) { geany_debug("%s: Unsupported regex flags found!", G_STRFUNC); } @@ -1952,14 +1967,45 @@ static gint find_regex(ScintillaObject *sci, guint pos, GRegex *regex, GeanyMatc const gchar *text; GMatchInfo *minfo; gint ret = -1; + gint offset = 0;
g_return_val_if_fail(pos <= (guint)sci_get_length(sci), -1);
- /* Warning: any SCI calls will invalidate 'text' after calling SCI_GETCHARACTERPOINTER */ - text = (void*)scintilla_send_message(sci, SCI_GETCHARACTERPOINTER, 0, 0); + if (g_regex_get_compile_flags(regex) & G_REGEX_MULTILINE) + { + /* Warning: any SCI calls will invalidate 'text' after calling SCI_GETCHARACTERPOINTER */ + text = (void*)scintilla_send_message(sci, SCI_GETCHARACTERPOINTER, 0, 0); + g_regex_match_full(regex, text, -1, pos, 0, &minfo, NULL); + } + else /* single-line mode, manually match against each line */ + { + gint line = sci_get_line_from_position(sci, pos); + + for (;;) + { + gint start = sci_get_position_from_line(sci, line); + gint end = sci_get_line_end_position(sci, line); + + text = (void*)scintilla_send_message(sci, SCI_GETRANGEPOINTER, start, end - start); + if (g_regex_match_full(regex, text, end - start, pos - start, 0, &minfo, NULL)) + { + offset = start; + break; + } + else /* not found, try next line */ + { + line ++; + if (line >= sci_get_line_count(sci)) + break; + pos = sci_get_position_from_line(sci, line); + /* don't free last info, it's freed below */ + g_match_info_free(minfo); + } + } + }
/* Warning: minfo will become invalid when 'text' does! */ - if (g_regex_match_full(regex, text, -1, pos, 0, &minfo, NULL)) + if (g_match_info_matches(minfo)) { guint i;
@@ -1971,8 +2017,8 @@ static gint find_regex(ScintillaObject *sci, guint pos, GRegex *regex, GeanyMatc gint start = -1, end = -1;
g_match_info_fetch_pos(minfo, (gint)i, &start, &end); - match->matches[i].start = start; - match->matches[i].end = end; + match->matches[i].start = offset + start; + match->matches[i].end = offset + end; } match->start = match->matches[0].start; match->end = match->matches[0].end; @@ -1987,7 +2033,7 @@ gint search_find_prev(ScintillaObject *sci, const gchar *str, gint flags, GeanyM { gint ret;
- g_return_val_if_fail(! (flags & SCFIND_REGEXP), -1); + g_return_val_if_fail(! (flags & GEANY_FIND_REGEXP), -1);
ret = sci_search_prev(sci, flags, str); if (ret != -1 && match_) @@ -1996,6 +2042,17 @@ gint search_find_prev(ScintillaObject *sci, const gchar *str, gint flags, GeanyM }
+static gint geany_find_flags_to_sci_flags(gint flags) +{ + g_warn_if_fail(! (flags & GEANY_FIND_MULTILINE)); + + return ((flags & GEANY_FIND_MATCHCASE) ? SCFIND_MATCHCASE : 0) | + ((flags & GEANY_FIND_WHOLEWORD) ? SCFIND_WHOLEWORD : 0) | + ((flags & GEANY_FIND_REGEXP) ? SCFIND_REGEXP | SCFIND_POSIX : 0) | + ((flags & GEANY_FIND_WORDSTART) ? SCFIND_WORDSTART : 0); +} + + gint search_find_next(ScintillaObject *sci, const gchar *str, gint flags, GeanyMatchInfo **match_) { GeanyMatchInfo *match; @@ -2003,9 +2060,9 @@ gint search_find_next(ScintillaObject *sci, const gchar *str, gint flags, GeanyM gint ret = -1; gint pos;
- if (~flags & SCFIND_REGEXP) + if (~flags & GEANY_FIND_REGEXP) { - ret = sci_search_next(sci, flags, str); + ret = sci_search_next(sci, geany_find_flags_to_sci_flags(flags), str); if (ret != -1 && match_) *match_ = match_info_new(flags, ret, ret + strlen(str)); return ret; @@ -2087,9 +2144,9 @@ gint search_find_text(ScintillaObject *sci, gint flags, struct Sci_TextToFind *t GRegex *regex; gint ret;
- if (~flags & SCFIND_REGEXP) + if (~flags & GEANY_FIND_REGEXP) { - ret = sci_find_text(sci, flags, ttf); + ret = sci_find_text(sci, geany_find_flags_to_sci_flags(flags), ttf); if (ret != -1 && match_) *match_ = match_info_new(flags, ttf->chrgText.cpMin, ttf->chrgText.cpMax); return ret;
Modified: src/search.h 9 lines changed, 9 insertions(+), 0 deletions(-) =================================================================== @@ -61,6 +61,15 @@ enum GeanyFindSelOptions GEANY_FIND_SEL_AGAIN };
+enum GeanyFindFlags +{ + GEANY_FIND_MATCHCASE = 1 << 0, + GEANY_FIND_WHOLEWORD = 1 << 1, + GEANY_FIND_WORDSTART = 1 << 2, + GEANY_FIND_REGEXP = 1 << 3, + GEANY_FIND_MULTILINE = 1 << 4 +}; + /** Search preferences */ typedef struct GeanySearchPrefs {
-------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).