diff options
author | Karoly Lorentey <lorentey@elte.hu> | 2006-10-14 16:56:21 +0000 |
---|---|---|
committer | Karoly Lorentey <lorentey@elte.hu> | 2006-10-14 16:56:21 +0000 |
commit | 3f87f67ee215ffeecbd2f53bd7f342cdf03f47df (patch) | |
tree | 16f2af9111af08a94d608d96a957f5c3ec5effcc /lisp/progmodes | |
parent | 350e4fb815d7413ef6d339dd664014706f742927 (diff) | |
parent | 7a210b69c7f92650c524766d1b9d3f3eefdd67c7 (diff) | |
download | emacs-3f87f67ee215ffeecbd2f53bd7f342cdf03f47df.tar.gz |
Merged from emacs@sv.gnu.org
Patches applied:
* emacs@sv.gnu.org/emacs--devo--0--patch-371
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-372
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-373
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-374
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-375
Merge from gnus--rel--5.10
* emacs@sv.gnu.org/emacs--devo--0--patch-376
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-377
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-378
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-379
Merge from erc--emacs--21
* emacs@sv.gnu.org/emacs--devo--0--patch-380
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-381
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-382
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-383
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-384
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-385
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-386
Update from erc--emacs--22
* emacs@sv.gnu.org/emacs--devo--0--patch-387
Fix ERC bug introduced in last patch
* emacs@sv.gnu.org/emacs--devo--0--patch-388
Update from erc--emacs--22
* emacs@sv.gnu.org/emacs--devo--0--patch-389
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-390
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-391
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-392
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-393
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-394
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-395
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-396
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-397
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-398
Merge from gnus--rel--5.10
* emacs@sv.gnu.org/emacs--devo--0--patch-399
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-400
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-401
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-402
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-403
Rcirc update from Ryan Yeske
* emacs@sv.gnu.org/emacs--devo--0--patch-404
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-405
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-406
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-407
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-408
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-409
Update from CVS
* emacs@sv.gnu.org/emacs--devo--0--patch-410
Merge from gnus--rel--5.10
* emacs@sv.gnu.org/emacs--devo--0--patch-411
Miscellaneous tq-related fixes.
* emacs@sv.gnu.org/emacs--devo--0--patch-412
Update from CVS
* emacs@sv.gnu.org/gnus--rel--5.10--patch-121
Update from CVS
* emacs@sv.gnu.org/gnus--rel--5.10--patch-122
Update from CVS
* emacs@sv.gnu.org/gnus--rel--5.10--patch-123
Update from CVS
* emacs@sv.gnu.org/gnus--rel--5.10--patch-124
Update from CVS
* emacs@sv.gnu.org/gnus--rel--5.10--patch-125
Update from CVS
* emacs@sv.gnu.org/gnus--rel--5.10--patch-126
Merge from emacs--devo--0
* emacs@sv.gnu.org/gnus--rel--5.10--patch-127
Update from CVS
git-archimport-id: lorentey@elte.hu--2004/emacs--multi-tty--0--patch-581
Diffstat (limited to 'lisp/progmodes')
-rw-r--r-- | lisp/progmodes/gdb-ui.el | 278 | ||||
-rw-r--r-- | lisp/progmodes/grep.el | 122 | ||||
-rw-r--r-- | lisp/progmodes/gud.el | 10 | ||||
-rw-r--r-- | lisp/progmodes/python.el | 1540 | ||||
-rw-r--r-- | lisp/progmodes/sh-script.el | 13 | ||||
-rw-r--r-- | lisp/progmodes/vhdl-mode.el | 6 |
6 files changed, 1290 insertions, 679 deletions
diff --git a/lisp/progmodes/gdb-ui.el b/lisp/progmodes/gdb-ui.el index dca6fa16df0..f45bb2fe524 100644 --- a/lisp/progmodes/gdb-ui.el +++ b/lisp/progmodes/gdb-ui.el @@ -115,6 +115,7 @@ address for root variables.") (defvar gdb-main-file nil "Source file from which program execution begins.") (defvar gud-old-arrow nil) (defvar gdb-overlay-arrow-position nil) +(defvar gdb-stack-position nil) (defvar gdb-server-prefix nil) (defvar gdb-flush-pending-output nil) (defvar gdb-location-alist nil @@ -314,14 +315,14 @@ Also display the main routine in the disassembly buffer if present." "Nil means just pop up the GUD buffer unless `gdb-show-main' is t. In this case it starts with two windows: one displaying the GUD buffer and the other with the source file with the main routine -of the inferior. Non-nil means display the layout shown for +of the debugged program. Non-nil means display the layout shown for `gdba'." :type 'boolean :group 'gud :version "22.1") (defcustom gdb-use-separate-io-buffer nil - "Non-nil means display output from the inferior in a separate buffer." + "Non-nil means display output from the debugged program in a separate buffer." :type 'boolean :group 'gud :version "22.1") @@ -353,14 +354,14 @@ With arg, display additional buffers iff arg is positive." (error nil)))) (defun gdb-use-separate-io-buffer (arg) - "Toggle separate IO for inferior. + "Toggle separate IO for debugged program. With arg, use separate IO iff arg is positive." (interactive "P") (setq gdb-use-separate-io-buffer (if (null arg) (not gdb-use-separate-io-buffer) (> (prefix-numeric-value arg) 0))) - (message (format "Separate inferior IO %sabled" + (message (format "Separate IO %sabled" (if gdb-use-separate-io-buffer "en" "dis"))) (if (and gud-comint-buffer (buffer-name gud-comint-buffer)) @@ -383,8 +384,7 @@ With arg, use separate IO iff arg is positive." (list t nil) nil "-c" (concat gdb-cpp-define-alist-program " " gdb-cpp-define-alist-flags))))) - (define-list (split-string output "\n" t)) - (name)) + (define-list (split-string output "\n" t)) (name)) (setq gdb-define-alist nil) (dolist (define define-list) (setq name (nth 1 (split-string define "[( ]"))) @@ -1030,7 +1030,7 @@ The key should be one of the cars in `gdb-buffer-rules-assoc'." (minibuffer . nil))) (defun gdb-frame-separate-io-buffer () - "Display IO of inferior in a new frame." + "Display IO of debugged program in a new frame." (interactive) (if gdb-use-separate-io-buffer (let ((special-display-regexps (append special-display-regexps '(".*"))) @@ -1290,12 +1290,14 @@ not GDB." (progn (setq gud-running t) (setq gdb-inferior-status "running") + (setq gdb-signalled nil) (gdb-force-mode-line-update (propertize gdb-inferior-status 'face font-lock-type-face)) (gdb-remove-text-properties) (setq gud-old-arrow gud-overlay-arrow-position) (setq gud-overlay-arrow-position nil) (setq gdb-overlay-arrow-position nil) + (setq gdb-stack-position nil) (if gdb-use-separate-io-buffer (setq gdb-output-sink 'inferior)))) (t @@ -1330,6 +1332,7 @@ directives." (setq gdb-active-process nil) (setq gud-overlay-arrow-position nil) (setq gdb-overlay-arrow-position nil) + (setq gdb-stack-position nil) (setq gud-old-arrow nil) (setq gdb-inferior-status "exited") (gdb-force-mode-line-update @@ -1358,6 +1361,23 @@ directives." :type 'boolean :version "22.1") +(defcustom gdb-find-source-frame nil + "Non-nil means try to find a source frame further up stack e.g after signal." + :group 'gud + :type 'boolean + :version "22.1") + +(defun gdb-find-source-frame (arg) + "Toggle trying to find a source frame further up stack. +With arg, look for a source frame further up stack iff arg is positive." + (interactive "P") + (setq gdb-find-source-frame + (if (null arg) + (not gdb-find-source-frame) + (> (prefix-numeric-value arg) 0))) + (message (format "Looking for source frame %sabled" + (if gdb-find-source-frame "en" "dis")))) + (defun gdb-stopped (ignored) "An annotation handler for `stopped'. It is just like `gdb-stopping', except that if we already set the output @@ -1371,14 +1391,15 @@ sink to `user' in `gdb-stopping', that is fine." (if gdb-same-frame (gdb-display-gdb-buffer) (gdb-frame-gdb-buffer)) + (if gdb-find-source-frame ;;Try to find source further up stack e.g after signal. - (setq gdb-look-up-stack - (if (gdb-get-buffer 'gdb-stack-buffer) - 'keep - (progn - (gdb-get-buffer-create 'gdb-stack-buffer) - (gdb-invalidate-frames) - 'delete))))) + (setq gdb-look-up-stack + (if (gdb-get-buffer 'gdb-stack-buffer) + 'keep + (progn + (gdb-get-buffer-create 'gdb-stack-buffer) + (gdb-invalidate-frames) + 'delete)))))) (unless (member gdb-inferior-status '("exited" "signal")) (setq gdb-inferior-status "stopped") (gdb-force-mode-line-update @@ -1754,52 +1775,69 @@ static char *magick[] = { (gdb-remove-breakpoint-icons (point-min) (point-max))))) (with-current-buffer (gdb-get-buffer 'gdb-breakpoints-buffer) (save-excursion + (let ((buffer-read-only nil)) (goto-char (point-min)) (while (< (point) (- (point-max) 1)) (forward-line 1) - (if (looking-at "[^\t].*?breakpoint") + (if (looking-at gdb-breakpoint-regexp) (progn - (looking-at "\\([0-9]+\\)\\s-+\\S-+\\s-+\\S-+\\s-+\\(.\\)") (setq bptno (match-string 1)) (setq flag (char-after (match-beginning 2))) - (beginning-of-line) - (if (re-search-forward " in \\(.*\\) at\\s-+" nil t) - (progn - (let ((buffer-read-only nil)) - (add-text-properties (match-beginning 1) (match-end 1) - '(face font-lock-function-name-face))) - (looking-at "\\(\\S-+\\):\\([0-9]+\\)") - (let ((line (match-string 2)) (buffer-read-only nil) - (file (match-string 1))) - (add-text-properties (line-beginning-position) - (line-end-position) - '(mouse-face highlight - help-echo "mouse-2, RET: visit breakpoint")) - (unless (file-exists-p file) - (setq file (cdr (assoc bptno gdb-location-alist)))) - (if (and file - (not (string-equal file "File not found"))) - (with-current-buffer - (find-file-noselect file 'nowarn) - (set (make-local-variable 'gud-minor-mode) - 'gdba) - (set (make-local-variable 'tool-bar-map) - gud-tool-bar-map) - ;; Only want one breakpoint icon at each - ;; location. - (save-excursion - (goto-line (string-to-number line)) - (gdb-put-breakpoint-icon (eq flag ?y) bptno))) - (gdb-enqueue-input - (list - (concat gdb-server-prefix "list " - (match-string-no-properties 1) ":1\n") - 'ignore)) - (gdb-enqueue-input - (list (concat gdb-server-prefix "info source\n") - `(lambda () (gdb-get-location - ,bptno ,line ,flag)))))))))) - (end-of-line))))) + (add-text-properties + (match-beginning 2) (match-end 2) + (if (eq flag ?y) + '(face font-lock-warning-face) + '(face font-lock-type-face))) + (let ((bl (point)) + (el (line-end-position))) + (if (re-search-forward " in \\(.*\\) at\\s-+" el t) + (progn + (add-text-properties + (match-beginning 1) (match-end 1) + '(face font-lock-function-name-face)) + (looking-at "\\(\\S-+\\):\\([0-9]+\\)") + (let ((line (match-string 2)) + (file (match-string 1))) + (add-text-properties bl el + '(mouse-face highlight + help-echo "mouse-2, RET: visit breakpoint")) + (unless (file-exists-p file) + (setq file (cdr (assoc bptno gdb-location-alist)))) + (if (and file + (not (string-equal file "File not found"))) + (with-current-buffer + (find-file-noselect file 'nowarn) + (set (make-local-variable 'gud-minor-mode) + 'gdba) + (set (make-local-variable 'tool-bar-map) + gud-tool-bar-map) + ;; Only want one breakpoint icon at each + ;; location. + (save-excursion + (goto-line (string-to-number line)) + (gdb-put-breakpoint-icon (eq flag ?y) bptno))) + (gdb-enqueue-input + (list + (concat gdb-server-prefix "list " + (match-string-no-properties 1) ":1\n") + 'ignore)) + (gdb-enqueue-input + (list (concat gdb-server-prefix "info source\n") + `(lambda () (gdb-get-location + ,bptno ,line ,flag))))))) + (if (re-search-forward + "<\\(\\(\\sw\\|[_.]\\)+\\)\\(\\+[0-9]+\\)?>" + el t) + (add-text-properties + (match-beginning 1) (match-end 1) + '(face font-lock-function-name-face)) + (end-of-line) + (re-search-backward "\\s-\\(\\S-*\\)" + bl t) + (add-text-properties + (match-beginning 1) (match-end 1) + '(face font-lock-variable-name-face))))))) + (end-of-line)))))) (if (gdb-get-buffer 'gdb-assembler-buffer) (gdb-assembler-custom))) (defun gdb-mouse-set-clear-breakpoint (event) @@ -2002,8 +2040,14 @@ static char *magick[] = { (goto-char bl) (when (looking-at "^#\\([0-9]+\\)") (when (string-equal (match-string 1) gdb-frame-number) - (put-text-property bl (+ bl 4) - 'face '(:inverse-video t))) + (if (> (car (window-fringes)) 0) + (progn + (or gdb-stack-position + (setq gdb-stack-position (make-marker))) + (set-marker gdb-stack-position (point))) + (set-marker gdb-stack-position nil) + (put-text-property bl (+ bl 4) + 'face '(:inverse-video t)))) (when (re-search-forward (concat (if (string-equal (match-string 1) "0") "" " in ") @@ -2036,9 +2080,10 @@ static char *magick[] = { (setq gdb-look-up-stack nil)) (defun gdb-set-hollow () - (with-current-buffer (gud-find-file (car gud-last-last-frame)) - (setq fringe-indicator-alist - '((overlay-arrow . hollow-right-triangle))))) + (if gud-last-last-frame + (with-current-buffer (gud-find-file (car gud-last-last-frame)) + (setq fringe-indicator-alist + '((overlay-arrow . hollow-right-triangle)))))) (defun gdb-stack-buffer-name () (with-current-buffer gud-comint-buffer @@ -2073,6 +2118,8 @@ static char *magick[] = { (kill-all-local-variables) (setq major-mode 'gdb-frames-mode) (setq mode-name "Frames") + (setq gdb-stack-position nil) + (add-to-list 'overlay-arrow-variable-list 'gdb-stack-position) (setq buffer-read-only t) (use-local-map gdb-frames-mode-map) (run-mode-hooks 'gdb-frames-mode-hook) @@ -2524,18 +2571,18 @@ corresponding to the mode line clicked." 'local-map (gdb-make-header-line-mouse-map 'mouse-1 - #'(lambda () (interactive) - (let ((gdb-memory-address - ;; Let GDB do the arithmetic. - (concat - gdb-memory-address " - " - (number-to-string - (* gdb-memory-repeat-count - (cond ((string= gdb-memory-unit "b") 1) - ((string= gdb-memory-unit "h") 2) - ((string= gdb-memory-unit "w") 4) - ((string= gdb-memory-unit "g") 8))))))) - (gdb-invalidate-memory))))) + (lambda () (interactive) + (let ((gdb-memory-address + ;; Let GDB do the arithmetic. + (concat + gdb-memory-address " - " + (number-to-string + (* gdb-memory-repeat-count + (cond ((string= gdb-memory-unit "b") 1) + ((string= gdb-memory-unit "h") 2) + ((string= gdb-memory-unit "w") 4) + ((string= gdb-memory-unit "g") 8))))))) + (gdb-invalidate-memory))))) "|" (propertize "+" 'face font-lock-warning-face @@ -2543,9 +2590,9 @@ corresponding to the mode line clicked." 'mouse-face 'mode-line-highlight 'local-map (gdb-make-header-line-mouse-map 'mouse-1 - #'(lambda () (interactive) - (let ((gdb-memory-address nil)) - (gdb-invalidate-memory))))) + (lambda () (interactive) + (let ((gdb-memory-address nil)) + (gdb-invalidate-memory))))) "]: " (propertize gdb-memory-address 'face font-lock-warning-face @@ -2592,8 +2639,11 @@ corresponding to the mode line clicked." (defun gdb-frame-memory-buffer () "Display memory contents in a new frame." (interactive) - (let ((special-display-regexps (append special-display-regexps '(".*"))) - (special-display-frame-alist gdb-frame-parameters)) + (let* ((special-display-regexps (append special-display-regexps '(".*"))) + (special-display-frame-alist + (cons '(left-fringe . 0) + (cons '(right-fringe . 0) + (cons '(width . 83) gdb-frame-parameters))))) (display-buffer (gdb-get-buffer-create 'gdb-memory-buffer)))) @@ -2610,13 +2660,14 @@ corresponding to the mode line clicked." (defvar gdb-locals-watch-map (let ((map (make-sparse-keymap))) - (define-key map "\r" '(lambda () (interactive) - (beginning-of-line) - (gud-watch))) - (define-key map [mouse-2] '(lambda (event) (interactive "e") - (mouse-set-point event) - (beginning-of-line) - (gud-watch))) + (suppress-keymap map) + (define-key map "\r" (lambda () (interactive) + (beginning-of-line) + (gud-watch))) + (define-key map [mouse-2] (lambda (event) (interactive "e") + (mouse-set-point event) + (beginning-of-line) + (gud-watch))) map) "Keymap to create watch expression of a complex data type local variable.") @@ -2739,7 +2790,7 @@ corresponding to the mode line clicked." (define-key menu [gdb] '("Gdb" . gdb-display-gdb-buffer)) (define-key menu [threads] '("Threads" . gdb-display-threads-buffer)) (define-key menu [inferior] - '(menu-item "Inferior IO" gdb-display-separate-io-buffer + '(menu-item "Separate IO" gdb-display-separate-io-buffer :enable gdb-use-separate-io-buffer)) (define-key menu [memory] '("Memory" . gdb-display-memory-buffer)) (define-key menu [registers] '("Registers" . gdb-display-registers-buffer)) @@ -2758,7 +2809,7 @@ corresponding to the mode line clicked." (define-key menu [threads] '("Threads" . gdb-frame-threads-buffer)) (define-key menu [memory] '("Memory" . gdb-frame-memory-buffer)) (define-key menu [inferior] - '(menu-item "Inferior IO" gdb-frame-separate-io-buffer + '(menu-item "Separate IO" gdb-frame-separate-io-buffer :enable gdb-use-separate-io-buffer)) (define-key menu [registers] '("Registers" . gdb-frame-registers-buffer)) (define-key menu [disassembly] '("Disassembly" . gdb-frame-assembler-buffer)) @@ -2771,10 +2822,15 @@ corresponding to the mode line clicked." (define-key gud-menu-map [ui] `(menu-item (if (eq gud-minor-mode 'gdba) "GDB-UI" "GDB-MI") ,menu :visible (memq gud-minor-mode '(gdbmi gdba)))) + (define-key menu [gdb-find-source-frame] + '(menu-item "Look For Source Frame" gdb-find-source-frame + :visible (eq gud-minor-mode 'gdba) + :help "Toggle look for source frame." + :button (:toggle . gdb-find-source-frame))) (define-key menu [gdb-use-separate-io] - '(menu-item "Separate inferior IO" gdb-use-separate-io-buffer + '(menu-item "Separate IO" gdb-use-separate-io-buffer :visible (eq gud-minor-mode 'gdba) - :help "Toggle separate IO for inferior." + :help "Toggle separate IO for debugged program." :button (:toggle . gdb-use-separate-io-buffer))) (define-key menu [gdb-many-windows] '(menu-item "Display Other Windows" gdb-many-windows @@ -2871,12 +2927,13 @@ Kills the gdb buffers, and resets variables and the source buffers." (setq gud-minor-mode nil) (kill-local-variable 'tool-bar-map) (kill-local-variable 'gdb-define-alist)))))) - (when (markerp gdb-overlay-arrow-position) - (move-marker gdb-overlay-arrow-position nil) - (setq gdb-overlay-arrow-position nil)) + (setq gdb-overlay-arrow-position nil) (setq overlay-arrow-variable-list (delq 'gdb-overlay-arrow-position overlay-arrow-variable-list)) (setq fringe-indicator-alist '((overlay-arrow . right-triangle))) + (setq gdb-stack-position nil) + (setq overlay-arrow-variable-list + (delq 'gdb-stack-position overlay-arrow-variable-list)) (if (boundp 'speedbar-frame) (speedbar-timer-fn)) (setq gud-running nil) (setq gdb-active-process nil) @@ -3098,8 +3155,7 @@ BUFFER nil or omitted means use the current buffer." '((overlay-arrow . hollow-right-triangle)))) (or gdb-overlay-arrow-position (setq gdb-overlay-arrow-position (make-marker))) - (set-marker gdb-overlay-arrow-position - (point) (current-buffer)))))) + (set-marker gdb-overlay-arrow-position (point)))))) ;; remove all breakpoint-icons in assembler buffer before updating. (gdb-remove-breakpoint-icons (point-min) (point-max)))) (with-current-buffer (gdb-get-buffer 'gdb-breakpoints-buffer) @@ -3460,10 +3516,32 @@ in_scope=\"\\(.*?\\)\".*?}") (defvar gdb-locals-watch-map-1 (let ((map (make-sparse-keymap))) + (suppress-keymap map) + (define-key map "\r" 'gud-watch) (define-key map [mouse-2] 'gud-watch) map) "Keymap to create watch expression of a complex data type local variable.") +(defvar gdb-edit-locals-map-1 + (let ((map (make-sparse-keymap))) + (suppress-keymap map) + (define-key map "\r" 'gdb-edit-locals-value) + (define-key map [mouse-2] 'gdb-edit-locals-value) + map) + "Keymap to edit value of a simple data type local variable.") + +(defun gdb-edit-locals-value (&optional event) + "Assign a value to a variable displayed in the locals buffer." + (interactive (list last-input-event)) + (save-excursion + (if event (posn-set-point (event-end event))) + (beginning-of-line) + (let* ((var (current-word)) + (value (read-string (format "New value (%s): " var)))) + (gdb-enqueue-input + (list (concat gdb-server-prefix"set variable " var " = " value "\n") + 'ignore))))) + ;; Dont display values of arrays or structures. ;; These can be expanded using gud-watch. (defun gdb-stack-list-locals-handler () @@ -3491,20 +3569,26 @@ in_scope=\"\\(.*?\\)\".*?}") (let* ((window (get-buffer-window buf 0)) (start (window-start window)) (p (window-point window)) - (buffer-read-only nil)) + (buffer-read-only nil) (name) (value)) (erase-buffer) (dolist (local locals-list) (setq name (car local)) - (if (or (not (nth 2 local)) - (string-match "^\\0x" (nth 2 local))) + (setq value (nth 2 local)) + (if (or (not value) + (string-match "^\\0x" value)) (add-text-properties 0 (length name) `(mouse-face highlight help-echo "mouse-2: create watch expression" local-map ,gdb-locals-watch-map-1) - name)) + name) + (add-text-properties 0 (length value) + `(mouse-face highlight + help-echo "mouse-2: edit value" + local-map ,gdb-edit-locals-map-1) + value)) (insert (concat name "\t" (nth 1 local) - "\t" (nth 2 local) "\n"))) + "\t" value "\n"))) (set-window-start window start) (set-window-point window p)))))))) diff --git a/lisp/progmodes/grep.el b/lisp/progmodes/grep.el index e7d85910a63..48692f9742f 100644 --- a/lisp/progmodes/grep.el +++ b/lisp/progmodes/grep.el @@ -335,7 +335,7 @@ This variable's value takes effect when `grep-compute-defaults' is called.") (defvar grep-find-use-xargs nil "Whether \\[grep-find] uses the `xargs' utility by default. -If nil, it uses `find -exec'; if `gnu', it uses `find -print0' and `xargs -0'; +If `exec', it uses `find -exec'; if `gnu', it uses `find -print0' and `xargs -0'; if not nil and not `gnu', it uses `find -print' and `xargs'. This variable's value takes effect when `grep-compute-defaults' is called.") @@ -419,21 +419,29 @@ Set up `compilation-exit-message-function' and run `grep-setup-hook'." (format "%s <C> %s <R> <F>" grep-program grep-options))) (unless grep-find-use-xargs (setq grep-find-use-xargs - (if (and - (grep-probe find-program `(nil nil nil ,null-device "-print0")) - (grep-probe "xargs" `(nil nil nil "-0" "-e" "echo"))) - 'gnu))) + (cond + ((and + (grep-probe find-program `(nil nil nil ,null-device "-print0")) + (grep-probe "xargs" `(nil nil nil "-0" "-e" "echo"))) + 'gnu) + (t + 'exec)))) (unless grep-find-command (setq grep-find-command (cond ((eq grep-find-use-xargs 'gnu) (format "%s . -type f -print0 | xargs -0 -e %s" find-program grep-command)) - (grep-find-use-xargs + ((eq grep-find-use-xargs 'exec) + (let ((cmd0 (format "%s . -type f -exec %s" + find-program grep-command))) + (cons + (format "%s {} %s %s" + cmd0 null-device + (shell-quote-argument ";")) + (1+ (length cmd0))))) + (t (format "%s . -type f -print | xargs %s" - find-program grep-command)) - (t (cons (format "%s . -type f -exec %s {} %s \\;" - find-program grep-command null-device) - (+ 22 (length grep-command))))))) + find-program grep-command))))) (unless grep-find-template (setq grep-find-template (let ((gcmd (format "%s <C> %s <R>" @@ -441,11 +449,13 @@ Set up `compilation-exit-message-function' and run `grep-setup-hook'." (cond ((eq grep-find-use-xargs 'gnu) (format "%s . <X> -type f <F> -print0 | xargs -0 -e %s" find-program gcmd)) - (grep-find-use-xargs + ((eq grep-find-use-xargs 'exec) + (format "%s . <X> -type f <F> -exec %s {} %s %s" + find-program gcmd null-device + (shell-quote-argument ";"))) + (t (format "%s . <X> -type f <F> -print | xargs %s" - find-program gcmd)) - (t (format "%s . <X> -type f <F> -exec %s {} %s \\;" - find-program gcmd null-device)))))))) + find-program gcmd)))))))) (unless (or (not grep-highlight-matches) (eq grep-highlight-matches t)) (setq grep-highlight-matches (with-temp-buffer @@ -455,34 +465,48 @@ Set up `compilation-exit-message-function' and run `grep-setup-hook'." (search-forward "--color" nil t)) t))))) +(defun grep-tag-default () + (or (and transient-mark-mode mark-active + (/= (point) (mark)) + (buffer-substring-no-properties (point) (mark))) + (funcall (or find-tag-default-function + (get major-mode 'find-tag-default-function) + 'find-tag-default)) + "")) + (defun grep-default-command () - (let ((tag-default - (shell-quote-argument - (or (funcall (or find-tag-default-function - (get major-mode 'find-tag-default-function) - 'find-tag-default)) - ""))) + "Compute the default grep command for C-u M-x grep to offer." + (let ((tag-default (shell-quote-argument (grep-tag-default))) + ;; This a regexp to match single shell arguments. + ;; Could someone please add comments explaining it? (sh-arg-re "\\(\\(?:\"\\(?:[^\"]\\|\\\\\"\\)+\"\\|'[^']+'\\|[^\"' \t\n]\\)+\\)") (grep-default (or (car grep-history) grep-command))) - ;; Replace the thing matching for with that around cursor. + ;; In the default command, find the arg that specifies the pattern. (when (or (string-match (concat "[^ ]+\\s +\\(?:-[^ ]+\\s +\\)*" sh-arg-re "\\(\\s +\\(\\S +\\)\\)?") grep-default) ;; If the string is not yet complete. (string-match "\\(\\)\\'" grep-default)) - (unless (or (not (stringp buffer-file-name)) - (when (match-beginning 2) - (save-match-data - (string-match - (wildcard-to-regexp - (file-name-nondirectory - (match-string 3 grep-default))) - (file-name-nondirectory buffer-file-name))))) - (setq grep-default (concat (substring grep-default - 0 (match-beginning 2)) - " *." - (file-name-extension buffer-file-name)))) + ;; Maybe we will replace the pattern with the default tag. + ;; But first, maybe replace the file name pattern. + (condition-case nil + (unless (or (not (stringp buffer-file-name)) + (when (match-beginning 2) + (save-match-data + (string-match + (wildcard-to-regexp + (file-name-nondirectory + (match-string 3 grep-default))) + (file-name-nondirectory buffer-file-name))))) + (setq grep-default (concat (substring grep-default + 0 (match-beginning 2)) + " *." + (file-name-extension buffer-file-name)))) + ;; In case wildcard-to-regexp gets an error + ;; from invalid data. + (error nil)) + ;; Now replace the pattern with the default tag. (replace-match tag-default t t grep-default 1)))) @@ -590,15 +614,11 @@ substitution string. Note dynamic scoping of variables.") (defun grep-read-regexp () "Read regexp arg for interactive grep." - (let ((default - (or (funcall (or find-tag-default-function - (get major-mode 'find-tag-default-function) - 'find-tag-default)) - ""))) + (let ((default (grep-tag-default))) (read-string (concat "Search for" (if (and default (> (length default) 0)) - (format " (default %s): " default) ": ")) + (format " (default \"%s\"): " default) ": ")) nil 'grep-regexp-history default))) (defun grep-read-files (regexp) @@ -620,7 +640,9 @@ substitution string. Note dynamic scoping of variables.") (cdr alias))) (and fn (let ((ext (file-name-extension fn))) - (and ext (concat "*." ext)))))) + (and ext (concat "*." ext)))) + (car grep-files-history) + (car (car grep-files-aliases)))) (files (read-string (concat "Search for \"" regexp "\" in files" @@ -724,18 +746,26 @@ This command shares argument histories with \\[lgrep] and \\[grep-find]." (let ((command (grep-expand-template grep-find-template regexp - (concat "\\( -name " + (concat (shell-quote-argument "(") + " -name " (mapconcat #'shell-quote-argument (split-string files) " -o -name ") - " \\)") + " " + (shell-quote-argument ")")) dir (and grep-find-ignored-directories - (concat "\\( -path '*/" - (mapconcat #'identity + (concat (shell-quote-argument "(") + ;; we should use shell-quote-argument here + " -path " + (mapconcat #'(lambda (dir) + (shell-quote-argument + (concat "*/" dir))) grep-find-ignored-directories - "' -o -path '*/") - "' \\) -prune -o "))))) + " -o -path ") + " " + (shell-quote-argument ")") + " -prune -o "))))) (when command (if current-prefix-arg (setq command diff --git a/lisp/progmodes/gud.el b/lisp/progmodes/gud.el index 97e54135a6f..84b40e8ba80 100644 --- a/lisp/progmodes/gud.el +++ b/lisp/progmodes/gud.el @@ -3180,7 +3180,15 @@ class of the file (using s to separate nested class ids)." (defvar gdb-script-font-lock-syntactic-keywords '(("^document\\s-.*\\(\n\\)" (1 "< b")) ;; It would be best to change the \n in front, but it's more difficult. - ("^en\\(d\\)\\>" (1 "> b")))) + ("^end\\>" + (0 (progn + (unless (eq (match-beginning 0) (point-min)) + (put-text-property (1- (match-beginning 0)) (match-beginning 0) + 'syntax-table (eval-when-compile + (string-to-syntax "> b"))) + (put-text-property (1- (match-beginning 0)) (match-end 0) + 'font-lock-multiline t) + nil)))))) (defun gdb-script-font-lock-syntactic-face (state) (cond diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 9636f7eaeae..c38a6e82f83 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -9,19 +9,19 @@ ;; This file is part of GNU Emacs. -;; This file is free software; you can redistribute it and/or modify +;; GNU Emacs is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. -;; This file is distributed in the hope that it will be useful, +;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to -;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ;; Boston, MA 02110-1301, USA. ;;; Commentary: @@ -32,41 +32,44 @@ ;; maintained with Python. That isn't covered by an FSF copyright ;; assignment, unlike this code, and seems not to be well-maintained ;; for Emacs (though I've submitted fixes). This mode is rather -;; simpler and is, perhaps, better in other ways. In particular, -;; using the syntax functions with text properties maintained by -;; font-lock should make it more correct with arbitrary string and -;; comment contents. +;; simpler and is better in other ways. In particular, using the +;; syntax functions with text properties maintained by font-lock makes +;; it more correct with arbitrary string and comment contents. ;; 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). [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 -;; I've installed a minor mode to do the job properly in Emacs 22. +;; `compilation-shell-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 I've installed a minor mode to do the +;; job properly in Emacs 23. [CC mode 5.31 contains an incompatible +;; feature, `c-subword-mode' which is intended to have a similar +;; effect, but actually only affects word-oriented keybindings.] + ;; 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 +;; 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. +;; `python-send-region' & al via `compilation-shell-minor-mode'.) +;; There is (limited) symbol completion using lookup in Python and +;; Eldoc support also using the inferior process. Successive TABs +;; cycle between possible indentations for the line. -;; 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. +;; Even where it has similar facilities, this mode is incompatible +;; with python-mode.el in some respects. For instance, various key +;; bindings are changed to obey Emacs conventions. ;; TODO: See various Fixmes below. ;;; Code: -;; 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 'cl) (require 'compile) - (autoload 'info-lookup-maybe-add-help "info-look")) + (require 'comint)) + +(autoload 'comint-mode "comint") (defgroup python nil "Silly walks in the Python language." @@ -84,31 +87,37 @@ ;;;; Font lock (defvar python-font-lock-keywords - `(,(rx (and word-start - ;; From v 2.3 reference. - ;; def and class dealt with separately below - (or "and" "assert" "break" "continue" "del" "elif" "else" - "except" "exec" "finally" "for" "from" "global" "if" - "import" "in" "is" "lambda" "not" "or" "pass" "print" - "raise" "return" "try" "while" "yield" - ;; Future keywords - "as" "None") - word-end)) - (,(rx (and word-start (group "class") (1+ space) (group (1+ word)))) - (1 font-lock-keyword-face) (2 font-lock-type-face)) - (,(rx (and word-start (group "def") (1+ space) (group (1+ word)))) - (1 font-lock-keyword-face) (2 font-lock-function-name-face)))) + `(,(rx symbol-start + ;; From v 2.4 reference. + ;; def and class dealt with separately below + (or "and" "assert" "break" "continue" "del" "elif" "else" + "except" "exec" "finally" "for" "from" "global" "if" + "import" "in" "is" "lambda" "not" "or" "pass" "print" + "raise" "return" "try" "while" "yield" + ;; Future keywords + "as" "None") + symbol-end) + ;; Definitions + (,(rx symbol-start (group "class") (1+ space) (group (1+ (or word ?_)))) + (1 font-lock-keyword-face) (2 font-lock-type-face)) + (,(rx symbol-start (group "def") (1+ space) (group (1+ (or word ?_)))) + (1 font-lock-keyword-face) (2 font-lock-function-name-face)) + ;; Top-level assignments are worth highlighting. + (,(rx line-start (group (1+ (or word ?_))) (0+ space) "=") + (1 font-lock-variable-name-face)) + (,(rx "@" (1+ (or word ?_))) ; decorators + (0 font-lock-preprocessor-face)))) (defconst python-font-lock-syntactic-keywords ;; Make outer chars of matching triple-quote sequences into generic ;; string delimiters. Fixme: Is there a better way? - `((,(rx (and (or line-start buffer-start (not (syntax escape))) ; avoid escaped - ; leading quote - (group (optional (any "uUrR"))) ; prefix gets syntax property - (optional (any "rR")) ; possible second prefix - (group (syntax string-quote)) ; maybe gets property - (backref 2) ; per first quote - (group (backref 2)))) ; maybe gets property + `((,(rx (or line-start buffer-start + (not (syntax escape))) ; avoid escaped leading quote + (group (optional (any "uUrR"))) ; prefix gets syntax property + (optional (any "rR")) ; possible second prefix + (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)) (3 (python-quote-syntax 3))) @@ -132,6 +141,8 @@ Used for syntactic keywords. N is the match number (1, 2 or 3)." ;; x = ''' """ ' a ;; ''' ;; x '"""' x """ \"""" x + ;; Fixme: """""" goes wrong (due to syntax-ppss not getting the string + ;; fence context). (save-excursion (goto-char (match-beginning 0)) (cond @@ -140,19 +151,17 @@ Used for syntactic keywords. N is the match number (1, 2 or 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")) + (skip-chars-forward "uUrR") ; skip any prefix ;; 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 + ((or (and (= n 2) ; leading quote (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 "|")))) + (eval-when-compile (string-to-syntax "|")))) ;; Otherwise (we're in a non-matching string) the property is ;; nil, which is OK. ))) @@ -204,23 +213,37 @@ Used for syntactic keywords. N is the match number (1, 2 or 3)." (define-key map "\C-c\C-l" 'python-load-file) ; a la cmuscheme (substitute-key-definition 'complete-symbol 'python-complete-symbol map global-map) - ;; Fixme: Add :help to menu. + (define-key map "\C-c\C-i" 'python-find-imports) + (define-key map "\C-c\C-t" 'python-expand-template) (easy-menu-define python-menu map "Python Mode menu" - '("Python" - ["Shift region left" python-shift-left :active mark-active] - ["Shift region right" python-shift-right :active mark-active] + `("Python" + :help "Python-specific Features" + ["Shift region left" python-shift-left :active mark-active + :help "Shift by a single indentation step"] + ["Shift region right" python-shift-right :active mark-active + :help "Shift by a single indentation step"] "-" - ["Mark block" python-mark-block] + ["Mark block" python-mark-block + :help "Mark innermost block around point"] ["Mark def/class" mark-defun :help "Mark innermost definition around point"] "-" - ["Start of block" python-beginning-of-block] - ["End of block" python-end-of-block] + ["Start of block" python-beginning-of-block + :help "Go to start of innermost definition around point"] + ["End of block" python-end-of-block + :help "Go to end of innermost definition around point"] ["Start of def/class" beginning-of-defun :help "Go to start of innermost definition around point"] ["End of def/class" end-of-defun :help "Go to end of innermost definition around point"] "-" + ("Templates..." + :help "Expand templates for compound statements" + :filter (lambda (&rest junk) + (mapcar (lambda (elt) + (vector (car elt) (cdr elt) t)) + python-skeletons))) ; defined later + "-" ["Start interpreter" run-python :help "Run `inferior' Python in separate buffer"] ["Import/reload file" python-load-file @@ -233,12 +256,23 @@ Used for syntactic keywords. N is the match number (1, 2 or 3)." :help "Evaluate current definition in inferior Python session"] ["Switch to interpreter" python-switch-to-python :help "Switch to inferior Python buffer"] + ["Set default process" python-set-proc + :help "Make buffer's inferior process the default" + :active (buffer-live-p python-buffer)] ["Check file" python-check :help "Run pychecker"] ["Debugger" pdb :help "Run pdb under GUD"] "-" ["Help on symbol" python-describe-symbol - :help "Use pydoc on symbol at point"])) + :help "Use pydoc on symbol at point"] + ["Complete symbol" python-complete-symbol + :help "Complete (qualified) symbol before point"] + ["Update imports" python-find-imports + :help "Update list of top-level imports for completion"])) map)) +;; Fixme: add toolbar stuff for useful things like symbol help, send +;; region, at least. (Shouldn't be specific to Python, obviously.) +;; eric has items including: (un)indent, (un)comment, restart script, +;; run script, debug script; also things for profiling, unit testing. (defvar python-mode-syntax-table (let ((table (make-syntax-table))) @@ -263,7 +297,8 @@ Used for syntactic keywords. N is the match number (1, 2 or 3)." (defsubst python-in-string/comment () "Return non-nil if point is in a Python literal (a comment or string)." - (syntax-ppss-context (syntax-ppss))) + ;; We don't need to save the match data. + (nth 8 (syntax-ppss))) (defconst python-space-backslash-table (let ((table (copy-syntax-table python-mode-syntax-table))) @@ -273,13 +308,21 @@ Used for syntactic keywords. N is the match number (1, 2 or 3)." (defun python-skip-comments/blanks (&optional backward) "Skip comments and blank lines. -BACKWARD non-nil means go backwards, otherwise go forwards. Backslash is -treated as whitespace so that continued blank lines are skipped. -Doesn't move out of comments -- should be outside or at end of line." - (with-syntax-table python-space-backslash-table - (forward-comment (if backward - most-negative-fixnum - most-positive-fixnum)))) +BACKWARD non-nil means go backwards, otherwise go forwards. +Backslash is treated as whitespace so that continued blank lines +are skipped. Doesn't move out of comments -- should be outside +or at end of line." + (let ((arg (if backward + ;; If we're in a comment (including on the trailing + ;; newline), forward-comment doesn't move backwards out + ;; of it. Don't set the syntax table round this bit! + (let ((syntax (syntax-ppss))) + (if (nth 4 syntax) + (goto-char (nth 8 syntax))) + (- (point-max))) + (point-max)))) + (with-syntax-table python-space-backslash-table + (forward-comment arg)))) (defun python-backslash-continuation-line-p () "Non-nil if preceding line ends with backslash that is not in a comment." @@ -289,12 +332,17 @@ Doesn't move out of comments -- should be outside or at end of line." (defun python-continuation-line-p () "Return non-nil if current line continues a previous one. The criteria are that the previous line ends in a backslash outside -comments and strings, or that the bracket/paren nesting depth is nonzero." - (or (and (eq ?\\ (char-before (line-end-position 0))) - (not (syntax-ppss-context (syntax-ppss)))) - (< 0 (syntax-ppss-depth - (save-excursion ; syntax-ppss with arg changes point - (syntax-ppss (line-beginning-position))))))) +comments and strings, or that point is within brackets/parens." + (or (python-backslash-continuation-line-p) + (let ((depth (syntax-ppss-depth + (save-excursion ; syntax-ppss with arg changes point + (syntax-ppss (line-beginning-position)))))) + (or (> depth 0) + (if (< depth 0) ; Unbalanced brackets -- act locally + (save-excursion + (condition-case () + (progn (backward-up-list) t) ; actually within brackets + (error nil)))))))) (defun python-comment-line-p () "Return non-nil iff current line has only a comment." @@ -304,6 +352,12 @@ comments and strings, or that the bracket/paren nesting depth is nonzero." (back-to-indentation) (looking-at (rx (or (syntax comment-start) line-end)))))) +(defun python-blank-line-p () + "Return non-nil iff current line is blank." + (save-excursion + (beginning-of-line) + (looking-at "\\s-*$"))) + (defun python-beginning-of-string () "Go to beginning of string around point. Do nothing if not in string." @@ -316,83 +370,70 @@ Do nothing if not in string." BOS non-nil means point is known to be at beginning of statement." (save-excursion (unless bos (python-beginning-of-statement)) - (and (not (python-comment-line-p)) - (re-search-forward (rx (and ?: (0+ space) - (optional (and (syntax comment-start) - (0+ not-newline))) - line-end)) - (save-excursion (python-end-of-statement)) - t) - (not (progn (goto-char (match-beginning 0)) - (python-in-string/comment)))))) + (looking-at (rx (and (or "if" "else" "elif" "while" "for" "def" + "class" "try" "except" "finally") + symbol-end))))) (defun python-close-block-statement-p (&optional bos) "Return non-nil if current line is a statement closing a block. BOS non-nil means point is at beginning of statement. -The criteria are that the line isn't a comment or in string and starts with -keyword `raise', `break', `continue' or `pass'." +The criteria are that the line isn't a comment or in string and + starts with keyword `raise', `break', `continue' or `pass'." (save-excursion (unless bos (python-beginning-of-statement)) (back-to-indentation) - (looking-at (rx (and (or "return" "raise" "break" "continue" "pass") - symbol-end))))) + (looking-at (rx (or "return" "raise" "break" "continue" "pass") + symbol-end)))) (defun python-outdent-p () "Return non-nil if current line should outdent a level." (save-excursion (back-to-indentation) - (and (looking-at (rx (and (or (and (or "else" "finally") symbol-end) - (and (or "except" "elif") symbol-end - (1+ (not (any ?:))))) - (optional space) ":" (optional space) - (or (syntax comment-start) line-end)))) - (progn (end-of-line) - (not (python-in-string/comment))) + (and (looking-at (rx (and (or "else" "finally" "except" "elif") + symbol-end))) + (not (python-in-string/comment)) ;; Ensure there's a previous statement and move to it. (zerop (python-previous-statement)) (not (python-close-block-statement-p t)) ;; Fixme: check this - (not (looking-at (rx (and (or (and (or "if" "elif" "except" - "for" "while") - symbol-end (1+ (not (any ?:)))) - (and "try" symbol-end)) - (optional space) ":" (optional space) - (or (syntax comment-start) line-end))))) - (progn (end-of-line) - (not (python-in-string/comment)))))) + (not (python-open-block-statement-p))))) ;;;; Indentation. (defcustom python-indent 4 - "*Number of columns for a unit of indentation in Python mode. + "Number of columns for a unit of indentation in Python mode. See also `\\[python-guess-indent]'" :group 'python :type 'integer) (defcustom python-guess-indent t - "*Non-nil means Python mode guesses `python-indent' for the buffer." + "Non-nil means Python mode guesses `python-indent' for the buffer." :type 'boolean :group 'python) (defcustom python-indent-string-contents t - "*Non-nil means indent contents of multi-line strings together. + "Non-nil means indent contents of multi-line strings together. This means indent them the same as the preceding non-blank line. -Otherwise indent them to column zero." +Otherwise preserve their indentation. + +This only applies to `doc' strings, i.e. those that form statements; +the indentation is preserved in others." :type '(choice (const :tag "Align with preceding" t) - (const :tag "Indent to column 0" nil)) + (const :tag "Preserve indentation" nil)) :group 'python) (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. This doesn't apply to comment lines, which are always indented -in lines with preceding comments." +Only do this for comments where the leading comment character is +followed by space. This doesn't apply to comment lines, which +are always indented in lines with preceding comments." :type 'boolean :group 'python) (defcustom python-continuation-offset 4 - "*Number of columns of additional indentation for continuation lines. -Continuation lines follow a backslash-terminated line starting a statement." + "Number of columns of additional indentation for continuation lines. +Continuation lines follow a backslash-terminated line starting a +statement." :group 'python :type 'integer) @@ -406,9 +447,9 @@ Set `python-indent' locally to the value guessed." (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))) + (when (and (re-search-forward (rx ?: (0+ space) + (or (syntax comment-start) + line-end)) nil 'move) (python-open-block-statement-p)) (save-excursion @@ -425,8 +466,21 @@ Set `python-indent' locally to the value guessed." (setq indent-tabs-mode nil))) indent))))) +;; Alist of possible indentations and start of statement they would +;; close. Used in indentation cycling (below). +(defvar python-indent-list nil + "Internal use.") +;; Length of the above +(defvar python-indent-list-length nil + "Internal use.") +;; Current index into the alist. +(defvar python-indent-index nil + "Internal use.") + (defun python-calculate-indentation () "Calculate Python indentation for line at point." + (setq python-indent-list nil + python-indent-list-length 1) (save-excursion (beginning-of-line) (let ((syntax (syntax-ppss)) @@ -434,17 +488,25 @@ Set `python-indent' locally to the value guessed." (cond ((eq 'string (syntax-ppss-context syntax)) ; multi-line string (if (not python-indent-string-contents) - 0 - (save-excursion + (current-indentation) + ;; Only respect `python-indent-string-contents' in doc + ;; strings (defined as those which form statements). + (if (not (save-excursion + (python-beginning-of-statement) + (looking-at (rx (or (syntax string-delimiter) + (syntax string-quote)))))) + (current-indentation) ;; Find indentation of preceding non-blank line within string. (setq start (nth 8 syntax)) (forward-line -1) (while (and (< start (point)) (looking-at "\\s-*$")) (forward-line -1)) (current-indentation)))) - ((python-continuation-line-p) + ((python-continuation-line-p) ; after backslash, or bracketed (let ((point (point)) - (open-start (cadr syntax))) + (open-start (cadr syntax)) + (backslash (python-backslash-continuation-line-p)) + (colon (eq ?: (char-before (1- (line-beginning-position)))))) (if open-start ;; Inside bracketed expression. (progn @@ -458,7 +520,11 @@ Set `python-indent' locally to the value guessed." (backward-sexp) (< (point) point)) (error nil)))) - (current-column) + ;; Extra level if we're backslash-continued or + ;; following a key. + (if (or backslash colon) + (+ python-indent (current-column)) + (current-column)) ;; Otherwise indent relative to statement start, one ;; level per bracketing level. (goto-char (1+ open-start)) @@ -472,112 +538,153 @@ Set `python-indent' locally to the value guessed." (current-indentation) ;; First continuation line. Indent one step, with an ;; extra one if statement opens a block. - (save-excursion - (python-beginning-of-statement) - (+ (current-indentation) python-continuation-offset - (if (python-open-block-statement-p t) - python-indent - 0))))))) + (python-beginning-of-statement) + (+ (current-indentation) python-continuation-offset + (if (python-open-block-statement-p t) + python-indent + 0)))))) ((bobp) 0) ;; Fixme: Like python-mode.el; not convinced by this. - ((looking-at (rx (and (0+ space) (syntax comment-start) - (not (any " \t\n"))))) ; non-indentable comment + ((looking-at (rx (0+ space) (syntax comment-start) + (not (any " \t\n")))) ; non-indentable comment (current-indentation)) - (t (let ((point (point))) - (if python-honour-comment-indentation - ;; Back over whitespace, newlines, non-indentable comments. - (catch 'done - (while t - (if (cond ((bobp)) - ;; not at comment start - ((not (forward-comment -1)) - (python-beginning-of-statement) - t) - ;; trailing comment - ((/= (current-column) (current-indentation)) - (python-beginning-of-statement) - t) - ;; indentable comment like python-mode.el - ((and (looking-at (rx (and (syntax comment-start) - (or space line-end)))) - (/= 0 (current-column))))) - (throw 'done t)))) - ;; Else back over all comments. - (python-skip-comments/blanks t) - (python-beginning-of-statement)) - ;; don't lose on bogus outdent - (max 0 (+ (current-indentation) - (or (cond ((python-open-block-statement-p t) - python-indent) - ((python-close-block-statement-p t) - (- python-indent))) - (progn (goto-char point) - (if (python-outdent-p) - (- python-indent))) - 0))))))))) - -(defun python-comment-indent () - "`comment-indent-function' for Python." - ;; If previous non-blank line was a comment, use its indentation. - ;; FIXME: This seems unnecessary since the default code delegates to - ;; indent-according-to-mode. --Stef - (unless (bobp) - (save-excursion - (forward-comment -1) - (if (eq ?# (char-after)) (current-column))))) + (t (if python-honour-comment-indentation + ;; Back over whitespace, newlines, non-indentable comments. + (catch 'done + (while t + (if (cond ((bobp)) + ;; not at comment start + ((not (forward-comment -1)) + (python-beginning-of-statement) + t) + ;; trailing comment + ((/= (current-column) (current-indentation)) + (python-beginning-of-statement) + t) + ;; indentable comment like python-mode.el + ((and (looking-at (rx (syntax comment-start) + (or space line-end))) + (/= 0 (current-column))))) + (throw 'done t))))) + (python-indentation-levels) + ;; Prefer to indent comments with an immediately-following + ;; statement, e.g. + ;; ... + ;; # ... + ;; def ... + (when (and (> python-indent-list-length 1) + (python-comment-line-p)) + (forward-line) + (unless (python-comment-line-p) + (let ((elt (assq (current-indentation) python-indent-list))) + (setq python-indent-list + (nconc (delete elt python-indent-list) + (list elt)))))) + (caar (last python-indent-list))))))) ;;;; Cycling through the possible indentations with successive TABs. ;; These don't need to be buffer-local since they're only relevant ;; during a cycle. -;; Alist of possible indentations and start of statement they would close. -(defvar python-indent-list nil - "Internal use.") -;; Length of the above -(defvar python-indent-list-length nil - "Internal use.") -;; Current index into the alist. -(defvar python-indent-index nil - "Internal use.") - (defun python-initial-text () "Text of line following indentation and ignoring any trailing comment." - (buffer-substring (+ (line-beginning-position) (current-indentation)) - (save-excursion - (end-of-line) - (forward-comment -1) - (point)))) + (save-excursion + (buffer-substring (progn + (back-to-indentation) + (point)) + (progn + (end-of-line) + (forward-comment -1) + (point))))) + +(defconst python-block-pairs + '(("else" "if" "elif" "while" "for" "try" "except") + ("elif" "if" "elif") + ("except" "try" "except") + ("finally" "try")) + "Alist of keyword matches. +The car of an element is a keyword introducing a statement which +can close a block opened by a keyword in the cdr.") + +(defun python-first-word () + "Return first word (actually symbol) on the line." + (save-excursion + (back-to-indentation) + (current-word t))) (defun python-indentation-levels () "Return a list of possible indentations for this line. +It is assumed not to be a continuation line or in a multi-line string. Includes the default indentation and those which would close all -enclosing blocks. Assumes the line has already been indented per -`python-indent-line'. Elements of the list are actually pairs: +enclosing blocks. 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) - (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) - (save-excursion (and (python-previous-statement) - (python-open-block-statement-p t)))) - (while (python-beginning-of-block) - (push (cons (current-indentation) (python-initial-text)) - levels))) - levels))) + (let ((initial "") + levels indent) + ;; Only one possibility immediately following a block open + ;; statement, assuming it doesn't have a `suite' on the same line. + (cond + ((save-excursion (and (python-previous-statement) + (python-open-block-statement-p t) + (setq indent (current-indentation)) + ;; Check we don't have something like: + ;; if ...: ... + (if (progn (python-end-of-statement) + (python-skip-comments/blanks t) + (eq ?: (char-before))) + (setq indent (+ python-indent indent))))) + (push (cons indent initial) levels)) + ;; Only one possibility for comment line immediately following + ;; another. + ((save-excursion + (when (python-comment-line-p) + (forward-line -1) + (if (python-comment-line-p) + (push (cons (current-indentation) initial) levels))))) + ;; Fixme: Maybe have a case here which indents (only) first + ;; line after a lambda. + (t + (let ((start (car (assoc (python-first-word) python-block-pairs)))) + (python-previous-statement) + ;; Is this a valid indentation for the line of interest? + (unless (or (if start ; potentially only outdentable + ;; Check for things like: + ;; if ...: ... + ;; else ...: + ;; where the second line need not be outdented. + (not (member (python-first-word) + (cdr (assoc start + python-block-pairs))))) + ;; Not sensible to indent to the same level as + ;; previous `return' &c. + (python-close-block-statement-p)) + (push (cons (current-indentation) (python-initial-text)) + levels)) + (while (python-beginning-of-block) + (when (or (not start) + (member (python-first-word) + (cdr (assoc start python-block-pairs)))) + (push (cons (current-indentation) (python-initial-text)) + levels)))))) + (prog1 (or levels (setq levels '((0 . "")))) + (setq python-indent-list levels + python-indent-list-length (length python-indent-list)))))) ;; This is basically what `python-indent-line' would be if we didn't ;; do the cycling. -(defun python-indent-line-1 () - "Subroutine of `python-indent-line'." +(defun python-indent-line-1 (&optional leave) + "Subroutine of `python-indent-line'. +Does non-repeated indentation. LEAVE non-nil means leave +indentation if it is valid, i.e. one of the positions returned by +`python-calculate-indentation'." (let ((target (python-calculate-indentation)) (pos (- (point-max) (point)))) - (if (= target (current-indentation)) + (if (or (= target (current-indentation)) + ;; Maybe keep a valid indentation. + (and leave python-indent-list + (assq (current-indentation) python-indent-list))) (if (< (current-column) (current-indentation)) (back-to-indentation)) (beginning-of-line) @@ -589,29 +696,41 @@ corresponding block opening (or nil)." (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 -from `indent-for-tab-command', i.e. successive TABs do the cycling." +indentations for current line. The cycle is broken by a command +different from `indent-for-tab-command', i.e. successive TABs do +the cycling." (interactive) - ;; Don't do extra work if invoked via `indent-region', for instance. - (if (not (eq this-command 'indent-for-tab-command)) - (python-indent-line-1) - (if (eq last-command this-command) - (if (= 1 python-indent-list-length) - (message "Sole indentation") - (progn (setq python-indent-index (% (1+ python-indent-index) - python-indent-list-length)) - (beginning-of-line) - (delete-horizontal-space) - (indent-to (car (nth python-indent-index python-indent-list))) - (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))))) + (if (and (eq this-command 'indent-for-tab-command) + (eq last-command this-command)) + (if (= 1 python-indent-list-length) + (message "Sole indentation") + (progn (setq python-indent-index + (% (1+ python-indent-index) python-indent-list-length)) + (beginning-of-line) + (delete-horizontal-space) + (indent-to (car (nth python-indent-index python-indent-list))) + (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-index (1- python-indent-list-length)))) + +(defun python-indent-region (start end) + "`indent-region-function' for Python. +Leaves validly-indented lines alone, i.e. doesn't indent to +another valid position." + (save-excursion + (goto-char end) + (setq end (point-marker)) + (goto-char start) + (or (bolp) (forward-line 1)) + (while (< (point) end) + (or (and (bolp) (eolp)) + (python-indent-line-1 t)) + (forward-line 1)) + (move-marker end nil))) (defun python-block-end-p () "Non-nil if this is a line in a statement closing a block, @@ -622,40 +741,41 @@ or a blank line indented to where it would close a block." (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. +;; Fixme: Define {for,back}ward-sexp-function? Maybe skip units like +;; block, statement, depending on context. + (defun python-beginning-of-defun () "`beginning-of-defun-function' for Python. Finds beginning of innermost nested class or method definition. -Returns the name of the definition found at the end, or nil if reached -start of buffer." +Returns the name of the definition found at the end, or nil if +reached start of buffer." (let ((ci (current-indentation)) - (def-re (rx (and line-start (0+ space) (or "def" "class") - (1+ space) - (group (1+ (or word (syntax symbol))))))) - found lep def-line) + (def-re (rx line-start (0+ space) (or "def" "class") (1+ space) + (group (1+ (or word (syntax symbol)))))) + found lep) ;; def-line (if (python-comment-line-p) (setq ci most-positive-fixnum)) (while (and (not (bobp)) (not found)) ;; Treat bol at beginning of function as outside function so ;; that successive C-M-a makes progress backwards. - (setq def-line (looking-at def-re)) + ;;(setq def-line (looking-at def-re)) (unless (bolp) (end-of-line)) (setq lep (line-end-position)) (if (and (re-search-backward def-re nil 'move) ;; Must be less indented or matching top level, or ;; equally indented if we started on a definition line. (let ((in (current-indentation))) - (or (and (zerop ci) (zerop in)) - (= lep (line-end-position)) ; on initial line - (and def-line (= in ci)) - (< in ci))) + (or (and (zerop ci) (zerop in)) + (= lep (line-end-position)) ; on initial line + ;; Not sure why it was like this -- fails in case of + ;; last internal function followed by first + ;; non-def statement of the main body. + ;;(and def-line (= in ci)) + (= in ci) + (< in ci))) (not (python-in-string/comment))) (setq found t))))) @@ -663,7 +783,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 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. @@ -692,8 +812,9 @@ Finds end of innermost nested class or method definition." (python-end-of-block) ;; Count trailing space in defun (but not trailing comments). (skip-syntax-forward " >") - (beginning-of-line)) - ;; Catch pathological case like this, where the beginning-of-defun + (unless (eobp) ; e.g. missing final newline + (beginning-of-line))) + ;; Catch pathological cases like this, where the beginning-of-defun ;; skips to a definition we're not in: ;; if ...: ;; ... @@ -706,26 +827,43 @@ Finds end of innermost nested class or method definition." (defun python-beginning-of-statement () "Go to start of current statement. -Accounts for continuation lines, multi-line strings, and multi-line bracketed -expressions." +Accounts for continuation lines, multi-line strings, and +multi-line bracketed expressions." (beginning-of-line) (python-beginning-of-string) - (catch 'foo - (while (python-continuation-line-p) - (beginning-of-line) - (if (python-backslash-continuation-line-p) + (while (python-continuation-line-p) + (beginning-of-line) + (if (python-backslash-continuation-line-p) + (progn + (forward-line -1) (while (python-backslash-continuation-line-p) - (forward-line -1)) - (python-beginning-of-string) - ;; Skip forward out of nested brackets. - (condition-case () ; beware invalid syntax - (let ((depth (syntax-ppss-depth (syntax-ppss)))) - ;; Beware negative depths. - (if (> depth 0) (backward-up-list depth)) - t) - (error (throw 'foo nil)))))) + (forward-line -1))) + (python-beginning-of-string) + (python-skip-out))) (back-to-indentation)) +(defun python-skip-out (&optional forward syntax) + "Skip out of any nested brackets. +Skip forward if FORWARD is non-nil, else backward. +If SYNTAX is non-nil it is the state returned by `syntax-ppss' at point. +Return non-nil iff skipping was done." + (let ((depth (syntax-ppss-depth (or syntax (syntax-ppss)))) + (forward (if forward -1 1))) + (unless (zerop depth) + (if (> depth 0) + ;; Skip forward out of nested brackets. + (condition-case () ; beware invalid syntax + (progn (backward-up-list (* forward depth)) t) + (error nil)) + ;; Invalid syntax (too many closed brackets). + ;; Skip out of as many as possible. + (let (done) + (while (condition-case () + (progn (backward-up-list forward) + (setq done t)) + (error nil))) + done))))) + (defun python-end-of-statement () "Go to the end of the current statement and return point. Usually this is the start of the next line, but if this is a @@ -745,13 +883,7 @@ On a comment line, go to end of line." (condition-case () ; beware invalid syntax (progn (forward-sexp) t) (error (end-of-line)))) - ((> (syntax-ppss-depth s) 0) - ;; Skip forward out of nested brackets. - (condition-case () ; beware invalid syntax - (progn (backward-up-list - (- (syntax-ppss-depth s))) - t) - (error (end-of-line)))))) + ((python-skip-out t s)))) (end-of-line)) (unless comment (eq ?\\ (char-before)))) ; Line continued? @@ -785,7 +917,8 @@ Return count of statements left to move." (while (and (> count 0) (not (eobp))) (python-end-of-statement) (python-skip-comments/blanks) - (setq count (1- count))) + (unless (eobp) + (setq count (1- count)))) count)) (defun python-beginning-of-block (&optional arg) @@ -802,7 +935,8 @@ Otherwise return non-nil." ((< arg 0) (python-end-of-block (- arg))) (t (let ((point (point))) - (if (python-comment-line-p) + (if (or (python-comment-line-p) + (python-blank-line-p)) (python-skip-comments/blanks t)) (python-beginning-of-statement) (let ((ci (current-indentation))) @@ -830,32 +964,31 @@ Otherwise return non-nil." (defun python-end-of-block (&optional arg) "Go to end of current block. -With numeric arg, do it that many times. If ARG is negative, call -`python-beginning-of-block' instead. -If current statement is in column zero and doesn't open a block, don't -move and return nil. Otherwise return t." +With numeric arg, do it that many times. If ARG is negative, +call `python-beginning-of-block' instead. +If current statement is in column zero and doesn't open a block, +don't move and return nil. Otherwise return t." (interactive "p") (unless arg (setq arg 1)) (if (< arg 0) - (python-beginning-of-block (- arg))) - (while (and (> arg 0) - (let* ((point (point)) - (_ (if (python-comment-line-p) - (python-skip-comments/blanks t))) - (ci (current-indentation)) - (open (python-open-block-statement-p))) - (if (and (zerop ci) (not open)) - (not (goto-char point)) - (catch 'done - (while (zerop (python-next-statement)) - (when (or (and open (<= (current-indentation) ci)) - (< (current-indentation) ci)) - (python-skip-comments/blanks t) - (beginning-of-line 2) - (throw 'done t))) - (not (goto-char point)))))) - (setq arg (1- arg))) - (zerop arg)) + (python-beginning-of-block (- arg)) + (while (and (> arg 0) + (let* ((point (point)) + (_ (if (python-comment-line-p) + (python-skip-comments/blanks t))) + (ci (current-indentation)) + (open (python-open-block-statement-p))) + (if (and (zerop ci) (not open)) + (not (goto-char point)) + (catch 'done + (while (zerop (python-next-statement)) + (when (or (and open (<= (current-indentation) ci)) + (< (current-indentation) ci)) + (python-skip-comments/blanks t) + (beginning-of-line 2) + (throw 'done t))))))) + (setq arg (1- arg))) + (zerop arg))) ;;;; Imenu. @@ -868,14 +1001,23 @@ The nested menus are headed by an item referencing the outer definition; it has a space prepended to the name so that it sorts first with `imenu--sort-by-name' (though, unfortunately, sub-menus precede it)." - (unless (boundp 'python-recursing) ; dynamically bound below - (goto-char (point-min))) ; normal call from Imenu - (let (index-alist ; accumulated value to return - name) + (unless (boundp 'python-recursing) ; dynamically bound below + ;; Normal call from Imenu. + (goto-char (point-min)) + ;; Without this, we can get an infloop if the buffer isn't all + ;; fontified. I guess this is really a bug in syntax.el. OTOH, + ;; _with_ this, imenu doesn't immediately work; I can't figure out + ;; what's going on, but it must be something to do with timers in + ;; font-lock. + ;; This can't be right, especially not when jit-lock is not used. --Stef + ;; (unless (get-text-property (1- (point-max)) 'fontified) + ;; (font-lock-fontify-region (point-min) (point-max))) + ) + (let (index-alist) ; accumulated value to return (while (re-search-forward - (rx (and line-start (0+ space) ; leading space - (or (group "def") (group "class")) ; type - (1+ space) (group (1+ (or word ?_))))) ; name + (rx line-start (0+ space) ; leading space + (or (group "def") (group "class")) ; type + (1+ space) (group (1+ (or word ?_)))) ; name nil t) (unless (python-in-string/comment) (let ((pos (match-beginning 0)) @@ -890,7 +1032,22 @@ precede it)." (progn (push (cons (concat " " name) pos) sublist) (push (cons name sublist) index-alist)) (push (cons name pos) index-alist))))))) - (nreverse index-alist))) + (unless (boundp 'python-recursing) + ;; Look for module variables. + (let (vars) + (goto-char (point-min)) + (while (re-search-forward + (rx line-start (group (1+ (or word ?_))) (0+ space) "=") + nil t) + (unless (python-in-string/comment) + (push (cons (match-string 1) (match-beginning 1)) + vars))) + (setq index-alist (nreverse index-alist)) + (if vars + (push (cons "Module variables" + (nreverse vars)) + index-alist)))) + index-alist)) ;;;; `Electric' commands. @@ -910,20 +1067,26 @@ just insert a single colon." (defun python-backspace (arg) "Maybe delete a level of indentation on the current line. -If not at the end of line's indentation, or on a comment line, just call -`backward-delete-char-untabify'. With ARG, repeat that many times." +Do so if point is at the end of the line's indentation. +Otherwise just call `backward-delete-char-untabify'. +Repeat ARG times." (interactive "*p") (if (or (/= (current-indentation) (current-column)) (bolp) (python-continuation-line-p)) (backward-delete-char-untabify arg) - (let ((indent 0)) - (save-excursion - (while (and (> arg 0) (python-beginning-of-block)) - (setq arg (1- arg))) - (when (zerop arg) - (setq indent (current-indentation)) - (message "Closes %s" (python-initial-text)))) + ;; Look for the largest valid indentation which is smaller than + ;; the current indentation. + (let ((indent 0) + (ci (current-indentation)) + (indents (python-indentation-levels)) + initial) + (dolist (x indents) + (if (< (car x) ci) + (setq indent (max indent (car x))))) + (setq initial (cdr (assq indent indents))) + (if (> (length initial) 0) + (message "Closes %s" initial)) (delete-horizontal-space) (indent-to indent)))) (put 'python-backspace 'delete-selection 'supersede) @@ -931,7 +1094,7 @@ If not at the end of line's indentation, or on a comment line, just call ;;;; pychecker (defcustom python-check-command "pychecker --stdlib" - "*Command used to check a Python file." + "Command used to check a Python file." :type 'string :group 'python) @@ -963,66 +1126,54 @@ See `python-check-command' for the default." ;; Fixme: Make sure we can work with IPython. (defcustom python-python-command "python" - "*Shell command to run Python interpreter. + "Shell command to run Python interpreter. 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'." +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) (defcustom python-jython-command "jython" - "*Shell command to run Jython interpreter. + "Shell command to run Jython interpreter. Any arguments can't contain whitespace." :group 'python :type 'string) (defvar python-command python-python-command "Actual command used to run Python. -May be `python-python-command' or `python-jython-command'. -Additional arguments are added when the command is used by `run-python' -et al.") +May be `python-python-command' or `python-jython-command', possibly +modified by the user. Additional arguments are added when the command +is used by `run-python' et al.") (defvar python-buffer nil - "The current python process buffer." - ;; Fixme: a single process is currently assumed, so that this doc - ;; is misleading. - -;; "*The current python process buffer. -;; To run multiple Python processes, start the first with \\[run-python]. -;; It will be in a buffer named *Python*. Rename that with -;; \\[rename-buffer]. Now start a new process with \\[run-python]. It -;; will be in a new buffer, named *Python*. Switch between the different -;; process buffers with \\[switch-to-buffer]. - -;; Commands that send text from source buffers to Python processes have -;; to choose a process to send to. This is determined by global variable -;; `python-buffer'. Suppose you have three inferior Pythons running: -;; Buffer Process -;; foo python -;; bar python<2> -;; *Python* python<3> -;; If you do a \\[python-send-region-and-go] command on some Python source -;; code, what process does it go to? - -;; - In a process buffer (foo, bar, or *Python*), send it to that process. -;; - In some other buffer (e.g. a source file), send it to the process -;; attached to `python-buffer'. -;; Process selection is done by function `python-proc'. - -;; Whenever \\[run-python] starts a new process, it resets `python-buffer' -;; to be the new process's buffer. If you only run one process, this will -;; do the right thing. If you run multiple processes, you can change -;; `python-buffer' to another process buffer with \\[set-variable]." - ) + "*The current python process buffer. + +Commands that send text from source buffers to Python processes have +to choose a process to send to. This is determined by buffer-local +value of `python-buffer'. If its value in the current buffer, +i.e. both any local value and the default one, is nil, `run-python' +and commands that send to the Python process will start a new process. + +Whenever \\[run-python] starts a new process, it resets the default +value of `python-buffer' to be the new process's buffer and sets the +buffer-local value similarly if the current buffer is in Python mode +or Inferior Python mode, so that source buffer stays associated with a +specific sub-process. + +Use \\[python-set-proc] to set the default value from a buffer with a +local value.") +(make-variable-buffer-local 'python-buffer) (defconst python-compilation-regexp-alist ;; FIXME: maybe these should move to compilation-error-regexp-alist-alist. - `((,(rx (and line-start (1+ (any " \t")) "File \"" - (group (1+ (not (any "\"<")))) ; avoid `<stdin>' &c - "\", line " (group (1+ digit)))) + ;; The first already is (for CAML), but the second isn't. Anyhow, + ;; these are specific to the inferior buffer. -- fx + `((,(rx line-start (1+ (any " \t")) "File \"" + (group (1+ (not (any "\"<")))) ; avoid `<stdin>' &c + "\", line " (group (1+ digit))) 1 2) - (,(rx (and " in file " (group (1+ not-newline)) " on line " - (group (1+ digit)))) + (,(rx " in file " (group (1+ not-newline)) " on line " + (group (1+ digit))) 1 2)) "`compilation-error-regexp-alist' for inferior Python.") @@ -1040,9 +1191,9 @@ et al.") ;; (define-key map "\C-c\C-f" 'python-describe-symbol) map)) -;; Fixme: This should inherit some stuff from python-mode, but I'm not -;; sure how much: at least some keybindings, like C-c C-f; syntax?; -;; font-locking, e.g. for triple-quoted strings? +;; Fixme: This should inherit some stuff from `python-mode', but I'm +;; not sure how much: at least some keybindings, like C-c C-f; +;; syntax?; font-locking, e.g. for triple-quoted strings? (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]. @@ -1050,14 +1201,15 @@ A Python process can be started with \\[run-python]. Hooks `comint-mode-hook' and `inferior-python-mode-hook' are run in that order. -You can send text to the inferior Python process from other buffers containing -Python source. - * `python-switch-to-python' switches the current buffer to the Python +You can send text to the inferior Python process from other buffers +containing Python source. + * \\[python-switch-to-python] switches the current buffer to the Python process buffer. - * `python-send-region' sends the current region to the Python process. - * `python-send-region-and-go' switches to the Python process buffer + * \\[python-send-region] sends the current region to the Python process. + * \\[python-send-region-and-go] switches to the Python process buffer after sending the text. -For running multiple processes in multiple buffers, see `python-buffer'. +For running multiple processes in multiple buffers, see `run-python' and +`python-buffer'. \\{inferior-python-mode-map}" :group 'python @@ -1069,13 +1221,13 @@ For running multiple processes in multiple buffers, see `python-buffer'. ;; Still required by `comint-redirect-send-command', for instance ;; (and we need to match things like `>>> ... >>> '): (set (make-local-variable 'comint-prompt-regexp) - (rx (and line-start (1+ (and (repeat 3 (any ">.")) ?\s))))) + (rx line-start (1+ (and (repeat 3 (any ">.")) " ")))) (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. + "Input matching this regexp is not saved on the history list. Default ignores all inputs of 0, 1, or 2 non-blank characters." :type 'regexp :group 'python) @@ -1098,98 +1250,134 @@ Don't save anything for STR matching `inferior-python-filter-regexp'." (defvar python-preoutput-result nil "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'.") - (defvar python-preoutput-leftover nil) +(defvar python-preoutput-skip-next-prompt nil) ;; 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." (when python-preoutput-leftover (setq s (concat python-preoutput-leftover s)) (setq python-preoutput-leftover nil)) - (cond ((and (string-match (rx (and string-start (repeat 3 (any ".>")) - " " string-end)) - 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)) - "") - ((string-match ".*\n" s) - s) - ((or (eq t (compare-strings s nil nil "_emacs_ok\n" nil (length s))) - (let ((end (min (length "_emacs_out ") (length s)))) - (eq t (compare-strings s nil end "_emacs_out " nil end)))) - (setq python-preoutput-leftover s) - "") - (t s))) + (let ((start 0) + (res "")) + ;; First process whole lines. + (while (string-match "\n" s start) + (let ((line (substring s start (setq start (match-end 0))))) + ;; Skip prompt if needed. + (when (and python-preoutput-skip-next-prompt + (string-match comint-prompt-regexp line)) + (setq python-preoutput-skip-next-prompt nil) + (setq line (substring line (match-end 0)))) + ;; Recognize special _emacs_out lines. + (if (and (string-match "\\`_emacs_out \\(.*\\)\n\\'" line) + (local-variable-p 'python-preoutput-result)) + (progn + (setq python-preoutput-result (match-string 1 line)) + (set (make-local-variable 'python-preoutput-skip-next-prompt) t)) + (setq res (concat res line))))) + ;; Then process the remaining partial line. + (unless (zerop start) (setq s (substring s start))) + (cond ((and (string-match comint-prompt-regexp s) + ;; Drop this prompt if it follows an _emacs_out... + (or python-preoutput-skip-next-prompt + ;; ... or if it's not gonna be inserted at BOL. + ;; Maybe we could be more selective here. + (if (zerop (length res)) + (not (bolp)) + (string-match res ".\\'")))) + ;; The need for this seems to be system-dependent: + ;; What is this all about, exactly? --Stef + ;; (if (and (eq ?. (aref s 0))) + ;; (accept-process-output (get-buffer-process (current-buffer)) 1)) + (setq python-preoutput-skip-next-prompt nil) + res) + ((let ((end (min (length "_emacs_out ") (length s)))) + (eq t (compare-strings s nil end "_emacs_out " nil end))) + ;; The leftover string is a prefix of _emacs_out so we don't know + ;; yet whether it's an _emacs_out or something else: wait until we + ;; get more output so we can resolve this ambiguity. + (set (make-local-variable 'python-preoutput-leftover) s) + res) + (t (concat res s))))) + +(autoload 'comint-check-proc "comint") ;;;###autoload -(defun run-python (&optional cmd noshow) +(defun run-python (&optional cmd noshow new) "Run an inferior Python process, input and output via buffer *Python*. 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 `python-command'); `-i' etc. args will be added -to this as appropriate. Runs the hook `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))) + +Normally, if there is a process already running in `python-buffer', +switch to 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. A new process is started if: +one isn't running attached to `python-buffer', or interactively the +default `python-command', or argument NEW is non-nil. See also the +documentation for `python-buffer'. + +Runs the hook `inferior-python-mode-hook' \(after the +`comint-mode-hook' is run). \(Type \\[describe-mode] in the process +buffer for a list of commands.)" + (interactive (if current-prefix-arg + (list (read-string "Run Python: " python-command) nil t) + (list 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. - (unless (comint-check-proc python-buffer) - (let* ((cmdlist (append (python-args-to-list cmd) '("-i"))) - (path (getenv "PYTHONPATH")) - (process-environment ; to import emacs.py - (cons (concat "PYTHONPATH=" data-directory - (if path (concat ":" path))) - process-environment))) - (set-buffer (apply 'make-comint "Python" (car cmdlist) nil - (cdr cmdlist))) - (setq python-buffer (buffer-name))) - (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. 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))) + (when (or new (not (comint-check-proc python-buffer))) + (save-current-buffer + (let* ((cmdlist (append (python-args-to-list cmd) '("-i"))) + (path (getenv "PYTHONPATH")) + (process-environment ; to import emacs.py + (cons (concat "PYTHONPATH=" data-directory + (if path (concat ":" path))) + process-environment))) + (set-buffer (apply 'make-comint-in-buffer "Python" + (generate-new-buffer "*Python*") + (car cmdlist) nil (cdr cmdlist))) + (setq-default python-buffer (current-buffer)) + (setq python-buffer (current-buffer))) + (accept-process-output (get-buffer-process python-buffer) 5) + (inferior-python-mode))) + (if (derived-mode-p 'python-mode) + (setq python-buffer (default-value 'python-buffer))) ; buffer-local + ;; Load function definitions 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. 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. + ;; Ensure we're at a prompt before doing anything else. + (python-send-receive "import emacs; print '_emacs_out ()'") + ;; Without this, help output goes into the inferior python buffer if + ;; the process isn't already running. + (sit-for 1 t) ;Should we use accept-process-output instead? --Stef + (unless noshow (pop-to-buffer python-buffer t))) ;; 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 +;; 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. +;; be the place to check it, e.g. we might actually want to send +;; commands having set up such a state. (defun python-send-command (command) - "Like `python-send-string' but resets `compilation-minor-mode'." - (goto-char (point-max)) + "Like `python-send-string' but resets `compilation-shell-minor-mode'. +COMMAND should be a single statement." + (assert (not (string-match "\n" command))) (let ((end (marker-position (process-mark (python-proc))))) + (with-current-buffer python-buffer (goto-char (point-max))) (compilation-forget-errors) - (python-send-string command) - (set-marker compilation-parsing-end end) - (setq compilation-last-buffer (current-buffer)))) + ;; Must wait until this has completed before re-setting variables below. + (python-send-receive (concat command "; print '_emacs_out ()'")) + (with-current-buffer python-buffer + (set-marker compilation-parsing-end end) + (setq compilation-last-buffer (current-buffer))))) (defun python-send-region (start end) "Send the region to the inferior Python process." @@ -1202,8 +1390,8 @@ to this as appropriate. Runs the hook `inferior-python-mode-hook' ;; 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. + ;; The `compilation-shell-minor-mode' parsing takes care of relating + ;; the reference to the temporary file to the source. ;; ;; Fixme: Write a `coding' header to the temp file if the region is ;; non-ASCII. @@ -1220,18 +1408,22 @@ to this as appropriate. Runs the hook `inferior-python-mode-hook' (set-marker orig-start (line-beginning-position 0))) (write-region "if True:\n" nil f nil 'nomsg)) (write-region start end f t 'nomsg) - (with-current-buffer (process-buffer (python-proc)) ;Runs python if needed. - (python-send-command command) + (python-send-command command) + (with-current-buffer (process-buffer (python-proc)) ;; Tell compile.el to redirect error locations in file `f' to ;; positions past marker `orig-start'. It has to be done *after* - ;; python-send-command's call to compilation-forget-errors. + ;; `python-send-command''s call to `compilation-forget-errors'. (compilation-fake-loc orig-start f)))) (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) "\n\n")) + (comint-send-string (python-proc) + ;; If the string is single-line or if it ends with \n, + ;; only add a single \n, otherwise add 2, so as to + ;; make sure we terminate the multiline instruction. + (if (string-match "\n.+\\'" string) "\n\n" "\n"))) (defun python-send-buffer () "Send the current buffer to the inferior Python process." @@ -1247,10 +1439,10 @@ to this as appropriate. Runs the hook `inferior-python-mode-hook' (progn (end-of-defun) (point))))) (defun python-switch-to-python (eob-p) - "Switch to the Python process buffer. + "Switch to the Python process buffer, maybe starting new process. With prefix arg, position cursor at end of buffer." (interactive "P") - (pop-to-buffer (process-buffer (python-proc))) ;Runs python if needed. + (pop-to-buffer (process-buffer (python-proc)) t) ;Runs python if needed. (when eob-p (push-mark) (goto-char (point-max)))) @@ -1263,10 +1455,10 @@ Then switch to the process buffer." (python-switch-to-python t)) (defcustom python-source-modes '(python-mode jython-mode) - "*Used to determine if a buffer contains Python source code. -If it's loaded into a buffer that is in one of these major modes, it's -considered a Python source file by `python-load-file'. -Used by these commands to determine defaults." + "Used to determine if a buffer contains Python source code. +If a file is loaded into a buffer that is in one of these major modes, +it is considered Python source by `python-load-file', which uses the +value to determine defaults." :type '(repeat function) :group 'python) @@ -1274,6 +1466,8 @@ Used by these commands to determine defaults." "Caches (directory . file) pair used in the last `python-load-file' command. Used for determining the default in the next one.") +(autoload 'comint-get-source "comint") + (defun python-load-file (file-name) "Load a Python file FILE-NAME into the inferior Python process. If the file has extension `.py' import or reload it as a module. @@ -1297,17 +1491,27 @@ module-qualified names." (format "execfile(%S)" file-name))) (message "%s loaded" file-name))) -;; 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'. 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)))) + ;; Fixme: Maybe should look for another active process if there + ;; isn't one for `python-buffer'. + (unless (comint-check-proc python-buffer) + (run-python nil t)) + (get-buffer-process (or (if (derived-mode-p 'inferior-python-mode) + (current-buffer) + python-buffer)))) + +(defun python-set-proc () + "Set the default value of `python-buffer' to correspond to this buffer. +If the current buffer has a local value of `python-buffer', set the +default (global) value to that. The associated Python process is +the one that gets input from \\[python-send-region] et al when used +in a buffer that doesn't have a local value of `python-buffer'." + (interactive) + (if (local-variable-p 'python-buffer) + (setq-default python-buffer python-buffer) + (error "No local value of `python-buffer'"))) ;;;; Context-sensitive help. @@ -1322,16 +1526,22 @@ Otherwise inherits from `python-mode-syntax-table'.") (defvar view-return-to-alist) (eval-when-compile (autoload 'help-buffer "help-fns")) +(defvar python-imports) ; forward declaration + ;; Fixme: Should this actually be used instead of info-look, i.e. be -;; bound to C-h S? Can we use other pydoc stuff before python 2.2? +;; bound to C-h S? [Probably not, since info-look may work in cases +;; where this doesn't.] (defun python-describe-symbol (symbol) "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'." +Symbol may be anything recognized by the interpreter's `help' +command -- e.g. `CALLS' -- not just variables in scope in the +interpreter. This only works for Python version 2.2 or newer +since earlier interpreters don't support `help'. + +In some cases where this doesn't find documentation, \\[info-lookup-symbol] +will." ;; Note that we do this in the inferior process, not a separate one, to ;; ensure the environment is appropriate. (interactive @@ -1343,53 +1553,65 @@ don't support `help'." "Describe symbol: ") nil nil symbol)))) (if (equal symbol "") (error "No symbol")) - (let* ((func `(lambda () - (comint-redirect-send-command - (format "emacs.ehelp(%S, globals(), locals())\n" ,symbol) - "*Help*" nil)))) - ;; Ensure we have a suitable help buffer. - ;; 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)))))) - (help-setup-xref (list 'python-describe-symbol symbol) (interactive-p)) - (with-output-to-temp-buffer (help-buffer) - (with-current-buffer standard-output - (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)) - (setq python-preoutput-continuation func) - (run-python nil t)))) + ;; Ensure we have a suitable help buffer. + ;; 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-buffer) + (with-current-buffer standard-output + ;; Fixme: Is this actually useful? + (help-setup-xref (list 'python-describe-symbol symbol) (interactive-p)) + (set (make-local-variable 'comint-redirect-subvert-readonly) t) + (print-help-return-message)))) + (comint-redirect-send-command-to-process (format "emacs.ehelp(%S, %s)" + symbol python-imports) + "*Help*" (python-proc) nil nil)) (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)." +The result is what follows `_emacs_out' in the output." + (python-send-string string) (let ((proc (python-proc))) - (python-send-string string) - (setq python-preoutput-result nil) - (while (progn - (accept-process-output proc 5) - python-preoutput-leftover)) - 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? + (with-current-buffer (process-buffer proc) + (set (make-local-variable 'python-preoutput-result) nil) + (while (progn + (accept-process-output proc 5) + (null python-preoutput-result))) + (prog1 python-preoutput-result + (kill-local-variable 'python-preoutput-result))))) + +;; Fixme: 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." +Only works when point is in a function name, not its arg list, for +instance. Assumes an inferior Python is running." (let ((symbol (with-syntax-table python-dotty-syntax-table (current-word)))) - (when symbol - (python-send-receive (format "emacs.eargs(%S)" symbol))))) + ;; First try the symbol we're on. + (or (and symbol + (python-send-receive (format "emacs.eargs(%S, %s)" + symbol python-imports))) + ;; Try moving to symbol before enclosing parens. + (let ((s (syntax-ppss))) + (unless (zerop (car s)) + (when (eq ?\( (char-after (nth 1 s))) + (save-excursion + (goto-char (nth 1 s)) + (skip-syntax-backward "-") + (let ((point (point))) + (skip-chars-backward "a-zA-Z._") + (if (< (point) point) + (python-send-receive + (format "emacs.eargs(%S, %s)" + (buffer-substring-no-properties (point) point) + python-imports))))))))))) ;;;; Info-look functionality. @@ -1443,7 +1665,7 @@ Used with `eval-after-load'." ("(python-lib)Miscellaneous Index" nil "")))))) (eval-after-load "info-look" '(python-after-info-look)) -;;;; Miscellancy. +;;;; Miscellany. (defcustom python-jython-packages '("java" "javax" "org" "com") "Packages implying `jython-mode'. @@ -1473,8 +1695,8 @@ The criterion is either a match for `jython-mode' via (jython-mode) (if (catch 'done (while (re-search-forward - (rx (and line-start (or "import" "from") (1+ space) - (group (1+ (not (any " \t\n.")))))) + (rx line-start (or "import" "from") (1+ space) + (group (1+ (not (any " \t\n."))))) (+ (point-min) 10000) ; Probably not worth customizing. t) (if (member (match-string 1) python-jython-packages) @@ -1562,7 +1784,7 @@ END lie." "`outline-level' function for Python mode. The level is the number of `python-indent' steps of indentation of current line." - (/ (current-indentation) python-indent)) + (1+ (/ (current-indentation) python-indent))) ;; Fixme: Consider top-level assignments, imports, &c. (defun python-current-defun () @@ -1577,10 +1799,8 @@ of current line." (python-beginning-of-block) (end-of-line) (beginning-of-defun) - (if (looking-at (rx (and (0+ space) (or "def" "class") (1+ space) - (group (1+ (or word (syntax symbol)))) - ;; Greediness makes this unnecessary? --Stef - symbol-end))) + (if (looking-at (rx (0+ space) (or "def" "class") (1+ space) + (group (1+ (or word (syntax symbol)))))) (push (match-string 1) accum))) (if accum (mapconcat 'identity accum "."))))) @@ -1593,17 +1813,68 @@ Uses `python-beginning-of-block', `python-end-of-block'." (push-mark (point) nil t) (python-end-of-block) (exchange-point-and-mark)) + +;; Fixme: Provide a find-function-like command to find source of a +;; definition (separate from BicycleRepairMan). Complicated by +;; finding the right qualified name. ;;;; Completion. +(defvar python-imports nil + "String of top-level import statements updated by `python-find-imports'.") +(make-variable-buffer-local 'python-imports) + +;; Fixme: Should font-lock try to run this when it deals with an import? +;; Maybe not a good idea if it gets run multiple times when the +;; statement is being edited, and is more likely to end up with +;; something syntactically incorrect. +;; However, what we should do is to trundle up the block tree from point +;; to extract imports that appear to be in scope, and add those. +(defun python-find-imports () + "Find top-level imports, updating `python-imports'." + (interactive) + (save-excursion + (let (lines) + (goto-char (point-min)) + (while (re-search-forward "^import\\>\\|^from\\>" nil t) + (unless (syntax-ppss-context (syntax-ppss)) + (push (buffer-substring (line-beginning-position) + (line-beginning-position 2)) + lines))) + (setq python-imports + (if lines + (apply #'concat +;; This is probably best left out since you're unlikely to need the +;; doc for a function in the buffer and the import will lose if the +;; Python sub-process' working directory isn't the same as the +;; buffer's. +;; (if buffer-file-name +;; (concat +;; "import " +;; (file-name-sans-extension +;; (file-name-nondirectory buffer-file-name)))) + (nreverse lines)) + "None")) + (when lines + (set-text-properties 0 (length python-imports) nil python-imports) + ;; The output ends up in the wrong place if the string we + ;; send contains newlines (from the imports). + (setq python-imports + (replace-regexp-in-string "\n" "\\n" + (format "%S" python-imports) t t)))))) + +;; Fixme: This fails the first time if the sub-process isn't already +;; running. Presumably a timing issue with i/o to the process. (defun python-symbol-completions (symbol) "Return a list of completions of the string SYMBOL from Python process. -The list is sorted." +The list is sorted. +Uses `python-imports' to load modules against which to complete." (when symbol (let ((completions (condition-case () - (car (read-from-string (python-send-receive - (format "emacs.complete(%S)" symbol)))) + (car (read-from-string + (python-send-receive + (format "emacs.complete(%S,%s)" symbol python-imports)))) (error nil)))) (sort ;; We can get duplicates from the above -- don't know why. @@ -1615,15 +1886,12 @@ The list is sorted." (let ((end (point)) (start (save-excursion (and (re-search-backward - (rx (and (or buffer-start (regexp "[^[:alnum:]._]")) - (group (1+ (regexp "[[:alnum:]._]"))) - point)) + (rx (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." @@ -1658,11 +1926,9 @@ Repeating the command scrolls the completion window." (display-completion-list completions symbol)) (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 + (when (derived-mode-p 'python-mode) ; though we only add it locally (unless old (let ((symbol (python-partial-symbol))) (he-init-string (- (point) (length symbol)) (point)) @@ -1680,16 +1946,212 @@ Repeating the command scrolls the completion window." (if old (he-reset-string)) nil))) +;;;; FFAP support + +(defun python-module-path (module) + "Function for `ffap-alist' to return path to MODULE." + (python-send-receive (format "emacs.modpath (%S)" module))) + +(eval-after-load "ffap" + '(push '(python-mode . python-module-path) ffap-alist)) + +;;;; Skeletons + +(defvar python-skeletons nil + "Alist of named skeletons for Python mode. +Elements are of the form (NAME . EXPANDER-FUNCTION).") + +(defvar python-mode-abbrev-table nil + "Abbrev table for Python mode. +The default contents correspond to the elements of `python-skeletons'.") +(define-abbrev-table 'python-mode-abbrev-table ()) + +(eval-when-compile + ;; Define a user-level skeleton and add it to `python-skeletons' and + ;; the abbrev table. +(defmacro def-python-skeleton (name &rest elements) + (let* ((name (symbol-name name)) + (function (intern (concat "python-insert-" name)))) + `(progn + (add-to-list 'python-skeletons ',(cons name function)) + (define-abbrev python-mode-abbrev-table ,name "" ',function nil t) + (define-skeleton ,function + ,(format "Insert Python \"%s\" template." name) + ,@elements))))) +(put 'def-python-skeleton 'lisp-indent-function 2) + +;; From `skeleton-further-elements': +;; `<': outdent a level; +;; `^': delete indentation on current line and also previous newline. +;; Not quote like `delete-indentation'. Assumes point is at +;; beginning of indentation. + +(def-python-skeleton if + "Condition: " + "if " str ":" \n + > _ \n + ("other condition, %s: " + < ; Avoid wrong indentation after block opening. + "elif " str ":" \n + > _ \n nil) + (python-else) | ^) + +(define-skeleton python-else + "Auxiliary skeleton." + nil + (unless (eq ?y (read-char "Add `else' clause? (y for yes or RET for no) ")) + (signal 'quit t)) + < "else:" \n + > _ \n) + +(def-python-skeleton while + "Condition: " + "while " str ":" \n + > _ \n + (python-else) | ^) + +(def-python-skeleton for + "Target, %s: " + "for " str " in " (skeleton-read "Expression, %s: ") ":" \n + > _ \n + (python-else) | ^) + +(def-python-skeleton try/except + nil + "try:" \n + > _ \n + ("Exception, %s: " + < "except " str (python-target) ":" \n + > _ \n nil) + < "except:" \n + > _ \n + (python-else) | ^) + +(define-skeleton python-target + "Auxiliary skeleton." + "Target, %s: " ", " str | -2) + +(def-python-skeleton try/finally + nil + "try:" \n + > _ \n + < "finally:" \n + > _ \n) + +(def-python-skeleton def + "Name: " + "def " str " (" ("Parameter, %s: " (unless (equal ?\( (char-before)) ", ") + str) "):" \n + "\"\"\"" @ " \"\"\"" \n ; Fixme: syntaxification wrong for """""" + > _ \n) + +(def-python-skeleton class + "Name: " + "class " str " (" ("Inheritance, %s: " + (unless (equal ?\( (char-before)) ", ") + str) + & ")" | -2 ; close list or remove opening + ":" \n + "\"\"\"" @ " \"\"\"" \n + > _ \n) + +(defvar python-default-template "if" + "Default template to expand by `python-insert-template'. +Updated on each expansion.") + +(defun python-expand-template (name) + "Expand template named NAME. +Interactively, prompt for the name with completion." + (interactive + (list (completing-read (format "Template to expand (default %s): " + python-default-template) + python-skeletons nil t))) + (if (equal "" name) + (setq name python-default-template) + (setq python-default-template name)) + (let ((func (cdr (assoc name python-skeletons)))) + (if func + (funcall func) + (error "Undefined template: %s" name)))) + +;;;; Bicycle Repair Man support + +(autoload 'pymacs-load "pymacs" nil t) +(autoload 'brm-init "bikemacs") + +;; I'm not sure how useful BRM really is, and it's certainly dangerous +;; the way it modifies files outside Emacs... Also note that the +;; current BRM loses with tabs used for indentation -- I submitted a +;; fix <URL:http://www.loveshack.ukfsn.org/emacs/bikeemacs.py.diff>. +(defun python-setup-brm () + "Set up Bicycle Repair Man refactoring tool (if available). + +Note that the `refactoring' features change files independently of +Emacs and may modify and save the contents of the current buffer +without confirmation." + (interactive) + (condition-case data + (unless (fboundp 'brm-rename) + (pymacs-load "bikeemacs" "brm-") ; first line of normal recipe + (let ((py-mode-map (make-sparse-keymap)) ; it assumes this + (features (cons 'python-mode features))) ; and requires this + (brm-init)) ; second line of normal recipe + (remove-hook 'python-mode-hook ; undo this from `brm-init' + '(lambda () (easy-menu-add brm-menu))) + (easy-menu-define + python-brm-menu python-mode-map + "Bicycle Repair Man" + '("BicycleRepairMan" + :help "Interface to navigation and refactoring tool" + "Queries" + ["Find References" brm-find-references + :help "Find references to name at point in compilation buffer"] + ["Find Definition" brm-find-definition + :help "Find definition of name at point"] + "-" + "Refactoring" + ["Rename" brm-rename + :help "Replace name at point with a new name everywhere"] + ["Extract Method" brm-extract-method + :active (and mark-active (not buffer-read-only)) + :help "Replace statements in region with a method"] + ["Extract Local Variable" brm-extract-local-variable + :active (and mark-active (not buffer-read-only)) + :help "Replace expression in region with an assignment"] + ["Inline Local Variable" brm-inline-local-variable + :help + "Substitute uses of variable at point with its definition"] + ;; Fixme: Should check for anything to revert. + ["Undo Last Refactoring" brm-undo :help ""]))) + (error (error "Bicyclerepairman setup failed: %s" data)))) + ;;;; Modes. (defvar outline-heading-end-regexp) (defvar eldoc-documentation-function) +;; Stuff to allow expanding abbrevs with non-word constituents. +(defun python-abbrev-pc-hook () + "Set the syntax table before possibly expanding abbrevs." + (remove-hook 'post-command-hook 'python-abbrev-pc-hook t) + (set-syntax-table python-mode-syntax-table)) + +(defvar python-abbrev-syntax-table + (copy-syntax-table python-mode-syntax-table) + "Syntax table used when expanding abbrevs.") + +(defun python-pea-hook () + "Reset the syntax table after possibly expanding abbrevs." + (set-syntax-table python-abbrev-syntax-table) + (add-hook 'post-command-hook 'python-abbrev-pc-hook nil t)) +(modify-syntax-entry ?/ "w" python-abbrev-syntax-table) + +(defvar python-mode-running) ;Dynamically scoped var. + ;;;###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. +Font Lock mode is currently required for correct parsing of the source. See also `jython-mode', which is actually invoked if the buffer appears to contain Jython code. See also `run-python' and associated Python mode commands for running Python under Emacs. @@ -1703,21 +2165,27 @@ 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 deletes a level of indentation to close the current block; otherwise it -deletes a charcter backward. TAB indents the current line relative to +deletes a character backward. TAB indents the current line relative to 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 +\\[fill-paragraph] fills comments and multi-line 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. +lines count as headers. Symbol completion is available in the +same way as in the Python shell using the `rlcompleter' module +and this is added to the Hippie Expand functions locally if +Hippie Expand mode is turned on. Completion of symbols of the +form x.y only works if the components are literal +module/attribute names, not variables. An abbrev table is set up +with skeleton expansions for compound statement templates. \\{python-mode-map}" :group 'python (set (make-local-variable 'font-lock-defaults) - '(python-font-lock-keywords nil nil ((?_ . "w")) nil + '(python-font-lock-keywords nil nil nil nil (font-lock-syntactic-keywords . python-font-lock-syntactic-keywords) ;; This probably isn't worth it. @@ -1726,15 +2194,17 @@ lines count as headers. )) (set (make-local-variable 'parse-sexp-lookup-properties) t) (set (make-local-variable 'comment-start) "# ") - (set (make-local-variable 'comment-indent-function) #'python-comment-indent) (set (make-local-variable 'indent-line-function) #'python-indent-line) + (set (make-local-variable 'indent-region-function) #'python-indent-region) (set (make-local-variable 'paragraph-start) "\\s-*$") (set (make-local-variable 'fill-paragraph-function) 'python-fill-paragraph) (set (make-local-variable 'require-final-newline) mode-require-final-newline) (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) + (rx (* space) (or "class" "def" "elif" "else" "except" "finally" + "for" "if" "try" "while") + symbol-end)) (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) @@ -1746,30 +2216,45 @@ lines count as headers. (set (make-local-variable 'eldoc-documentation-function) #'python-eldoc-function) (add-hook 'eldoc-mode-hook - '(lambda () (run-python nil t)) nil t) ; need it running - (unless (assoc 'python-mode hs-special-modes-alist) - (setq - hs-special-modes-alist - (cons (list - 'python-mode "^\\s-*def\\>" nil "#" - (lambda (arg)(python-end-of-defun)(skip-chars-backward " \t\n")) - nil) - hs-special-modes-alist))) + (lambda () (run-python nil t)) ; need it running + nil t) + ;; Fixme: should be in hideshow. This seems to be of limited use + ;; since it isn't (can't be) indentation-based. Also hide-level + ;; doesn't seem to work properly. + (add-to-list 'hs-special-modes-alist + `(python-mode "^\\s-*def\\>" nil "#" + ,(lambda (arg) + (python-end-of-defun) + (skip-chars-backward " \t\n")) + nil)) + (set (make-local-variable 'skeleton-further-elements) + '((< '(backward-delete-char-untabify (min python-indent + (current-column)))) + (^ '(- (1+ (current-indentation)))))) + (add-hook 'pre-abbrev-expand-hook 'python-pea-hook nil t) (if (featurep 'hippie-exp) (set (make-local-variable 'hippie-expand-try-functions-list) (cons 'python-try-complete hippie-expand-try-functions-list))) + ;; Python defines TABs as being 8-char wide. + (set (make-local-variable 'tab-width) 8) (when python-guess-indent (python-guess-indent)) + ;; Let's make it harder for the user to shoot himself in the foot. + (unless (= tab-width python-indent) + (setq indent-tabs-mode nil)) (set (make-local-variable 'python-command) python-python-command) + (python-find-imports) (unless (boundp 'python-mode-running) ; kill the recursion from jython-mode (let ((python-mode-running t)) (python-maybe-jython)))) (custom-add-option 'python-mode-hook 'imenu-add-menubar-index) (custom-add-option 'python-mode-hook - '(lambda () - "Turn on Indent Tabs mode." - (set (make-local-variable 'indent-tabs-mode) t))) + (lambda () + "Turn off Indent Tabs mode." + (set (make-local-variable 'indent-tabs-mode) nil))) (custom-add-option 'python-mode-hook 'turn-on-eldoc-mode) +(custom-add-option 'python-mode-hook 'abbrev-mode) +(custom-add-option 'python-mode-hook 'python-setup-brm) ;;;###autoload (define-derived-mode jython-mode python-mode "Jython" @@ -1780,5 +2265,6 @@ Runs `jython-mode-hook' after `python-mode-hook'." (set (make-local-variable 'python-command) python-jython-command)) (provide 'python) +(provide 'python-21) ;; arch-tag: 6fce1d99-a704-4de9-ba19-c6e4912b0554 ;;; python.el ends here diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el index 6098c8be067..f828c36917b 100644 --- a/lisp/progmodes/sh-script.el +++ b/lisp/progmodes/sh-script.el @@ -986,7 +986,9 @@ subshells can nest." ;; FIXME: This can (and often does) match multiple lines, yet it makes no ;; effort to handle multiline cases correctly, so it ends up being ;; rather flakey. - (when (re-search-forward "\"\\(?:\\(?:.\\|\n\\)*?[^\\]\\(?:\\\\\\\\\\)*\\)??\\(\\$(\\|`\\)" limit t) + (when (and (re-search-forward "\"\\(?:\\(?:.\\|\n\\)*?[^\\]\\(?:\\\\\\\\\\)*\\)??\\(\\$(\\|`\\)" limit t) + ;; Make sure the " we matched is an opening quote. + (eq ?\" (nth 3 (syntax-ppss)))) ;; bingo we have a $( or a ` inside a "" (let ((char (char-after (point))) (continue t) @@ -1081,9 +1083,6 @@ This is used to flag quote characters in subshell constructs inside strings ("\\(\\\\\\)'" 1 ,sh-st-punc) ;; Make sure $@ and @? are correctly recognized as sexps. ("\\$\\([?@]\\)" 1 ,sh-st-symbol) - ;; highlight (possibly nested) subshells inside "" quoted regions correctly. - (sh-quoted-subshell - (1 (sh-apply-quoted-subshell) t t)) ;; Find HEREDOC starters and add a corresponding rule for the ender. (sh-font-lock-here-doc (2 (sh-font-lock-open-heredoc @@ -1093,7 +1092,11 @@ This is used to flag quote characters in subshell constructs inside strings (and (match-beginning 3) (/= (match-beginning 3) (match-end 3)))) nil t)) ;; Distinguish the special close-paren in `case'. - (")" 0 (sh-font-lock-paren (match-beginning 0))))) + (")" 0 (sh-font-lock-paren (match-beginning 0))) + ;; highlight (possibly nested) subshells inside "" quoted regions correctly. + ;; This should be at the very end because it uses syntax-ppss. + (sh-quoted-subshell + (1 (sh-apply-quoted-subshell) t t)))) (defun sh-font-lock-syntactic-face-function (state) (let ((q (nth 3 state))) diff --git a/lisp/progmodes/vhdl-mode.el b/lisp/progmodes/vhdl-mode.el index 987b37cf2c2..cf887394e6b 100644 --- a/lisp/progmodes/vhdl-mode.el +++ b/lisp/progmodes/vhdl-mode.el @@ -14112,8 +14112,8 @@ if required." (defun vhdl-speedbar-display-directory (directory depth &optional rescan) "Display directory and hierarchy information in speedbar." (setq vhdl-speedbar-show-projects nil) - (setq speedbar-ignored-path-regexp - (speedbar-extension-list-to-regex speedbar-ignored-path-expressions)) + (setq speedbar-ignored-directory-regexp + (speedbar-extension-list-to-regex speedbar-ignored-directory-expressions)) (setq directory (abbreviate-file-name (file-name-as-directory directory))) (setq speedbar-last-selected-file nil) (speedbar-with-writable @@ -14133,7 +14133,7 @@ if required." (defun vhdl-speedbar-display-projects (project depth &optional rescan) "Display projects and hierarchy information in speedbar." (setq vhdl-speedbar-show-projects t) - (setq speedbar-ignored-path-regexp ".") + (setq speedbar-ignored-directory-regexp ".") (setq speedbar-last-selected-file nil) (setq vhdl-speedbar-last-selected-project nil) (speedbar-with-writable |