<commit_before>;;; python-mode.el --- Major mode for editing Python programs

;; Copyright (C) 1992,1993,1994  <NAME>

;; Author: 1995 <NAME> <<EMAIL>>
;;         1992-1994 <NAME> <<EMAIL>>
;; Maintainer:    <EMAIL>
;; Created:       Feb 1992
;; Version:       $Revision$
;; Last Modified: $Date$
;; Keywords: python editing language major-mode

;; This software is provided as-is, without express or implied
;; warranty.  Permission to use, copy, modify, distribute or sell this
;; software, without fee, for any purpose and by any individual or
;; organization, is hereby granted, provided that the above copyright
;; notice and this paragraph appear in all copies.

;;; Commentary:
;;
;; This is a major mode for editing Python programs.  It was developed
;; by <NAME> <<EMAIL>> after an original idea by <NAME>.  Tim doesn't appear to be on the 'net any longer so I
;; have undertaken maintenance of the mode.

;; At some point this mode will undergo a rewrite to bring it more in
;; line with GNU Emacs Lisp coding standards.  But all in all, the
;; mode works exceedingly well.

;; The following statements, placed in your .emacs file or
;; site-init.el, will cause this file to be autoloaded, and
;; python-mode invoked, when visiting .py files (assuming this file is
;; in your load-path):
;;
;;	(autoload 'python-mode "python-mode" "Python editing mode." t)
;;	(setq auto-mode-alist
;;	      (cons '("\\.py$" . python-mode) auto-mode-alist))

;; Here's a brief to do list:
;;
;; 1. Better integration with gud-mode for debugging.
;; 2. Rewrite according to GNU Emacs Lisp standards.

;; If you can think of more things you'd like to see, drop me a line.
;; If you want to report bugs, use py-submit-bug-report (C-c C-b).
;;
;; Note that I only test things on XEmacs (currently 19.11).  If you
;; port stuff to FSF Emacs 19, or Emacs 18, please send me your
;; patches.

;; LCD Archive Entry:
;; python-mode|<NAME>|<EMAIL>
;; |Major mode for editing Python programs
;; |$Date$|$Revision$|

;;; Code:



;; user definable variables
;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

(defvar py-python-command "python"
  "*Shell command used to start Python interpreter.")

(defvar py-indent-offset 8		; argue with Guido <grin>
  "*Indentation increment.
Note that `\\[py-guess-indent-offset]' can usually guess a good value
when you're editing someone else's Python code.")

(defvar py-block-comment-prefix "##"
  "*String used by `py-comment-region' to comment out a block of code.
This should follow the convention for non-indenting comment lines so
that the indentation commands won't get confused (i.e., the string
should be of the form `#x...' where `x' is not a blank or a tab, and
`...' is arbitrary).")

(defvar py-scroll-process-buffer t
  "*Scroll Python process buffer as output arrives.
If nil, the Python process buffer acts, with respect to scrolling, like
Shell-mode buffers normally act.  This is surprisingly complicated and
so won't be explained here; in fact, you can't get the whole story
without studying the Emacs C code.

If non-nil, the behavior is different in two respects (which are
slightly inaccurate in the interest of brevity):

  - If the buffer is in a window, and you left point at its end, the
    window will scroll as new output arrives, and point will move to the
    buffer's end, even if the window is not the selected window (that
    being the one the cursor is in).  The usual behavior for shell-mode
    windows is not to scroll, and to leave point where it was, if the
    buffer is in a window other than the selected window.

  - If the buffer is not visible in any window, and you left point at
    its end, the buffer will be popped into a window as soon as more
    output arrives.  This is handy if you have a long-running
    computation and don't want to tie up screen area waiting for the
    output.  The usual behavior for a shell-mode buffer is to stay
    invisible until you explicitly visit it.

Note the `and if you left point at its end' clauses in both of the
above:  you can `turn off' the special behaviors while output is in
progress, by visiting the Python buffer and moving point to anywhere
besides the end.  Then the buffer won't scroll, point will remain where
you leave it, and if you hide the buffer it will stay hidden until you
visit it again.  You can enable and disable the special behaviors as
often as you like, while output is in progress, by (respectively) moving
point to, or away from, the end of the buffer.

Warning:  If you expect a large amount of output, you'll probably be
happier setting this option to nil.

Obscure:  `End of buffer' above should really say `at or beyond the
process mark', but if you know what that means you didn't need to be
told <grin>.")

(defvar py-temp-directory
  (let ((ok '(lambda (x)
	       (and x
		    (setq x (expand-file-name x)) ; always true
		    (file-directory-p x)
		    (file-writable-p x)
		    x))))
    (or (funcall ok (getenv "TMPDIR"))
	(funcall ok "/usr/tmp")
	(funcall ok "/tmp")
	(funcall ok  ".")
	(error
	 "Couldn't find a usable temp directory -- set py-temp-directory")))
  "*Directory used for temp files created by a *Python* process.
By default, the first directory from this list that exists and that you
can write into:  the value (if any) of the environment variable TMPDIR,
/usr/tmp, /tmp, or the current directory.")

(defvar py-beep-if-tab-change t
  "*Ring the bell if tab-width is changed.
If a comment of the form

  \t# vi:set tabsize=<number>:

is found before the first code line when the file is entered, and the
current value of (the general Emacs variable) `tab-width' does not
equal <number>, `tab-width' is set to <number>, a message saying so is
displayed in the echo area, and if `py-beep-if-tab-change' is non-nil
the Emacs bell is also rung as a warning.")

(defvar python-font-lock-keywords
  (list
   (cons
    (concat
     "\\<\\("
     (mapconcat
      'identity
      '("access"  "and"      "break"  "continue"
	"del"     "elif"     "else"   "except"
	"exec"    "finally"  "for"    "from"
	"global"  "if"       "import" "in"
	"is"      "lambda"   "not"    "or"
	"pass"    "print"    "raise"  "return"
	"try"     "while"    "def"    "class"
	)
      "\\|")
     "\\)\\>")
    1)
   ;; functions
   '("\\bdef\\s +\\(\\sw+\\)(" 1 font-lock-function-name-face)
   ;; classes
   '("\\bclass\\s +\\(\\sw+\\)[(:]" 1 font-lock-function-name-face)
   )
  "*Additional keywords to highlight `python-mode' buffers.")



;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;; NO USER DEFINABLE VARIABLES BEYOND THIS POINT

;; Differentiate between Emacs 18, Lucid Emacs, and Emacs 19.  This
;; seems to be the standard way of checking this.
;; BAW - This is *not* the right solution.  When at all possible,
;; instead of testing for the version of Emacs, use feature tests.

(setq py-this-is-lucid-emacs-p (string-match "Lucid\\|XEmacs" emacs-version))
(setq py-this-is-emacs-19-p
      (and
       (not py-this-is-lucid-emacs-p)
       (string-match "^19\\." emacs-version)))

;; have to bind py-file-queue before installing the kill-emacs hook
(defvar py-file-queue nil
  "Queue of Python temp files awaiting execution.
Currently-active file is at the head of the list.")

;; define a mode-specific abbrev table for those who use such things
(defvar python-mode-abbrev-table nil
  "Abbrev table in use in `python-mode' buffers.")
(define-abbrev-table 'python-mode-abbrev-table nil)

(defvar python-mode-hook nil
  "*Hook called by `python-mode'.")

;; in previous version of python-mode.el, the hook was incorrectly
;; called py-mode-hook, and was not defvar'd.  deprecate its use.
(and (fboundp 'make-obsolete-variable)
     (make-obsolete-variable 'py-mode-hook 'python-mode-hook))

(defvar py-mode-map ()
  "Keymap used in `python-mode' buffers.")

(if py-mode-map
    ()
  (setq py-mode-map (make-sparse-keymap))

  ;; shadow global bindings for newline-and-indent w/ the py- version.
  ;; BAW - this is extremely bad form, but I'm not going to change it
  ;; for now.
  (mapcar (function (lambda (key)
		      (define-key
			py-mode-map key 'py-newline-and-indent)))
   (where-is-internal 'newline-and-indent))

  ;; BAW - you could do it this way, but its not considered proper
  ;; major-mode form.
  (mapcar (function
	   (lambda (x)
	     (define-key py-mode-map (car x) (cdr x))))
	  '((":"         . py-electric-colon)
	    ("\C-c\C-c"  . py-execute-buffer)
	    ("\C-c|"	 . py-execute-region)
	    ("\C-c!"	 . py-shell)
	    ("\177"	 . py-delete-char)
	    ("\n"	 . py-newline-and-indent)
	    ("\C-c:"	 . py-guess-indent-offset)
	    ("\C-c\t"	 . py-indent-region)
	    ("\C-c<"	 . py-shift-region-left)
	    ("\C-c>"	 . py-shift-region-right)
	    ("\C-c\C-n"  . py-next-statement)
	    ("\C-c\C-p"  . py-previous-statement)
	    ("\C-c\C-u"  . py-goto-block-up)
	    ("\C-c\C-m"  . py-mark-block)
	    ("\C-c#"	 . py-comment-region)
	    ("\C-c?"	 . py-describe-mode)
	    ("\C-c\C-hm" . py-describe-mode)
	    ("\e\C-a"	 . beginning-of-python-def-or-class)
	    ("\e\C-e"	 . end-of-python-def-or-class)
	    ( "\e\C-h"	 . mark-python-def-or-class)))
  ;; should do all keybindings this way
  (define-key py-mode-map "\C-c\C-b" 'py-submit-bug-report)
  (define-key py-mode-map "\C-c\C-v" 'py-version)
  )

(defvar py-mode-syntax-table nil
  "Syntax table used in `python-mode' buffers.")

(if py-mode-syntax-table
    ()
  (setq py-mode-syntax-table (make-syntax-table))
  ;; BAW - again, blech.
  (mapcar (function
	   (lambda (x) (modify-syntax-entry
			(car x) (cdr x) py-mode-syntax-table)))
	  '(( ?\( . "()" ) ( ?\) . ")(" )
	    ( ?\[ . "(]" ) ( ?\] . ")[" )
	    ( ?\{ . "(}" ) ( ?\} . "){" )
	    ;; fix operator symbols misassigned in the std table
	    ( ?\$ . "." ) ( ?\% . "." ) ( ?\& . "." )
	    ( ?\* . "." ) ( ?\+ . "." ) ( ?\- . "." )
	    ( ?\/ . "." ) ( ?\< . "." ) ( ?\= . "." )
	    ( ?\> . "." ) ( ?\| . "." )
	    ( ?\_ . "w" )	; underscore is legit in names
	    ( ?\' . "\"")	; single quote is string quote
	    ( ?\" . "\"" )	; double quote is string quote too
	    ( ?\` . "$")	; backquote is open and close paren
	    ( ?\# . "<")	; hash starts comment
	    ( ?\n . ">"))))	; newline ends comment

(defconst py-stringlit-re
  (concat
   "'\\([^'\n\\]\\|\\\\.\\)*'"		; single-quoted
   "\\|"				; or
   "\"\\([^\"\n\\]\\|\\\\.\\)*\"")	; double-quoted
  "Regexp matching a Python string literal.")

;; this is tricky because a trailing backslash does not mean
;; continuation if it's in a comment
(defconst py-continued-re
  (concat
   "\\(" "[^#'\"\n\\]" "\\|" py-stringlit-re "\\)*"
   "\\\\$")
  "Regexp matching Python lines that are continued via backslash.")

(defconst py-blank-or-comment-re "[ \t]*\\($\\|#\\)"
  "Regexp matching blank or comment lines.")




;;;###autoload
(defun python-mode ()
  "Major mode for editing Python files.
To submit a problem report, enter `\\[py-submit-bug-report]' from a
`python-mode' buffer.  Do `\\[py-describe-mode]' for detailed
documentation.  To see what version of `python-mode' you are running,
enter `\\[py-version]'.

This mode knows about Python indentation, tokens, comments and
continuation lines.  Paragraphs are separated by blank lines only.

COMMANDS
\\{py-mode-map}
VARIABLES

py-indent-offset\tindentation increment
py-block-comment-prefix\tcomment string used by py-comment-region
py-python-command\tshell command to invoke Python interpreter
py-scroll-process-buffer\talways scroll Python process buffer
py-temp-directory\tdirectory used for temp files (if needed)
py-beep-if-tab-change\tring the bell if tab-width is changed"
  (interactive)
  (kill-all-local-variables)
  (set-syntax-table py-mode-syntax-table)
  (setq major-mode 'python-mode
	mode-name "Python"
	local-abbrev-table python-mode-abbrev-table)
  (use-local-map py-mode-map)
  ;; BAW -- style...
  (mapcar (function (lambda (x)
		      (make-local-variable (car x))
		      (set (car x) (cdr x))))
	  '((paragraph-separate . "^[ \t]*$")
	    (paragraph-start	 . "^[ \t]*$")
	    (require-final-newline . t)
	    (comment-start .		"# ")
	    (comment-start-skip .	"# *")
	    (comment-column . 40)
	    (indent-region-function . py-indent-region)
	    (indent-line-function . py-indent-line)))
  ;; hack to allow overriding the tabsize in the file (see tokenizer.c)
  ;;
  ;; not sure where the magic comment has to be; to save time
  ;; searching for a rarity, we give up if it's not found prior to the
  ;; first executable statement.
  ;;
  ;; BAW - on first glance, this seems like complete hackery.  Why was
  ;; this necessary, and is it still necessary?
  (let ((case-fold-search nil)
	(start (point))
	new-tab-width)
    (if (re-search-forward
	 "^[ \t]*#[ \t]*vi:set[ \t]+tabsize=\\([0-9]+\\):"
	 (prog2 (py-next-statement 1) (point) (goto-char 1))
	 t)
	(progn
	  (setq new-tab-width
		(string-to-int
		 (buffer-substring (match-beginning 1) (match-end 1))))
	  (if (= tab-width new-tab-width)
	      nil
	    (setq tab-width new-tab-width)
	    (message "Caution: tab-width changed to %d" new-tab-width)
	    (if py-beep-if-tab-change (beep)))))
    (goto-char start))

  ;; run the mode hook. py-mode-hook use is deprecated
  (if python-mode-hook
      (run-hooks 'python-mode-hook)
    (run-hooks 'py-mode-hook)))



;; electric characters
(defun py-electric-colon (arg)
  "Insert a colon.
In certain cases the line is outdented appropriately.  If a numeric
argument is provided, that many colons are inserted non-electrically."
  (interactive "P")
  (self-insert-command (prefix-numeric-value arg))
  (let (this-indent)
    (if (and (not arg)
	     (save-excursion
	       (forward-word -1)
	       (looking-at "\\(else\\|except\\|finally\\elif\\):"))
	     (= (setq this-indent (py-compute-indentation))
		(save-excursion
		  (forward-line -1)
		  (py-compute-indentation)))
	     )
	(save-excursion
	  (beginning-of-line)
	  (delete-horizontal-space)
	  (indent-to (- this-indent py-indent-offset)))
      )))



;;; Functions that execute Python commands in a subprocess
(defun py-shell ()
  "Start an interactive Python interpreter in another window.
This is like Shell mode, except that Python is running in the window
instead of a shell.  See the `Interactive Shell' and `Shell Mode'
sections of the Emacs manual for details, especially for the key
bindings active in the `*Python*' buffer.

See the docs for variable `py-scroll-buffer' for info on scrolling
behavior in the process window.

Warning: Don't use an interactive Python if you change sys.ps1 or
sys.ps2 from their default values, or if you're running code that
prints `>>> ' or `... ' at the start of a line.  `python-mode' can't
distinguish your output from Python's output, and assumes that `>>> '
at the start of a line is a prompt from Python.  Similarly, the Emacs
Shell mode code assumes that both `>>> ' and `... ' at the start of a
line are Python prompts.  Bad things can happen if you fool either
mode.

Warning:  If you do any editing *in* the process buffer *while* the
buffer is accepting output from Python, do NOT attempt to `undo' the
changes.  Some of the output (nowhere near the parts you changed!) may
be lost if you do.  This appears to be an Emacs bug, an unfortunate
interaction between undo and process filters; the same problem exists in
non-Python process buffers using the default (Emacs-supplied) process
filter."
  ;; BAW - should undo be disabled in the python process buffer, if
  ;; this bug still exists?
  (interactive)
  (if py-this-is-emacs-19-p
      (progn
	(require 'comint)
	(switch-to-buffer-other-window
	 (make-comint "Python" py-python-command)))
    (progn
      (require 'shell)
      (switch-to-buffer-other-window
       (make-shell "Python" py-python-command))))
  (make-local-variable 'shell-prompt-pattern)
  (setq shell-prompt-pattern "^>>> \\|^\\.\\.\\. ")
  (set-process-filter (get-buffer-process (current-buffer))
		      'py-process-filter)
  (set-syntax-table py-mode-syntax-table))

(defun py-execute-region (start end)
  "Send the region between START and END to a Python interpreter.
If there is a *Python* process it is used.

Hint: If you want to execute part of a Python file several times
\(e.g., perhaps you're developing a function and want to flesh it out
a bit at a time), use `\\[narrow-to-region]' to restrict the buffer to
the region of interest, and send the code to a *Python* process via
`\\[py-execute-buffer]' instead.

Following are subtleties to note when using a *Python* process:

If a *Python* process is used, the region is copied into a temporary
file (in directory `py-temp-directory'), and an `execfile' command is
sent to Python naming that file.  If you send regions faster than
Python can execute them, `python-mode' will save them into distinct
temp files, and execute the next one in the queue the next time it
sees a `>>> ' prompt from Python.  Each time this happens, the process
buffer is popped into a window (if it's not already in some window) so
you can see it, and a comment of the form

  \t## working on region in file <name> ...

is inserted at the end.

Caution: No more than 26 regions can be pending at any given time.
This limit is (indirectly) inherited from libc's mktemp(3).
`python-mode' does not try to protect you from exceeding the limit.
It's extremely unlikely that you'll get anywhere close to the limit in
practice, unless you're trying to be a jerk <grin>.

See the `\\[py-shell]' docs for additional warnings."
  (interactive "r")
  (or (< start end) (error "Region is empty"))
  (let ((pyproc (get-process "Python"))
	fname)
    (if (null pyproc)
	(shell-command-on-region start end py-python-command)
      ;; else feed it thru a temp file
      (setq fname (py-make-temp-name))
      (write-region start end fname nil 'no-msg)
      (setq py-file-queue (append py-file-queue (list fname)))
      (if (cdr py-file-queue)
	  (message "File %s queued for execution" fname)
	;; else
	(py-execute-file pyproc fname)))))

(defun py-execute-file (pyproc fname)
  (py-append-to-process-buffer
   pyproc
   (format "## working on region in file %s ...\n" fname))
  (process-send-string pyproc (format "execfile('%s')\n" fname)))

(defun py-process-filter (pyproc string)
  (let ((curbuf (current-buffer))
	(pbuf (process-buffer pyproc))
	(pmark (process-mark pyproc))
	file-finished)

    ;; make sure we switch to a different buffer at least once.  if we
    ;; *don't* do this, then if the process buffer is in the selected
    ;; window, and point is before the end, and lots of output is
    ;; coming at a fast pace, then (a) simple cursor-movement commands
    ;; like C-p, C-n, C-f, C-b, C-a, C-e take an incredibly long time
    ;; to have a visible effect (the window just doesn't get updated,
    ;; sometimes for minutes(!)), and (b) it takes about 5x longer to
    ;; get all the process output (until the next python prompt).
    ;;
    ;; #b makes no sense to me at all.  #a almost makes sense: unless
    ;; we actually change buffers, set_buffer_internal in buffer.c
    ;; doesn't set windows_or_buffers_changed to 1, & that in turn
    ;; seems to make the Emacs command loop reluctant to update the
    ;; display.  Perhaps the default process filter in process.c's
    ;; read_process_output has update_mode_lines++ for a similar
    ;; reason?  beats me ...

    ;; BAW - we want to check to see if this still applies
    (if (eq curbuf pbuf)		; mysterious ugly hack
	(set-buffer (get-buffer-create "*scratch*")))

    (set-buffer pbuf)
    (let* ((start (point))
	   (goback (< start pmark))
	   (buffer-read-only nil))
      (goto-char pmark)
      (insert string)
      (move-marker pmark (point))
      (setq file-finished
	    (and py-file-queue
		 (equal ">>> "
			(buffer-substring
			 (prog2 (beginning-of-line) (point)
				(goto-char pmark))
			 (point)))))
      (if goback (goto-char start)
	;; else
	(if py-scroll-process-buffer
	    (let* ((pop-up-windows t)
		   (pwin (display-buffer pbuf)))
	      (set-window-point pwin (point))))))
    (set-buffer curbuf)
    (if file-finished
	(progn
	  (py-delete-file-silently (car py-file-queue))
	  (setq py-file-queue (cdr py-file-queue))
	  (if py-file-queue
		(py-execute-file pyproc (car py-file-queue)))))))

(defun py-execute-buffer ()
  "Send the contents of the buffer to a Python interpreter.
If there is a *Python* process buffer it is used.  If a clipping
restriction is in effect, only the accessible portion of the buffer is
sent.  A trailing newline will be supplied if needed.

See the `\\[py-execute-region]' docs for an account of some subtleties."
  (interactive)
  (py-execute-region (point-min) (point-max)))




;; Functions for Python style indentation
(defun py-delete-char ()
  "Reduce indentation or delete character.
If point is at the leftmost column, deletes the preceding newline.

Else if point is at the leftmost non-blank character of a line that is
neither a continuation line nor a non-indenting comment line, or if
point is at the end of a blank line, reduces the indentation to match
that of the line that opened the current block of code.  The line that
opened the block is displayed in the echo area to help you keep track
of where you are.

Else the preceding character is deleted, converting a tab to spaces if
needed so that only a single column position is deleted."
  (interactive "*")
  (if (or (/= (current-indentation) (current-column))
	  (bolp)
	  (py-continuation-line-p)
	  (looking-at "#[^ \t\n]"))	; non-indenting #
      (backward-delete-char-untabify 1)
    ;; else indent the same as the colon line that opened the block

    ;; force non-blank so py-goto-block-up doesn't ignore it
    (insert-char ?* 1)
    (backward-char)
    (let ((base-indent 0)		; indentation of base line
	  (base-text "")		; and text of base line
	  (base-found-p nil))
      (condition-case nil		; in case no enclosing block
	  (save-excursion
	    (py-goto-block-up 'no-mark)
	    (setq base-indent (current-indentation)
		  base-text   (py-suck-up-leading-text)
		  base-found-p t))
	(error nil))
      (delete-char 1)			; toss the dummy character
      (delete-horizontal-space)
      (indent-to base-indent)
      (if base-found-p
	  (message "Closes block: %s" base-text)))))

;; required for pending-del and delsel modes
(put 'py-delete-char 'delete-selection 'supersede)
(put 'py-delete-char 'pending-delete   'supersede)

(defun py-indent-line ()
  "Fix the indentation of the current line according to Python rules."
  (interactive)
  (let* ((ci (current-indentation))
	 (move-to-indentation-p (<= (current-column) ci))
	 (need (py-compute-indentation)))
    (if (/= ci need)
	(save-excursion
	  (beginning-of-line)
	  (delete-horizontal-space)
	  (indent-to need)))
    (if move-to-indentation-p (back-to-indentation))))

(defun py-newline-and-indent ()<commit_msg>(py-outdent-re): new constant<commit_after>;;; python-mode.el --- Major mode for editing Python programs

;; Copyright (C) 1992,1993,1994  <NAME>

;; Author: 1995 <NAME> <<EMAIL>>
;;         1992-1994 <NAME> <<EMAIL>>
;; Maintainer:    <EMAIL>
;; Created:       Feb 1992
;; Version:       $Revision$
;; Last Modified: $Date$
;; Keywords: python editing language major-mode

;; This software is provided as-is, without express or implied
;; warranty.  Permission to use, copy, modify, distribute or sell this
;; software, without fee, for any purpose and by any individual or
;; organization, is hereby granted, provided that the above copyright
;; notice and this paragraph appear in all copies.

;;; Commentary:
;;
;; This is a major mode for editing Python programs.  It was developed
;; by <NAME> <<EMAIL>> after an original idea by <NAME>.  Tim doesn't appear to be on the 'net any longer so I
;; have undertaken maintenance of the mode.

;; At some point this mode will undergo a rewrite to bring it more in
;; line with GNU Emacs Lisp coding standards.  But all in all, the
;; mode works exceedingly well.

;; The following statements, placed in your .emacs file or
;; site-init.el, will cause this file to be autoloaded, and
;; python-mode invoked, when visiting .py files (assuming this file is
;; in your load-path):
;;
;;	(autoload 'python-mode "python-mode" "Python editing mode." t)
;;	(setq auto-mode-alist
;;	      (cons '("\\.py$" . python-mode) auto-mode-alist))

;; Here's a brief to do list:
;;
;; 1. Better integration with gud-mode for debugging.
;; 2. Rewrite according to GNU Emacs Lisp standards.

;; If you can think of more things you'd like to see, drop me a line.
;; If you want to report bugs, use py-submit-bug-report (C-c C-b).
;;
;; Note that I only test things on XEmacs (currently 19.11).  If you
;; port stuff to FSF Emacs 19, or Emacs 18, please send me your
;; patches.

;; LCD Archive Entry:
;; python-mode|<NAME>|<EMAIL>
;; |Major mode for editing Python programs
;; |$Date$|$Revision$|

;;; Code:



;; user definable variables
;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

(defvar py-python-command "python"
  "*Shell command used to start Python interpreter.")

(defvar py-indent-offset 8		; argue with Guido <grin>
  "*Indentation increment.
Note that `\\[py-guess-indent-offset]' can usually guess a good value
when you're editing someone else's Python code.")

(defvar py-block-comment-prefix "##"
  "*String used by `py-comment-region' to comment out a block of code.
This should follow the convention for non-indenting comment lines so
that the indentation commands won't get confused (i.e., the string
should be of the form `#x...' where `x' is not a blank or a tab, and
`...' is arbitrary).")

(defvar py-scroll-process-buffer t
  "*Scroll Python process buffer as output arrives.
If nil, the Python process buffer acts, with respect to scrolling, like
Shell-mode buffers normally act.  This is surprisingly complicated and
so won't be explained here; in fact, you can't get the whole story
without studying the Emacs C code.

If non-nil, the behavior is different in two respects (which are
slightly inaccurate in the interest of brevity):

  - If the buffer is in a window, and you left point at its end, the
    window will scroll as new output arrives, and point will move to the
    buffer's end, even if the window is not the selected window (that
    being the one the cursor is in).  The usual behavior for shell-mode
    windows is not to scroll, and to leave point where it was, if the
    buffer is in a window other than the selected window.

  - If the buffer is not visible in any window, and you left point at
    its end, the buffer will be popped into a window as soon as more
    output arrives.  This is handy if you have a long-running
    computation and don't want to tie up screen area waiting for the
    output.  The usual behavior for a shell-mode buffer is to stay
    invisible until you explicitly visit it.

Note the `and if you left point at its end' clauses in both of the
above:  you can `turn off' the special behaviors while output is in
progress, by visiting the Python buffer and moving point to anywhere
besides the end.  Then the buffer won't scroll, point will remain where
you leave it, and if you hide the buffer it will stay hidden until you
visit it again.  You can enable and disable the special behaviors as
often as you like, while output is in progress, by (respectively) moving
point to, or away from, the end of the buffer.

Warning:  If you expect a large amount of output, you'll probably be
happier setting this option to nil.

Obscure:  `End of buffer' above should really say `at or beyond the
process mark', but if you know what that means you didn't need to be
told <grin>.")

(defvar py-temp-directory
  (let ((ok '(lambda (x)
	       (and x
		    (setq x (expand-file-name x)) ; always true
		    (file-directory-p x)
		    (file-writable-p x)
		    x))))
    (or (funcall ok (getenv "TMPDIR"))
	(funcall ok "/usr/tmp")
	(funcall ok "/tmp")
	(funcall ok  ".")
	(error
	 "Couldn't find a usable temp directory -- set py-temp-directory")))
  "*Directory used for temp files created by a *Python* process.
By default, the first directory from this list that exists and that you
can write into:  the value (if any) of the environment variable TMPDIR,
/usr/tmp, /tmp, or the current directory.")

(defvar py-beep-if-tab-change t
  "*Ring the bell if tab-width is changed.
If a comment of the form

  \t# vi:set tabsize=<number>:

is found before the first code line when the file is entered, and the
current value of (the general Emacs variable) `tab-width' does not
equal <number>, `tab-width' is set to <number>, a message saying so is
displayed in the echo area, and if `py-beep-if-tab-change' is non-nil
the Emacs bell is also rung as a warning.")

(defvar python-font-lock-keywords
  (list
   (cons
    (concat
     "\\<\\("
     (mapconcat
      'identity
      '("access"  "and"      "break"  "continue"
	"del"     "elif"     "else"   "except"
	"exec"    "finally"  "for"    "from"
	"global"  "if"       "import" "in"
	"is"      "lambda"   "not"    "or"
	"pass"    "print"    "raise"  "return"
	"try"     "while"    "def"    "class"
	)
      "\\|")
     "\\)\\>")
    1)
   ;; functions
   '("\\bdef\\s +\\(\\sw+\\)(" 1 font-lock-function-name-face)
   ;; classes
   '("\\bclass\\s +\\(\\sw+\\)[(:]" 1 font-lock-function-name-face)
   )
  "*Additional keywords to highlight `python-mode' buffers.")



;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;; NO USER DEFINABLE VARIABLES BEYOND THIS POINT

;; Differentiate between Emacs 18, Lucid Emacs, and Emacs 19.  This
;; seems to be the standard way of checking this.
;; BAW - This is *not* the right solution.  When at all possible,
;; instead of testing for the version of Emacs, use feature tests.

(setq py-this-is-lucid-emacs-p (string-match "Lucid\\|XEmacs" emacs-version))
(setq py-this-is-emacs-19-p
      (and
       (not py-this-is-lucid-emacs-p)
       (string-match "^19\\." emacs-version)))

;; have to bind py-file-queue before installing the kill-emacs hook
(defvar py-file-queue nil
  "Queue of Python temp files awaiting execution.
Currently-active file is at the head of the list.")

;; define a mode-specific abbrev table for those who use such things
(defvar python-mode-abbrev-table nil
  "Abbrev table in use in `python-mode' buffers.")
(define-abbrev-table 'python-mode-abbrev-table nil)

(defvar python-mode-hook nil
  "*Hook called by `python-mode'.")

;; in previous version of python-mode.el, the hook was incorrectly
;; called py-mode-hook, and was not defvar'd.  deprecate its use.
(and (fboundp 'make-obsolete-variable)
     (make-obsolete-variable 'py-mode-hook 'python-mode-hook))

(defvar py-mode-map ()
  "Keymap used in `python-mode' buffers.")

(if py-mode-map
    ()
  (setq py-mode-map (make-sparse-keymap))

  ;; shadow global bindings for newline-and-indent w/ the py- version.
  ;; BAW - this is extremely bad form, but I'm not going to change it
  ;; for now.
  (mapcar (function (lambda (key)
		      (define-key
			py-mode-map key 'py-newline-and-indent)))
   (where-is-internal 'newline-and-indent))

  ;; BAW - you could do it this way, but its not considered proper
  ;; major-mode form.
  (mapcar (function
	   (lambda (x)
	     (define-key py-mode-map (car x) (cdr x))))
	  '((":"         . py-electric-colon)
	    ("\C-c\C-c"  . py-execute-buffer)
	    ("\C-c|"	 . py-execute-region)
	    ("\C-c!"	 . py-shell)
	    ("\177"	 . py-delete-char)
	    ("\n"	 . py-newline-and-indent)
	    ("\C-c:"	 . py-guess-indent-offset)
	    ("\C-c\t"	 . py-indent-region)
	    ("\C-c<"	 . py-shift-region-left)
	    ("\C-c>"	 . py-shift-region-right)
	    ("\C-c\C-n"  . py-next-statement)
	    ("\C-c\C-p"  . py-previous-statement)
	    ("\C-c\C-u"  . py-goto-block-up)
	    ("\C-c\C-m"  . py-mark-block)
	    ("\C-c#"	 . py-comment-region)
	    ("\C-c?"	 . py-describe-mode)
	    ("\C-c\C-hm" . py-describe-mode)
	    ("\e\C-a"	 . beginning-of-python-def-or-class)
	    ("\e\C-e"	 . end-of-python-def-or-class)
	    ( "\e\C-h"	 . mark-python-def-or-class)))
  ;; should do all keybindings this way
  (define-key py-mode-map "\C-c\C-b" 'py-submit-bug-report)
  (define-key py-mode-map "\C-c\C-v" 'py-version)
  )

(defvar py-mode-syntax-table nil
  "Syntax table used in `python-mode' buffers.")

(if py-mode-syntax-table
    ()
  (setq py-mode-syntax-table (make-syntax-table))
  ;; BAW - again, blech.
  (mapcar (function
	   (lambda (x) (modify-syntax-entry
			(car x) (cdr x) py-mode-syntax-table)))
	  '(( ?\( . "()" ) ( ?\) . ")(" )
	    ( ?\[ . "(]" ) ( ?\] . ")[" )
	    ( ?\{ . "(}" ) ( ?\} . "){" )
	    ;; fix operator symbols misassigned in the std table
	    ( ?\$ . "." ) ( ?\% . "." ) ( ?\& . "." )
	    ( ?\* . "." ) ( ?\+ . "." ) ( ?\- . "." )
	    ( ?\/ . "." ) ( ?\< . "." ) ( ?\= . "." )
	    ( ?\> . "." ) ( ?\| . "." )
	    ( ?\_ . "w" )	; underscore is legit in names
	    ( ?\' . "\"")	; single quote is string quote
	    ( ?\" . "\"" )	; double quote is string quote too
	    ( ?\` . "$")	; backquote is open and close paren
	    ( ?\# . "<")	; hash starts comment
	    ( ?\n . ">"))))	; newline ends comment

(defconst py-stringlit-re
  (concat
   "'\\([^'\n\\]\\|\\\\.\\)*'"		; single-quoted
   "\\|"				; or
   "\"\\([^\"\n\\]\\|\\\\.\\)*\"")	; double-quoted
  "Regexp matching a Python string literal.")

;; this is tricky because a trailing backslash does not mean
;; continuation if it's in a comment
(defconst py-continued-re
  (concat
   "\\(" "[^#'\"\n\\]" "\\|" py-stringlit-re "\\)*"
   "\\\\$")
  "Regexp matching Python lines that are continued via backslash.")

(defconst py-blank-or-comment-re "[ \t]*\\($\\|#\\)"
  "Regexp matching blank or comment lines.")

(defconst py-outdent-re
  (concat "\\(" (mapconcat 'identity
			   '("else:"
			     "except\\s +.*:"
			     "finally:"
			     "elif\\s +.*:")
			   "\\|")
	  "\\)")
  "Regexp matching clauses to be outdented one level.")



;;;###autoload
(defun python-mode ()
  "Major mode for editing Python files.
To submit a problem report, enter `\\[py-submit-bug-report]' from a
`python-mode' buffer.  Do `\\[py-describe-mode]' for detailed
documentation.  To see what version of `python-mode' you are running,
enter `\\[py-version]'.

This mode knows about Python indentation, tokens, comments and
continuation lines.  Paragraphs are separated by blank lines only.

COMMANDS
\\{py-mode-map}
VARIABLES

py-indent-offset\tindentation increment
py-block-comment-prefix\tcomment string used by py-comment-region
py-python-command\tshell command to invoke Python interpreter
py-scroll-process-buffer\talways scroll Python process buffer
py-temp-directory\tdirectory used for temp files (if needed)
py-beep-if-tab-change\tring the bell if tab-width is changed"
  (interactive)
  (kill-all-local-variables)
  (set-syntax-table py-mode-syntax-table)
  (setq major-mode 'python-mode
	mode-name "Python"
	local-abbrev-table python-mode-abbrev-table)
  (use-local-map py-mode-map)
  ;; BAW -- style...
  (mapcar (function (lambda (x)
		      (make-local-variable (car x))
		      (set (car x) (cdr x))))
	  '((paragraph-separate . "^[ \t]*$")
	    (paragraph-start	 . "^[ \t]*$")
	    (require-final-newline . t)
	    (comment-start .		"# ")
	    (comment-start-skip .	"# *")
	    (comment-column . 40)
	    (indent-region-function . py-indent-region)
	    (indent-line-function . py-indent-line)))
  ;; hack to allow overriding the tabsize in the file (see tokenizer.c)
  ;;
  ;; not sure where the magic comment has to be; to save time
  ;; searching for a rarity, we give up if it's not found prior to the
  ;; first executable statement.
  ;;
  ;; BAW - on first glance, this seems like complete hackery.  Why was
  ;; this necessary, and is it still necessary?
  (let ((case-fold-search nil)
	(start (point))
	new-tab-width)
    (if (re-search-forward
	 "^[ \t]*#[ \t]*vi:set[ \t]+tabsize=\\([0-9]+\\):"
	 (prog2 (py-next-statement 1) (point) (goto-char 1))
	 t)
	(progn
	  (setq new-tab-width
		(string-to-int
		 (buffer-substring (match-beginning 1) (match-end 1))))
	  (if (= tab-width new-tab-width)
	      nil
	    (setq tab-width new-tab-width)
	    (message "Caution: tab-width changed to %d" new-tab-width)
	    (if py-beep-if-tab-change (beep)))))
    (goto-char start))

  ;; run the mode hook. py-mode-hook use is deprecated
  (if python-mode-hook
      (run-hooks 'python-mode-hook)
    (run-hooks 'py-mode-hook)))



;; electric characters
(defun py-electric-colon (arg)
  "Insert a colon.
In certain cases the line is outdented appropriately.  If a numeric
argument is provided, that many colons are inserted non-electrically."
  (interactive "P")
  (self-insert-command (prefix-numeric-value arg))
  (let (this-indent)
    (if (and (not arg)
	     (save-excursion
	       (back-to-indentation)
	       (looking-at py-outdent-re))
	     (= (setq this-indent (py-compute-indentation))
		(save-excursion
		  (forward-line -1)
		  (py-compute-indentation)))
	     )
	(save-excursion
	  (beginning-of-line)
	  (delete-horizontal-space)
	  (indent-to (- this-indent py-indent-offset)))
      )))



;;; Functions that execute Python commands in a subprocess
(defun py-shell ()
  "Start an interactive Python interpreter in another window.
This is like Shell mode, except that Python is running in the window
instead of a shell.  See the `Interactive Shell' and `Shell Mode'
sections of the Emacs manual for details, especially for the key
bindings active in the `*Python*' buffer.

See the docs for variable `py-scroll-buffer' for info on scrolling
behavior in the process window.

Warning: Don't use an interactive Python if you change sys.ps1 or
sys.ps2 from their default values, or if you're running code that
prints `>>> ' or `... ' at the start of a line.  `python-mode' can't
distinguish your output from Python's output, and assumes that `>>> '
at the start of a line is a prompt from Python.  Similarly, the Emacs
Shell mode code assumes that both `>>> ' and `... ' at the start of a
line are Python prompts.  Bad things can happen if you fool either
mode.

Warning:  If you do any editing *in* the process buffer *while* the
buffer is accepting output from Python, do NOT attempt to `undo' the
changes.  Some of the output (nowhere near the parts you changed!) may
be lost if you do.  This appears to be an Emacs bug, an unfortunate
interaction between undo and process filters; the same problem exists in
non-Python process buffers using the default (Emacs-supplied) process
filter."
  ;; BAW - should undo be disabled in the python process buffer, if
  ;; this bug still exists?
  (interactive)
  (if py-this-is-emacs-19-p
      (progn
	(require 'comint)
	(switch-to-buffer-other-window
	 (make-comint "Python" py-python-command)))
    (progn
      (require 'shell)
      (switch-to-buffer-other-window
       (make-shell "Python" py-python-command))))
  (make-local-variable 'shell-prompt-pattern)
  (setq shell-prompt-pattern "^>>> \\|^\\.\\.\\. ")
  (set-process-filter (get-buffer-process (current-buffer))
		      'py-process-filter)
  (set-syntax-table py-mode-syntax-table))

(defun py-execute-region (start end)
  "Send the region between START and END to a Python interpreter.
If there is a *Python* process it is used.

Hint: If you want to execute part of a Python file several times
\(e.g., perhaps you're developing a function and want to flesh it out
a bit at a time), use `\\[narrow-to-region]' to restrict the buffer to
the region of interest, and send the code to a *Python* process via
`\\[py-execute-buffer]' instead.

Following are subtleties to note when using a *Python* process:

If a *Python* process is used, the region is copied into a temporary
file (in directory `py-temp-directory'), and an `execfile' command is
sent to Python naming that file.  If you send regions faster than
Python can execute them, `python-mode' will save them into distinct
temp files, and execute the next one in the queue the next time it
sees a `>>> ' prompt from Python.  Each time this happens, the process
buffer is popped into a window (if it's not already in some window) so
you can see it, and a comment of the form

  \t## working on region in file <name> ...

is inserted at the end.

Caution: No more than 26 regions can be pending at any given time.
This limit is (indirectly) inherited from libc's mktemp(3).
`python-mode' does not try to protect you from exceeding the limit.
It's extremely unlikely that you'll get anywhere close to the limit in
practice, unless you're trying to be a jerk <grin>.

See the `\\[py-shell]' docs for additional warnings."
  (interactive "r")
  (or (< start end) (error "Region is empty"))
  (let ((pyproc (get-process "Python"))
	fname)
    (if (null pyproc)
	(shell-command-on-region start end py-python-command)
      ;; else feed it thru a temp file
      (setq fname (py-make-temp-name))
      (write-region start end fname nil 'no-msg)
      (setq py-file-queue (append py-file-queue (list fname)))
      (if (cdr py-file-queue)
	  (message "File %s queued for execution" fname)
	;; else
	(py-execute-file pyproc fname)))))

(defun py-execute-file (pyproc fname)
  (py-append-to-process-buffer
   pyproc
   (format "## working on region in file %s ...\n" fname))
  (process-send-string pyproc (format "execfile('%s')\n" fname)))

(defun py-process-filter (pyproc string)
  (let ((curbuf (current-buffer))
	(pbuf (process-buffer pyproc))
	(pmark (process-mark pyproc))
	file-finished)

    ;; make sure we switch to a different buffer at least once.  if we
    ;; *don't* do this, then if the process buffer is in the selected
    ;; window, and point is before the end, and lots of output is
    ;; coming at a fast pace, then (a) simple cursor-movement commands
    ;; like C-p, C-n, C-f, C-b, C-a, C-e take an incredibly long time
    ;; to have a visible effect (the window just doesn't get updated,
    ;; sometimes for minutes(!)), and (b) it takes about 5x longer to
    ;; get all the process output (until the next python prompt).
    ;;
    ;; #b makes no sense to me at all.  #a almost makes sense: unless
    ;; we actually change buffers, set_buffer_internal in buffer.c
    ;; doesn't set windows_or_buffers_changed to 1, & that in turn
    ;; seems to make the Emacs command loop reluctant to update the
    ;; display.  Perhaps the default process filter in process.c's
    ;; read_process_output has update_mode_lines++ for a similar
    ;; reason?  beats me ...

    ;; BAW - we want to check to see if this still applies
    (if (eq curbuf pbuf)		; mysterious ugly hack
	(set-buffer (get-buffer-create "*scratch*")))

    (set-buffer pbuf)
    (let* ((start (point))
	   (goback (< start pmark))
	   (buffer-read-only nil))
      (goto-char pmark)
      (insert string)
      (move-marker pmark (point))
      (setq file-finished
	    (and py-file-queue
		 (equal ">>> "
			(buffer-substring
			 (prog2 (beginning-of-line) (point)
				(goto-char pmark))
			 (point)))))
      (if goback (goto-char start)
	;; else
	(if py-scroll-process-buffer
	    (let* ((pop-up-windows t)
		   (pwin (display-buffer pbuf)))
	      (set-window-point pwin (point))))))
    (set-buffer curbuf)
    (if file-finished
	(progn
	  (py-delete-file-silently (car py-file-queue))
	  (setq py-file-queue (cdr py-file-queue))
	  (if py-file-queue
		(py-execute-file pyproc (car py-file-queue)))))))

(defun py-execute-buffer ()
  "Send the contents of the buffer to a Python interpreter.
If there is a *Python* process buffer it is used.  If a clipping
restriction is in effect, only the accessible portion of the buffer is
sent.  A trailing newline will be supplied if needed.

See the `\\[py-execute-region]' docs for an account of some subtleties."
  (interactive)
  (py-execute-region (point-min) (point-max)))




;; Functions for Python style indentation
(defun py-delete-char ()
  "Reduce indentation or delete character.
If point is at the leftmost column, deletes the preceding newline.

Else if point is at the leftmost non-blank character of a line that is
neither a continuation line nor a non-indenting comment line, or if
point is at the end of a blank line, reduces the indentation to match
that of the line that opened the current block of code.  The line that
opened the block is displayed in the echo area to help you keep track
of where you are.

Else the preceding character is deleted, converting a tab to spaces if
needed so that only a single column position is deleted."
  (interactive "*")
  (if (or (/= (current-indentation) (current-column))
	  (bolp)
	  (py-continuation-line-p)
	  (looking-at "#[^ \t\n]"))	; non-indenting #
      (backward-delete-char-untabify 1)
    ;; else indent the same as the colon line that opened the block

    ;; force non-blank so py-goto-block-up doesn't ignore it
    (insert-char ?* 1)
    (backward-char)
    (let ((base-indent 0)		; indentation of base line
	  (base-text "")		; and text of base line
	  (base-found-p nil))
      (condition-case nil		; in case no enclosing block
	  (save-excursion
	    (py-goto-block-up 'no-mark)
	    (setq base-indent (current-indentation)
		  base-text   (py-suck-up-leading-text)
		  base-found-p t))
	(error nil))
      (delete-char 1)			; toss the dummy character
      (delete-horizontal-space)
      (indent-to base-indent)
      (if base-found-p
	  (message "Closes block: %s" base-text)))))

;; required for pending-del and delsel modes
(put 'py-delete-char 'delete-selection 'supersede)
(put 'py-delete-char 'pending-delete   'supersede)

(defun py-indent-line ()
  "Fix the indentation of the current line according to Python rules."
  (interactive)
  (let* ((ci (current-indentation))
	 (move-to-indentation-p (<= (current-column) ci))
	 (need (py-compute-indentation)))
    ;; watch for outdents
    (if (save-excursion
	  (back-to-indentation)
	  (looking-at py-outdent-re))
	(setq need (- need py-indent-offset)))
    (if (/= ci need)
	(save-excursion
	  (beginning-of-line)
	  (delete-horizontal-space)
	  (indent-to need)))
    (if move-to-indentation-p (back-to-indentation))))

(defun py-newline-and-indent ()
{'fraction_non_alphanumeric': 0.1351527388783951, 'fraction_numerical': 0.003365248919863653, 'mean_word_length': 3.0833333333333335, 'pattern_counts': {'":': 2, '<': 59, '<?xml version=': 0, '>': 89, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '307765', 'n_tokens_mistral': 15766, 'n_tokens_neox': 14765, 'n_words': 5572}