2004-05-05 Dave Love
* progmodes/python.el: Doc fixes.
(python-mode-map): Add python-complete-symbol.
(python-comment-line-p, python-beginning-of-string): Use
syntax-ppss.
(python-comment-indent, python-complete-symbol)
(python-symbol-completions, python-partial-symbol)
(python-try-complete): New.
(python-indent-line): Remove optional arg. Use python-block-end-p.
(python-check): Bind compilation-error-regexp-alist.
(inferior-python-mode): Use rx. Move keybindings to top level.
Set comint-input-filter. Use python-input-filter-2.
(python-compilation-line-number): Use start, not
python-orig-start.
(python-preoutput-filter): Use rx.
(python-input-filter): Don't set python-orig-start.
(python-input-filter-2): New.
(python-proc): Start new process if necessary. Check
python-buffer non-nil.
(view-return-to-alist): Defvar.
(python-send-receive): New.
(python-eldoc-function): Use it.
(python-mode-running): Don't defvar.
(python-mode): Set comment-indent-function. Maybe update
hippie-expand-try-functions-list.
(python-indentation-levels): Initialize differently.
(python-block-end-p): New.
(python-indent-line): Use it.
(python-compilation-regexp-alist): Augment.
(run-python): Import `emacs' module to Python rather than loading
code directly. Set python-buffer differently.
(python-send-region): Use emacs.eexecfile. Fix orig-start
calculation. Use python-proc. Go to end of comint buffer. Use
python-input-filter-2, not python-input-filter.
(python-load-file): Use python-proc, emacs.eimport. Go to
point-max. Don't bind comint-input-filter-functions.
(python-describe-symbol): Simplify interactive form. Use
emacs.help. Do use temp-buffer-show-hook. Call
print-help-return-message.
(hippie-exp): Require when compiling.
(python-preoutput-continuation): Use rx.
Index: python.el
===================================================================
RCS file: /cvsroot/emacs/emacs/lisp/progmodes/python.el,v
retrieving revision 1.9
diff -u -p -u -r1.9 python.el
--- python.el 21 Apr 2004 20:48:47 -0000 1.9
+++ python.el 5 May 2004 17:00:32 -0000
@@ -46,19 +46,18 @@
;; I've installed a minor mode to do the job properly in Emacs 22.
;; Other things seem more natural or canonical here, e.g. the
;; {beginning,end}-of-defun implementation dealing with nested
-;; definitions, and the inferior mode following `cmuscheme'. (The
-;; inferior mode should be able to find the source of errors from
-;; `python-send-region' & al via `compilation-minor-mode', but I can't
-;; make that work with the current (March '04) compile.el.)
-;; Successive TABs cycle between possible indentations for the line.
+;; definitions, and the inferior mode following `cmuscheme'. The
+;; inferior mode can find the source of errors from
+;; `python-send-region' & al via `compilation-minor-mode'. Successive
+;; TABs cycle between possible indentations for the line. There is
+;; symbol completion using lookup in Python.
;; Even where it has similar facilities, this is incompatible with
;; python-mode.el in various respects. For instance, various key
;; bindings are changed to obey Emacs conventions, and things like
;; marking blocks and `beginning-of-defun' behave differently.
-;; TODO: See various Fixmes below. It should be possible to arrange
-;; some sort of completion using the inferior interpreter.
+;; TODO: See various Fixmes below.
;;; Code:
@@ -203,6 +202,8 @@ Used for syntactic keywords. N is the m
(define-key map "\C-c\C-z" 'python-switch-to-python)
(define-key map "\C-c\C-m" 'python-load-file)
(define-key map "\C-c\C-l" 'python-load-file) ; a la cmuscheme
+ (define-key map (car (where-is-internal 'complete-symbol (list global-map)))
+ 'python-complete-symbol)
;; Fixme: Add :help to menu.
(easy-menu-define python-menu map "Python Mode menu"
'("Python"
@@ -261,9 +262,7 @@ Used for syntactic keywords. N is the m
;;;; Utility stuff
(defsubst python-in-string/comment ()
- "Return non-nil if point is in a Python literal (a comment or string).
-Optional argument LIM indicates the beginning of the containing form,
-i.e. the limit on how far back to scan."
+ "Return non-nil if point is in a Python literal (a comment or string)."
(syntax-ppss-context (syntax-ppss)))
(defconst python-space-backslash-table
@@ -300,14 +299,16 @@ comments and strings, or that the bracke
(defun python-comment-line-p ()
"Return non-nil if current line has only a comment or is blank."
(save-excursion
- (back-to-indentation)
- (looking-at (rx (or (syntax comment-start) line-end)))))
+ (end-of-line)
+ (when (eq 'comment (syntax-ppss-context (syntax-ppss)))
+ (back-to-indentation)
+ (looking-at (rx (or (syntax comment-start) line-end))))))
(defun python-beginning-of-string ()
"Go to beginning of string around point.
Do nothing if not in string."
(let ((state (syntax-ppss)))
- (when (nth 3 state)
+ (when (eq 'string (syntax-ppss-context state))
(goto-char (nth 8 state)))))
(defun python-open-block-statement-p (&optional bos)
@@ -383,7 +384,8 @@ Otherwise indent them to column zero."
(defcustom python-honour-comment-indentation nil
"Non-nil means indent relative to preceding comment line.
Only do this for comments where the leading comment character is followed
-by space."
+by space. This doesn't apply to comment lines, which are always indented
+in lines with preceding comments."
:type 'boolean
:group 'python)
@@ -513,6 +515,14 @@ Set `python-indent' locally to the value
(- python-indent)))
0)))))))))
+(defun python-comment-indent ()
+ "`comment-indent-function' for Python."
+ ;; If previous non-blank line was a comment, use its indentation.
+ (unless (bobp)
+ (save-excursion
+ (forward-comment -1)
+ (if (eq ?# (char-after)) (current-column)))))
+
;;;; Cycling through the possible indentations with successive TABs.
;; These don't need to be buffer-local since they're only relevant
@@ -537,11 +547,17 @@ Set `python-indent' locally to the value
(point))))
(defun python-indentation-levels ()
- "Return a list of possible indentations for this statement.
+ "Return a list of possible indentations for this line.
Includes the default indentation and those which would close all
-enclosing blocks."
+enclosing blocks. Assumes the line has already been indented per
+`python-indent-line'. Elements of the list are actually pairs:
+\(INDENTATION . TEXT), where TEXT is the initial text of the
+corresponding block opening (or nil)."
(save-excursion
- (let ((levels (list (cons (current-indentation) nil))))
+ (let ((levels (list (cons (current-indentation)
+ (save-excursion
+ (if (python-beginning-of-block)
+ (python-initial-text)))))))
;; Only one possibility if we immediately follow a block open or
;; are in a continuation line.
(unless (or (python-continuation-line-p)
@@ -567,8 +583,7 @@ enclosing blocks."
(if (> (- (point-max) pos) (point))
(goto-char (- (point-max) pos))))))
-;; Fixme: Is the arg necessary?
-(defun python-indent-line (&optional arg)
+(defun python-indent-line ()
"Indent current line as Python code.
When invoked via `indent-for-tab-command', cycle through possible
indentations for current line. The cycle is broken by a command different
@@ -585,13 +600,30 @@ from `indent-for-tab-command', i.e. succ
(beginning-of-line)
(delete-horizontal-space)
(indent-to (car (nth python-indent-index python-indent-list)))
- (let ((text (cdr (nth python-indent-index
- python-indent-list))))
- (if text (message "Closes: %s" text)))))
+ (if (python-block-end-p)
+ (let ((text (cdr (nth python-indent-index
+ python-indent-list))))
+ (if text
+ (message "Closes: %s" text))))))
(python-indent-line-1)
(setq python-indent-list (python-indentation-levels)
python-indent-list-length (length python-indent-list)
python-indent-index (1- python-indent-list-length)))))
+
+(defun python-block-end-p ()
+ "Non-nil if this is a line in a statement closing a block,
+or a blank line indented to where it would close a block."
+ (and (not (python-comment-line-p))
+ (or (python-close-block-statement-p t)
+ (< (current-indentation)
+ (save-excursion
+ (python-previous-statement)
+ (current-indentation))))))
+
+;; Fixme: Define an indent-region-function. It should probably leave
+;; lines alone if the indentation is already at one of the allowed
+;; levels. Otherwise, M-C-\ typically keeps indenting more deeply
+;; down a function.
;;;; Movement.
@@ -628,8 +660,7 @@ start of buffer."
"`end-of-defun-function' for Python.
Finds end of innermost nested class or method definition."
(let ((orig (point))
- (pattern (rx (and line-start (0+ space)
- (or "def" "class") space))))
+ (pattern (rx (and line-start (0+ space) (or "def" "class") space))))
;; Go to start of current block and check whether it's at top
;; level. If it is, and not a block start, look forward for
;; definition statement.
@@ -914,13 +945,20 @@ See `python-check-command' for the defau
(file-name-nondirectory name))))))))
(setq python-saved-check-command command)
(save-some-buffers (not compilation-ask-about-save) nil)
- (compilation-start command))
+ (let ((compilation-error-regexp-alist
+ (cons '("(\\([^,]+\\), line \\([0-9]+\\))" 1 2)
+ compilation-error-regexp-alist)))
+ (compilation-start command)))
;;;; Inferior mode stuff (following cmuscheme).
+;; Fixme: Make sure we can work with IPython.
+
(defcustom python-python-command "python"
"*Shell command to run Python interpreter.
-Any arguments can't contain whitespace."
+Any arguments can't contain whitespace.
+Note that IPython may not work properly; it must at least be used with the
+`-cl' flag, i.e. use `ipython -cl'."
:group 'python
:type 'string)
@@ -973,6 +1011,9 @@ et al.")
`((,(rx (and line-start (1+ (any " \t")) "File \""
(group (1+ (not (any "\"<")))) ; avoid `' &c
"\", line " (group (1+ digit))))
+ 1 python-compilation-line-number)
+ (,(rx (and " in file " (group (1+ not-newline)) " on line "
+ (group (1+ digit))))
1 python-compilation-line-number))
"`compilation-error-regexp-alist' for inferior Python.")
@@ -999,19 +1040,29 @@ For running multiple processes in multip
:group 'python
(set-syntax-table python-mode-syntax-table)
(setq mode-line-process '(":%s"))
- ;; Fixme: Maybe install some python-mode bindings too.
- (define-key inferior-python-mode-map "\C-c\C-l" 'python-load-file)
- (define-key inferior-python-mode-map "\C-c\C-z" 'python-switch-to-python)
- (add-hook 'comint-input-filter-functions 'python-input-filter nil t)
+ (set (make-local-variable 'comint-input-filter) 'python-input-filter)
+ (add-hook 'comint-input-filter-functions 'python-input-filter-2 nil t)
(add-hook 'comint-preoutput-filter-functions #'python-preoutput-filter
nil t)
;; Still required by `comint-redirect-send-command', for instance
;; (and we need to match things like `>>> ... >>> '):
- (set (make-local-variable 'comint-prompt-regexp) "^\\([>.]\\{3\\} \\)+")
+ (set (make-local-variable 'comint-prompt-regexp)
+ (rx (and line-start (1+ (and (repeat 3 (any ">.")) ?\ )))))
(set (make-local-variable 'compilation-error-regexp-alist)
python-compilation-regexp-alist)
(compilation-shell-minor-mode 1))
+(define-key inferior-python-mode-map "\C-c\C-l" 'python-load-file)
+(define-key inferior-python-mode-map "\C-c\C-z" 'python-switch-to-python)
+(define-key inferior-python-mode-map "\C-c\C-v" 'python-check)
+;; Note that we _can_ still use these commands which send to the
+;; Python process even at the prompt iff we have a normal prompt,
+;; i.e. '>>> ' and not '... '. See the comment before
+;; python-send-region. Fixme: uncomment these if we address that.
+
+;; (define-key inferior-python-mode-map [(meta ?\t)] 'python-complete-symbol)
+;; (define-key inferior-python-mode-map "\C-c\C-f" 'python-describe-symbol)
+
(defcustom inferior-python-filter-regexp "\\`\\s-*\\S-?\\S-?\\s-*\\'"
"*Input matching this regexp is not saved on the history list.
Default ignores all inputs of 0, 1, or 2 non-blank characters."
@@ -1024,11 +1075,13 @@ It can also be a filename.")
(defun python-input-filter (str)
"`comint-input-filter' function for inferior Python.
-Don't save anything for STR matching `inferior-python-filter-regexp'.
-Also resets variables for adjusting error messages."
- (setq python-orig-start nil)
+Don't save anything for STR matching `inferior-python-filter-regexp'."
(not (string-match inferior-python-filter-regexp str)))
+(defun python-input-filter-2 (str)
+ "Reset `python-orig-start' in `comint-input-filter-functions'."
+ (setq python-orig-start nil))
+
;; Fixme: Loses with quoted whitespace.
(defun python-args-to-list (string)
(let ((where (string-match "[ \t]" string)))
@@ -1047,7 +1100,7 @@ Used as line-number hook function in `py
(if (and (markerp python-orig-start)
(marker-buffer python-orig-start))
(let ((start python-orig-start))
- (with-current-buffer (marker-buffer python-orig-start)
+ (with-current-buffer (marker-buffer start)
(goto-char start)
(forward-line (1- line))
(point-marker)))
@@ -1057,7 +1110,7 @@ Used as line-number hook function in `py
line col)))))
(defvar python-preoutput-result nil
- "Data from output line last `_emacs_out' line seen by the preoutput filter.")
+ "Data from last `_emacs_out' line seen by the preoutput filter.")
(defvar python-preoutput-continuation nil
"If non-nil, funcall this when `python-preoutput-filter' sees `_emacs_ok'.")
@@ -1068,7 +1121,9 @@ Used as line-number hook function in `py
;; `python-preoutput-continuation' if we get it.
(defun python-preoutput-filter (s)
"`comint-preoutput-filter-functions' function: ignore prompts not at bol."
- (cond ((and (string-match "\\`[.>]\\{3\\} \\'" s)
+ (cond ((and (string-match (rx (and string-start (repeat 3 (any ".>"))
+ " " string-end))
+ s)
(/= (let ((inhibit-field-text-motion t))
(line-beginning-position))
(point)))
@@ -1089,10 +1144,10 @@ Used as line-number hook function in `py
CMD is the Python command to run. NOSHOW non-nil means don't show the
buffer automatically.
If there is a process already running in `*Python*', switch to
-that buffer. Interactively a prefix arg, allows you to edit the initial
-command line (default is the value of `python-command'); `-i' etc. args
-will be added to this as appropriate. Runs the hooks
-`inferior-python-mode-hook' (after the `comint-mode-hook' is run).
+that buffer. Interactively, a prefix arg allows you to edit the initial
+command line (default is `python-command'); `-i' etc. args will be added
+to this as appropriate. Runs the hooks `inferior-python-mode-hook'
+\(after the `comint-mode-hook' is run).
\(Type \\[describe-mode] in the process buffer for a list of commands.)"
(interactive (list (if current-prefix-arg
(read-string "Run Python: " python-command)
@@ -1102,79 +1157,68 @@ will be added to this as appropriate. R
;; Fixme: Consider making `python-buffer' buffer-local as a buffer
;; (not a name) in Python buffers from which `run-python' &c is
;; invoked. Would support multiple processes better.
- (unless (comint-check-proc "*Python*")
- (let ((cmdlist (append (python-args-to-list cmd) '("-i"))))
+ (unless (comint-check-proc python-buffer)
+ (let ((cmdlist (append (python-args-to-list cmd) '("-i")))
+ (process-environment ; to import emacs.py
+ (push (concat "PYTHONPATH=" data-directory)
+ process-environment)))
(set-buffer (apply 'make-comint "Python" (car cmdlist) nil
- (cdr cmdlist))))
+ (cdr cmdlist)))
+ (setq python-buffer "*Python*"))
(inferior-python-mode)
;; Load function defintions we need.
;; Before the preoutput function was used, this was done via -c in
;; cmdlist, but that loses the banner and doesn't run the startup
- ;; file.
- (python-send-string "\
-def _emacs_execfile (file): # execute file and remove it
- from os import remove
- try: execfile (file, globals (), globals ())
- finally: remove (file)
-
-def _emacs_args (name): # get arglist of name for eldoc &c
- import inspect
- parts = name.split ('.')
- if len (parts) > 1:
- try: exec 'import ' + parts[0]
- except: return None
- try: exec 'func='+name # lose if name is keyword or undefined
- except: return None
- if inspect.isbuiltin (func):
- doc = func.__doc__
- if doc.find (' ->') != -1:
- print '_emacs_out', doc.split (' ->')[0]
- elif doc.find ('\\n') != -1:
- print '_emacs_out', doc.split ('\\n')[0]
- return None
- if inspect.ismethod (func): func = func.im_func
- if not inspect.isfunction (func):
- return None
- (args, varargs, varkw, defaults) = inspect.getargspec (func)
- print '_emacs_out', func.__name__+inspect.formatargspec (args, varargs, varkw, defaults)
-
-print '_emacs_ok'"))
- (unless noshow (pop-to-buffer (setq python-buffer "*Python*"))))
+ ;; file. The code might be inline here, but there's enough that it
+ ;; seems worth putting in a separate file, and it's probably cleaner
+ ;; to put it in a module.
+ (python-send-string "import emacs"))
+ (unless noshow (pop-to-buffer python-buffer)))
+
+;; Fixme: We typically lose if the inferior isn't in the normal REPL,
+;; e.g. prompt is `help> '. Probably raise an error if the form of
+;; the prompt is unexpected; actually, it needs to be `>>> ', not
+;; `... ', i.e. we're not inputting a block &c. However, this may not
+;; be the place to do it, e.g. we might actually want to send commands
+;; having set up such a state.
(defun python-send-region (start end)
"Send the region to the inferior Python process."
;; The region is evaluated from a temporary file. This avoids
;; problems with blank lines, which have different semantics
;; interactively and in files. It also saves the inferior process
- ;; buffer filling up with interpreter prompts. We need a function
- ;; to remove the temporary file when it has been evaluated, which
- ;; unfortunately means using a not-quite pristine interpreter
- ;; initially. Unfortunately we also get tracebacks which look like:
- ;;
- ;; >>> Traceback (most recent call last):
- ;; File "", line 1, in ?
- ;; File "", line 4, in _emacs_execfile
- ;; File "/tmp/py7734RSB", line 11
+ ;; buffer filling up with interpreter prompts. We need a Python
+ ;; function to remove the temporary file when it has been evaluated
+ ;; (though we could probably do it in Lisp with a Comint output
+ ;; filter). This function also catches exceptions and truncates
+ ;; tracebacks not to mention the frame of the function itself.
;;
;; The compilation-minor-mode parsing takes care of relating the
- ;; reference to the temporary file to the source. Fixme:
- ;; comint-filter the first two lines of the traceback?
+ ;; reference to the temporary file to the source.
+ ;;
+ ;; Fixme: Write a `coding' header to the temp file if the region is
+ ;; non-ASCII.
(interactive "r")
(let* ((f (make-temp-file "py"))
- (command (format "_emacs_execfile(%S)" f))
+ (command (format "emacs.eexecfile(%S)" f))
(orig-start (copy-marker start)))
- (if (save-excursion
- (goto-char start)
- (/= 0 (current-indentation))) ; need dummy block
- (write-region "if True:\n" nil f nil 'nomsg))
+ (when (save-excursion
+ (goto-char start)
+ (/= 0 (current-indentation))) ; need dummy block
+ (save-excursion
+ (goto-char orig-start)
+ ;; Wrong if we had indented code at buffer start.
+ (set-marker orig-start (line-beginning-position 0)))
+ (write-region "if True:\n" nil f nil 'nomsg))
(write-region start end f t 'nomsg)
- (when python-buffer
+ (let ((proc (python-proc)))
(with-current-buffer python-buffer
- (let ((end (marker-position (process-mark (python-proc)))))
+ (let ((end (marker-position (process-mark proc))))
+ (goto-char (point-max))
(set (make-local-variable 'python-orig-start) orig-start)
(set (make-local-variable 'compilation-error-list) nil)
(let ((comint-input-filter-functions
- (delete 'python-input-filter comint-input-filter-functions)))
+ (delete 'python-input-filter-2 comint-input-filter-functions)))
(python-send-string command))
(set-marker compilation-parsing-end end)
(setq compilation-last-buffer (current-buffer)))))))
@@ -1190,6 +1234,8 @@ print '_emacs_ok'"))
(interactive)
(python-send-region (point-min) (point-max)))
+;; Fixme: Try to define the function or class within the relevant
+;; module, not just at top level.
(defun python-send-defun ()
"Send the current defun (class or method) to the inferior Python process."
(interactive)
@@ -1237,38 +1283,39 @@ module-qualified names."
(interactive (comint-get-source "Load Python file: " python-prev-dir/file
python-source-modes
t)) ; because execfile needs exact name
- (comint-check-source file-name) ; Check to see if buffer needs saved.
+ (comint-check-source file-name) ; Check to see if buffer needs saving.
(setq python-prev-dir/file (cons (file-name-directory file-name)
(file-name-nondirectory file-name)))
- (when python-buffer
+ (let ((proc (python-proc)))
(with-current-buffer python-buffer
- (let ((end (marker-position (process-mark (python-proc)))))
+ (let ((end (marker-position (process-mark proc))))
(set (make-local-variable 'compilation-error-list) nil)
- ;; (set (make-local-variable 'compilation-old-error-list) nil)
- (let ((comint-input-filter-functions
- (delete 'python-input-filter comint-input-filter-functions)))
- (set (make-local-variable 'python-orig-start) nil)
- ;; Fixme: I'm not convinced by this logic from python-mode.el.
- (python-send-string
- (if (string-match "\\.py\\'" file-name)
- ;; Fixme: make sure the directory is in the path list
- (let ((module (file-name-sans-extension
- (file-name-nondirectory file-name))))
- (format "\
-if globals().has_key(%S): reload(%s)
-else: import %s
-" module module module))
- (format "execfile('%s')" file-name))))
+ (set (make-local-variable 'python-orig-start) nil)
+ (goto-char (point-max))
+ ;; Fixme: I'm not convinced by this logic from python-mode.el.
+ (python-send-string
+ (if (string-match "\\.py\\'" file-name)
+ ;; Fixme: make sure the directory is in the path list
+ (let ((module (file-name-sans-extension
+ (file-name-nondirectory file-name))))
+ (format "emacs.eimport(%S,%S)"
+ module (file-name-directory file-name)))
+ (format "execfile(%S)" file-name)))
+ (message "%s loaded" file-name)
(set-marker compilation-parsing-end end)
(setq compilation-last-buffer (current-buffer))))))
-;; Fixme: Should this start a process if there isn't one? (Unlike cmuscheme.)
+;; Fixme: If we need to start the process, wait until we've got the OK
+;; from the startup.
(defun python-proc ()
- "Return the current Python process. See variable `python-buffer'."
- (let ((proc (get-buffer-process (if (eq major-mode 'inferior-python-mode)
- (current-buffer)
- python-buffer))))
- (or proc (error "No current process. See variable `python-buffer'"))))
+ "Return the current Python process.
+See variable `python-buffer'. Starts a new process if necessary."
+ (or (if python-buffer
+ (get-buffer-process (if (eq major-mode 'inferior-python-mode)
+ (current-buffer)
+ python-buffer)))
+ (progn (run-python nil t)
+ (python-proc))))
;;;; Context-sensitive help.
@@ -1280,33 +1327,45 @@ else: import %s
"Syntax table giving `.' symbol syntax.
Otherwise inherits from `python-mode-syntax-table'.")
+(defvar view-return-to-alist)
+
;; Fixme: Should this actually be used instead of info-look, i.e. be
-;; bound to C-h S?
+;; bound to C-h S? Can we use other pydoc stuff before python 2.2?
(defun python-describe-symbol (symbol)
- "Get help on SYMBOL using `pydoc'.
-Interactively, prompt for symbol."
- ;; Note that we do this in the inferior process, not a separate one to
+ "Get help on SYMBOL using `help'.
+Interactively, prompt for symbol.
+
+Symbol may be anything recognized by the interpreter's `help' command --
+e.g. `CALLS' -- not just variables in scope.
+This only works for Python version 2.2 or newer since earlier interpreters
+don't support `help'."
+ ;; Note that we do this in the inferior process, not a separate one, to
;; ensure the environment is appropriate.
(interactive
(let ((symbol (with-syntax-table python-dotty-syntax-table
(current-word)))
- (enable-recursive-minibuffers t)
- val)
- (setq val (read-string (if symbol
- (format "Describe symbol (default %s): "
- symbol)
- "Describe symbol: ")
- nil nil symbol))
- (list (or val symbol))))
+ (enable-recursive-minibuffers t))
+ (list (read-string (if symbol
+ (format "Describe symbol (default %s): " symbol)
+ "Describe symbol: ")
+ nil nil symbol))))
(if (equal symbol "") (error "No symbol"))
(let* ((func `(lambda ()
- (comint-redirect-send-command (format "help(%S)\n" ,symbol)
+ (comint-redirect-send-command (format "emacs.ehelp(%S)\n"
+ ,symbol)
"*Help*" nil))))
;; Ensure we have a suitable help buffer.
- (let (temp-buffer-show-hook) ; avoid xref stuff
+ ;; Fixme: Maybe process `Related help topics' a la help xrefs and
+ ;; allow C-c C-f in help buffer.
+ (let ((temp-buffer-show-hook ; avoid xref stuff
+ (lambda ()
+ (toggle-read-only 1)
+ (setq view-return-to-alist
+ (list (cons (selected-window) help-return-method))))))
(with-output-to-temp-buffer "*Help*"
(with-current-buffer standard-output
- (set (make-local-variable 'comint-redirect-subvert-readonly) t))))
+ (set (make-local-variable 'comint-redirect-subvert-readonly) t)
+ (print-help-return-message))))
(if (and python-buffer (get-buffer python-buffer))
(with-current-buffer python-buffer
(funcall func))
@@ -1315,6 +1374,15 @@ Interactively, prompt for symbol."
(add-to-list 'debug-ignored-errors "^No symbol")
+(defun python-send-receive (string)
+ "Send STRING to inferior Python (if any) and return result.
+The result is what follows `_emacs_out' in the output (or nil)."
+ (let ((proc (python-proc)))
+ (python-send-string string)
+ (setq python-preoutput-result nil)
+ (accept-process-output proc 5)
+ python-preoutput-result))
+
;; Fixme: try to make it work with point in the arglist. Also, is
;; there anything reasonable we can do with random methods?
;; (Currently only works with functions.)
@@ -1323,14 +1391,9 @@ Interactively, prompt for symbol."
Only works when point is in a function name, not its arglist, for instance.
Assumes an inferior Python is running."
(let ((symbol (with-syntax-table python-dotty-syntax-table
- (current-word)))
- (proc (and python-buffer (python-proc))))
- (when (and proc symbol)
- (python-send-string
- (format "_emacs_args(%S)" symbol))
- (setq python-preoutput-result nil)
- (accept-process-output proc 1)
- python-preoutput-result)))
+ (current-word))))
+ (when symbol
+ (python-send-receive (format "emacs.eargs(%S)" symbol)))))
;;;; Info-look functionality.
@@ -1533,11 +1596,97 @@ Uses `python-beginning-of-block', `pytho
(python-end-of-block)
(exchange-point-and-mark))
+;;;; Completion.
+
+(defun python-symbol-completions (symbol)
+ "Return a list of completions of the string SYMBOL from Python process.
+The list is sorted."
+ (when symbol
+ (let ((completions
+ (condition-case ()
+ (car (read-from-string (python-send-receive
+ (format "emacs.complete(%S)" symbol))))
+ (error nil))))
+ (sort
+ ;; We can get duplicates from the above -- don't know why.
+ (delete-dups completions)
+ #'string<))))
+
+(defun python-partial-symbol ()
+ "Return the partial symbol before point (for completion)."
+ (let ((end (point))
+ (start (save-excursion
+ (and (re-search-backward
+ (rx (and (or buffer-start (regexp "[^[:alnum:]._]"))
+ (group (1+ (regexp "[[:alnum:]._]")))
+ point))
+ nil t)
+ (match-beginning 1)))))
+ (if start (buffer-substring-no-properties start end))))
+
+;; Fixme: We should have an abstraction of this sort of thing in the
+;; core.
+(defun python-complete-symbol ()
+ "Perform completion on the Python symbol preceding point.
+Repeating the command scrolls the completion window."
+ (interactive)
+ (let ((window (get-buffer-window "*Completions*")))
+ (if (and (eq last-command this-command)
+ window (window-live-p window) (window-buffer window)
+ (buffer-name (window-buffer window)))
+ (with-current-buffer (window-buffer window)
+ (if (pos-visible-in-window-p (point-max) window)
+ (set-window-start window (point-min))
+ (save-selected-window
+ (select-window window)
+ (scroll-up))))
+ ;; Do completion.
+ (let* ((end (point))
+ (symbol (python-partial-symbol))
+ (completions (python-symbol-completions symbol))
+ (completion (if completions
+ (try-completion symbol completions))))
+ (when symbol
+ (cond ((eq completion t))
+ ((null completion)
+ (message "Can't find completion for \"%s\"" symbol)
+ (ding))
+ ((not (string= symbol completion))
+ (delete-region (- end (length symbol)) end)
+ (insert completion))
+ (t
+ (message "Making completion list...")
+ (with-output-to-temp-buffer "*Completions*"
+ (display-completion-list completions))
+ (message "Making completion list...%s" "done"))))))))
+
+(eval-when-compile (require 'hippie-exp))
+
+(defun python-try-complete (old)
+ "Completion function for Python for use with `hippie-expand'."
+ (when (eq major-mode 'python-mode) ; though we only add it locally
+ (unless old
+ (let ((symbol (python-partial-symbol)))
+ (he-init-string (- (point) (length symbol)) (point))
+ (if (not (he-string-member he-search-string he-tried-table))
+ (push he-search-string he-tried-table))
+ (setq he-expand-list
+ (and symbol (python-symbol-completions symbol)))))
+ (while (and he-expand-list
+ (he-string-member (car he-expand-list) he-tried-table))
+ (pop he-expand-list))
+ (if he-expand-list
+ (progn
+ (he-substitute-string (pop he-expand-list))
+ t)
+ (if old (he-reset-string))
+ nil)))
+
;;;; Modes.
(defvar outline-heading-end-regexp)
(defvar eldoc-print-current-symbol-info-function)
-(defvar python-mode-running)
+
;;;###autoload
(define-derived-mode python-mode fundamental-mode "Python"
"Major mode for editing Python files.
@@ -1579,11 +1728,10 @@ lines count as headers.
))
(set (make-local-variable 'parse-sexp-lookup-properties) t)
(set (make-local-variable 'comment-start) "# ")
- ;; Fixme: define a comment-indent-function?
+ (set (make-local-variable 'comment-indent-function) #'python-comment-indent)
(set (make-local-variable 'indent-line-function) #'python-indent-line)
(set (make-local-variable 'paragraph-start) "\\s-*$")
- (set (make-local-variable 'fill-paragraph-function)
- 'python-fill-paragraph)
+ (set (make-local-variable 'fill-paragraph-function) 'python-fill-paragraph)
(set (make-local-variable 'require-final-newline) t)
(set (make-local-variable 'add-log-current-defun-function)
#'python-current-defun)
@@ -1601,6 +1749,9 @@ lines count as headers.
#'python-eldoc-function)
(add-hook 'eldoc-mode-hook
'(lambda () (run-python 0 t)) nil t) ; need it running
+ (if (featurep 'hippie-exp)
+ (set (make-local-variable 'hippie-expand-try-functions-list)
+ (cons 'python-try-complete hippie-expand-try-functions-list)))
(unless font-lock-mode (font-lock-mode 1))
(when python-guess-indent (python-guess-indent))
(set (make-local-variable 'python-command) python-python-command)