]> code.delx.au - gnu-emacs/blobdiff - lisp/net/xesam.el
Merge from emacs-23
[gnu-emacs] / lisp / net / xesam.el
index c864bc2cb83113039955bf5a4b4f26af763cb1cb..234a89393a9606cc2200896d5263972a26837327 100644 (file)
@@ -1,16 +1,16 @@
 ;;; xesam.el --- Xesam interface to search engines.
 
-;; Copyright (C) 2008 Free Software Foundation, Inc.
+;; Copyright (C) 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
 
 ;; Author: Michael Albinus <michael.albinus@gmx.de>
 ;; Keywords: tools, hypermedia
 
 ;; This file is part of GNU Emacs.
 
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs is free software: you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 3, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, see
-;; <http://www.gnu.org/licenses/>.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 
-;; This package provides an interface to the Xesam, a D-Bus based "eXtEnsible
+;; This package provides an interface to Xesam, a D-Bus based "eXtEnsible
 ;; Search And Metadata specification".  It has been tested with
 ;;
 ;; xesam-glib 0.3.4, xesam-tools 0.6.1
@@ -33,7 +32,7 @@
 ;; The precondition for this package is a D-Bus aware Emacs.  This is
 ;; configured per default, when Emacs is built on a machine running
 ;; D-Bus.  Furthermore, there must be at least one search engine
-;; running, which support the Xesam interface.  Beagle and strigi have
+;; running, which supports the Xesam interface.  Beagle and strigi have
 ;; been tested; tracker, pinot and recoll are also said to support
 ;; Xesam.  You can check the existence of such a search engine by
 ;;
 ;; 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
 
 ;; Widgets are used to highlight the search results.
 (require 'widget)
-
-(eval-when-compile
-  (require 'wid-edit))
+(require 'wid-edit)
 
 ;; `run-at-time' is used in the signal handler.
 (require 'timer)
 (defgroup xesam nil
   "Xesam compatible interface to search engines."
   :group 'extensions
-  :group 'hypermedia
+  :group 'comm
   :version "23.1")
 
 (defcustom xesam-query-type 'user-query
          (const :tag "Xesam fulltext query" fulltext-query)))
 
 (defcustom xesam-hits-per-page 20
-  "Number of search hits to be displayed in the result buffer"
+  "Number of search hits to be displayed in the result buffer."
   :group 'xesam
   :type 'integer)
 
+(defface xesam-mode-line '((t :inherit mode-line-emphasis))
+  "Face to highlight mode line."
+  :group 'xesam)
+
+(defface xesam-highlight '((t :inherit match))
+  "Face to highlight query entries.
+It will be overlayed by `widget-documentation-face', so it shall
+be different at least in one face property not set in that face."
+  :group 'xesam)
+
 (defvar xesam-debug nil
   "Insert debug information in the help echo.")
 
@@ -238,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)))
@@ -289,14 +322,20 @@ from VALUE, depending on what the search engine accepts."
   "Interactive query history.")
 
 ;; Pacify byte compiler.
+(defvar xesam-vendor nil)
+(make-variable-buffer-local 'xesam-vendor)
+(put 'xesam-vendor 'permanent-local t)
+
 (defvar xesam-engine nil)
 (defvar xesam-search nil)
 (defvar xesam-type nil)
 (defvar xesam-query nil)
 (defvar xesam-xml-string nil)
+(defvar xesam-objects nil)
 (defvar xesam-current nil)
 (defvar xesam-count nil)
 (defvar xesam-to nil)
+(defvar xesam-notify-function nil)
 (defvar xesam-refreshing nil)
 
 \f
@@ -338,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'.
@@ -372,20 +413,25 @@ If there is no registered search engine at all, the function returns `nil'."
        ;; That is not the case now, so we set it ourselves.
        ;; Hopefully, this will change later.
        (setq hit-fields
-             (cond
-              ((string-equal vendor-id "Beagle")
-               '("xesam:mimeType" "xesam:url"))
-              ((string-equal vendor-id "Strigi")
-               '("xesam:author" "xesam:cc" "xesam:cc" "xesam:charset"
-                 "xesam:contentType" "xesam:fileExtension" "xesam:id"
-                 "xesam:lineCount" "xesam:links" "xesam:mimeType" "xesam:name"
-                 "xesam:size" "xesam:sourceModified" "xesam:subject"
-                 "xesam:to" "xesam:url"))
-              ((string-equal vendor-id "TrackerXesamSession")
-               '("xesam:relevancyRating" "xesam:url"))
-              ;; xesam-tools yahoo service.
-              (t '("xesam:contentModified" "xesam:mimeType" "xesam:summary"
-                   "xesam:title" "xesam:url" "yahoo:displayUrl"))))
+             (case (intern vendor-id)
+               ('Beagle
+                '("xesam:mimeType" "xesam:url"))
+               ('Strigi
+                '("xesam:author" "xesam:cc" "xesam:charset"
+                  "xesam:contentType" "xesam:fileExtension"
+                  "xesam:id" "xesam:lineCount" "xesam:links"
+                  "xesam:mimeType" "xesam:name" "xesam:size"
+                  "xesam:sourceModified" "xesam:subject" "xesam:to"
+                  "xesam:url"))
+               ('TrackerXesamSession
+                '("xesam:relevancyRating" "xesam:url"))
+               ('Debbugs
+                '("xesam:keyword" "xesam:owner" "xesam:title"
+                  "xesam:url" "xesam:sourceModified" "xesam:mimeType"
+                  "debbugs:key"))
+               ;; xesam-tools yahoo service.
+               (t '("xesam:contentModified" "xesam:mimeType" "xesam:summary"
+                    "xesam:title" "xesam:url" "yahoo:displayUrl"))))
 
        (xesam-set-property engine "hit.fields" hit-fields)
        (xesam-set-property engine "hit.fields.extended" '("xesam:snippet"))
@@ -405,7 +451,11 @@ If there is no registered search engine at all, the function returns `nil'."
 In this mode, widgets represent the search results.
 
 \\{xesam-mode-map}
-Turning on Xesam mode runs the normal hook `xesam-mode-hook'."
+Turning on Xesam mode runs the normal hook `xesam-mode-hook'.  It
+can be used to set `xesam-notify-function', which must a search
+engine specific, widget :notify function to visualize xesam:url."
+  (set (make-local-variable 'xesam-notify-function) nil)
+
   ;; Keymap.
   (setq xesam-mode-map (copy-keymap special-mode-map))
   (set-keymap-parent xesam-mode-map widget-keymap)
@@ -420,12 +470,16 @@ Turning on Xesam mode runs the normal hook `xesam-mode-hook'."
   (set (make-local-variable 'xesam-type) "")
   (set (make-local-variable 'xesam-query) "")
   (set (make-local-variable 'xesam-xml-string) "")
+  (set (make-local-variable 'xesam-objects) nil)
   ;; `xesam-current' is the last hit put into the search buffer,
   (set (make-local-variable 'xesam-current) 0)
   ;; `xesam-count' is the number of hits reported by the search engine.
   (set (make-local-variable 'xesam-count) 0)
   ;; `xesam-to' is the upper hit number to be presented.
   (set (make-local-variable 'xesam-to) xesam-hits-per-page)
+  ;; `xesam-notify-function' can be a search engine specific function
+  ;; to visualize xesam:url.  It can be overwritten in `xesam-mode'.
+  (set (make-local-variable 'xesam-notify-function) nil)
   ;; `xesam-refreshing' is an indicator, whether the buffer is just
   ;; being updated.  Needed, because `xesam-refresh-search-buffer'
   ;; can be triggered by an event.
@@ -439,16 +493,16 @@ Turning on Xesam mode runs the normal hook `xesam-mode-hook'."
        (list '(20
               (:eval
                (list "Type: "
-                     (propertize xesam-type 'face 'font-lock-type-face))))
+                     (propertize xesam-type 'face 'xesam-mode-line))))
             '(10
               (:eval
                (list " Query: "
                      (propertize
                       xesam-query
-                      'face 'font-lock-type-face
+                      '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))
@@ -457,28 +511,64 @@ Turning on Xesam mode runs the normal hook `xesam-mode-hook'."
 ;; It doesn't make sense to call it interactively.
 (put 'xesam-mode 'disabled t)
 
+;; The very first buffer created with `xesam-mode' does not have the
+;; 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.
 SEARCH is the search identification in that engine.  Both must be strings."
   (format "*%s/%s*" service search))
 
-(defun xesam-refresh-entry (engine search)
+(defun xesam-highlight-string (string)
+  "Highlight text enclosed by <b> and </b>.
+Return propertized STRING."
+  (while (string-match "\\(.*\\)\\(<b>\\)\\(.*\\)\\(</b>\\)\\(.*\\)" string)
+    (setq string
+         (format
+          "%s%s%s"
+          (match-string 1 string)
+          (propertize (match-string 3 string) 'face 'xesam-highlight)
+          (match-string 5 string))))
+  string)
+
+(defun xesam-refresh-entry (engine entry)
   "Refreshes one entry in the search buffer."
-  (let* ((result
-         (car
-          (xesam-dbus-call-method
-           :session (car engine) xesam-path-search
-           xesam-interface-search "GetHits" search 1)))
-        (snippet)
-        ;; We must disable this for the time being; the search
-        ;; engines don't return usable values so far.
-;        (caaar
-;         (dbus-ignore-errors
-;           (xesam-dbus-call-method
-;            :session  (car engine) xesam-path-search
-;            xesam-interface-search "GetHitData"
-;            search (list xesam-current) '("snippet")))))
+  (let* ((result (nth (1- xesam-current) xesam-objects))
         widget)
 
     ;; Create widget.
@@ -488,7 +578,10 @@ SEARCH is the search identification in that engine.  Both must be strings."
 
     ;; Take all results.
     (dolist (field (xesam-get-cached-property engine "hit.fields"))
-      (when (not (zerop (length (caar result))))
+      (when (cond
+            ((stringp (caar result)) (not (zerop (length (caar result)))))
+            ((numberp (caar result)) (not (zerop (caar result))))
+            ((caar result) t))
        (when xesam-debug
          (widget-put
           widget :help-echo
@@ -503,6 +596,12 @@ SEARCH is the search identification in that engine.  Both must be strings."
       (widget-put
        widget :xesam:url (concat "file://" (widget-get widget :xesam:url))))
 
+    ;; Strigi returns xesam:size as string.  We must fix this.
+    (when (and (widget-member widget :xesam:size)
+              (stringp (widget-get widget :xesam:size)))
+      (widget-put
+       widget :xesam:size (string-to-number (widget-get widget :xesam:url))))
+
     ;; First line: :tag.
     (cond
      ((widget-member widget :xesam:title)
@@ -514,8 +613,16 @@ SEARCH is the search identification in that engine.  Both must be strings."
      ((widget-member widget :xesam:name)
       (widget-put widget :tag (widget-get widget :xesam:name))))
 
+    ;; Highlight the search items.
+    (when (widget-member widget :tag)
+      (widget-put
+       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
@@ -530,6 +637,11 @@ SEARCH is the search identification in that engine.  Both must be strings."
     (widget-put widget :value (widget-get widget :xesam:url))
 
     (cond
+     ;; A search engine can set `xesam-notify-function' via
+     ;; `xesam-mode-hooks'.
+     (xesam-notify-function
+      (widget-put widget :notify xesam-notify-function))
+
      ;; In case of HTML, we use a URL link.
      ((and (widget-member widget :xesam:mimeType)
           (string-equal "text/html" (widget-get widget :xesam:mimeType)))
@@ -541,9 +653,12 @@ SEARCH is the search identification in that engine.  Both must be strings."
                               (widget-get widget :xesam:url))))
       (widget-put
        widget :notify
-       '(lambda (widget &rest ignore)
-         (find-file
-          (url-filename (url-generic-parse-url (widget-value widget))))))
+       (lambda (widget &rest ignore)
+        (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))))))
@@ -556,11 +671,12 @@ SEARCH is the search identification in that engine.  Both must be strings."
       (widget-put widget :doc (widget-get widget :xesam:snippet))))
 
     (when (widget-member widget :doc)
-      (widget-put widget :help-echo (widget-get widget :doc))
       (with-temp-buffer
-       (insert (widget-get widget :doc))
+       (insert
+        (xesam-highlight-string (widget-get widget :doc)))
        (fill-region-as-paragraph (point-min) (point-max))
-       (widget-put widget :doc (buffer-string))))
+       (widget-put widget :doc (buffer-string)))
+      (widget-put widget :help-echo (widget-get widget :doc)))
 
     ;; Format the widget.
     (widget-put
@@ -576,33 +692,62 @@ SEARCH is the search identification in that engine.  Both must be strings."
     (force-mode-line-update)
     (redisplay)))
 
+(defun xesam-get-hits (engine search hits)
+  "Retrieve hits from ENGINE."
+  (with-current-buffer (xesam-buffer-name (car engine) search)
+    (setq xesam-objects
+         (append xesam-objects
+                 (xesam-dbus-call-method
+                  :session (car engine) xesam-path-search
+                  xesam-interface-search "GetHits" search hits)))))
+
 (defun xesam-refresh-search-buffer (engine search)
   "Refreshes the buffer, presenting results of SEARCH."
   (with-current-buffer (xesam-buffer-name (car engine) search)
     ;; Work only if nobody else is here.
-    (unless xesam-refreshing
+    (unless (or xesam-refreshing (>= xesam-current xesam-to))
       (setq xesam-refreshing t)
       (unwind-protect
-         (let (widget updated)
-           ;; Add all result widgets.  The upper boundary is always
-           ;; computed, because new hits might have arrived while
-           ;; running.
+         (let (widget)
+
+           ;; Retrieve needed hits for visualization.
+           (while (> (min xesam-to xesam-count) (length xesam-objects))
+             (xesam-get-hits
+              engine search
+              (min xesam-hits-per-page
+                   (- (min xesam-to xesam-count) (length xesam-objects)))))
+
+           ;; Add all result widgets.
            (while (< xesam-current (min xesam-to xesam-count))
-             (setq updated t
-                   xesam-current (1+ xesam-current))
+             (setq xesam-current (1+ xesam-current))
              (xesam-refresh-entry engine search))
 
            ;; Add "NEXT" widget.
-           (when (and updated (> xesam-count xesam-to))
+           (when (> xesam-count xesam-to)
              (goto-char (point-max))
              (widget-create
               'link
               :notify
-              '(lambda (widget &rest ignore)
-                 (setq xesam-to (+ xesam-to xesam-hits-per-page))
-                 (widget-delete widget)
-                 (xesam-refresh-search-buffer xesam-engine xesam-search))
+              (lambda (widget &rest ignore)
+                (setq xesam-to (+ xesam-to xesam-hits-per-page))
+                (widget-delete widget)
+                (xesam-refresh-search-buffer xesam-engine xesam-search))
               "NEXT")
+             (widget-beginning-of-line))
+
+           ;; Prefetch next hits.
+           (when (> (min (+ xesam-hits-per-page xesam-to) xesam-count)
+                    (length xesam-objects))
+             (xesam-get-hits
+              engine search
+              (min xesam-hits-per-page
+                   (- (min (+ xesam-hits-per-page xesam-to) xesam-count)
+                      (length xesam-objects)))))
+
+           ;; Add "DONE" widget.
+           (when (= xesam-current xesam-count)
+             (goto-char (point-max))
+             (widget-create 'link :notify 'ignore "DONE")
              (widget-beginning-of-line)))
 
        ;; Return with save settings.
@@ -632,9 +777,17 @@ SEARCH is the search identification in that engine.  Both must be strings."
 
         ((string-equal member "SearchDone")
          (setq mode-line-process
-               (propertize " Done" 'face 'font-lock-type-face))
+               (propertize " Done" 'face 'xesam-mode-line))
          (force-mode-line-update)))))))
 
+(defun xesam-kill-buffer-function ()
+  "Send the CloseSearch indication."
+  (when (and (eq major-mode 'xesam-mode) (stringp 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.
 ENGINE identifies the search engine.  TYPE is the query type, it
@@ -646,7 +799,7 @@ search, is returned."
         (xml-string
          (format
           (if (eq type 'user-query) xesam-user-query xesam-fulltext-query)
-          query))
+          (url-insert-entities-in-string query)))
         (search (xesam-dbus-call-method
                  :session service xesam-path-search
                  xesam-interface-search "NewSearch" session xml-string)))
@@ -667,6 +820,10 @@ search, is returned."
     (with-current-buffer
        (generate-new-buffer (xesam-buffer-name service search))
       (switch-to-buffer-other-window (current-buffer))
+      ;; Inialize buffer with `xesam-mode'.  `xesam-vendor' must be
+      ;; set before calling `xesam-mode', because we want to give the
+      ;; hook functions a chance to identify their search engine.
+      (setq xesam-vendor (xesam-get-cached-property engine "vendor.id"))
       (xesam-mode)
       (setq xesam-engine engine
            xesam-search search
@@ -675,26 +832,26 @@ search, is returned."
            xesam-type (symbol-name type)
            xesam-query query
            xesam-xml-string xml-string
+           xesam-objects nil
            ;; The buffer identification shall indicate the search
            ;; engine.  The `help-echo' property is used for debug
            ;; information, when applicable.
            mode-line-buffer-identification
            (if (not xesam-debug)
-               (list
-                12 (propertized-buffer-identification
-                    (xesam-get-cached-property engine "vendor.id")))
+               (list 12 (propertized-buffer-identification xesam-vendor))
              (propertize
-              (xesam-get-cached-property engine "vendor.id")
+              xesam-vendor
               'help-echo
               (mapconcat
-               '(lambda (x)
-                  (format "%s: %s" x (xesam-get-cached-property engine x)))
+               (lambda (x)
+                 (format "%s: %s" x (xesam-get-cached-property engine x)))
                '("vendor.id" "vendor.version" "vendor.display" "vendor.xesam"
                  "vendor.ontology.fields" "vendor.ontology.contents"
                  "vendor.ontology.sources" "vendor.extensions"
                  "vendor.ontologies" "vendor.maxhits")
                "\n"))))
-         (force-mode-line-update))
+      (add-hook 'kill-buffer-hook 'xesam-kill-buffer-function)
+      (force-mode-line-update))
 
     ;; Start the search.
     (xesam-dbus-call-method
@@ -704,6 +861,7 @@ search, is returned."
     ;; Return search id.
     search))
 
+;;;###autoload
 (defun xesam-search (engine query)
   "Perform an interactive search.
 ENGINE is the Xesam search engine to be applied, it must be one of the
@@ -717,7 +875,7 @@ Example:
   (xesam-search (car (xesam-search-engines)) \"emacs\")"
   (interactive
    (let* ((vendors (mapcar
-                   '(lambda (x) (xesam-get-cached-property x "vendor.display"))
+                   (lambda (x) (xesam-get-cached-property x "vendor.display"))
                    (xesam-search-engines)))
          (vendor
           (if (> (length vendors) 1)
@@ -748,18 +906,17 @@ Example:
 
 ;;; TODO:
 
-;; * Solve error, that xesam-mode does not work the very first time.
-;; * Retrieve several results at once.
-;; * Retrieve hits for the next page in advance.
+;; * 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.
 ;; * `revert-buffer-function' implementation.
-;; * Close search when search buffer is killed.
 ;;
 ;; * Mid term
 ;;   - If available, use ontologies for field selection.
 ;;   - Search engines for Emacs bugs database, wikipedia, google,
 ;;     yahoo, ebay, ...
+;;   - Construct complex queries via widgets, like in mairix.el.
 
 ;; arch-tag: 7fb9fc6c-c2ff-4bc7-bb42-bacb80cce2b2
 ;;; xesam.el ends here