# HG changeset patch # User Stefan Monnier # Date 1081879246 0 # Node ID 3768540a819c360b68f62e8d7c0342fa8091bc76 # Parent 4cafb6ad0ccc6394dafd73b8627851317c367333 Doc fixes. Changes for compiler warnings. (syntax): Don't require. (python) : Add :version. (python-quote-syntax): Re-written. (inferior-python-mode): Move stuff here from run-python and add some more. (python-preoutput-continuation, python-preoutput-result) (python-dotty-syntax-table): New. (python-describe-symbol): Use them. (run-python): Move stuff to inferior-python-mode. Modify code loaded into Python. (python-send-region): Use python-proc, python-send-string. (python-send-string): Send newlines too. Callers changed. (python-load-file): Re-written. (python-eldoc-function): New. (info-look): Don't require. (python-after-info-look): New. A modified version of former top-level code for use with eval-after-load. (python-maybe-jython, python-guess-indent): Use widened buffer. (python-fill-paragraph): Re-written. (python-mode): Fix outline-regexp. Set outline-heading-end-regexp, eldoc-print-current-symbol-info-function. Add to eldoc-mode-hook. diff -r 4cafb6ad0ccc -r 3768540a819c lisp/progmodes/python.el --- a/lisp/progmodes/python.el Tue Apr 13 16:06:38 2004 +0000 +++ b/lisp/progmodes/python.el Tue Apr 13 18:00:46 2004 +0000 @@ -38,7 +38,7 @@ ;; This doesn't implement all the facilities of python-mode.el. Some ;; just need doing, e.g. catching exceptions in the inferior Python -;; buffer (but see M-x pdb for debugging). [Atually, the use of +;; buffer (but see M-x pdb for debugging). [Actually, the use of ;; `compilation-minor-mode' now is probably enough for that.] Others ;; don't seem appropriate. For instance, `forward-into-nomenclature' ;; should be done separately, since it's not specific to Python, and @@ -56,16 +56,25 @@ ;; 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. + ;;; Code: -(require 'syntax) ; ensure appropriate version ;; It's messy to autoload the relevant comint functions so that comint ;; is only required when inferior Python is used. (require 'comint) +(eval-when-compile + (require 'compile) + (autoload 'Info-last "info") + (autoload 'Info-exit "info") + (autoload 'info-lookup-maybe-add-help "info-look")) +(autoload 'compilation-start "compile") ; spurious compiler warning anyway (defgroup python nil "Silly walks in the Python language" :group 'languages + :version "21.4" :link '(emacs-commentary-link "python")) ;;;###autoload @@ -101,7 +110,8 @@ (group (syntax string-quote)) ; maybe gets property (backref 2) ; per first quote (group (backref 2)))) ; maybe gets property - (1 (python-quote-syntax 1)) (2 (python-quote-syntax 2)) + (1 (python-quote-syntax 1)) + (2 (python-quote-syntax 2)) (3 (python-quote-syntax 3))) ;; This doesn't really help. ;;; (,(rx (and ?\\ (group ?\n))) (1 " ")) @@ -110,40 +120,43 @@ (defun python-quote-syntax (n) "Put `syntax-table' property correctly on triple quote. Used for syntactic keywords. N is the match number (1, 2 or 3)." - ;; Given a triple quote, we have to look backwards for a previous - ;; occurrence of the sequence to know whether this is an opening or - ;; closing triple. We also have to sort out a possible prefix -- - ;; well, we don't _have_ to, but I think it should be treated as - ;; part of the string. - (let ((tag (save-excursion - (goto-char (match-beginning 0)) - (unless (eq ?\\ (char-before)) - (cond - ;; Look for a previous string fence. - ((or (zerop (skip-syntax-backward "^|")) - (bobp)) - 'start) ; no previous string fence - ;; Check fence on a matching quote. - ((eq (char-before) (char-after (match-beginning 2))) - (if (eq (char-before) (char-after)) - 'end ; fence ended string - 'start)) ; began string - ;; Check for matching prefixed string. - ((and (memq (char-before) '(?u ?U ?r ?R)) - (skip-chars-forward "rR")) - (if (eq (char-after) (char-after (match-beginning 2))) - 'end)) ; else non-matching: return nil - ;; For non-matching quote, start new string. - ((/= (char-before) (char-after)) - 'start)))))) - (if (if (eq tag 'start) ; Maybe starts a new string. - ;; Initial property if this is the prefix (empty or not) or - ;; isn't the prefix and the prefix is empty. - (or (= n 1) (and (= n 2) (= (match-beginning 1) (match-end 1)))) - (and (eq tag 'end) ; Maybe ends existing string. - (= n 3))) ; Match is at end. - (eval-when-compile (string-to-syntax "|")) - ;; Otherwise the property is nil, which is OK. + ;; Given a triple quote, we have to check the context to know + ;; whether this is an opening or closing triple or whether it's + ;; quoted anyhow, and should be ignored. (For that we need to do + ;; the same job as `syntax-ppss' to be correct and it seems to be OK + ;; to use it here despite initial worries.) We also have to sort + ;; out a possible prefix -- well, we don't _have_ to, but I think it + ;; should be treated as part of the string. + + ;; Test cases: + ;; ur"""ar""" x='"' # """ + ;; x = ''' """ ' a + ;; ''' + ;; x '"""' x + (save-excursion + (goto-char (match-beginning 0)) + (unless (eq ?\\ (char-before)) + (cond + ;; Consider property for the last char if in a fenced string. + ((= n 3) + (let ((syntax (syntax-ppss))) + (when (eq t (nth 3 syntax)) ; after unclosed fence + (goto-char (nth 8 syntax)) ; fence position + ;; Skip any prefix. + (if (memq (char-after) '(?u ?U ?R ?r)) + (skip-chars-forward "uUrR")) + ;; Is it a matching sequence? + (if (eq (char-after) (char-after (match-beginning 2))) + (eval-when-compile (string-to-syntax "|")))))) + ;; Consider property for initial char, accounting for prefixes. + ((or (and (= n 2) ; not prefix + (= (match-beginning 1) (match-end 1))) ; prefix is null + (and (= n 1) ; prefix + (/= (match-beginning 1) (match-end 1)))) ; non-empty + (unless (eq 'string (syntax-ppss-context (syntax-ppss))) + (eval-when-compile (string-to-syntax "|"))))) + ;; Otherwise (we're in a non-matching string) the property is + ;; nil, which is OK. ))) ;; This isn't currently in `font-lock-defaults' as probably not worth @@ -386,27 +399,29 @@ Set `python-indent' locally to the value guessed." (interactive) (save-excursion - (goto-char (point-min)) - (let (done indent) - (while (and (not done) (not (eobp))) - (when (and (re-search-forward (rx (and ?: (0+ space) - (or (syntax comment-start) - line-end))) - nil 'move) - (python-open-block-statement-p)) - (save-excursion - (python-beginning-of-statement) - (let ((initial (current-indentation))) - (if (zerop (python-next-statement)) - (setq indent (- (current-indentation) initial))) - (if (and (>= indent 2) (<= indent 8)) ; sanity check - (setq done t)))))) - (when done - (when (/= indent (default-value 'python-indent)) - (set (make-local-variable 'python-indent) indent) - (unless (= tab-width python-indent) - (setq indent-tabs-mode nil))) - indent)))) + (save-restriction + (widen) + (goto-char (point-min)) + (let (done indent) + (while (and (not done) (not (eobp))) + (when (and (re-search-forward (rx (and ?: (0+ space) + (or (syntax comment-start) + line-end))) + nil 'move) + (python-open-block-statement-p)) + (save-excursion + (python-beginning-of-statement) + (let ((initial (current-indentation))) + (if (zerop (python-next-statement)) + (setq indent (- (current-indentation) initial))) + (if (and (>= indent 2) (<= indent 8)) ; sanity check + (setq done t)))))) + (when done + (when (/= indent (default-value 'python-indent)) + (set (make-local-variable 'python-indent) indent) + (unless (= tab-width python-indent) + (setq indent-tabs-mode nil))) + indent))))) (defun python-calculate-indentation () "Calculate Python indentation for line at point." @@ -884,8 +899,6 @@ (defvar python-saved-check-command nil "Internal use.") -(autoload 'compilation-start "compile") - ;; After `sgml-validate-command'. (defun python-check (command) "Check a Python file (default current buffer's file). @@ -950,6 +963,13 @@ do the right thing. If you run multiple processes, you can change `python-buffer' to another process buffer with \\[set-variable].") +(defconst python-compilation-regexp-alist + `((,(rx (and line-start (1+ (any " \t")) "File \"" + (group (1+ (not (any "\"<")))) ; avoid `' &c + "\", line " (group (1+ digit)))) + 1 python-compilation-line-number)) + "`compilation-error-regexp-alist' for inferior Python.") + (define-derived-mode inferior-python-mode comint-mode "Inferior Python" "Major mode for interacting with an inferior Python process. A Python process can be started with \\[run-python]. @@ -973,7 +993,14 @@ ;; 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) - (setq comint-input-filter #'python-input-filter)) + (add-hook 'comint-input-filter-functions 'python-input-filter nil t) + (add-hook 'comint-preoutput-filter-functions #'python-preoutput-filter + nil t) + ;; Still required by `comint-redirect-send-command', for instance: + (set (make-local-variable 'comint-prompt-regexp) "^\\([>.]\\{3\\} \\)+") + (set (make-local-variable 'compilation-error-regexp-alist) + python-compilation-regexp-alist) + (compilation-shell-minor-mode 1)) (defcustom inferior-python-filter-regexp "\\`\\s-*\\S-?\\S-?\\s-*\\'" "*Input matching this regexp is not saved on the history list. @@ -989,7 +1016,7 @@ (defun python-input-filter (str) "`comint-input-filter' function for inferior Python. -Don't save anything matching `inferior-python-filter-regexp'. +Don't save anything for STR matching `inferior-python-filter-regexp'. Also resets variables for adjusting error messages." (setq python-orig-file nil python-orig-start-line 1) @@ -1005,17 +1032,6 @@ (t (let ((pos (string-match "[^ \t]" string))) (if pos (python-args-to-list (substring string pos)))))))) -(defvar compilation-minor-mode-map) -(defvar compilation-error-list) -(defvar compilation-parsing-end) - -(defconst python-compilation-regexp-alist - `((,(rx (and line-start (1+ (any " \t")) "File \"" - (group (1+ (not (any "\"<")))) ; avoid `' &c - "\", line " (group (1+ digit)))) - 1 python-compilation-line-number)) - "`compilation-error-regexp-alist' for inferior Python.") - (defun python-compilation-line-number (file col) "Return error descriptor of error found for FILE, column COL. Used as line-number hook function in `python-compilation-regexp-alist'." @@ -1028,44 +1044,90 @@ (+ line (1- python-orig-start-line)) nil))) +(defvar python-preoutput-result nil + "Data from output line 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'.") + +;; Using this stops us getting lines in the buffer like +;; >>> ... ... >>> +;; Also look for (and delete) an `_emacs_ok' string and call +;; `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) + (/= (let ((inhibit-field-text-motion t)) + (line-beginning-position)) + (point))) + "") + ((string= s "_emacs_ok\n") + (when python-preoutput-continuation + (funcall python-preoutput-continuation) + (setq python-preoutput-continuation nil)) + "") + ((string-match "_emacs_out \\(.*\\)\n" s) + (setq python-preoutput-result (match-string 1 s)) + "") + (t s))) + ;;;###autoload -(defun run-python (cmd) +(defun run-python (&optional cmd noshow) "Run an inferior Python process, input and output via buffer *Python*. -CMD is the Python command to run. +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. With argument, allows you to edit the initial -command line (default is the value of `python-command'); `-i' and -`-c' args will be added to this to support evaluations in the -REPL. 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.)" +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). +\(Type \\[describe-mode] in the process buffer for a list of commands.)" (interactive (list (if current-prefix-arg (read-string "Run Python: " python-command) python-command))) + (unless cmd (setq cmd python-python-command)) + (setq python-command cmd) ;; 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. - (if (not (comint-check-proc "*Python*")) - (let ((cmdlist (append (python-args-to-list cmd) - '("-i" "-c" "\ -from os import remove as _emacs_rem -def _emacs_execfile (file): - try: - execfile (file) - finally: - _emacs_rem (file) -")))) - (set-buffer (apply 'make-comint "Python" (car cmdlist) nil - (cdr cmdlist))) - (inferior-python-mode) - (setq comint-output-filter-functions nil))) - (setq python-command cmd) - (setq python-buffer "*Python*") - (pop-to-buffer "*Python*") - (set (make-local-variable 'compilation-error-regexp-alist) - python-compilation-regexp-alist) - (compilation-shell-minor-mode 1) - (add-hook 'comint-input-filter-functions 'python-input-filter nil t)) + (unless (comint-check-proc "*Python*") + (let ((cmdlist (append (python-args-to-list cmd) '("-i")))) + (set-buffer (apply 'make-comint "Python" (car cmdlist) nil + (cdr cmdlist)))) + (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*")))) (defun python-mouse-2-command (event) "Command bound to `mouse-2' in inferior Python buffer. @@ -1114,7 +1176,7 @@ ;; comint-filter the first two lines of the traceback? (interactive "r") (let* ((f (make-temp-file "py")) - (command (format "_emacs_execfile(%S)\n" f)) + (command (format "_emacs_execfile(%S)" f)) (orig-file (buffer-file-name)) (orig-line (save-restriction (widen) @@ -1126,22 +1188,21 @@ (write-region start end f t 'nomsg) (when python-buffer (with-current-buffer python-buffer - (let ((end (marker-position (process-mark - (get-buffer-process python-buffer))))) + (let ((end (marker-position (process-mark (python-proc))))) (set (make-local-variable 'python-orig-file) orig-file) (set (make-local-variable 'python-orig-start-line) orig-line) (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))) - (comint-send-string (python-proc) command)) + (python-send-string command)) (set-marker compilation-parsing-end end) (setq compilation-last-buffer (current-buffer))))))) (defun python-send-string (string) "Evaluate STRING in inferior Python process." (interactive "sPython command: ") - (comint-send-string (python-proc) string)) + (comint-send-string (python-proc) string) + (comint-send-string (python-proc) "\n\n")) (defun python-send-buffer () "Send the current buffer to the inferior Python process." @@ -1198,19 +1259,31 @@ (comint-check-source file-name) ; Check to see if buffer needs saved. (setq python-prev-dir/file (cons (file-name-directory file-name) (file-name-nondirectory file-name))) - (comint-send-string - (python-proc) - (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 + (when python-buffer + (with-current-buffer python-buffer + (let ((end (marker-position (process-mark (python-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))) + (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)))) + (set (make-local-variable 'python-orig-file) nil) + (set (make-local-variable 'python-orig-start-line) nil) + (format "\ +try: + if globals().has_key(%S): reload(%s) + else: import %s +except: None " module module module)) - (format "execfile('%s')\n" filename)))) + (set (make-local-variable 'python-orig-file) file-name) + (set (make-local-variable 'python-orig-start-line) 1) + (format "execfile('%s')" 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.) (defun python-proc () @@ -1222,74 +1295,114 @@ ;;;; Context-sensitive help. -;; Fixme: Investigate adapting this to eldoc. +(defconst python-dotty-syntax-table + (let ((table (make-syntax-table))) + (set-char-table-parent table python-mode-syntax-table) + (modify-syntax-entry ?. "_" table) + table) + "Syntax table giving `.' symbol syntax. +Otherwise inherits from `python-mode-syntax-table'.") +;; Fixme: Should this actually be used instead of info-look, i.e. be +;; bound to C-h S? (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 + ;; ensure the environment is appropriate. (interactive - (let ((symbol (current-word)) + (let ((symbol (with-syntax-table python-dotty-syntax-table + (current-word))) (enable-recursive-minibuffers t) val) (setq val (read-string (if symbol (format "Describe variable (default %s): " symbol) - "Describe variable: ") + "Describe symbol: ") nil nil symbol)) (list (or val symbol)))) (if (equal symbol "") (error "No symbol")) - (let* ((mod (if (string-match "\\(\\.[^.]+\\)\\'" symbol) - (substring symbol 0 (match-beginning 1)))) - (import (when mod (concat "import " mod "\n")))) - (with-output-to-temp-buffer "*Python Output*" - (princ (shell-command-to-string - (format "%s -c 'import pydoc %s -try: pydoc.help(%S) -except: print \"No help available on:\", %S'" - ;; Fixme: Is this necessarily right? - (car (split-string python-command)) - (or import "") symbol symbol)))))) + (let* ((func `(lambda () + (comint-redirect-send-command (format "help(%S)\n" ,symbol) + "*Help*" nil)))) + ;; Ensure we have a suitable help buffer. + (let (temp-buffer-show-hook) ; avoid xref stuff + (with-output-to-temp-buffer "*Help*" + (with-current-buffer standard-output + (set (make-local-variable 'comint-redirect-subvert-readonly) t)))) + (if (and python-buffer (get-buffer python-buffer)) + (with-current-buffer python-buffer + (funcall func)) + (setq python-preoutput-continuation func) + (run-python nil t)))) (add-to-list 'debug-ignored-errors "^No symbol") + +;; 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.) +(defun python-eldoc-function () + "`eldoc-print-current-symbol-info' for Python. +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))) ;;;; Info-look functionality. -(require 'info-look) -;; We should be able to add info-lookup specs without loading the file first. -;; E.g. by setting a buffer-local var or something like that. --Stef -(let ((version (let ((s (shell-command-to-string (concat python-command - " -V")))) - (string-match "^Python \\([0-9]+\\.[0-9]+\\>\\)" s) - (match-string 1 s))) - ;; Whether info files have a Python version suffix, e.g. in Debian. - (versioned (save-window-excursion - (condition-case () - ;; The call to `info' might create a new frame if - ;; pop-up-frames or special-display-buffers are used. - ;; So I disabled it until we find a better way - ;; to handle this situation. Maybe Debian should just - ;; fix their install somehow. --Stef - ;; (progn (info "(python2.3-lib)Miscellaneous Index") - ;; (Info-last) - ;; (Info-exit) - ;; t) - nil - (error nil))))) - (info-lookup-maybe-add-help - :mode 'python-mode - :regexp "[[:alnum:]_]+" - :doc-spec - ;; Fixme: Add python-ref? Can this reasonably be made specific - ;; to indices with different rules? - (if versioned - '((,(concat "(python" version "-lib)Module Index")) - (,(concat "(python" version "-lib)Class-Exception-Object Index")) - (,(concat "(python" version "-lib)Function-Method-Variable Index")) - (,(concat "(python" version "-lib)Miscellaneous Index"))) - '(("(python-lib)Module Index") - ("(python-lib)Class-Exception-Object Index") - ("(python-lib)Function-Method-Variable Index") - ("(python-lib)Miscellaneous Index"))))) +(defun python-after-info-look () + "Set up info-look for Python. +Used with `eval-after-load'." + (let* ((version (let ((s (shell-command-to-string (concat python-command + " -V")))) + (string-match "^Python \\([0-9]+\\.[0-9]+\\>\\)" s) + (match-string 1 s))) + ;; Whether info files have a Python version suffix, e.g. in Debian. + (versioned + (with-temp-buffer + (Info-mode) + (condition-case () + (Info-goto-node (format "(python%s-lib)Miscellaneous Index" + version)) + (error nil))))) + (info-lookup-maybe-add-help + :mode 'python-mode + :regexp "[[:alnum:]_]+" + :doc-spec + ;; Fixme: Can this reasonably be made specific to indices with + ;; different rules? Is the order of indices optimal? + ;; (Miscellaneous in -ref first prefers lookup of keywords, for + ;; instance.) + (if versioned + ;; The empty prefix just gets us highlighted terms. + `((,(concat "(python" version "-ref)Miscellaneous Index") nil "") + (,(concat "(python" version "-ref)Module Index" nil "")) + (,(concat "(python" version "-ref)Function-Method-Variable Index" + nil "")) + (,(concat "(python" version "-ref)Class-Exception-Object Index" + nil "")) + (,(concat "(python" version "-lib)Module Index" nil "")) + (,(concat "(python" version "-lib)Class-Exception-Object Index" + nil "")) + (,(concat "(python" version "-lib)Function-Method-Variable Index" + nil "")) + (,(concat "(python" version "-lib)Miscellaneous Index" nil ""))) + '(("(python-ref)Miscellaneous Index" nil "") + ("(python-ref)Module Index" nil "") + ("(python-ref)Function-Method-Variable Index" nil "") + ("(python-ref)Class-Exception-Object Index" nil "") + ("(python-lib)Module Index" nil "") + ("(python-lib)Class-Exception-Object Index" nil "") + ("(python-lib)Function-Method-Variable Index" nil "") + ("(python-lib)Miscellaneous Index" nil "")))))) +(eval-after-load "info-look" '(python-after-info-look)) ;;;; Miscellancy. @@ -1309,34 +1422,65 @@ `python-jython-packages'." ;; The logic is taken from python-mode.el. (save-excursion - (goto-char (point-min)) - (let ((interpreter (if (looking-at auto-mode-interpreter-regexp) - (match-string 2)))) - (if (and interpreter (eq 'jython-mode - (cdr (assoc (file-name-nondirectory interpreter) - interpreter-mode-alist)))) - (jython-mode) - (if (catch 'done - (while (re-search-forward - (rx (and line-start (or "import" "from") (1+ space) - (group (1+ (not (any " \t\n.")))))) - (+ (point) 10000) ; Probably not worth customizing. - t) - (if (member (match-string 1) python-jython-packages) - (throw 'done t)))) - (jython-mode)))))) + (save-restriction + (widen) + (goto-char (point-min)) + (let ((interpreter (if (looking-at auto-mode-interpreter-regexp) + (match-string 2)))) + (if (and interpreter (eq 'jython-mode + (cdr (assoc (file-name-nondirectory + interpreter) + interpreter-mode-alist)))) + (jython-mode) + (if (catch 'done + (while (re-search-forward + (rx (and line-start (or "import" "from") (1+ space) + (group (1+ (not (any " \t\n.")))))) + 10000 ; Probably not worth customizing. + t) + (if (member (match-string 1) python-jython-packages) + (throw 'done t)))) + (jython-mode))))))) (defun python-fill-paragraph (&optional justify) - "Like \\[fill-paragraph], but handle comments and multi-line strings. + "`fill-paragraph-function' handling comments and multi-line strings. If any of the current line is a comment, fill the comment or the -paragraph of it that point is in, preserving the comment's indentation -and initial comment characters." +paragraph of it that point is in, preserving the comment's +indentation and initial comment characters. Similarly if the end +of the current line is in or at the end of a multi-line string. +Otherwise, do nothing." (interactive "P") (or (fill-comment-paragraph justify) - (let ((paragraph-start (concat paragraph-start - "\\|\\s-*\\(:?#\\s\"\\|\\s|\\|#"))) - (fill-paragraph justify)) - t)) + ;; The `paragraph-start' and `paragraph-separate' variables + ;; don't allow us to delimit the last paragraph in a multi-line + ;; string properly, so narrow to the string and then fill around + ;; (the end of) the current line. + (save-excursion + (end-of-line) + (let* ((syntax (syntax-ppss)) + (orig (point)) + (start (nth 8 syntax)) + end) + (cond ((eq t (nth 3 syntax)) ; in fenced string + (goto-char (nth 8 syntax)) ; string start + (condition-case () ; for unbalanced quotes + (progn (forward-sexp) + (setq end (point))) + (error (setq end (point-max))))) + ((re-search-backward "\\s|\\s-*\\=" nil t) ; end of fenced + ; string + (forward-char) + (setq end (point)) + (condition-case () + (progn (backward-sexp) + (setq start (point))) + (error nil)))) + (when end + (save-restriction + (narrow-to-region start end) + (goto-char orig) + (fill-paragraph justify)))))) + t) (defun python-shift-left (start end &optional count) "Shift lines in region COUNT (the prefix arg) columns to the left. @@ -1377,7 +1521,8 @@ (defun python-outline-level () "`outline-level' function for Python mode. -The level is the number of `python-indent' steps of indentation." +The level is the number of `python-indent' steps of indentation +of current line." (/ (current-indentation) python-indent)) ;; Fixme: Consider top-level assignments, imports, &c. @@ -1411,17 +1556,23 @@ ;;;; Modes. +(defvar outline-heading-end-regexp) +(defvar eldoc-print-current-symbol-info-function) + ;;;###autoload (define-derived-mode python-mode fundamental-mode "Python" "Major mode for editing Python files. Turns on Font Lock mode unconditionally since it is required for correct parsing of the source. See also `jython-mode', which is actually invoked if the buffer appears to -contain Jython code. +contain Jython code. See also `run-python' and associated Python mode +commands for running Python under Emacs. The Emacs commands which work with `defun's, e.g. \\[beginning-of-defun], deal with nested `def' and `class' blocks. They take the innermost one as -current without distinguishing method and class definitions. +current without distinguishing method and class definitions. Used multiple +times, they move over others at the same indentation level until they reach +the end of definitions at that level, when they move up a level. \\ Colon is electric: it outdents the line if appropriate, e.g. for an else statement. \\[python-backspace] at the beginning of an indented statement @@ -1430,6 +1581,13 @@ the preceding code. Successive TABs, with no intervening command, cycle through the possibilities for indentation on the basis of enclosing blocks. +\\[fill-paragraph] fills comments and multiline strings appropriately, but has no +effect outside them. + +Supports Eldoc mode (only for functions, using a Python process), +Info-Look and Imenu. In Outline minor mode, `class' and `def' +lines count as headers. + \\{python-mode-map}" :group 'python (set (make-local-variable 'font-lock-defaults) @@ -1451,7 +1609,8 @@ (set (make-local-variable 'add-log-current-defun-function) #'python-current-defun) ;; Fixme: Generalize to do all blocks? - (set (make-local-variable 'outline-regexp) "\\s-+\\(def\\|class\\)\\>") + (set (make-local-variable 'outline-regexp) "\\s-*\\(def\\|class\\)\\>") + (set (make-local-variable 'outline-heading-end-regexp) ":\\s-*\n") (set (make-local-variable 'outline-level) #'python-outline-level) (set (make-local-variable 'open-paren-in-column-0-is-defun-start) nil) (make-local-variable 'python-saved-check-command) @@ -1459,6 +1618,10 @@ 'python-beginning-of-defun) (set (make-local-variable 'end-of-defun-function) 'python-end-of-defun) (setq imenu-create-index-function #'python-imenu-create-index) + (set (make-local-variable 'eldoc-print-current-symbol-info-function) + #'python-eldoc-function) + (add-hook 'eldoc-mode-hook + '(lambda () (run-python 0 t)) nil t) ; need it running (unless font-lock-mode (font-lock-mode 1)) (when python-guess-indent (python-guess-indent)) (set (make-local-variable 'python-command) python-python-command) @@ -1471,6 +1634,7 @@ '(lambda () "Turn on Indent Tabs mode." (set (make-local-variable 'indent-tabs-mode) t))) +(custom-add-option 'python-mode-hook 'turn-on-eldoc-mode) ;;;###autoload (define-derived-mode jython-mode python-mode "Jython"