help-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: writing Python in Emacs


From: Rob Wolfe
Subject: Re: writing Python in Emacs
Date: Sun, 20 Jan 2008 16:42:38 +0100
User-agent: Gnus/5.1008 (Gnus v5.10.8) Emacs/22.1 (gnu/linux)

Terry Jones <terry@jon.es> writes:

>>>>>> "Richard" == Richard Szopa <ryszard.szopa@gmail.com> writes:

I don't see Richard's original post, so I reply to Terry.

>
> Richard> I am a devoted Emacs user and I write a lot in Python.
>
> Me too.

The good news is that I managed to configure completion for Python 
in Emacs using pymacs, python-mode.el, pycomplete.el and pycomplete.py.
For contents of my pycomplete.el, pycomplete.py and necessary
settings in .emacs see below.

>
> Richard> I need the following features:
>
> Richard> 1) Tab completion, ideally Slime like. That is, when there's not
> Richard> enough letters to unambiguously complete a symbol, I want it to
> Richard> show a buffer (w/o taking the focus) w/ the possible
> Richard> completions. In an ideal world, it would be able to complete
> Richard> fo.ba<TAB> to foo.bar. I imagine this would require quite tight
> Richard> Emacs-Python integration.

Works for me.

[...]

> Richard> 2) Sending the toplevel definition (class or function) to the Python
> Richard> buffer.

That feature is defined in python-mode.el:
"\e\C-x"    'py-execute-def-or-class
"\C-c|"     'py-execute-region


[...]

> Richard> 3) Hints on function/method arguments. IDLE has this done nearly
> Richard> right, but the hints are a bit too intrusive for me. I would like to
> Richard> see them in the minibuffer.

Works for me, but only for pure python functions 
(`inspect.getargspec` constraint).

[...]

> Richard> I have tried a couple of times both python-modes (the one shipped w/
> Richard> Python and the one shipped w/ Emacs), pymacs and stuff like that...
> Richard> And, as I said, never got it right. But, maybe I just cannot find the
> Richard> way to configure it, and some configuration hints will be enough...

I mixed solutions found around the net and finally got it working:
- hitting TAB complete function/method name
- f1 shows description of object at point
- hitting '(' and ',' shows function parameters

Copy `pycomplete.py` on your PYTHONPATH (e.g. /usr/lib/python2.5/site-packages)
and `pycomplete.el` on your Emacs load_path (e.g. /usr/share/emacs/site-lisp).
Copy my settings to your `.emacs` file and hopefully it will work. ;)

My files:

# .emacs
(require 'pycomplete)
(setq auto-mode-alist (cons '("\\.py$" . python-mode) auto-mode-alist))
(autoload 'python-mode "python-mode" "Python editing mode." t)

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

(setq interpreter-mode-alist(cons '("python" . python-mode) 
                                  interpreter-mode-alist))
(setq python-mode-hook
      '(lambda () (progn
                    (set-variable 'py-python-command "/usr/bin/python2.5")
                    (set-variable 'py-indent-offset 4)
                    (set-variable 'py-smart-indentation nil)
                    (set-variable 'indent-tabs-mode nil))))
# end of .emacs


# pycomplete.el
(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 ((imports nil))
      (goto-char (point-min))
      (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)))


(defun py-complete-python-dotexpr-begin nil
  (interactive)
  (re-search-backward "[^a-zA-Z_0-9\\.]")
  (forward-char))


(defun py-complete-python-dotexpr-end nil
  (interactive)
  (re-search-forward "[a-zA-Z_0-9\\.]*"))

(put 'python-dotexpr 'beginning-op 'py-complete-python-dotexpr-begin)
(put 'python-dotexpr 'end-op 'py-complete-python-dotexpr-end)


(defun py-complete-show (string)
  (display-message-or-buffer string "*PythonHelp*"))


(defun py-complete-help (string)
  "get help on a python expression"
  (let ((help-string 
         (pycomplete-pyhelp string (py-find-global-imports))))
    (if (and help-string (> (length help-string) 300))
        (with-output-to-temp-buffer "*Python Help*"
          (print help-string))
      (py-complete-show help-string))))


(defun py-complete-help-thing-at-point nil
  (interactive)
  (require 'thingatpt)
  (let ((sym (thing-at-point 'python-dotexpr)))
    (if sym
        (py-complete-help sym))))


(set 'py-complete-current-signature nil)

(defun py-complete-signature (function)
  "get signature of a python function or method"
  (interactive)
  (set 'py-complete-current-signature
       (pycomplete-pysignature function)))


(defun py-complete-signature-show nil
  (interactive)
  (require 'thingatpt) 
  (let ((sym (thing-at-point 'python-dotexpr)))
    (if sym
        (progn 
          (py-complete-show (py-complete-signature sym))))))


(defun py-complete-signature-expr nil
  (interactive)
  (require 'thingatpt)
  (let ((dotexpr (read-string "signature on: "
                              (thing-at-point 'python-dotexpr))))
    (if dotexpr
        (py-complete-show
         (py-complete-signature dotexpr)))))


(defun py-complete-electric-lparen nil
  "electricly insert '(', and try to get a signature for the stuff to the left"
  (interactive)
  (py-complete-signature-show)
  (self-insert-command 1))


(defun py-complete-electric-comma nil
  "electricly insert ',', and redisplay latest signature"
  (interactive)
  (self-insert-command 1)
  (if py-complete-current-signature
      (py-complete-show (format "%s" py-complete-current-signature))))


(define-key py-mode-map "\M-\C-i" 'py-complete)
(define-key py-mode-map "\t" 'py-complete)
(define-key py-mode-map [f1] 'py-complete-help-thing-at-point)
(define-key py-mode-map "(" 'py-complete-electric-lparen)
(define-key py-mode-map "," 'py-complete-electric-comma)
(define-key py-mode-map [f2] 'py-complete-signature-expr)

(provide 'pycomplete)
# end of pycomplete.el


# pycomplete.py
import sys
import inspect
from StringIO import StringIO
import os.path

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

from Pymacs import lisp


sys.path.append('.')


def pycomplete(s, imports=None, debug=False):
    """Display completion in Emacs window"""
    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:
            if debug:
                width = 80
            else:
                width = lisp.window_width() - 2

            column = width / 20
            white = " " * 20
            msg = ""

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

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

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


def pyhelp(s, imports=None):
    """Return object description"""
    _import_modules(imports, globals(), None)
    return _getdoc(s)
        

def pysignature(s):
    """Return info about function parameters"""
    f = None
    try:
        f = eval(s)
    except Exception, ex:
        return "%s" % ex

    if inspect.ismethod(f):
        f = f.im_func
    if not inspect.isfunction(f):
        return ''
    (args, varargs, varkw, defaults) = inspect.getargspec(f)
    return('%s: %s'
           % (f.__name__, inspect.formatargspec(args,varargs,varkw,defaults)))


def _getdoc(s):
    """Return string printed by `help` function"""
    obj = None
    try:
        obj = eval(s)
    except Exception, ex:
        return "%s" % ex
    out = StringIO()
    old = sys.stdout
    sys.stdout = out
    help(obj)
    sys.stdout = old
    return out.getvalue()


def _import_modules(imports, dglobals, dlocals):
    """If given, execute import statements"""
    
    if imports is not None:
        for stmt in imports:
            try:
                exec stmt in dglobals, dlocals
            except TypeError:
                raise TypeError, 'invalid type: %s' % stmt
            except:
                continue


def _get_all_completions(s, imports=None):
    """Return contextual completion of s (string of >= zero chars)"""
    
    dlocals = {}
    _import_modules(imports, globals(), dlocals)
    dots = s.split(".") 
    if not s or len(dots) == 1:
        keys = set()
        keys.update(dlocals.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(), dlocals)
        except NameError:
            try:
                sym = __import__(s, globals(), dlocals, [])
            except ImportError:
                return []
    if sym is not None:  
        s = dots[-1]     
        return [k for k in dir(sym) if k.startswith(s)]


def _test():
    print ' ->', pycomplete('', debug=True)
    print 'sys.get ->', pycomplete('sys.get', debug=True)
    print 'settr ->', pycomplete('settr', debug=True)
    print 'settr (plat in context) ->',
    print pycomplete('settr', imports=['from sys import settrace'], debug=True)
    print 'foo. ->', pycomplete('foo.', debug=True)
    print 'Enc (email * imported) ->', 
    print pycomplete('Enc', imports=['from email import *'], debug=True)
    print 'E (email * imported) ->',
    print pycomplete('E', imports=['from email import *'], debug=True)
    print 'Enc ->', pycomplete('Enc', debug=True)
    print 'E ->', pycomplete('E', debug=True)


if __name__ == "__main__":
    _test()
# end of pycomplete.py


HTH,
Rob


reply via email to

[Prev in Thread] Current Thread [Next in Thread]