Download
(require 'url)
(require 'xml)
(require 'cl)
(defcustom gds-search-url ""
"Search URL for the GDS installation.
You must specify this value. It can be found in the Windows
registry, under
HKEY_CURRENT_USER\\Software\\Google\\Google Desktop\\AP\I\search_url"
:type 'string
:group 'gds)
(defun gds-search-url ()
"Get GDS search url. Raise error if empty or nil.
Maybe this is ugly, I don't know, but it sure should make the
user configure this..."
(if (or (string= "" gds-search-url)
(not gds-search-url))
(error "Option gds-search-url is empty or nil")
gds-search-url))
(defun gds-get-xml-response (search &optional num)
"Do a Google Desktop Search for SEARCH, return result as xml.
Optional argument NUM is used to set the number of hits to get."
(with-temp-buffer
(url-insert-file-contents
(format "%s%s&format=xml%s"
(gds-search-url)
search
(if num
(format "&num=%d" num)
"")))
(goto-char (point-min))
(if (not (search-forward-regexp "^<\\?xml" nil t))
(message "No XML data from GDS search")
(xml-parse-region (match-beginning 0) (point-max)))))
(defcustom gds-ignored-file-names-regexp ""
"Regexp of file names to remove from the hit list."
:type 'string
:group 'gds)
(defun gds-get-matching-files (search &optional num)
"Search GDS for files containing SEARCH.
Returns a list of file names.
Optional argument NUM sets the number of search results. Default
is 10 hits.
Only hits of category \"file\" are returned. File names matching
`gds-ignored-file-names-regexp' will be removed."
(let ((xml (xml-get-children
(car (gds-get-xml-response search num)) 'result))
result results)
(while xml
(setq result (car xml))
(let ((category (car (xml-node-children (assoc 'category result))))
(file-name (car (xml-node-children (assoc 'url result)))))
(if (and (string= "file" category)
(not (string-match gds-ignored-file-names-regexp file-name)))
(setq results (append
results
(list file-name)))))
(setq xml (cdr xml)))
results))
(defvar gds-ignored-categories nil
"List of strings of ignored categories.
Example of categories are file, web and email. The whole list can
be found here: http://desktop.google.com/dev/queryapi.html. For
some reason the category can sometimes be nil, so you can add
this as an item in the list as well.")
(defun gds-get-matching-items (search &optional num)
"Search GDS for items containing SEARCH.
Returns a list of items names.
Optional argument NUM sets the number of search results. Default
is 10 hits.
Hits with a category in `gds-ignored-categories' will not be
returned.
File names matching `gds-ignored-file-names-regexp' will be
removed."
(let ((xml (xml-get-children
(car (gds-get-xml-response search num)) 'result))
result results)
(while xml
(setq result (car xml))
(let ((category (car (xml-node-children (assoc 'category result))))
(file-name (car (xml-node-children (assoc 'url result))))
(title (car (xml-node-children (assoc 'title result))))
(cache-url (car (xml-node-children (assoc 'cache_url result)))))
(if (and (not (member category gds-ignored-categories))
(not (and (string= category "file")
(string-match gds-ignored-file-names-regexp file-name))))
(setq results (append
results
(list (list category title file-name cache-url))))))
(setq xml (cdr xml)))
results))
(defun gds-browse-url ()
"Browse to URL given by the title's URL property."
(interactive)
(browse-url (get-text-property (point) 'url)))
(defun gds-mouse-browse-url (start-event)
"Browse to URL given by the title's URL property.
Argument START-EVENT is not documented."
(interactive "e")
(gds-browse-url))
(defvar gds-title-map nil
"Keymap for the title in the result list.")
(progn
(setq gds-title-map (make-sparse-keymap))
(define-key gds-title-map (kbd "RET") 'gds-browse-url)
(define-key gds-title-map (kbd "<mouse-1>") 'gds-mouse-browse-url))
(define-derived-mode gds-display-mode fundamental-mode "GDS Display"
"Major mode for browsing hits from GDS")
(progn
(setq gds-display-mode-map (make-sparse-keymap))
(define-key gds-display-mode-map (kbd "q") 'bury-buffer)
(define-key gds-display-mode-map (kbd "TAB") 'gds-find-next-title))
(defface gds-title-face '((t (:foreground "Red" :weight bold)))
"*Face used to highlight the title of search results.."
:group 'gds :group 'faces)
(defface gds-url-face '((t (:foreground "Gray")))
"*Face used to highlight the URL of search results.."
:group 'gds :group 'faces)
(defcustom gds-max-results 40
"Maximum number of results to search for.
Note that this is the number of hits to get from GDS *before*
filtering non-file hits and file names matching
`gds-ignored-file-names-regexp'. This means that you probably
want to make this number higher that the number of files you want
to choose from at the prompt."
:type 'integer
:group 'gds)
(defun gds (search-string)
"Make a Google Desktop search and display the results.
Argument SEARCH-STRING is the string to search for."
(interactive "MSearch string: ")
(let ((results (gds-get-matching-items search-string gds-max-results))
(buf (get-buffer-create "*gds-search*")))
(switch-to-buffer buf)
(gds-display-mode)
(toggle-truncate-lines 1)
(toggle-read-only 0)
(erase-buffer)
(insert "\n*** Click mouse-1 or type `RET' on an item title
to find the item in GDS. Type `TAB' to jump to the next
search result or use the normal movement keys to move
around. Type `q' to quit.\n\n")
(mapc
(lambda (x)
(insert (format "%-5s %-60s (%s)\n" (if (car x)
(capitalize (car x))
"Other")
(propertize (cadr x)
'face 'gds-title-face
'mouse-face 'highlight
'url (cadddr x)
'keymap gds-title-map)
(propertize (cadddr x)
'face 'gds-url-face))))
results))
(goto-char (point-min))
(set-buffer-modified-p nil)
(toggle-read-only 1)
(gds-find-next-title))
(defun gds-next-url-property-change ()
"Return position of next change in the url property."
(next-single-property-change (point) 'url nil (point-max)))
(defun gds-find-next-title ()
"Go to next search result.
If at the bottom, wrap around to the top."
(interactive)
(goto-char (gds-next-url-property-change))
(let (p)
(when (not (get-text-property (point) 'url))
(setq p (gds-next-url-property-change))
(if (eq p (point-max))
(progn
(goto-char (point-min))
(goto-char (gds-next-url-property-change)))
(goto-char p)))))
(defcustom gds-iswitchb-open-file-number-of-hits 20
"Number of hits to fetch from GDS when using `gds-iswitchb-open-file'.
Note that this is the number of hits to get from GDS *before*
filtering non-file hits and file names matching
`gds-ignored-file-names-regexp'. This means that you probably
want to make this number higher that the number of files you want
to choose from at the prompt."
:type 'integer
:group 'gds)
(defun gds-iswitchb-open-file (search)
"Use iswitchb to open file matching SEARCH in GDS."
(interactive "sSearch for: ")
(let ((iswitchb-make-buflist-hook
(lambda ()
(setq iswitchb-temp-buflist (gds-get-matching-files search 20)))))
(find-file (iswitchb-read-buffer "Find file: "))))
(defcustom gds-dired-number-of-hits 100
"Number of hits to fetch from GDS when using `gds-dired'.
Note that this is the number of hits to get from GDS *before*
filtering non-file hits and file names matching
`gds-ignored-file-names-regexp'. This means that you probably
want to make this number higher that the number of files you want
to choose from at the prompt."
:type 'integer
:group 'gds)
(defun gds-dired ()
"Use Google Desktop Search to find files and list them in dired.
It generates a result like `find-dired' does, but uses Google
Desktop Search to find matching files."
(interactive)
(let ((dir (read-directory-name "Set current directory: "))
(search (read-string "Search string: "))
(buf (get-buffer-create "*gds-dired*")))
(switch-to-buffer buf)
(kill-all-local-variables)
(setq buffer-read-only nil)
(erase-buffer)
(setq default-directory dir)
(dired-mode dir)
(use-local-map (append (make-sparse-keymap) (current-local-map)))
(define-key (current-local-map) "g" 'undefined)
(if (fboundp 'dired-simple-subdir-alist)
(dired-simple-subdir-alist)
(set (make-local-variable 'dired-subdir-alist)
(list (cons default-directory (point-min-marker)))))
(setq buffer-read-only nil)
(insert " " dir ":\n")
(insert " GDS search results for \"" search "\"\n")
(let ((buffer-read-only nil)
(saved-pos nil))
(goto-char (point-max))
(setq mode-line-process (concat ": GDS search"))
(mapc
(lambda (x)
(when (file-exists-p x)
(insert " ")
(insert-directory (expand-file-name x) "")))
(gds-get-matching-files search gds-dired-number-of-hits))
(insert " at " (substring (current-time-string) 0 19))
(force-mode-line-update))
(goto-char (point-min))
(forward-line 1)
(dired-next-line 1)))
(defun gds-dired-2 ()
"Use Google Desktop Search to find files and list them in dired.
It generates a result like `find-dired' does, but uses Google
Desktop Search to find matching files."
(interactive)
(let ((dir (read-directory-name "Set current directory: "))
(search (read-string "Search string: ")))
(dired (cons dir (gds-get-matching-files
search gds-dired-number-of-hits)))))
(provide 'gds)