From 9c32266c6738d57acf7c9ba69f2a17f977122e1a Mon Sep 17 00:00:00 2001 From: Carsten Dominik Date: Mon, 4 Jan 2010 23:28:23 +0100 Subject: [PATCH] Improve the logic of the search view. --- doc/ChangeLog | 5 ++ doc/org.texi | 4 +- lisp/ChangeLog | 9 ++++ lisp/org-agenda.el | 142 ++++++++++++++++++++++++++++++++++++----------------- 4 files changed, 115 insertions(+), 45 deletions(-) diff --git a/doc/ChangeLog b/doc/ChangeLog index a715dfd6c..f062f068d 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,8 @@ +2010-01-05 Carsten Dominik + + * org.texi (Search view): Point to the docstring of + `org-search-view' for more details. + 2009-12-14 Carsten Dominik * org.texi (Agenda commands): Document that `>' prompts for a diff --git a/doc/org.texi b/doc/org.texi index de8646cd5..5aed68180 100644 --- a/doc/org.texi +++ b/doc/org.texi @@ -6958,7 +6958,9 @@ logic. The search string @samp{+computer +wifi -ethernet -@{8\.11[bg]@}} will search for note entries that contain the keywords @code{computer} and @code{wifi}, but not the keyword @code{ethernet}, and which are also not matched by the regular expression @code{8\.11[bg]}, meaning to -exclude both 8.11b and 8.11g. +exclude both 8.11b and 8.11g. The first @samp{+} is necessary to turn on +word search, other @samp{+} characters are optional. For more details, see +the docstring of the command @code{org-search-view}. @vindex org-agenda-text-search-extra-files Note that in addition to the agenda files, this command will also search diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 91fd4949e..239995088 100755 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,12 @@ +2010-01-05 Carsten Dominik + + * org-agenda.el (org-agenda-search-view-always-boolean): New option. + (org-agenda-search-view-search-words-only): Obsolete variable, is + now an alias for `org-agenda-search-view-always-boolean'. + (org-agenda-search-view-force-full-words): New option. + (org-search-view): Improve docstring, and implement a better logic + for Boolean and phrase searches. + 2010-01-04 Carsten Dominik * org-latex.el (org-export-as-latex): Do the first letbind in the diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el index fb472c3cf..7f21afdb6 100644 --- a/lisp/org-agenda.el +++ b/lisp/org-agenda.el @@ -891,14 +891,39 @@ current display in the agenda." :group 'org-agenda-daily/weekly :type 'plist) -(defcustom org-agenda-search-view-search-words-only nil - "Non-nil means, the search string is interpreted as individual words -The search then looks for each word separately in each entry and -selects entries that have matches for all words. -When nil, matching as loose words will only take place if the first -word is preceded by + or -. If that is not the case, the search -string will just be matched as a substring in the entry, but with -each space character allowing for any whitespace, including newlines." +(defcustom org-agenda-search-view-always-boolean nil + "Non-nil means, the search string is interpreted as individual parts. + +The search string for search view can either be interpreted as a phrase, +or as a list of snippets that define a boolean search for a number of +strings. + +When this is non-nil, the string will be split on whitespace, and each +snippet will be searched individually, and all must match in order to +select an entry. If a snippet is preceeded by \"-\", the snippet +must *not* match. \"+\" is syntactic sugar for positive selection. +Each snipped may be found as a full word or a partial word, but see +the variable `org-agenda-search-view-force-full-words'. + +When this is nil, search will look for the entire search phrase as one, +with each space character matching any amount of whitespace, including +line breaks. + +Even when this is nil, you can still switch to Boolean search dynamically +by preceeding the first snippet with \"+\" or \"-\". If the first snippet +is a regexp marked with braces like \"{abc}\", this will also switch to +boolean search." + :group 'org-agenda-search-view + :type 'boolean) + +(if (fboundp 'defvaralias) + (defvaralias 'org-agenda-search-view-search-words-only + 'org-agenda-search-view-always-boolean)) + +(defcustom org-agenda-search-view-force-full-words nil + "Non-nil me +ans, search words must be matches as complete words. +When nil, they may also match part of a word." :group 'org-agenda-search-view :type 'boolean) @@ -3209,8 +3234,6 @@ that when \"+Ameli\" is searched as a work, it will also match \"Ameli's\"") ;;;###autoload (defun org-search-view (&optional todo-only string edit-at) "Show all entries that contain words or regular expressions. -If the first character of the search string is an asterisks, -search only the headlines. With optional prefix argument TODO-ONLY, only consider entries that are TODO entries. The argument STRING can be used to pass a default search @@ -3218,28 +3241,36 @@ string into this function. If EDIT-AT is non-nil, it means that the user should get a chance to edit this string, with cursor at position EDIT-AT. -The search string is broken into \"words\" by splitting at whitespace. -Depending on the variable `org-agenda-search-view-search-words-only' -and on whether the first character in the search string is \"+\" or \"-\", -The string is then interpreted either as a substring with variable amounts -of whitespace, or as a list or individual words that should be matched. - -The default is a substring match, where each space in the search string -can expand to an arbitrary amount of whitespace, including newlines. - -If matching individual words, these words are then interpreted as a -boolean expression with logical AND. Words prefixed with a minus must -not occur in the entry. Words without a prefix or prefixed with a plus -must occur in the entry. Matching is case-insensitive and the words -are enclosed by word delimiters. - -Words enclosed by curly braces are interpreted as regular expressions -that must or must not match in the entry. - -If the search string starts with an asterisk, search only in headlines. -If (possibly after the leading star) the search string starts with an -exclamation mark, this also means to look at TODO entries only, an effect -that can also be achieved with a prefix argument. +The search string can be viewed either as a phrase that should be found as +is, or it can be broken into a number of snippets, each of which must match +in a Boolean way to select an entry. The default depends on the variable +`org-agenda-search-view-always-boolean'. +Even if this is turned off (the default) you can always switch to +Boolean search dynamically by preceeding the first word with \"+\" or \"-\". + +The default is a direct search of the whole phrase, where each space in +the search string can expand to an arbitrary amount of whitespace, +including newlines. + +If using a Boolean search, the search string is split on whitespace and +each snipped is search separately, with logical AND to select an entry. +Words prefixed with a minus must *not* occur in the entry. Words without +a prefix or prefixed with a plus must occur in the entry. Matching is +case-insensitive. Words are enclosed by word delimiters (i.e. they must +match whole words, not parts of a word) if +`org-agenda-search-view-force-full-words' is set (default is nil). + +Boolean search snippets enclosed by curly braces are interpreted as +regular expressions that must or (when preceeded with \"-\") must not +match in the entry. + +- If the search string starts with an asterisk, search only in headlines. +- If (possibly after the leading star) the search string starts with an + exclamation mark, this also means to look at TODO entries only, an effect + that can also be achieved with a prefix argument. +- If (possibly after star and exclamation mark) the seatch string starts + with a colon, this will mean that the snippets of the boolean search + must match as full words. This command searches the agenda files, and in addition the files listed in `org-agenda-text-search-extra-files'." @@ -3254,17 +3285,21 @@ in `org-agenda-text-search-extra-files'." 'org-complex-heading-regexp org-complex-heading-regexp 'mouse-face 'highlight 'help-echo (format "mouse-2 or RET jump to location"))) + (full-words org-agenda-search-view-force-full-words) regexp rtn rtnall files file pos - marker category tags c neg re as-words + marker category tags c neg re boolean ee txt beg end words regexps+ regexps- hdl-only buffer beg1 str) (unless (and (not edit-at) (stringp string) (string-match "\\S-" string)) - (setq string (read-string "[+-]Word/{Regexp} ...: " - (cond - ((integerp edit-at) (cons string edit-at)) - (edit-at string)) - 'org-agenda-search-history))) + (setq string (read-string + (if org-agenda-search-view-always-boolean + "[+-]Word/{Regexp} ...: " + "Phrase, or [+-]Word/{Regexp} ...: ") + (cond + ((integerp edit-at) (cons string edit-at)) + (edit-at string)) + 'org-agenda-search-history))) (org-set-local 'org-todo-only todo-only) (setq org-agenda-redo-command (list 'org-search-view (if todo-only t nil) string @@ -3278,21 +3313,40 @@ in `org-agenda-text-search-extra-files'." (when (equal (string-to-char words) ?!) (setq todo-only t words (substring words 1))) - (if (or org-agenda-search-view-search-words-only - (member (string-to-char string) '(?- ?+))) - (setq as-words t)) + (when (equal (string-to-char words) ?:) + (setq full-words t + words (substring words 1))) + (if (or org-agenda-search-view-always-boolean + (member (string-to-char string) '(?- ?+ ?\{))) + (setq boolean t)) (setq words (org-split-string words)) - (if as-words + (when boolean + (let (wds w) + (while (setq w (pop words)) + (if (or (equal (substring w 0 1) "\"") + (and (> (length w) 1) + (member (substring w 0 1) '("+" "-")) + (equal (substring w 1 2) "\""))) + (while (and words (not (equal (substring w -1) "\""))) + (setq w (concat w " " (pop words))))) + (and (string-match "\\`\\([-+]?\\)\"" w) + (setq w (replace-match "\\1" nil nil w))) + (and (equal (substring w -1) "\"") (setq w (substring w 0 -1))) + (push w wds)) + (setq words (nreverse wds)))) + (if boolean (mapc (lambda (w) (setq c (string-to-char w)) (if (equal c ?-) (setq neg t w (substring w 1)) (if (equal c ?+) (setq neg nil w (substring w 1)) - (setq neg nil))) + (setq neg nil))) (if (string-match "\\`{.*}\\'" w) (setq re (substring w 1 -1)) - (setq re (concat "\\<" (regexp-quote (downcase w)) "\\>"))) + (if full-words + (setq re (concat "\\<" (regexp-quote (downcase w)) "\\>")) + (setq re (regexp-quote (downcase w))))) (if neg (push re regexps-) (push re regexps+))) words) (push (mapconcat (lambda (w) (regexp-quote w)) words "\\s-+") -- 2.11.4.GIT