;;; pylookup.el --- Look up python documents (reference) in Emacs ;; Copyright (C) 2010-2013 Taesoo Kim ;; Author: Taesoo Kim ;; Maintainer: Taesoo Kim ;; Created: 19 June 2009 ;; Keywords: python,reference,document,help ;; This file is not part of GNU Emacs. ;; This program 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 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (eval-when-compile (require 'browse-url) (require 'simple) (require 'cl-lib) (require 'ido)) ;;================================================================= ;; user options ;;================================================================= (defvar pylookup-db-file "pylookup.db" "Pylookup database file") (defvar pylookup-program "pylookup.py" "Pylookup execution file") (defvar pylookup-search-options nil "Pylookup search options (see ./pylookup.py -h)") ;;================================================================= ;; internal variables ;;================================================================= (defvar pylookup-html-locations '("http://docs.python.org")) (defvar pylookup-history nil) (defvar pylookup-cache nil) (defvar pylookup-return-window-config nil) (defvar pylookup-temp-buffer-name "*Pylookup Completions*") (defvar pylookup-mode-map (let ((map (make-sparse-keymap))) (define-key map [mouse-1] 'pylookup-mode-lookup-and-leave) (define-key map [mouse-2] 'pylookup-mode-lookup) (define-key map "\C-m" 'pylookup-mode-lookup-and-leave) (define-key map " " 'pylookup-mode-lookup-and-leave) (define-key map "f" 'pylookup-mode-lookup) (define-key map "q" 'pylookup-mode-quit-window) (define-key map "n" 'pylookup-mode-next-line) (define-key map "j" 'pylookup-mode-next-line) (define-key map "p" 'pylookup-move-prev-line) (define-key map "k" 'pylookup-move-prev-line) (define-key map "/" 'isearch-forward) (define-key map "v" 'scroll-down) (define-key map "V" 'scroll-up) (define-key map "l" 'recenter) (define-key map "<" 'beginning-of-buffer) (define-key map ">" 'end-of-buffer) (define-key map "v" 'scroll-down) map) "Keymap for `pylookup-mode-mode'.") (put 'pylookup-mode 'mode-class 'special) (defvar pylookup-completing-read (if (null ido-mode) 'completing-read 'ido-completing-read) "Ido support with convenience") ;;================================================================= ;; pylookup mode specific interactive functions ;;================================================================= (defun pylookup-trim (desc n) "Trim desc string to fit into the length, n" (if (> (length desc) (- n 2)) (concat (substring desc 0 (- n 2)) "..") desc)) (defun pylookup-mode () "Major mode for output from \\[pylookup-lookup]." (interactive) (kill-all-local-variables) (use-local-map pylookup-mode-map) (setq major-mode 'pylookup-mode) (setq mode-name "Pylookup") (setq buffer-read-only t) (run-mode-hooks)) (defun pylookup-move-prev-line () "Move to previous entry" (interactive) (when (< 3 (line-number-at-pos)) (call-interactively 'previous-line))) (defun pylookup-mode-next-line () "Move to next entry" (interactive) (when (< (line-number-at-pos) (- (line-number-at-pos (point-max)) 1)) (call-interactively 'next-line))) (defun pylookup-mode-lookup-and-leave () "Lookup the current line in a browser and leave the completions window." (interactive) (call-interactively 'pylookup-mode-lookup) (pylookup-mode-quit-window)) (defun pylookup-mode-lookup () "Lookup the current line in a browser." (interactive) (let ((url (get-text-property (point) 'pylookup-target-url))) (if url (progn (beginning-of-line) (message "Browsing: \"%s\"" url) (browse-url url)) (error "No URL on this line")))) (defun pylookup-mode-quit-window () "Leave the completions window." (interactive) (set-window-configuration pylookup-return-window-config)) ;;================================================================= ;; execute pylookup ;;================================================================= (defun pylookup-exec-get-cache () "Run a pylookup process and get a list of cache (db key)" (split-string (with-output-to-string (call-process pylookup-program nil standard-output nil "-d" (expand-file-name pylookup-db-file) "-c")))) (defun pylookup-exec-lookup (search-term) "Runs a pylookup process and returns a list of (term, url) pairs." (mapcar (lambda (x) (split-string x ";")) (split-string (with-output-to-string (apply 'call-process pylookup-program nil standard-output nil "-d" (expand-file-name pylookup-db-file) "-l" search-term "-f" "Emacs" pylookup-search-options)) "\n" t))) ;;================================================================= ;; interactive user interfaces ;;================================================================= ;;;###autoload (defun pylookup-lookup (search-term) "Lookup SEARCH-TERM in the Python HTML indexes." (interactive (list (let ((initial (thing-at-point 'word))) (funcall pylookup-completing-read "Search: " (if pylookup-cache pylookup-cache (setq pylookup-cache (pylookup-exec-get-cache))) nil nil initial 'pylookup-history)) )) (let ((matches (pylookup-exec-lookup search-term))) (cond ;; 0. No results. ((eq matches nil) (message "No matches for \"%s\"." search-term)) ;; 1. A single result. ((= (length matches) 1) ;; Point the browser at the unique result and get rid of the buffer (let ((data (car matches))) (message "Browsing: \"%s\"" (car data)) (browse-url (cadr data)))) ;; N. Multiple results. (t ;; Decorate the temporary buffer lines with appropriate properties for ;; selection. (let* ((cur-window-conf (current-window-configuration)) (tmpbuf (get-buffer-create pylookup-temp-buffer-name)) (index 0)) (display-buffer tmpbuf) (pop-to-buffer tmpbuf) (setq buffer-read-only nil) (erase-buffer) ;; Insert the text in the buffer (insert (format "Python index matches for %s:\n\n" search-term)) (dolist (x matches) ;; split like ;; waitpid() (in module os) [lib] ;; --------- -------------- ----- ;; => ;; waitpid (in module os) [lib] ;; api module type (let* ((tokens (split-string (car x))) (api (car tokens)) (iter (cdr tokens)) (type (car (last tokens))) (module "")) (while (not (or (equal iter nil) (string= (car iter) type))) (setq module (concat module " " (car iter))) (setq iter (cdr iter))) (cl-incf index) (insert (format " %03d) %-25s %-30s %10s" index (pylookup-trim api 25) (pylookup-trim module 30) (pylookup-trim type 10)))) (put-text-property (line-beginning-position) (line-end-position) 'pylookup-target-url (cadr x)) (insert "\n")) ;; goto first entry (goto-line 3) ;; turn mode on (pylookup-mode) ;; highlighting (font-lock-add-keywords nil `((,(format "\\(%s\\|%s\\|%s\\)" search-term (upcase search-term) (upcase-initials search-term)) 1 font-lock-keyword-face prepend))) (font-lock-add-keywords nil '(("\\<\\(lib\\)" 1 font-lock-constant-face prepend))) (font-lock-add-keywords nil '(("\\<\\(in module.*)\\)" 1 font-lock-doc-face prepend))) ;; store window configuration (set (make-local-variable 'pylookup-return-window-config) cur-window-conf) ;; make fit to screen (shrink-window-if-larger-than-buffer (get-buffer-window tmpbuf))))))) ;;;###autoload (defun pylookup-set-search-option (option-string) "Set search option interactively" (interactive (list (read-string "Search option: " (mapconcat 'identity pylookup-search-options " ")))) (setq pylookup-search-options (split-string option-string " "))) ;;;###autoload (defun pylookup-update (src &optional append) "Run pylookup-update and create the database at `pylookup-db-file'." (interactive (list (funcall pylookup-completing-read "Python Html Documentation source: " pylookup-html-locations))) ;; pylookup.py -d /home/myuser/.pylookup/pylookup.db -l (message (with-output-to-string (call-process pylookup-program nil standard-output nil "-u" src "-d" (expand-file-name pylookup-db-file) (if append "-a" ""))))) ;;;###autoload (defun pylookup-update-all () "Run pylookup-update for all sources and create the database at `pylookup-db-file'." (interactive) ;; truncate db file (with-temp-buffer (write-file pylookup-db-file)) (mapc (lambda (s) (pylookup-update s t)) pylookup-html-locations)) ;;;###autoload (defun pylookup-lookup-at-point () "Query the for string with help of word read at point and call `pylookup-lookup'" (interactive) (let* ((default-word (thing-at-point 'word)) (default-prompt (concat "Lookup Word " (if default-word (concat "(" default-word ")") nil) ": ")) (pylookup-query (funcall #'(lambda (str) "Remove Whitespace from beginning and end of a string." (replace-regexp-in-string "^[ \n\t]*\\(.*?\\)[ \n\t]*$" "\\1" str)) (read-string default-prompt nil nil default-word)))) (if (= (length pylookup-query) 0) nil (pylookup-lookup pylookup-query)))) (provide 'pylookup) ;;; pylookup.el ends here