cython-mode.el 11.7 KB
Newer Older
Yasuyuki Oka's avatar
Yasuyuki Oka committed
1
;;; cython-mode.el --- Major mode for editing Cython files
Ivan Andrus's avatar
Ivan Andrus committed
2 3 4 5 6 7 8

;;; Commentary:

;; This should work with python-mode.el as well as either the new
;; python.el or the old.

;;; Code:
9

10
;; Load python-mode if available, otherwise use builtin emacs python package
Ivan Andrus's avatar
Ivan Andrus committed
11
(when (not (require 'python-mode nil t))
12
  (require 'python))
Ivan Andrus's avatar
Ivan Andrus committed
13
(eval-when-compile (require 'rx))
14

Ivan Andrus's avatar
Ivan Andrus committed
15
;;;###autoload
16
(add-to-list 'auto-mode-alist '("\\.pyx\\'" . cython-mode))
Ivan Andrus's avatar
Ivan Andrus committed
17
;;;###autoload
18
(add-to-list 'auto-mode-alist '("\\.pxd\\'" . cython-mode))
Ivan Andrus's avatar
Ivan Andrus committed
19
;;;###autoload
20 21 22
(add-to-list 'auto-mode-alist '("\\.pxi\\'" . cython-mode))


Ivan Andrus's avatar
Ivan Andrus committed
23 24 25
(defvar cython-buffer nil
  "Variable pointing to the cython buffer which was compiled.")

26 27 28 29 30 31 32 33
(defun cython-compile ()
  "Compile the file via Cython."
  (interactive)
  (let ((cy-buffer (current-buffer)))
    (with-current-buffer
        (compile compile-command)
      (set (make-local-variable 'cython-buffer) cy-buffer)
      (add-to-list (make-local-variable 'compilation-finish-functions)
34
                   'cython-compilation-finish))))
35 36 37 38 39 40 41 42 43 44 45 46 47 48

(defun cython-compilation-finish (buffer how)
  "Called when Cython compilation finishes."
  ;; XXX could annotate source here
  )

(defvar cython-mode-map
  (let ((map (make-sparse-keymap)))
    ;; Will inherit from `python-mode-map' thanks to define-derived-mode.
    (define-key map "\C-c\C-c" 'cython-compile)
    map)
  "Keymap used in `cython-mode'.")

(defvar cython-font-lock-keywords
49 50 51 52 53 54 55 56 57 58 59 60 61 62
  `(;; ctypedef statement: "ctypedef (...type... alias)?"
    (,(rx
       ;; keyword itself
       symbol-start (group "ctypedef")
       ;; type specifier: at least 1 non-identifier symbol + 1 identifier
       ;; symbol and anything but a comment-starter after that.
       (opt (regexp "[^a-zA-z0-9_\n]+[a-zA-Z0-9_][^#\n]*")
            ;; type alias: an identifier
            symbol-start (group (regexp "[a-zA-Z_]+[a-zA-Z0-9_]*"))
            ;; space-or-comments till the end of the line
            (* space) (opt "#" (* nonl)) line-end))
     (1 font-lock-keyword-face)
     (2 font-lock-type-face nil 'noerror))
    ;; new keywords in Cython language
63
    (,(rx symbol-start
64
          (or "by" "cdef" "cimport" "cpdef"
65
              "extern" "gil" "include" "nogil" "property" "public"
66 67 68
              "readonly" "DEF" "IF" "ELIF" "ELSE"
              "new" "del" "cppclass" "namespace" "const"
              "__stdcall" "__cdecl" "__fastcall" "inline" "api")
69
          symbol-end)
70 71 72 73 74
     . font-lock-keyword-face)
    ;; Question mark won't match at a symbol-end, so 'except?' must be
    ;; special-cased.  It's simpler to handle it separately than weaving it
    ;; into the lengthy list of other keywords.
    (,(rx symbol-start "except?") . font-lock-keyword-face)
75
    ;; C and Python types (highlight as builtins)
76
    (,(rx symbol-start
77 78 79 80 81 82 83 84
          (or
           "object" "dict" "list"
           ;; basic c type names
           "void" "char" "int" "float" "double" "bint"
           ;; longness/signed/constness
           "signed" "unsigned" "long" "short"
           ;; special basic c types
           "size_t" "Py_ssize_t" "Py_UNICODE" "Py_UCS4" "ssize_t" "ptrdiff_t")
85
          symbol-end)
86 87 88
     . font-lock-builtin-face)
    (,(rx symbol-start "NULL" symbol-end)
     . font-lock-constant-face)
89 90
    ;; cdef is used for more than functions, so simply highlighting the next
    ;; word is problematic. struct, enum and property work though.
91 92 93 94 95
    (,(rx symbol-start
          (group (or "struct" "enum" "union"
                     (seq "ctypedef" (+ space "fused"))))
          (+ space) (group (regexp "[a-zA-Z_]+[a-zA-Z0-9_]*")))
     (1 font-lock-keyword-face prepend) (2 font-lock-type-face))
96
    ("\\_<property[ \t]+\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)"
97 98 99
     1 font-lock-function-name-face))
  "Additional font lock keywords for Cython mode.")

100 101 102 103 104 105 106 107 108 109 110 111 112
;;;###autoload
(defgroup cython nil "Major mode for editing and compiling Cython files"
  :group 'languages
  :prefix "cython-"
  :link '(url-link :tag "Homepage" "http://cython.org"))

;;;###autoload
(defcustom cython-default-compile-format "cython -a %s"
  "Format for the default command to compile a Cython file.
It will be passed to `format' with `buffer-file-name' as the only other argument."
  :group 'cython
  :type 'string)

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
;; Some functions defined differently in the different python modes
(defun cython-comment-line-p ()
  "Return non-nil if current line is a comment."
  (save-excursion
    (back-to-indentation)
    (eq ?# (char-after (point)))))

(defun cython-in-string/comment ()
  "Return non-nil if point is in a comment or string."
  (nth 8 (syntax-ppss)))

(defalias 'cython-beginning-of-statement
  (cond
   ;; python-mode.el
   ((fboundp 'py-beginning-of-statement)
    'py-beginning-of-statement)
   ;; old python.el
   ((fboundp 'python-beginning-of-statement)
    'python-beginning-of-statement)
   ;; new python.el
   ((fboundp 'python-nav-beginning-of-statement)
    'python-nav-beginning-of-statement)
   (t (error "Couldn't find implementation for `cython-beginning-of-statement'"))))

(defalias 'cython-beginning-of-block
  (cond
   ;; python-mode.el
   ((fboundp 'py-beginning-of-block)
    'py-beginning-of-block)
   ;; old python.el
   ((fboundp 'python-beginning-of-block)
    'python-beginning-of-block)
   ;; new python.el
   ((fboundp 'python-nav-beginning-of-block)
    'python-nav-beginning-of-block)
   (t (error "Couldn't find implementation for `cython-beginning-of-block'"))))

(defalias 'cython-end-of-statement
  (cond
   ;; python-mode.el
   ((fboundp 'py-end-of-statement)
    'py-end-of-statement)
   ;; old python.el
   ((fboundp 'python-end-of-statement)
    'python-end-of-statement)
   ;; new python.el
   ((fboundp 'python-nav-end-of-statement)
    'python-nav-end-of-statement)
   (t (error "Couldn't find implementation for `cython-end-of-statement'"))))

(defun cython-open-block-statement-p (&optional bos)
  "Return non-nil if statement at point opens a Cython block.
BOS non-nil means point is known to be at beginning of statement."
  (save-excursion
    (unless bos (cython-beginning-of-statement))
    (looking-at (rx (and (or "if" "else" "elif" "while" "for" "def" "cdef" "cpdef"
                             "class" "try" "except" "finally" "with"
                             "EXAMPLES:" "TESTS:" "INPUT:" "OUTPUT:")
                         symbol-end)))))

(defun cython-beginning-of-defun ()
  "`beginning-of-defun-function' for Cython.
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."
  (let ((ci (current-indentation))
        (def-re (rx line-start (0+ space) (or "def" "cdef" "cpdef" "class") (1+ space)
                    (group (1+ (or word (syntax symbol))))))
        found lep) ;; def-line
    (if (cython-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))
      (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
                     ;; 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 (cython-in-string/comment)))
          (setq found t)))))

(defun cython-end-of-defun ()
  "`end-of-defun-function' for Cython.
Finds end of innermost nested class or method definition."
  (let ((orig (point))
        (pattern (rx line-start (0+ space) (or "def" "cdef" "cpdef" "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.
    (when (cython-comment-line-p)
      (end-of-line)
      (forward-comment most-positive-fixnum))
    (when (not (cython-open-block-statement-p))
      (cython-beginning-of-block))
    (if (zerop (current-indentation))
        (unless (cython-open-block-statement-p)
          (while (and (re-search-forward pattern nil 'move)
                      (cython-in-string/comment))) ; just loop
          (unless (eobp)
            (beginning-of-line)))
      ;; Don't move before top-level statement that would end defun.
      (end-of-line)
      (beginning-of-defun))
    ;; If we got to the start of buffer, look forward for
    ;; definition statement.
    (when (and (bobp) (not (looking-at (rx (or "def" "cdef" "cpdef" "class")))))
      (while (and (not (eobp))
                  (re-search-forward pattern nil 'move)
                  (cython-in-string/comment)))) ; just loop
    ;; We're at a definition statement (or end-of-buffer).
    ;; This is where we should have started when called from end-of-defun
    (unless (eobp)
      (let ((block-indentation (current-indentation)))
        (python-nav-end-of-statement)
        (while (and (forward-line 1)
                    (not (eobp))
                    (or (and (> (current-indentation) block-indentation)
                             (or (cython-end-of-statement) t))
                        ;; comment or empty line
                        (looking-at (rx (0+ space) (or eol "#"))))))
        (forward-comment -1))
      ;; Count trailing space in defun (but not trailing comments).
      (skip-syntax-forward " >")
      (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 ...:
    ;;     ...
    ;; else:
    ;;     ...  # point here
    ;;     ...
    ;;     def ...
    (if (< (point) orig)
        (goto-char (point-max)))))

260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
(defun cython-current-defun ()
  "`add-log-current-defun-function' for Cython."
  (save-excursion
    ;; Move up the tree of nested `class' and `def' blocks until we
    ;; get to zero indentation, accumulating the defined names.
    (let ((start t)
          accum)
      (while (or start (> (current-indentation) 0))
        (setq start nil)
        (cython-beginning-of-block)
        (end-of-line)
        (beginning-of-defun)
        (if (looking-at (rx (0+ space) (or "def" "cdef" "cpdef" "class") (1+ space)
                            (group (1+ (or word (syntax symbol))))))
            (push (match-string 1) accum)))
      (if accum (mapconcat 'identity accum ".")))))

Ivan Andrus's avatar
Ivan Andrus committed
277
;;;###autoload
278 279 280 281
(define-derived-mode cython-mode python-mode "Cython"
  "Major mode for Cython development, derived from Python mode.

\\{cython-mode-map}"
282
  (font-lock-add-keywords nil cython-font-lock-keywords)
Ivan Andrus's avatar
Ivan Andrus committed
283 284 285 286
  (set (make-local-variable 'outline-regexp)
       (rx (* space) (or "class" "def" "cdef" "cpdef" "elif" "else" "except" "finally"
                         "for" "if" "try" "while" "with")
           symbol-end))
287 288 289 290
  (set (make-local-variable 'beginning-of-defun-function)
       #'cython-beginning-of-defun)
  (set (make-local-variable 'end-of-defun-function)
       #'cython-end-of-defun)
291
  (set (make-local-variable 'compile-command)
292
       (format cython-default-compile-format (shell-quote-argument (or buffer-file-name ""))))
293 294 295
  (set (make-local-variable 'add-log-current-defun-function)
       #'cython-current-defun)
  (add-hook 'which-func-functions #'cython-current-defun nil t)
296
  (add-to-list (make-local-variable 'compilation-finish-functions)
Ivan Andrus's avatar
Ivan Andrus committed
297
               'cython-compilation-finish))
298 299

(provide 'cython-mode)
Ivan Andrus's avatar
Ivan Andrus committed
300 301

;;; cython-mode.el ends here