星期四, 2月 15, 2007

Bash-like Tab Completion in Emacs python-mode

Just hacked a bit, and a bash-like tab-completion in emacs python-mode is done. :)

Thanks Yuan Liu's tip and my friend lloyd's advices and tests,
also pymacs and python-mode developers,

it works quite well for tab completion using just tab key in python-mode.

1. install python-mode and pymacs

2. change /usr/share/emacs/site-lisp/python-mode/pycomplete.el to:


;;; Complete symbols at point using Pymacs.
;;; See pycomplete.py for the Python side of things and a short description
;;; of what to expect.

(require 'pymacs)
(require 'python-mode)

(pymacs-load "pycomplete")

;;check if prev character is blank-type
(defun char-before-blank ()
(save-excursion
(forward-char -1)
(looking-at "[\n\t\r]")))

(defun py-complete ()
(interactive)
(let ((pymacs-forget-mutability t))
(if (and
(and (eolp) (not (bolp))
(not (char-before-blank))))
(insert (pycomplete-pycomplete (py-symbol-near-point) (py-find-global-imports)))
(indent-for-tab-command))))

(defun py-find-global-imports ()
(save-excursion
(let (first-class-or-def imports)
(goto-char (point-min))
(setq first-class-or-def
(re-search-forward "^ *\\(def\\|class\\) " nil t))
(goto-char (point-min))
(setq imports nil)
(while (re-search-forward
"\\(import \\|from \\([A-Za-z_][A-Za-z_0-9\\.]*\\) import \\).*"
nil t)
(setq imports (append imports
(list (buffer-substring
(match-beginning 0)
(match-end 0))))))
imports)))

(define-key py-mode-map "\M-\C-i" 'py-complete)
(define-key py-mode-map "\t" 'py-complete)

(provide 'pycomplete)


3. change /usr/lib/python2.4/site-packages/pycomplete.py to:
(change your python path if different python version)

"""
Python dot expression completion using Pymacs.

This almost certainly needs work, but if you add

(require 'pycomplete)

to your .xemacs/init.el file (.emacs for GNU Emacs) and have Pymacs
installed, when you hit TAB it will try to complete the dot expression
before point. For example, given this import at the top of the file:

import time

typing "time.cl" then hitting TAB should complete "time.clock".

See pycomplete.el for the Emacs Lisp side of things.
"""
import sys
import os.path
import string
from Pymacs import lisp

sys.path.append(".")

try:
x = set
except NameError:
from sets import Set as set
else:
del x

def get_all_completions(s, imports=None):
"""Return contextual completion of s (string of >= zero chars).

If given, imports is a list of import statements to be executed first.
"""
locald = {}
if imports is not None:
for stmt in imports:
try:
exec stmt in globals(), locald
except TypeError:
raise TypeError, "invalid type: %s" % stmt
except:
continue
dots = s.split(".")
if not s or len(dots) == 1:
keys = set()
keys.update(locald.keys())
keys.update(globals().keys())
import __builtin__
keys.update(dir(__builtin__))
keys = list(keys)
keys.sort()
if s:
return [k for k in keys if k.startswith(s)]
else:
return keys

sym = None
for i in range(1, len(dots)):
s = ".".join(dots[:i])
try:
sym = eval(s, globals(), locald)
except NameError:
try:
sym = __import__(s, globals(), locald, [])
except ImportError:
return []
if sym is not None:
s = dots[-1]
return [k for k in dir(sym) if k.startswith(s)]

def pycomplete(s, imports=None):
completions = get_all_completions(s, imports)
dots = s.split(".")
result = os.path.commonprefix([k[len(dots[-1]):] for k in completions])

if result == "":
if completions:
width = lisp.window_width() - 2
colum = width / 20
white = " "

msg = ""

counter = 0
for completion in completions :
if completion.__len__() < 20 :
msg += completion + white[completion.__len__():]
counter += 1
else :
msg += completion + white[completion.__len__() - 20:]
counter += 2

if counter >= colum :
counter = 0
msg += '\n'

else:
msg = "no completions!"
lisp.message(msg)
return result

if __name__ == "__main__":
print " ->", pycomplete("")
print "sys.get ->", pycomplete("sys.get")
print "sy ->", pycomplete("sy")
print "sy (sys in context) ->", pycomplete("sy", imports=["import sys"])
print "foo. ->", pycomplete("foo.")
print "Enc (email * imported) ->",
print pycomplete("Enc", imports=["from email import *"])
print "E (email * imported) ->",
print pycomplete("E", imports=["from email import *"])

print "Enc ->", pycomplete("Enc")
print "E ->", pycomplete("E")

# Local Variables :
# pymacs-auto-reload : t
# End :


4. add this line into your .emacs
(require 'pycomplete)

That's it!

ps: you can also grab pycomplete.el and pycomplete.py here

ps2: if you install python-mode and pymacs from scratch,
don't forget add these lines in your .emacs:

(add-to-list 'load-path "/usr/share/emacs/site-lisp/python-mode")

(autoload 'python-mode "python-mode" "Python editing mode." t)
(autoload 'jython-mode "python-mode" "Python editing mode." t)
(autoload 'py-shell "python-mode" "Start an interactive Python interpreter in another window." t)
(autoload 'doctest-mode "doctest-mode" "Editing mode for Python Doctest examples." t)
(autoload 'py-complete "pycomplete" "Complete a symbol at point using Pymacs." t)

(add-to-list 'auto-mode-alist '("\\.py$" . python-mode))
(add-to-list 'auto-mode-alist '("\\.doctest$" . doctest-mode))

(add-to-list 'interpreter-mode-alist '("python" . python-mode))
(add-to-list 'interpreter-mode-alist '("jython" . jython-mode))

(add-to-list 'load-path "/usr/share/emacs/site-lisp/pymacs")

(autoload 'pymacs-load "pymacs" nil t)
(autoload 'pymacs-eval "pymacs" nil t)
(autoload 'pymacs-apply "pymacs")
(autoload 'pymacs-call "pymacs")

1 則留言:

free ps3 提到...

Thanks for the nice post!