Commit | Line | Data |
---|---|---|
24008bc4 MA |
1 | ;;; xesam.el --- Xesam interface to search engines. |
2 | ||
acaf905b | 3 | ;; Copyright (C) 2008-2012 Free Software Foundation, Inc. |
24008bc4 MA |
4 | |
5 | ;; Author: Michael Albinus <michael.albinus@gmx.de> | |
6 | ;; Keywords: tools, hypermedia | |
7 | ||
8 | ;; This file is part of GNU Emacs. | |
9 | ||
5d4cc458 | 10 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
24008bc4 | 11 | ;; it under the terms of the GNU General Public License as published by |
5d4cc458 GM |
12 | ;; the Free Software Foundation, either version 3 of the License, or |
13 | ;; (at your option) any later version. | |
24008bc4 MA |
14 | |
15 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | ;; GNU General Public License for more details. | |
19 | ||
20 | ;; You should have received a copy of the GNU General Public License | |
5d4cc458 | 21 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
24008bc4 MA |
22 | |
23 | ;;; Commentary: | |
24 | ||
3cf235eb | 25 | ;; This package provides an interface to Xesam, a D-Bus based "eXtEnsible |
24008bc4 MA |
26 | ;; Search And Metadata specification". It has been tested with |
27 | ;; | |
28 | ;; xesam-glib 0.3.4, xesam-tools 0.6.1 | |
29 | ;; beagle 0.3.7, beagle-xesam 0.2 | |
5dd33078 | 30 | ;; strigi 0.5.11 |
24008bc4 MA |
31 | |
32 | ;; The precondition for this package is a D-Bus aware Emacs. This is | |
33 | ;; configured per default, when Emacs is built on a machine running | |
34 | ;; D-Bus. Furthermore, there must be at least one search engine | |
3cf235eb | 35 | ;; running, which supports the Xesam interface. Beagle and strigi have |
24008bc4 MA |
36 | ;; been tested; tracker, pinot and recoll are also said to support |
37 | ;; Xesam. You can check the existence of such a search engine by | |
38 | ;; | |
39 | ;; (dbus-list-queued-owners :session "org.freedesktop.xesam.searcher") | |
40 | ||
41 | ;; In order to start a search, you must load xesam.el: | |
42 | ;; | |
43 | ;; (require 'xesam) | |
44 | ||
45 | ;; xesam.el supports two types of queries, which are explained *very* short: | |
46 | ;; | |
47 | ;; * Full text queries. Just search keys shall be given, like | |
48 | ;; | |
49 | ;; hello world | |
50 | ;; | |
51 | ;; A full text query in xesam.el is restricted to files. | |
52 | ;; | |
53 | ;; * Xesam End User Search Language queries. The Xesam query language | |
54 | ;; is described at <http://xesam.org/main/XesamUserSearchLanguage>, | |
55 | ;; which must be consulted for the whole features. | |
56 | ;; | |
57 | ;; A query string consists of search keys, collectors, selectors, | |
58 | ;; and phrases. Search keys are words like in a full text query: | |
59 | ;; | |
60 | ;; hello word | |
61 | ;; | |
62 | ;; A selector is a tuple <keyword><relation>. <keyword> can be any | |
63 | ;; predefined Xesam keyword, the most common keywords are "ext" | |
64 | ;; (file name extension), "format " (mime type), "tag" (user | |
65 | ;; keywords) and "type" (types of items, like "audio", "file", | |
66 | ;; "picture", "attachment"). <relation> is a comparison to a value, | |
67 | ;; which must be a string (relation ":" or "=") or number (relation | |
68 | ;; "<=", ">=", "<", ">"): | |
69 | ;; | |
70 | ;; type:attachment ext=el | |
71 | ;; | |
72 | ;; A collector is one of the items "AND", "and", "&&", "OR", "or", | |
73 | ;; "||", or "-". The default collector on multiple terms is "AND"; | |
74 | ;; "-" means "AND NOT". | |
75 | ;; | |
76 | ;; albinus -type:file | |
77 | ;; | |
78 | ;; A phrase is a string enclosed in quotes, with appended modifiers | |
79 | ;; (single letters). Examples of modifiers are "c" (case | |
80 | ;; sensitive), "C" (case insensitive), "e" (exact match), "r" | |
81 | ;; (regular expression): | |
82 | ;; | |
83 | ;; "Hello world"c | |
84 | ||
85 | ;; You can customize, whether you want to apply a Xesam user query, or | |
86 | ;; a full text query. Note, that not every search engine supports | |
87 | ;; both query types. | |
88 | ;; | |
89 | ;; (setq xesam-query-type 'fulltext-query) | |
90 | ;; | |
e1dbe924 | 91 | ;; Another option to be customized is the number of hits to be |
24008bc4 MA |
92 | ;; presented at once. |
93 | ;; | |
94 | ;; (setq xesam-hits-per-page 50) | |
95 | ||
96 | ;; A search can be started by the command | |
97 | ;; | |
98 | ;; M-x xesam-search | |
99 | ;; | |
100 | ;; When several search engines are registered, the engine to be used | |
101 | ;; can be selected via minibuffer completion. Afterwards, the query | |
102 | ;; shall be entered in the minibuffer. | |
103 | ||
17668903 MA |
104 | ;; Search results are presented in a new buffer. This buffer has the |
105 | ;; major mode `xesam-mode', with the following keybindings: | |
106 | ||
107 | ;; SPC `scroll-up' | |
108 | ;; DEL `scroll-down' | |
109 | ;; < `beginning-of-buffer' | |
110 | ;; > `end-of-buffer' | |
111 | ;; q `quit-window' | |
112 | ;; z `kill-this-buffer' | |
113 | ;; g `revert-buffer' | |
114 | ||
115 | ;; The search results are represented by widgets. Navigation commands | |
116 | ;; are the usual widget navigation commands: | |
117 | ||
118 | ;; TAB `widget-forward' | |
119 | ;; <backtab> `widget-backward' | |
120 | ||
121 | ;; Applying RET, <down-mouse-1>, or <down-mouse-2> on a URL belonging | |
122 | ;; to the widget, brings up more details of the search hit. The way, | |
123 | ;; how this hit is presented, depends on the type of the hit. HTML | |
124 | ;; files are opened via `browse-url'. Local files are opened in a new | |
125 | ;; buffer, with highlighted search hits (highlighting can be toggled | |
126 | ;; by `xesam-minor-mode' in that buffer). | |
127 | ||
24008bc4 MA |
128 | ;;; Code: |
129 | ||
24008bc4 MA |
130 | (require 'dbus) |
131 | ||
24008bc4 MA |
132 | ;; Widgets are used to highlight the search results. |
133 | (require 'widget) | |
4edefb46 | 134 | (require 'wid-edit) |
24008bc4 MA |
135 | |
136 | ;; `run-at-time' is used in the signal handler. | |
137 | (require 'timer) | |
138 | ||
139 | ;; The default search field is "xesam:url". It must be inspected. | |
140 | (require 'url) | |
141 | ||
142 | (defgroup xesam nil | |
143 | "Xesam compatible interface to search engines." | |
144 | :group 'extensions | |
26f4b8ab | 145 | :group 'comm |
24008bc4 MA |
146 | :version "23.1") |
147 | ||
148 | (defcustom xesam-query-type 'user-query | |
149 | "Xesam query language type." | |
150 | :group 'xesam | |
151 | :type '(choice | |
152 | (const :tag "Xesam user query" user-query) | |
153 | (const :tag "Xesam fulltext query" fulltext-query))) | |
154 | ||
155 | (defcustom xesam-hits-per-page 20 | |
4edefb46 | 156 | "Number of search hits to be displayed in the result buffer." |
24008bc4 MA |
157 | :group 'xesam |
158 | :type 'integer) | |
159 | ||
4edefb46 MA |
160 | (defface xesam-mode-line '((t :inherit mode-line-emphasis)) |
161 | "Face to highlight mode line." | |
162 | :group 'xesam) | |
163 | ||
164 | (defface xesam-highlight '((t :inherit match)) | |
165 | "Face to highlight query entries. | |
333f9019 | 166 | It will be overlaid by `widget-documentation-face', so it shall |
4edefb46 MA |
167 | be different at least in one face property not set in that face." |
168 | :group 'xesam) | |
169 | ||
24008bc4 | 170 | (defvar xesam-debug nil |
de8bb89e | 171 | "Insert debug information in the help echo.") |
24008bc4 MA |
172 | |
173 | (defconst xesam-service-search "org.freedesktop.xesam.searcher" | |
174 | "The D-Bus name used to talk to Xesam.") | |
175 | ||
176 | (defconst xesam-path-search "/org/freedesktop/xesam/searcher/main" | |
177 | "The D-Bus object path used to talk to Xesam.") | |
178 | ||
179 | ;; Methods: "NewSession", "SetProperty", "GetProperty", | |
180 | ;; "CloseSession", "NewSearch", "StartSearch", "GetHitCount", | |
181 | ;; "GetHits", "GetHitData", "CloseSearch" and "GetState". | |
182 | ;; Signals: "HitsAdded", "HitsRemoved", "HitsModified", "SearchDone" | |
183 | ;; and "StateChanged". | |
184 | (defconst xesam-interface-search "org.freedesktop.xesam.Search" | |
185 | "The D-Bus Xesam search interface.") | |
186 | ||
187 | (defconst xesam-all-fields | |
818f12ce MA |
188 | '("xesam:35mmEquivalent" "xesam:aimContactMedium" "xesam:aperture" |
189 | "xesam:aspectRatio" "xesam:attachmentEncoding" "xesam:attendee" | |
ee7683eb | 190 | "xesam:audioBitrate" "xesam:audioChannels" "xesam:audioCodec" |
818f12ce MA |
191 | "xesam:audioCodecType" "xesam:audioSampleFormat" "xesam:audioSampleRate" |
192 | "xesam:author" "xesam:bcc" "xesam:birthDate" "xesam:blogContactURL" | |
24008bc4 MA |
193 | "xesam:cameraManufacturer" "xesam:cameraModel" "xesam:cc" "xesam:ccdWidth" |
194 | "xesam:cellPhoneNumber" "xesam:characterCount" "xesam:charset" | |
195 | "xesam:colorCount" "xesam:colorSpace" "xesam:columnCount" "xesam:comment" | |
196 | "xesam:commentCharacterCount" "xesam:conflicts" "xesam:contactMedium" | |
197 | "xesam:contactName" "xesam:contactNick" "xesam:contactPhoto" | |
198 | "xesam:contactURL" "xesam:contains" "xesam:contenKeyword" | |
199 | "xesam:contentComment" "xesam:contentCreated" "xesam:contentModified" | |
200 | "xesam:contentType" "xesam:contributor" "xesam:copyright" "xesam:creator" | |
201 | "xesam:definesClass" "xesam:definesFunction" "xesam:definesGlobalVariable" | |
202 | "xesam:deletionTime" "xesam:depends" "xesam:description" "xesam:device" | |
203 | "xesam:disclaimer" "xesam:documentCategory" "xesam:duration" | |
204 | "xesam:emailAddress" "xesam:eventEnd" "xesam:eventLocation" | |
205 | "xesam:eventStart" "xesam:exposureBias" "xesam:exposureProgram" | |
206 | "xesam:exposureTime" "xesam:faxPhoneNumber" "xesam:fileExtension" | |
207 | "xesam:fileSystemType" "xesam:flashUsed" "xesam:focalLength" | |
208 | "xesam:focusDistance" "xesam:formatSubtype" "xesam:frameCount" | |
209 | "xesam:frameRate" "xesam:freeSpace" "xesam:gender" "xesam:generator" | |
210 | "xesam:generatorOptions" "xesam:group" "xesam:hash" "xesam:hash" | |
211 | "xesam:height" "xesam:homeEmailAddress" "xesam:homePhoneNumber" | |
212 | "xesam:homePostalAddress" "xesam:homepageContactURL" | |
213 | "xesam:horizontalResolution" "xesam:icqContactMedium" "xesam:id" | |
214 | "xesam:imContactMedium" "xesam:interests" "xesam:interlaceMode" | |
215 | "xesam:isEncrypted" "xesam:isImportant" "xesam:isInProgress" | |
216 | "xesam:isPasswordProtected" "xesam:isRead" "xesam:isoEquivalent" | |
217 | "xesam:jabberContactMedium" "xesam:keyword" "xesam:language" "xesam:legal" | |
218 | "xesam:license" "xesam:licenseType" "xesam:lineCount" "xesam:links" | |
219 | "xesam:mailingPostalAddress" "xesam:maintainer" "xesam:md5Hash" | |
220 | "xesam:mediaCodec" "xesam:mediaCodecBitrate" "xesam:mediaCodecType" | |
221 | "xesam:meteringMode" "xesam:mimeType" "xesam:mountPoint" | |
222 | "xesam:msnContactMedium" "xesam:name" "xesam:occupiedSpace" | |
223 | "xesam:orientation" "xesam:originalLocation" "xesam:owner" | |
224 | "xesam:pageCount" "xesam:permissions" "xesam:phoneNumber" | |
225 | "xesam:physicalAddress" "xesam:pixelFormat" "xesam:primaryRecipient" | |
226 | "xesam:programmingLanguage" "xesam:rating" "xesam:receptionTime" | |
227 | "xesam:recipient" "xesam:related" "xesam:remoteUser" "xesam:rowCount" | |
228 | "xesam:sampleBitDepth" "xesam:sampleFormat" "xesam:secondaryRecipient" | |
229 | "xesam:sha1Hash" "xesam:size" "xesam:skypeContactMedium" | |
230 | "xesam:sourceCreated" "xesam:sourceModified" "xesam:storageSize" | |
231 | "xesam:subject" "xesam:supercedes" "xesam:title" "xesam:to" | |
232 | "xesam:totalSpace" "xesam:totalUncompressedSize" "xesam:url" | |
233 | "xesam:usageIntensity" "xesam:userComment" "xesam:userKeyword" | |
ee7683eb PE |
234 | "xesam:uuid" "xesam:version" "xesam:verticalResolution" |
235 | "xesam:videoBitrate" | |
24008bc4 MA |
236 | "xesam:videoCodec" "xesam:videoCodecType" "xesam:whiteBalance" |
237 | "xesam:width" "xesam:wordCount" "xesam:workEmailAddress" | |
238 | "xesam:workPhoneNumber" "xesam:workPostalAddress" | |
239 | "xesam:yahooContactMedium") | |
240 | "All fields from the Xesam Core Ontology. | |
241 | This defconst can be used to check for a new search engine, which | |
242 | fields are supported.") | |
243 | ||
244 | (defconst xesam-user-query | |
245 | "<?xml version=\"1.0\" encoding=\"UTF-8\"?> | |
246 | <request xmlns=\"http://freedesktop.org/standards/xesam/1.0/query\"> | |
247 | <userQuery> | |
248 | %s | |
249 | </userQuery> | |
250 | </request>" | |
251 | "The Xesam user query XML.") | |
252 | ||
253 | (defconst xesam-fulltext-query | |
254 | "<?xml version=\"1.0\" encoding=\"UTF-8\"?> | |
255 | <request xmlns=\"http://freedesktop.org/standards/xesam/1.0/query\"> | |
256 | <query content=\"xesam:Document\" source=\"xesam:File\"> | |
257 | <fullText> | |
258 | <string>%s</string> | |
259 | </fullText> | |
260 | </query> | |
261 | </request>" | |
262 | "The Xesam fulltext query XML.") | |
263 | ||
f0e35aeb GM |
264 | (declare-function dbus-get-unique-name "dbusbind.c" (bus)) |
265 | ||
818f12ce MA |
266 | (defvar xesam-dbus-unique-names |
267 | (list (cons :system (dbus-get-unique-name :system)) | |
268 | (cons :session (dbus-get-unique-name :session))) | |
269 | "The unique names, under which Emacs is registered at D-Bus.") | |
270 | ||
271 | (defun xesam-dbus-call-method (&rest args) | |
272 | "Apply a D-Bus method call. | |
fa463103 PE |
273 | `dbus-call-method' is preferred, because it performs better. |
274 | If the target D-Bus service is owned by Emacs, this | |
818f12ce MA |
275 | is not applicable, and `dbus-call-method-non-blocking' must be |
276 | used instead. ARGS are identical to the argument list of both | |
277 | functions." | |
278 | (apply | |
279 | ;; The first argument is the bus, the second argument the targt service. | |
280 | (if (string-equal (cdr (assoc (car args) xesam-dbus-unique-names)) | |
281 | (cadr args)) | |
282 | 'dbus-call-method-non-blocking 'dbus-call-method) | |
283 | args)) | |
284 | ||
24008bc4 MA |
285 | (defun xesam-get-property (engine property) |
286 | "Return the PROPERTY value of ENGINE." | |
287 | ;; "GetProperty" returns a variant, so we must use the car. | |
818f12ce | 288 | (car (xesam-dbus-call-method |
24008bc4 MA |
289 | :session (car engine) xesam-path-search |
290 | xesam-interface-search "GetProperty" | |
818f12ce | 291 | (xesam-get-cached-property engine "session") property))) |
24008bc4 MA |
292 | |
293 | (defun xesam-set-property (engine property value) | |
294 | "Set the PROPERTY of ENGINE to VALUE. | |
295 | VALUE can be a string, a non-negative integer, a boolean | |
296 | value (nil or t), or a list of them. It returns the new value of | |
297 | PROPERTY in the search engine. This new value can be different | |
298 | from VALUE, depending on what the search engine accepts." | |
299 | ;; "SetProperty" returns a variant, so we must use the car. | |
818f12ce | 300 | (car (xesam-dbus-call-method |
24008bc4 MA |
301 | :session (car engine) xesam-path-search |
302 | xesam-interface-search "SetProperty" | |
818f12ce | 303 | (xesam-get-cached-property engine "session") property |
24008bc4 MA |
304 | ;; The value must be a variant. It can be only a string, an |
305 | ;; unsigned int, a boolean, or an array of them. So we need | |
306 | ;; no type keyword; we let the type check to the search | |
307 | ;; engine. | |
308 | (list :variant value)))) | |
309 | ||
310 | (defvar xesam-minibuffer-vendor-history nil | |
311 | "Interactive vendor history.") | |
312 | ||
313 | (defvar xesam-minibuffer-query-history nil | |
314 | "Interactive query history.") | |
315 | ||
316 | ;; Pacify byte compiler. | |
96038f81 MA |
317 | (defvar xesam-vendor nil) |
318 | (make-variable-buffer-local 'xesam-vendor) | |
319 | (put 'xesam-vendor 'permanent-local t) | |
320 | ||
24008bc4 MA |
321 | (defvar xesam-engine nil) |
322 | (defvar xesam-search nil) | |
5dd33078 MA |
323 | (defvar xesam-type nil) |
324 | (defvar xesam-query nil) | |
325 | (defvar xesam-xml-string nil) | |
4edefb46 | 326 | (defvar xesam-objects nil) |
24008bc4 MA |
327 | (defvar xesam-current nil) |
328 | (defvar xesam-count nil) | |
24008bc4 | 329 | (defvar xesam-to nil) |
96038f81 | 330 | (defvar xesam-notify-function nil) |
24008bc4 MA |
331 | (defvar xesam-refreshing nil) |
332 | ||
333 | \f | |
334 | ;;; Search engines. | |
335 | ||
336 | (defvar xesam-search-engines nil | |
337 | "List of available Xesam search engines. | |
818f12ce MA |
338 | Every entry is an association list, with a car denoting the |
339 | unique D-Bus service name of the engine. The rest of the entry | |
340 | are cached associations of engine attributes, like the session | |
341 | identifier, and the display name. Example: | |
342 | ||
343 | \(\(\":1.59\" | |
344 | \(\"session\" . \"0t1214948020ut358230u0p2698r3912347765k3213849828\") | |
345 | \(\"vendor.display\" . \"Tracker Xesam Service\")) | |
346 | \(\":1.27\" | |
347 | \(\"session\" . \"strigisession1369133069\") | |
348 | \(\"vendor.display\" . \"Strigi Desktop Search\"))) | |
24008bc4 MA |
349 | |
350 | A Xesam-compatible search engine is identified as a queued D-Bus | |
818f12ce MA |
351 | service of the known service `xesam-service-search'.") |
352 | ||
353 | (defun xesam-get-cached-property (engine property) | |
354 | "Return the PROPERTY value of ENGINE from the cache. | |
355 | If PROPERTY is not existing, retrieve it from ENGINE first." | |
356 | ;; If the property has not been cached yet, we retrieve it from the | |
357 | ;; engine, and cache it. | |
358 | (unless (assoc property engine) | |
359 | (xesam-set-cached-property | |
360 | engine property (xesam-get-property engine property))) | |
361 | (cdr (assoc property engine))) | |
362 | ||
363 | (defun xesam-set-cached-property (engine property value) | |
364 | "Set the PROPERTY of ENGINE to VALUE in the cache." | |
365 | (setcdr engine (append (cdr engine) (list (cons property value))))) | |
24008bc4 MA |
366 | |
367 | (defun xesam-delete-search-engine (&rest args) | |
818f12ce MA |
368 | "Remove service from `xesam-search-engines'." |
369 | (setq xesam-search-engines | |
370 | (delete (assoc (car args) xesam-search-engines) xesam-search-engines))) | |
24008bc4 | 371 | |
f0e35aeb GM |
372 | (defvar dbus-debug) |
373 | ||
24008bc4 MA |
374 | (defun xesam-search-engines () |
375 | "Return Xesam search engines, stored in `xesam-search-engines'. | |
376 | The first search engine is the name owner of `xesam-service-search'. | |
377 | If there is no registered search engine at all, the function returns `nil'." | |
378 | (let ((services (dbus-ignore-errors | |
379 | (dbus-list-queued-owners | |
380 | :session xesam-service-search))) | |
381 | engine vendor-id hit-fields) | |
382 | (dolist (service services) | |
383 | (unless (assoc-string service xesam-search-engines) | |
384 | ||
385 | ;; Open a new session, and add it to the search engines list. | |
818f12ce MA |
386 | (add-to-list 'xesam-search-engines (list service) 'append) |
387 | (setq engine (assoc service xesam-search-engines)) | |
388 | ||
389 | ;; Add the session string. | |
390 | (xesam-set-cached-property | |
391 | engine "session" | |
392 | (xesam-dbus-call-method | |
393 | :session service xesam-path-search | |
394 | xesam-interface-search "NewSession")) | |
395 | ||
396 | ;; Unset the "search.live" property; we don't want to be | |
397 | ;; informed by changed results. | |
398 | (xesam-set-property engine "search.live" nil) | |
24008bc4 MA |
399 | |
400 | ;; Check the vendor properties. | |
401 | (setq vendor-id (xesam-get-property engine "vendor.id") | |
402 | hit-fields (xesam-get-property engine "hit.fields")) | |
403 | ||
8350f087 | 404 | ;; Usually, `hit.fields' shall describe supported fields. |
24008bc4 MA |
405 | ;; That is not the case now, so we set it ourselves. |
406 | ;; Hopefully, this will change later. | |
407 | (setq hit-fields | |
a464a6c7 SM |
408 | (pcase (intern vendor-id) |
409 | (`Beagle | |
3cf235eb | 410 | '("xesam:mimeType" "xesam:url")) |
a464a6c7 | 411 | (`Strigi |
3cf235eb MA |
412 | '("xesam:author" "xesam:cc" "xesam:charset" |
413 | "xesam:contentType" "xesam:fileExtension" | |
414 | "xesam:id" "xesam:lineCount" "xesam:links" | |
415 | "xesam:mimeType" "xesam:name" "xesam:size" | |
416 | "xesam:sourceModified" "xesam:subject" "xesam:to" | |
417 | "xesam:url")) | |
a464a6c7 | 418 | (`TrackerXesamSession |
3cf235eb | 419 | '("xesam:relevancyRating" "xesam:url")) |
a464a6c7 | 420 | (`Debbugs |
3cf235eb MA |
421 | '("xesam:keyword" "xesam:owner" "xesam:title" |
422 | "xesam:url" "xesam:sourceModified" "xesam:mimeType" | |
423 | "debbugs:key")) | |
424 | ;; xesam-tools yahoo service. | |
a464a6c7 | 425 | (_ '("xesam:contentModified" "xesam:mimeType" "xesam:summary" |
3cf235eb | 426 | "xesam:title" "xesam:url" "yahoo:displayUrl")))) |
24008bc4 MA |
427 | |
428 | (xesam-set-property engine "hit.fields" hit-fields) | |
429 | (xesam-set-property engine "hit.fields.extended" '("xesam:snippet")) | |
430 | ||
431 | ;; Let us notify, when the search engine disappears. | |
432 | (dbus-register-signal | |
433 | :session dbus-service-dbus dbus-path-dbus | |
434 | dbus-interface-dbus "NameOwnerChanged" | |
435 | 'xesam-delete-search-engine service)))) | |
436 | xesam-search-engines) | |
437 | ||
438 | \f | |
439 | ;;; Search buffers. | |
440 | ||
abef340a SS |
441 | (defvar xesam-mode-map |
442 | (let ((map (copy-keymap special-mode-map))) | |
2df41f9c | 443 | (set-keymap-parent map widget-keymap) |
abef340a SS |
444 | map)) |
445 | ||
446 | (define-derived-mode xesam-mode special-mode "Xesam" | |
24008bc4 MA |
447 | "Major mode for presenting search results of a Xesam search. |
448 | In this mode, widgets represent the search results. | |
449 | ||
450 | \\{xesam-mode-map} | |
96038f81 MA |
451 | Turning on Xesam mode runs the normal hook `xesam-mode-hook'. It |
452 | can be used to set `xesam-notify-function', which must a search | |
453 | engine specific, widget :notify function to visualize xesam:url." | |
454 | (set (make-local-variable 'xesam-notify-function) nil) | |
5dd33078 MA |
455 | ;; Maybe we implement something useful, later on. |
456 | (set (make-local-variable 'revert-buffer-function) 'ignore) | |
457 | ;; `xesam-engine', `xesam-search', `xesam-type', `xesam-query', and | |
458 | ;; `xesam-xml-string' will be set in `xesam-new-search'. | |
459 | (set (make-local-variable 'xesam-engine) nil) | |
460 | (set (make-local-variable 'xesam-search) nil) | |
461 | (set (make-local-variable 'xesam-type) "") | |
462 | (set (make-local-variable 'xesam-query) "") | |
463 | (set (make-local-variable 'xesam-xml-string) "") | |
4edefb46 | 464 | (set (make-local-variable 'xesam-objects) nil) |
5dd33078 MA |
465 | ;; `xesam-current' is the last hit put into the search buffer, |
466 | (set (make-local-variable 'xesam-current) 0) | |
467 | ;; `xesam-count' is the number of hits reported by the search engine. | |
468 | (set (make-local-variable 'xesam-count) 0) | |
469 | ;; `xesam-to' is the upper hit number to be presented. | |
470 | (set (make-local-variable 'xesam-to) xesam-hits-per-page) | |
96038f81 MA |
471 | ;; `xesam-notify-function' can be a search engine specific function |
472 | ;; to visualize xesam:url. It can be overwritten in `xesam-mode'. | |
473 | (set (make-local-variable 'xesam-notify-function) nil) | |
5dd33078 MA |
474 | ;; `xesam-refreshing' is an indicator, whether the buffer is just |
475 | ;; being updated. Needed, because `xesam-refresh-search-buffer' | |
476 | ;; can be triggered by an event. | |
477 | (set (make-local-variable 'xesam-refreshing) nil) | |
478 | ;; Mode line position returns hit counters. | |
479 | (set (make-local-variable 'mode-line-position) | |
480 | (list '(-3 "%p%") | |
481 | '(10 (:eval (format " (%d/%d)" xesam-current xesam-count))))) | |
482 | ;; Header line contains the query string. | |
483 | (set (make-local-variable 'header-line-format) | |
484 | (list '(20 | |
485 | (:eval | |
486 | (list "Type: " | |
4edefb46 | 487 | (propertize xesam-type 'face 'xesam-mode-line)))) |
5dd33078 MA |
488 | '(10 |
489 | (:eval | |
490 | (list " Query: " | |
491 | (propertize | |
492 | xesam-query | |
4edefb46 | 493 | 'face 'xesam-mode-line |
818f12ce | 494 | 'help-echo (when xesam-debug xesam-xml-string))))))) |
5dd33078 | 495 | |
32226619 | 496 | (when (not (called-interactively-p 'interactive)) |
de8bb89e MA |
497 | ;; Initialize buffer. |
498 | (setq buffer-read-only t) | |
499 | (let ((inhibit-read-only t)) | |
5dd33078 MA |
500 | (erase-buffer)))) |
501 | ||
502 | ;; It doesn't make sense to call it interactively. | |
503 | (put 'xesam-mode 'disabled t) | |
24008bc4 | 504 | |
4edefb46 MA |
505 | ;; The very first buffer created with `xesam-mode' does not have the |
506 | ;; keymap etc. So we create a dummy buffer. Stupid. | |
507 | (with-temp-buffer (xesam-mode)) | |
508 | ||
17668903 MA |
509 | (define-minor-mode xesam-minor-mode |
510 | "Toggle Xesam minor mode. | |
ac6c8639 CY |
511 | With a prefix argument ARG, enable Xesam minor mode if ARG is |
512 | positive, and disable it otherwise. If called from Lisp, enable | |
513 | the mode if ARG is omitted or nil. | |
17668903 MA |
514 | |
515 | When Xesam minor mode is enabled, all text which matches a | |
516 | previous Xesam query in this buffer is highlighted." | |
517 | :group 'xesam | |
518 | :init-value nil | |
519 | :lighter " Xesam" | |
520 | (when (local-variable-p 'xesam-query) | |
521 | ;; Run only if the buffer is related to a Xesam search. | |
522 | (save-excursion | |
523 | (if xesam-minor-mode | |
524 | ;; Highlight hits. | |
525 | (let ((query-regexp (regexp-opt (split-string xesam-query nil t) t)) | |
526 | (case-fold-search t)) | |
527 | ;; I have no idea whether people will like setting | |
528 | ;; `isearch-case-fold-search' and `query-regexp'. Maybe | |
529 | ;; this shall be controlled by a custom option. | |
530 | (unless isearch-case-fold-search (isearch-toggle-case-fold)) | |
531 | (isearch-update-ring query-regexp t) | |
532 | ;; Create overlays. | |
533 | (goto-char (point-min)) | |
534 | (while (re-search-forward query-regexp nil t) | |
535 | (overlay-put | |
536 | (make-overlay | |
537 | (match-beginning 0) (match-end 0)) 'face 'xesam-highlight))) | |
538 | ;; Remove overlays. | |
539 | (dolist (ov (overlays-in (point-min) (point-max))) | |
540 | (delete-overlay ov)))))) | |
541 | ||
24008bc4 MA |
542 | (defun xesam-buffer-name (service search) |
543 | "Return the buffer name where to present search results. | |
544 | SERVICE is the D-Bus unique service name of the Xesam search engine. | |
545 | SEARCH is the search identification in that engine. Both must be strings." | |
546 | (format "*%s/%s*" service search)) | |
547 | ||
4edefb46 | 548 | (defun xesam-highlight-string (string) |
f7a17e30 MA |
549 | "Highlight text enclosed by <b> and </b>. |
550 | Return propertized STRING." | |
4edefb46 MA |
551 | (while (string-match "\\(.*\\)\\(<b>\\)\\(.*\\)\\(</b>\\)\\(.*\\)" string) |
552 | (setq string | |
553 | (format | |
554 | "%s%s%s" | |
555 | (match-string 1 string) | |
556 | (propertize (match-string 3 string) 'face 'xesam-highlight) | |
557 | (match-string 5 string)))) | |
558 | string) | |
559 | ||
560 | (defun xesam-refresh-entry (engine entry) | |
24008bc4 | 561 | "Refreshes one entry in the search buffer." |
4edefb46 | 562 | (let* ((result (nth (1- xesam-current) xesam-objects)) |
24008bc4 MA |
563 | widget) |
564 | ||
565 | ;; Create widget. | |
566 | (setq widget (widget-convert 'link)) | |
de8bb89e MA |
567 | (when xesam-debug |
568 | (widget-put widget :help-echo "")) | |
24008bc4 MA |
569 | |
570 | ;; Take all results. | |
818f12ce | 571 | (dolist (field (xesam-get-cached-property engine "hit.fields")) |
4edefb46 MA |
572 | (when (cond |
573 | ((stringp (caar result)) (not (zerop (length (caar result))))) | |
574 | ((numberp (caar result)) (not (zerop (caar result)))) | |
575 | ((caar result) t)) | |
24008bc4 | 576 | (when xesam-debug |
de8bb89e MA |
577 | (widget-put |
578 | widget :help-echo | |
579 | (format "%s%s: %s\n" | |
580 | (widget-get widget :help-echo) field (caar result)))) | |
24008bc4 MA |
581 | (widget-put widget (intern (concat ":" field)) (caar result))) |
582 | (setq result (cdr result))) | |
583 | ||
584 | ;; Strigi doesn't return URLs in xesam:url. We must fix this. | |
585 | (when | |
586 | (not (url-type (url-generic-parse-url (widget-get widget :xesam:url)))) | |
587 | (widget-put | |
588 | widget :xesam:url (concat "file://" (widget-get widget :xesam:url)))) | |
589 | ||
4edefb46 MA |
590 | ;; Strigi returns xesam:size as string. We must fix this. |
591 | (when (and (widget-member widget :xesam:size) | |
592 | (stringp (widget-get widget :xesam:size))) | |
593 | (widget-put | |
594 | widget :xesam:size (string-to-number (widget-get widget :xesam:url)))) | |
595 | ||
24008bc4 MA |
596 | ;; First line: :tag. |
597 | (cond | |
598 | ((widget-member widget :xesam:title) | |
599 | (widget-put widget :tag (widget-get widget :xesam:title))) | |
600 | ((widget-member widget :xesam:subject) | |
601 | (widget-put widget :tag (widget-get widget :xesam:subject))) | |
602 | ((widget-member widget :xesam:mimeType) | |
603 | (widget-put widget :tag (widget-get widget :xesam:mimeType))) | |
604 | ((widget-member widget :xesam:name) | |
605 | (widget-put widget :tag (widget-get widget :xesam:name)))) | |
606 | ||
4edefb46 MA |
607 | ;; Highlight the search items. |
608 | (when (widget-member widget :tag) | |
609 | (widget-put | |
610 | widget :tag (xesam-highlight-string (widget-get widget :tag)))) | |
611 | ||
24008bc4 | 612 | ;; Last Modified. |
f7a17e30 MA |
613 | (when (and (widget-member widget :xesam:sourceModified) |
614 | (not | |
615 | (zerop | |
616 | (string-to-number (widget-get widget :xesam:sourceModified))))) | |
24008bc4 MA |
617 | (widget-put |
618 | widget :tag | |
619 | (format | |
620 | "%s\nLast Modified: %s" | |
621 | (or (widget-get widget :tag) "") | |
622 | (format-time-string | |
623 | "%d %B %Y, %T" | |
624 | (seconds-to-time | |
625 | (string-to-number (widget-get widget :xesam:sourceModified))))))) | |
626 | ||
627 | ;; Second line: :value. | |
628 | (widget-put widget :value (widget-get widget :xesam:url)) | |
629 | ||
630 | (cond | |
96038f81 MA |
631 | ;; A search engine can set `xesam-notify-function' via |
632 | ;; `xesam-mode-hooks'. | |
633 | (xesam-notify-function | |
634 | (widget-put widget :notify xesam-notify-function)) | |
635 | ||
24008bc4 MA |
636 | ;; In case of HTML, we use a URL link. |
637 | ((and (widget-member widget :xesam:mimeType) | |
638 | (string-equal "text/html" (widget-get widget :xesam:mimeType))) | |
639 | (setcar widget 'url-link)) | |
640 | ||
641 | ;; For local files, we will open the file as default action. | |
642 | ((string-match "file" | |
643 | (url-type (url-generic-parse-url | |
644 | (widget-get widget :xesam:url)))) | |
645 | (widget-put | |
646 | widget :notify | |
c7041c35 | 647 | (lambda (widget &rest ignore) |
f7a17e30 MA |
648 | (let ((query xesam-query)) |
649 | (find-file | |
650 | (url-filename (url-generic-parse-url (widget-value widget)))) | |
17668903 MA |
651 | (set (make-local-variable 'xesam-query) query) |
652 | (xesam-minor-mode 1)))) | |
24008bc4 MA |
653 | (widget-put |
654 | widget :value | |
655 | (url-filename (url-generic-parse-url (widget-get widget :xesam:url)))))) | |
656 | ||
657 | ;; Third line: :doc. | |
658 | (cond | |
659 | ((widget-member widget :xesam:summary) | |
660 | (widget-put widget :doc (widget-get widget :xesam:summary))) | |
661 | ((widget-member widget :xesam:snippet) | |
662 | (widget-put widget :doc (widget-get widget :xesam:snippet)))) | |
663 | ||
664 | (when (widget-member widget :doc) | |
24008bc4 | 665 | (with-temp-buffer |
4edefb46 MA |
666 | (insert |
667 | (xesam-highlight-string (widget-get widget :doc))) | |
24008bc4 | 668 | (fill-region-as-paragraph (point-min) (point-max)) |
4edefb46 MA |
669 | (widget-put widget :doc (buffer-string))) |
670 | (widget-put widget :help-echo (widget-get widget :doc))) | |
24008bc4 MA |
671 | |
672 | ;; Format the widget. | |
673 | (widget-put | |
674 | widget :format | |
de8bb89e | 675 | (format "%d. %s%%[%%v%%]\n%s\n" xesam-current |
24008bc4 MA |
676 | (if (widget-member widget :tag) "%{%t%}\n" "") |
677 | (if (widget-member widget :doc) "%h" ""))) | |
678 | ||
679 | ;; Write widget. | |
680 | (goto-char (point-max)) | |
681 | (widget-default-create widget) | |
682 | (set-buffer-modified-p nil) | |
de8bb89e | 683 | (force-mode-line-update) |
24008bc4 MA |
684 | (redisplay))) |
685 | ||
4edefb46 MA |
686 | (defun xesam-get-hits (engine search hits) |
687 | "Retrieve hits from ENGINE." | |
688 | (with-current-buffer (xesam-buffer-name (car engine) search) | |
689 | (setq xesam-objects | |
690 | (append xesam-objects | |
691 | (xesam-dbus-call-method | |
692 | :session (car engine) xesam-path-search | |
693 | xesam-interface-search "GetHits" search hits))))) | |
694 | ||
24008bc4 MA |
695 | (defun xesam-refresh-search-buffer (engine search) |
696 | "Refreshes the buffer, presenting results of SEARCH." | |
697 | (with-current-buffer (xesam-buffer-name (car engine) search) | |
698 | ;; Work only if nobody else is here. | |
4edefb46 | 699 | (unless (or xesam-refreshing (>= xesam-current xesam-to)) |
24008bc4 MA |
700 | (setq xesam-refreshing t) |
701 | (unwind-protect | |
4edefb46 MA |
702 | (let (widget) |
703 | ||
704 | ;; Retrieve needed hits for visualization. | |
705 | (while (> (min xesam-to xesam-count) (length xesam-objects)) | |
706 | (xesam-get-hits | |
707 | engine search | |
708 | (min xesam-hits-per-page | |
709 | (- (min xesam-to xesam-count) (length xesam-objects))))) | |
710 | ||
711 | ;; Add all result widgets. | |
de8bb89e | 712 | (while (< xesam-current (min xesam-to xesam-count)) |
4edefb46 | 713 | (setq xesam-current (1+ xesam-current)) |
de8bb89e | 714 | (xesam-refresh-entry engine search)) |
24008bc4 MA |
715 | |
716 | ;; Add "NEXT" widget. | |
4edefb46 | 717 | (when (> xesam-count xesam-to) |
24008bc4 MA |
718 | (goto-char (point-max)) |
719 | (widget-create | |
720 | 'link | |
721 | :notify | |
c7041c35 MA |
722 | (lambda (widget &rest ignore) |
723 | (setq xesam-to (+ xesam-to xesam-hits-per-page)) | |
724 | (widget-delete widget) | |
725 | (xesam-refresh-search-buffer xesam-engine xesam-search)) | |
24008bc4 | 726 | "NEXT") |
4edefb46 MA |
727 | (widget-beginning-of-line)) |
728 | ||
729 | ;; Prefetch next hits. | |
730 | (when (> (min (+ xesam-hits-per-page xesam-to) xesam-count) | |
731 | (length xesam-objects)) | |
732 | (xesam-get-hits | |
733 | engine search | |
734 | (min xesam-hits-per-page | |
735 | (- (min (+ xesam-hits-per-page xesam-to) xesam-count) | |
c7041c35 | 736 | (length xesam-objects))))) |
24008bc4 | 737 | |
3cf235eb MA |
738 | ;; Add "DONE" widget. |
739 | (when (= xesam-current xesam-count) | |
740 | (goto-char (point-max)) | |
741 | (widget-create 'link :notify 'ignore "DONE") | |
c7041c35 | 742 | (widget-beginning-of-line))) |
3cf235eb | 743 | |
24008bc4 MA |
744 | ;; Return with save settings. |
745 | (setq xesam-refreshing nil))))) | |
746 | ||
747 | \f | |
748 | ;;; Search functions. | |
749 | ||
750 | (defun xesam-signal-handler (&rest args) | |
751 | "Handles the different D-Bus signals of a Xesam search." | |
752 | (let* ((service (dbus-event-service-name last-input-event)) | |
753 | (member (dbus-event-member-name last-input-event)) | |
754 | (search (nth 0 args)) | |
755 | (buffer (xesam-buffer-name service search))) | |
756 | ||
757 | (when (get-buffer buffer) | |
758 | (with-current-buffer buffer | |
759 | (cond | |
760 | ||
761 | ((string-equal member "HitsAdded") | |
de8bb89e | 762 | (setq xesam-count (+ xesam-count (nth 1 args))) |
24008bc4 MA |
763 | ;; We use `run-at-time' in order to not block the event queue. |
764 | (run-at-time | |
765 | 0 nil | |
766 | 'xesam-refresh-search-buffer | |
767 | (assoc service xesam-search-engines) search)) | |
768 | ||
769 | ((string-equal member "SearchDone") | |
770 | (setq mode-line-process | |
4edefb46 | 771 | (propertize " Done" 'face 'xesam-mode-line)) |
24008bc4 MA |
772 | (force-mode-line-update))))))) |
773 | ||
4edefb46 MA |
774 | (defun xesam-kill-buffer-function () |
775 | "Send the CloseSearch indication." | |
776 | (when (and (eq major-mode 'xesam-mode) (stringp xesam-search)) | |
30fe660e MA |
777 | (ignore-errors ;; The D-Bus service could have disappeared. |
778 | (xesam-dbus-call-method | |
779 | :session (car xesam-engine) xesam-path-search | |
780 | xesam-interface-search "CloseSearch" xesam-search)))) | |
4edefb46 | 781 | |
5dd33078 | 782 | (defun xesam-new-search (engine type query) |
24008bc4 | 783 | "Create a new search session. |
5dd33078 MA |
784 | ENGINE identifies the search engine. TYPE is the query type, it |
785 | can be either `fulltext-query', or `user-query'. QUERY is a | |
786 | string in the Xesam query language. A string, identifying the | |
787 | search, is returned." | |
24008bc4 | 788 | (let* ((service (car engine)) |
818f12ce | 789 | (session (xesam-get-cached-property engine "session")) |
5dd33078 MA |
790 | (xml-string |
791 | (format | |
792 | (if (eq type 'user-query) xesam-user-query xesam-fulltext-query) | |
c7041c35 | 793 | (url-insert-entities-in-string query))) |
818f12ce | 794 | (search (xesam-dbus-call-method |
24008bc4 | 795 | :session service xesam-path-search |
5dd33078 | 796 | xesam-interface-search "NewSearch" session xml-string))) |
de8bb89e | 797 | |
24008bc4 MA |
798 | ;; Let us notify for relevant signals. We ignore "HitsRemoved", |
799 | ;; "HitsModified" and "StateChanged"; there is nothing to do for | |
800 | ;; us. | |
801 | (dbus-register-signal | |
802 | :session service xesam-path-search | |
803 | xesam-interface-search "HitsAdded" | |
804 | 'xesam-signal-handler search) | |
805 | (dbus-register-signal | |
806 | :session service xesam-path-search | |
807 | xesam-interface-search "SearchDone" | |
808 | 'xesam-signal-handler search) | |
de8bb89e | 809 | |
24008bc4 MA |
810 | ;; Create the search buffer. |
811 | (with-current-buffer | |
812 | (generate-new-buffer (xesam-buffer-name service search)) | |
813 | (switch-to-buffer-other-window (current-buffer)) | |
e4920bc9 | 814 | ;; Initialize buffer with `xesam-mode'. `xesam-vendor' must be |
96038f81 MA |
815 | ;; set before calling `xesam-mode', because we want to give the |
816 | ;; hook functions a chance to identify their search engine. | |
817 | (setq xesam-vendor (xesam-get-cached-property engine "vendor.id")) | |
24008bc4 MA |
818 | (xesam-mode) |
819 | (setq xesam-engine engine | |
820 | xesam-search search | |
5dd33078 MA |
821 | ;; `xesam-type', `xesam-query' and `xesam-xml-string' |
822 | ;; are displayed in the header line. | |
823 | xesam-type (symbol-name type) | |
824 | xesam-query query | |
825 | xesam-xml-string xml-string | |
4edefb46 | 826 | xesam-objects nil |
de8bb89e MA |
827 | ;; The buffer identification shall indicate the search |
828 | ;; engine. The `help-echo' property is used for debug | |
829 | ;; information, when applicable. | |
24008bc4 | 830 | mode-line-buffer-identification |
de8bb89e | 831 | (if (not xesam-debug) |
96038f81 | 832 | (list 12 (propertized-buffer-identification xesam-vendor)) |
de8bb89e | 833 | (propertize |
96038f81 | 834 | xesam-vendor |
818f12ce | 835 | 'help-echo |
de8bb89e | 836 | (mapconcat |
c7041c35 MA |
837 | (lambda (x) |
838 | (format "%s: %s" x (xesam-get-cached-property engine x))) | |
de8bb89e MA |
839 | '("vendor.id" "vendor.version" "vendor.display" "vendor.xesam" |
840 | "vendor.ontology.fields" "vendor.ontology.contents" | |
841 | "vendor.ontology.sources" "vendor.extensions" | |
842 | "vendor.ontologies" "vendor.maxhits") | |
5dd33078 | 843 | "\n")))) |
4edefb46 MA |
844 | (add-hook 'kill-buffer-hook 'xesam-kill-buffer-function) |
845 | (force-mode-line-update)) | |
5dd33078 MA |
846 | |
847 | ;; Start the search. | |
818f12ce | 848 | (xesam-dbus-call-method |
5dd33078 MA |
849 | :session (car engine) xesam-path-search |
850 | xesam-interface-search "StartSearch" search) | |
24008bc4 MA |
851 | |
852 | ;; Return search id. | |
853 | search)) | |
854 | ||
3cf235eb | 855 | ;;;###autoload |
24008bc4 MA |
856 | (defun xesam-search (engine query) |
857 | "Perform an interactive search. | |
858 | ENGINE is the Xesam search engine to be applied, it must be one of the | |
859 | entries of `xesam-search-engines'. QUERY is the search string in the | |
860 | Xesam user query language. If the search engine does not support | |
861 | the Xesam user query language, a Xesam fulltext search is applied. | |
862 | ||
863 | The default search engine is the first entry in `xesam-search-engines'. | |
864 | Example: | |
865 | ||
866 | (xesam-search (car (xesam-search-engines)) \"emacs\")" | |
867 | (interactive | |
868 | (let* ((vendors (mapcar | |
c7041c35 | 869 | (lambda (x) (xesam-get-cached-property x "vendor.display")) |
24008bc4 MA |
870 | (xesam-search-engines))) |
871 | (vendor | |
872 | (if (> (length vendors) 1) | |
873 | (completing-read | |
874 | "Enter search engine: " vendors nil t | |
875 | (try-completion "" vendors) 'xesam-minibuffer-vendor-history) | |
876 | (car vendors)))) | |
877 | (list | |
878 | ;; ENGINE. | |
879 | (when vendor | |
880 | (dolist (elt (xesam-search-engines) engine) | |
818f12ce MA |
881 | (when (string-equal |
882 | (xesam-get-cached-property elt "vendor.display") vendor) | |
24008bc4 MA |
883 | (setq engine elt)))) |
884 | ;; QUERY. | |
885 | (when vendor | |
886 | (read-from-minibuffer | |
887 | "Enter search string: " nil nil nil | |
888 | 'xesam-minibuffer-query-history))))) | |
889 | ||
5dd33078 MA |
890 | (if (null engine) |
891 | (message "No search engine running") | |
892 | (if (zerop (length query)) | |
893 | (message "No query applied") | |
894 | (xesam-new-search engine xesam-query-type query)))) | |
24008bc4 MA |
895 | |
896 | (provide 'xesam) | |
897 | ||
898 | ;;; TODO: | |
899 | ||
f7a17e30 | 900 | ;; * Buffer highlighting needs better analysis of query string. |
4edefb46 | 901 | ;; * Accept input while retrieving prefetched hits. `run-at-time'? |
818f12ce | 902 | ;; * With prefix, let's choose search engine. |
24008bc4 | 903 | ;; * Minibuffer completion for user queries. |
de8bb89e | 904 | ;; * `revert-buffer-function' implementation. |
de8bb89e | 905 | ;; |
24008bc4 MA |
906 | ;; * Mid term |
907 | ;; - If available, use ontologies for field selection. | |
908 | ;; - Search engines for Emacs bugs database, wikipedia, google, | |
909 | ;; yahoo, ebay, ... | |
4edefb46 | 910 | ;; - Construct complex queries via widgets, like in mairix.el. |
24008bc4 MA |
911 | |
912 | ;;; xesam.el ends here |