Merge changes from emacs-23 branch.
[bpt/emacs.git] / lisp / net / xesam.el
index dda1260..03c1880 100644 (file)
@@ -1,6 +1,6 @@
 ;;; xesam.el --- Xesam interface to search engines.
 
-;; Copyright (C) 2008 Free Software Foundation, Inc.
+;; Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc.
 
 ;; Author: Michael Albinus <michael.albinus@gmx.de>
 ;; Keywords: tools, hypermedia
 ;; can be selected via minibuffer completion.  Afterwards, the query
 ;; shall be entered in the minibuffer.
 
+;; Search results are presented in a new buffer.  This buffer has the
+;; major mode `xesam-mode', with the following keybindings:
+
+;;   SPC       `scroll-up'
+;;   DEL       `scroll-down'
+;;   <         `beginning-of-buffer'
+;;   >         `end-of-buffer'
+;;   q         `quit-window'
+;;   z         `kill-this-buffer'
+;;   g         `revert-buffer'
+
+;; The search results are represented by widgets.  Navigation commands
+;; are the usual widget navigation commands:
+
+;;   TAB       `widget-forward'
+;;   <backtab> `widget-backward'
+
+;; Applying RET, <down-mouse-1>, or <down-mouse-2> on a URL belonging
+;; to the widget, brings up more details of the search hit.  The way,
+;; how this hit is presented, depends on the type of the hit.  HTML
+;; files are opened via `browse-url'.  Local files are opened in a new
+;; buffer, with highlighted search hits (highlighting can be toggled
+;; by `xesam-minor-mode' in that buffer).
+
 ;;; Code:
 
 ;; D-Bus support in the Emacs core can be disabled with configuration
 (defgroup xesam nil
   "Xesam compatible interface to search engines."
   :group 'extensions
-  :group 'hypermedia
+  :group 'comm
   :version "23.1")
 
 (defcustom xesam-query-type 'user-query
@@ -245,6 +269,8 @@ fields are supported.")
 </request>"
   "The Xesam fulltext query XML.")
 
+(declare-function dbus-get-unique-name "dbusbind.c" (bus))
+
 (defvar xesam-dbus-unique-names
   (list (cons :system (dbus-get-unique-name :system))
        (cons :session (dbus-get-unique-name :session)))
@@ -351,6 +377,8 @@ If PROPERTY is not existing, retrieve it from ENGINE first."
   (setq xesam-search-engines
        (delete (assoc (car args) xesam-search-engines) xesam-search-engines)))
 
+(defvar dbus-debug)
+
 (defun xesam-search-engines ()
   "Return Xesam search engines, stored in `xesam-search-engines'.
 The first search engine is the name owner of `xesam-service-search'.
@@ -474,7 +502,7 @@ engine specific, widget :notify function to visualize xesam:url."
                       'face 'xesam-mode-line
                       'help-echo (when xesam-debug xesam-xml-string)))))))
 
-  (when (not (interactive-p))
+  (when (not (called-interactively-p 'interactive))
     ;; Initialize buffer.
     (setq buffer-read-only t)
     (let ((inhibit-read-only t))
@@ -487,6 +515,39 @@ engine specific, widget :notify function to visualize xesam:url."
 ;; keymap etc.  So we create a dummy buffer.  Stupid.
 (with-temp-buffer (xesam-mode))
 
+(define-minor-mode xesam-minor-mode
+  "Toggle Xesam minor mode.
+With no argument, this command toggles the mode.
+Non-null prefix argument turns on the mode.
+Null prefix argument turns off the mode.
+
+When Xesam minor mode is enabled, all text which matches a
+previous Xesam query in this buffer is highlighted."
+  :group 'xesam
+  :init-value nil
+  :lighter " Xesam"
+  (when (local-variable-p 'xesam-query)
+    ;; Run only if the buffer is related to a Xesam search.
+    (save-excursion
+      (if xesam-minor-mode
+         ;; Highlight hits.
+         (let ((query-regexp (regexp-opt (split-string xesam-query nil t) t))
+               (case-fold-search t))
+           ;; I have no idea whether people will like setting
+           ;; `isearch-case-fold-search' and `query-regexp'.  Maybe
+           ;; this shall be controlled by a custom option.
+           (unless isearch-case-fold-search (isearch-toggle-case-fold))
+           (isearch-update-ring query-regexp t)
+           ;; Create overlays.
+           (goto-char (point-min))
+           (while (re-search-forward query-regexp nil t)
+             (overlay-put
+              (make-overlay
+               (match-beginning 0) (match-end 0)) 'face 'xesam-highlight)))
+       ;; Remove overlays.
+       (dolist (ov (overlays-in (point-min) (point-max)))
+         (delete-overlay ov))))))
+
 (defun xesam-buffer-name (service search)
   "Return the buffer name where to present search results.
 SERVICE is the D-Bus unique service name of the Xesam search engine.
@@ -494,7 +555,8 @@ SEARCH is the search identification in that engine.  Both must be strings."
   (format "*%s/%s*" service search))
 
 (defun xesam-highlight-string (string)
-  "Highlight text enclosed by <b> and </b>."
+  "Highlight text enclosed by <b> and </b>.
+Return propertized STRING."
   (while (string-match "\\(.*\\)\\(<b>\\)\\(.*\\)\\(</b>\\)\\(.*\\)" string)
     (setq string
          (format
@@ -557,7 +619,10 @@ SEARCH is the search identification in that engine.  Both must be strings."
        widget :tag (xesam-highlight-string (widget-get widget :tag))))
 
     ;; Last Modified.
-    (when (widget-member widget :xesam:sourceModified)
+    (when (and (widget-member widget :xesam:sourceModified)
+              (not
+               (zerop
+                (string-to-number (widget-get widget :xesam:sourceModified)))))
       (widget-put
        widget :tag
        (format
@@ -589,8 +654,11 @@ SEARCH is the search identification in that engine.  Both must be strings."
       (widget-put
        widget :notify
        (lambda (widget &rest ignore)
-        (find-file
-         (url-filename (url-generic-parse-url (widget-value widget))))))
+        (let ((query xesam-query))
+          (find-file
+           (url-filename (url-generic-parse-url (widget-value widget))))
+          (set (make-local-variable 'xesam-query) query)
+          (xesam-minor-mode 1))))
       (widget-put
        widget :value
        (url-filename (url-generic-parse-url (widget-get widget :xesam:url))))))
@@ -715,9 +783,10 @@ SEARCH is the search identification in that engine.  Both must be strings."
 (defun xesam-kill-buffer-function ()
   "Send the CloseSearch indication."
   (when (and (eq major-mode 'xesam-mode) (stringp xesam-search))
-    (xesam-dbus-call-method
-     :session (car xesam-engine) xesam-path-search
-     xesam-interface-search "CloseSearch" xesam-search)))
+    (ignore-errors ;; The D-Bus service could have disappeared.
+      (xesam-dbus-call-method
+       :session (car xesam-engine) xesam-path-search
+       xesam-interface-search "CloseSearch" xesam-search))))
 
 (defun xesam-new-search (engine type query)
   "Create a new search session.
@@ -837,6 +906,7 @@ Example:
 
 ;;; TODO:
 
+;; * Buffer highlighting needs better analysis of query string.
 ;; * Accept input while retrieving prefetched hits. `run-at-time'?
 ;; * With prefix, let's choose search engine.
 ;; * Minibuffer completion for user queries.