;;; 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
</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)))
(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'.
'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))
;; 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.
(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
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
(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))))))
(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.
;;; 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.