Implemented internal python shell.
[bpt/emacs.git] / lisp / progmodes / python.el
index 5262fa1..98f37fc 100644 (file)
@@ -1,4 +1,4 @@
-;;; python.el -- Python's flying circus support for Emacs
+;;; python.el --- Python's flying circus support for Emacs
 
 ;; Copyright (C) 2010, 2011 Free Software Foundation, Inc.
 
 
 ;; Ordered by priority:
 
-;; Better decorator support for beginning of defun
-
-;; Review code and cleanup
+;; Give a better interface for virtualenv support in interactive
+;; shells
 
 ;;; Code:
 
     (,(rx symbol-start "class" (1+ space) (group (1+ (or word ?_))))
      (1 font-lock-type-face))
     ;; Constants
-    (,(rx symbol-start (group "None" symbol-end))
-     (1 font-lock-constant-face))
+    (,(rx symbol-start
+          ;; copyright, license, credits, quit, exit are added by the
+          ;; site module and since they are not intended to be used in
+          ;; programs they are not added here either.
+          (or "None" "True" "False" "Ellipsis" "__debug__" "NotImplemented")
+          symbol-end) . font-lock-constant-face)
     ;; Decorators.
     (,(rx line-start (* (any " \t")) (group "@" (1+ (or word ?_))
                                             (0+ "." (1+ (or word ?_)))))
               "FutureWarning" "GeneratorExit" "IOError" "ImportError"
               "ImportWarning" "IndentationError" "IndexError" "KeyError"
               "KeyboardInterrupt" "LookupError" "MemoryError" "NameError"
-              "NotImplemented" "NotImplementedError" "OSError" "OverflowError"
+              "NotImplementedError" "OSError" "OverflowError"
               "PendingDeprecationWarning" "ReferenceError" "RuntimeError"
               "RuntimeWarning" "StandardError" "StopIteration" "SyntaxError"
               "SyntaxWarning" "SystemError" "SystemExit" "TabError" "TypeError"
               "UserWarning" "ValueError" "Warning" "ZeroDivisionError")
           symbol-end) . font-lock-type-face)
     ;; Builtins
-    (,(rx (or line-start (not (any ". \t"))) (* (any " \t")) symbol-start
-         (group
-           (or "_" "__debug__" "__doc__" "__import__" "__name__" "__package__"
-               "abs" "all" "any" "apply" "basestring" "bin" "bool" "buffer"
-               "bytearray" "bytes" "callable" "chr" "classmethod" "cmp" "coerce"
-               "compile" "complex" "copyright" "credits" "delattr" "dict" "dir"
-               "divmod" "enumerate" "eval" "execfile" "exit" "file" "filter"
-               "float" "format" "frozenset" "getattr" "globals" "hasattr" "hash"
-               "help" "hex" "id" "input" "int" "intern" "isinstance" "issubclass"
-               "iter" "len" "license" "list" "locals" "long" "map" "max" "min"
-               "next" "object" "oct" "open" "ord" "pow" "print" "property" "quit"
-               "range" "raw_input" "reduce" "reload" "repr" "reversed" "round"
-               "set" "setattr" "slice" "sorted" "staticmethod" "str" "sum"
-               "super" "tuple" "type" "unichr" "unicode" "vars" "xrange" "zip"
-               "True" "False" "Ellipsis")) symbol-end)
-     (1 font-lock-builtin-face))
+    (,(rx symbol-start
+          (or "_" "__doc__" "__import__" "__name__" "__package__" "abs" "all"
+              "any" "apply" "basestring" "bin" "bool" "buffer" "bytearray"
+              "bytes" "callable" "chr" "classmethod" "cmp" "coerce" "compile"
+              "complex" "delattr" "dict" "dir" "divmod" "enumerate" "eval"
+              "execfile" "file" "filter" "float" "format" "frozenset"
+              "getattr" "globals" "hasattr" "hash" "help" "hex" "id" "input"
+              "int" "intern" "isinstance" "issubclass" "iter" "len" "list"
+              "locals" "long" "map" "max" "min" "next" "object" "oct" "open"
+              "ord" "pow" "print" "property" "range" "raw_input" "reduce"
+              "reload" "repr" "reversed" "round" "set" "setattr" "slice"
+              "sorted" "staticmethod" "str" "sum" "super" "tuple" "type"
+              "unichr" "unicode" "vars" "xrange" "zip")
+          symbol-end) . font-lock-builtin-face)
     ;; asignations
     ;; support for a = b = c = 5
     (,(lambda (limit)
               (set-match-data nil)))))
      (1 font-lock-variable-name-face nil nil))))
 
-;; Fixme: Is there a better way?
 (defconst python-font-lock-syntactic-keywords
+  ;; Make outer chars of matching triple-quote sequences into generic
+  ;; string delimiters.  Fixme: Is there a better way?
   ;; First avoid a sequence preceded by an odd number of backslashes.
-  `((,(rx (not (any ?\\))
-         ?\\ (* (and ?\\ ?\\))
-         (group (syntax string-quote))
-         (backref 1)
-         (group (backref 1)))
-     (2 ,(string-to-syntax "\"")))     ; dummy
-    (,(rx (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))))
-  "Make outer chars of triple-quote strings into generic string delimiters.")
-
-(defun python-quote-syntax (n)
+  `((,(concat "\\(?:\\([RUru]\\)[Rr]?\\|^\\|[^\\]\\(?:\\\\.\\)*\\)" ;Prefix.
+            "\\(?:\\('\\)'\\('\\)\\|\\(?2:\"\\)\"\\(?3:\"\\)\\)")
+     (3 (python-quote-syntax)))))
+
+(defun python-quote-syntax ()
   "Put `syntax-table' property correctly on triple quote.
 Used for syntactic keywords.  N is the match number (1, 2 or 3)."
   ;; Given a triple quote, we have to check the context to know
@@ -431,28 +421,25 @@ Used for syntactic keywords.  N is the match number (1, 2 or 3)."
   ;; x '"""' x """ \"""" x
   (save-excursion
     (goto-char (match-beginning 0))
-    (cond
-     ;; Consider property for the last char if in a fenced string.
-     ((= n 3)
-      (let* ((font-lock-syntactic-keywords nil)
-            (syntax (syntax-ppss)))
-       (when (eq t (nth 3 syntax))     ; after unclosed fence
-         (goto-char (nth 8 syntax))    ; fence position
-         (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)                 ; 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
-      (let ((font-lock-syntactic-keywords nil))
-       (unless (eq 'string (syntax-ppss-context (syntax-ppss)))
-         (eval-when-compile (string-to-syntax "|")))))
-     ;; Otherwise (we're in a non-matching string) the property is
-     ;; nil, which is OK.
-     )))
+    (let ((syntax (save-match-data (syntax-ppss))))
+      (cond
+       ((eq t (nth 3 syntax))           ; after unclosed fence
+        ;; Consider property for the last char if in a fenced string.
+        (goto-char (nth 8 syntax))     ; fence position
+        (skip-chars-forward "uUrR")    ; skip any prefix
+        ;; Is it a matching sequence?
+        (if (eq (char-after) (char-after (match-beginning 2)))
+            (put-text-property (match-beginning 3) (match-end 3)
+                               'syntax-table (string-to-syntax "|"))))
+       ((match-end 1)
+        ;; Consider property for initial char, accounting for prefixes.
+        (put-text-property (match-beginning 1) (match-end 1)
+                           'syntax-table (string-to-syntax "|")))
+       (t
+        ;; Consider property for initial char, accounting for prefixes.
+        (put-text-property (match-beginning 2) (match-end 2)
+                           'syntax-table (string-to-syntax "|"))))
+      )))
 
 (defvar python-mode-syntax-table
   (let ((table (make-syntax-table)))
@@ -1033,6 +1020,9 @@ With negative argument, move backward repeatedly to start of sentence."
   :group 'python
   :safe 'stringp)
 
+(defvar python-shell-internal-buffer-name "Python Internal"
+  "Default buffer name for the Internal Python interpreter.")
+
 (defcustom python-shell-interpreter-args "-i"
   "Default arguments for the Python interpreter."
   :type 'string
@@ -1139,6 +1129,20 @@ in the `same-window-buffer-names' list."
                                             (format "*%s*" process-name)))
     process-name))
 
+(defun python-shell-internal-get-process-name ()
+  "Calculate the appropiate process name for Internal Python process.
+The name is calculated from `python-shell-global-buffer-name' and
+a hash of all relevant global shell settings in order to ensure
+uniqueness for different types of configurations."
+  (format "%s [%s]"
+          python-shell-internal-buffer-name
+          (md5
+           (concat
+            (python-shell-parse-command)
+            (mapconcat #'symbol-value python-shell-setup-codes "")
+            (mapconcat #'indentity python-shell-process-environment "")
+            (mapconcat #'indentity python-shell-exec-path "")))))
+
 (defun python-shell-parse-command ()
   "Calculate the string used to execute the inferior Python process."
   (format "%s %s" python-shell-interpreter python-shell-interpreter-args))
@@ -1235,6 +1239,42 @@ run).
     (pop-to-buffer proc-buffer-name))
   dedicated)
 
+(defun run-python-internal ()
+  "Run an inferior Internal Python process.
+Input and output via buffer named after
+`python-shell-internal-buffer-name' and what
+`python-shell-internal-get-process-name' returns.  This new kind
+of shell is intended to be used for generic communication related
+to defined configurations.  The main difference with global or
+dedicated shells is that these ones are attached to a
+configuration, not a buffer.  This means that can be used for
+example to retrieve the sys.path and other stuff, without messing
+with user shells.  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)
+  (save-excursion
+    (let* ((cmd (python-shell-parse-command))
+           (proc-name (python-shell-internal-get-process-name))
+           (proc-buffer-name (format "*%s*" proc-name))
+           (process-environment
+            (if python-shell-process-environment
+                (python-util-merge 'list python-shell-process-environment
+                                   process-environment 'string=)
+              process-environment))
+           (exec-path
+            (if python-shell-exec-path
+                (python-util-merge 'list python-shell-exec-path
+                                   exec-path 'string=)
+              exec-path)))
+      (when (not (comint-check-proc proc-buffer-name))
+        (let ((cmdlist (split-string-and-unquote cmd)))
+          (set-buffer
+           (apply 'make-comint proc-name (car cmdlist) nil
+                  (cdr cmdlist)))
+          (inferior-python-mode))))))
+
 (defun python-shell-get-process ()
   "Get inferior Python process for current buffer and return it."
   (let* ((dedicated-proc-name (python-shell-get-process-name t))
@@ -1267,6 +1307,13 @@ run).
                             dedicated-proc-buffer-name
                           global-proc-buffer-name))))
 
+(defun python-shell-internal-get-or-create-process ()
+  "Get or create an inferior Internal Python process."
+  (let* ((proc-name (python-shell-internal-get-process-name))
+         (proc-buffer-name (format "*%s*" proc-name)))
+    (run-python-internal)
+    (get-buffer-process proc-buffer-name)))
+
 (defun python-shell-send-string (string &optional process msg)
   "Send STRING to inferior Python PROCESS.
 When MSG is non-nil messages the first line of STRING."
@@ -1320,6 +1367,21 @@ the output."
      (lambda (string) string)
      (butlast (split-string output-buffer "\n")) "\n")))
 
+(defun python-shell-internal-send-string (string)
+  "Send STRING to the Internal Python interpreter.
+Returns the output.  See `python-shell-send-string-no-output'."
+  (python-shell-send-string-no-output
+   ;; Makes this function compatible with the old
+   ;; python-send-receive. (At least for CEDET).
+   (replace-regexp-in-string "_emacs_out +" "" string)
+   (python-shell-internal-get-or-create-process) nil))
+
+(define-obsolete-function-alias
+  'python-send-receive 'python-shell-internal-send-string "23.3"
+  "Send STRING to inferior Python (if any) and return result.
+The result is what follows `_emacs_out' in the output.
+This is a no-op if `python-check-comint-prompt' returns nil.")
+
 (defun python-shell-send-region (start end)
   "Send the region delimited by START and END to inferior Python process."
   (interactive "r")
@@ -2149,7 +2211,8 @@ This function is compatible to be used as
 `add-log-current-defun-function' since it returns nil if point is
 not inside a defun."
   (let ((names '())
-        (min-indent))
+        (min-indent)
+        (first-run t))
     (save-restriction
       (widen)
       (save-excursion
@@ -2157,7 +2220,9 @@ not inside a defun."
         (forward-comment -9999)
         (setq min-indent (current-indentation))
         (while (python-beginning-of-defun-function 1 t)
-          (when (< (current-indentation) min-indent)
+          (when (or (< (current-indentation) min-indent)
+                    first-run)
+            (setq first-run nil)
             (setq min-indent (current-indentation))
             (looking-at python-nav-beginning-of-defun-regexp)
             (setq names (cons
@@ -2348,6 +2413,8 @@ if that value is non-nil."
                              ,(lambda (arg)
                                 (python-end-of-defun-function)) nil))
 
+  (set (make-local-variable 'mode-require-final-newline) t)
+
   (set (make-local-variable 'outline-regexp)
        (python-rx (* space) block-start))
   (set (make-local-variable 'outline-heading-end-regexp) ":\\s-*\n")