1;;; cython-mode.el --- Major mode for editing Cython files 2 3;;; Commentary: 4 5;; This should work with python-mode.el as well as either the new 6;; python.el or the old. 7 8;;; Code: 9 10;; Load python-mode if available, otherwise use builtin emacs python package 11(when (not (require 'python-mode nil t)) 12 (require 'python)) 13(eval-when-compile (require 'rx)) 14 15;;;###autoload 16(add-to-list 'auto-mode-alist '("\\.pyx\\'" . cython-mode)) 17;;;###autoload 18(add-to-list 'auto-mode-alist '("\\.pxd\\'" . cython-mode)) 19;;;###autoload 20(add-to-list 'auto-mode-alist '("\\.pxi\\'" . cython-mode)) 21 22 23(defvar cython-buffer nil 24 "Variable pointing to the cython buffer which was compiled.") 25 26(defun cython-compile () 27 "Compile the file via Cython." 28 (interactive) 29 (let ((cy-buffer (current-buffer))) 30 (with-current-buffer 31 (compile compile-command) 32 (set (make-local-variable 'cython-buffer) cy-buffer) 33 (add-to-list (make-local-variable 'compilation-finish-functions) 34 'cython-compilation-finish)))) 35 36(defun cython-compilation-finish (buffer how) 37 "Called when Cython compilation finishes." 38 ;; XXX could annotate source here 39 ) 40 41(defvar cython-mode-map 42 (let ((map (make-sparse-keymap))) 43 ;; Will inherit from `python-mode-map' thanks to define-derived-mode. 44 (define-key map "\C-c\C-c" 'cython-compile) 45 map) 46 "Keymap used in `cython-mode'.") 47 48(defvar cython-font-lock-keywords 49 `(;; new keywords in Cython language 50 (,(regexp-opt '("by" "cdef" "cimport" "cpdef" "ctypedef" "enum" "except?" 51 "extern" "gil" "include" "nogil" "property" "public" 52 "readonly" "struct" "union" "DEF" "IF" "ELIF" "ELSE") 'words) 53 1 font-lock-keyword-face) 54 ;; C and Python types (highlight as builtins) 55 (,(regexp-opt '("NULL" "bint" "char" "dict" "double" "float" "int" "list" 56 "long" "object" "Py_ssize_t" "short" "size_t" "void") 'words) 57 1 font-lock-builtin-face) 58 ;; cdef is used for more than functions, so simply highlighting the next 59 ;; word is problematic. struct, enum and property work though. 60 ("\\<\\(?:struct\\|enum\\)[ \t]+\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)" 61 1 py-class-name-face) 62 ("\\<property[ \t]+\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)" 63 1 font-lock-function-name-face)) 64 "Additional font lock keywords for Cython mode.") 65 66;;;###autoload 67(defgroup cython nil "Major mode for editing and compiling Cython files" 68 :group 'languages 69 :prefix "cython-" 70 :link '(url-link :tag "Homepage" "http://cython.org")) 71 72;;;###autoload 73(defcustom cython-default-compile-format "cython -a %s" 74 "Format for the default command to compile a Cython file. 75It will be passed to `format' with `buffer-file-name' as the only other argument." 76 :group 'cython 77 :type 'string) 78 79;; Some functions defined differently in the different python modes 80(defun cython-comment-line-p () 81 "Return non-nil if current line is a comment." 82 (save-excursion 83 (back-to-indentation) 84 (eq ?# (char-after (point))))) 85 86(defun cython-in-string/comment () 87 "Return non-nil if point is in a comment or string." 88 (nth 8 (syntax-ppss))) 89 90(defalias 'cython-beginning-of-statement 91 (cond 92 ;; python-mode.el 93 ((fboundp 'py-beginning-of-statement) 94 'py-beginning-of-statement) 95 ;; old python.el 96 ((fboundp 'python-beginning-of-statement) 97 'python-beginning-of-statement) 98 ;; new python.el 99 ((fboundp 'python-nav-beginning-of-statement) 100 'python-nav-beginning-of-statement) 101 (t (error "Couldn't find implementation for `cython-beginning-of-statement'")))) 102 103(defalias 'cython-beginning-of-block 104 (cond 105 ;; python-mode.el 106 ((fboundp 'py-beginning-of-block) 107 'py-beginning-of-block) 108 ;; old python.el 109 ((fboundp 'python-beginning-of-block) 110 'python-beginning-of-block) 111 ;; new python.el 112 ((fboundp 'python-nav-beginning-of-block) 113 'python-nav-beginning-of-block) 114 (t (error "Couldn't find implementation for `cython-beginning-of-block'")))) 115 116(defalias 'cython-end-of-statement 117 (cond 118 ;; python-mode.el 119 ((fboundp 'py-end-of-statement) 120 'py-end-of-statement) 121 ;; old python.el 122 ((fboundp 'python-end-of-statement) 123 'python-end-of-statement) 124 ;; new python.el 125 ((fboundp 'python-nav-end-of-statement) 126 'python-nav-end-of-statement) 127 (t (error "Couldn't find implementation for `cython-end-of-statement'")))) 128 129(defun cython-open-block-statement-p (&optional bos) 130 "Return non-nil if statement at point opens a Cython block. 131BOS non-nil means point is known to be at beginning of statement." 132 (save-excursion 133 (unless bos (cython-beginning-of-statement)) 134 (looking-at (rx (and (or "if" "else" "elif" "while" "for" "def" "cdef" "cpdef" 135 "class" "try" "except" "finally" "with" 136 "EXAMPLES:" "TESTS:" "INPUT:" "OUTPUT:") 137 symbol-end))))) 138 139(defun cython-beginning-of-defun () 140 "`beginning-of-defun-function' for Cython. 141Finds beginning of innermost nested class or method definition. 142Returns the name of the definition found at the end, or nil if 143reached start of buffer." 144 (let ((ci (current-indentation)) 145 (def-re (rx line-start (0+ space) (or "def" "cdef" "cpdef" "class") (1+ space) 146 (group (1+ (or word (syntax symbol)))))) 147 found lep) ;; def-line 148 (if (cython-comment-line-p) 149 (setq ci most-positive-fixnum)) 150 (while (and (not (bobp)) (not found)) 151 ;; Treat bol at beginning of function as outside function so 152 ;; that successive C-M-a makes progress backwards. 153 ;;(setq def-line (looking-at def-re)) 154 (unless (bolp) (end-of-line)) 155 (setq lep (line-end-position)) 156 (if (and (re-search-backward def-re nil 'move) 157 ;; Must be less indented or matching top level, or 158 ;; equally indented if we started on a definition line. 159 (let ((in (current-indentation))) 160 (or (and (zerop ci) (zerop in)) 161 (= lep (line-end-position)) ; on initial line 162 ;; Not sure why it was like this -- fails in case of 163 ;; last internal function followed by first 164 ;; non-def statement of the main body. 165 ;;(and def-line (= in ci)) 166 (= in ci) 167 (< in ci))) 168 (not (cython-in-string/comment))) 169 (setq found t))))) 170 171(defun cython-end-of-defun () 172 "`end-of-defun-function' for Cython. 173Finds end of innermost nested class or method definition." 174 (let ((orig (point)) 175 (pattern (rx line-start (0+ space) (or "def" "cdef" "cpdef" "class") space))) 176 ;; Go to start of current block and check whether it's at top 177 ;; level. If it is, and not a block start, look forward for 178 ;; definition statement. 179 (when (cython-comment-line-p) 180 (end-of-line) 181 (forward-comment most-positive-fixnum)) 182 (when (not (cython-open-block-statement-p)) 183 (cython-beginning-of-block)) 184 (if (zerop (current-indentation)) 185 (unless (cython-open-block-statement-p) 186 (while (and (re-search-forward pattern nil 'move) 187 (cython-in-string/comment))) ; just loop 188 (unless (eobp) 189 (beginning-of-line))) 190 ;; Don't move before top-level statement that would end defun. 191 (end-of-line) 192 (beginning-of-defun)) 193 ;; If we got to the start of buffer, look forward for 194 ;; definition statement. 195 (when (and (bobp) (not (looking-at (rx (or "def" "cdef" "cpdef" "class"))))) 196 (while (and (not (eobp)) 197 (re-search-forward pattern nil 'move) 198 (cython-in-string/comment)))) ; just loop 199 ;; We're at a definition statement (or end-of-buffer). 200 ;; This is where we should have started when called from end-of-defun 201 (unless (eobp) 202 (let ((block-indentation (current-indentation))) 203 (python-nav-end-of-statement) 204 (while (and (forward-line 1) 205 (not (eobp)) 206 (or (and (> (current-indentation) block-indentation) 207 (or (cython-end-of-statement) t)) 208 ;; comment or empty line 209 (looking-at (rx (0+ space) (or eol "#")))))) 210 (forward-comment -1)) 211 ;; Count trailing space in defun (but not trailing comments). 212 (skip-syntax-forward " >") 213 (unless (eobp) ; e.g. missing final newline 214 (beginning-of-line))) 215 ;; Catch pathological cases like this, where the beginning-of-defun 216 ;; skips to a definition we're not in: 217 ;; if ...: 218 ;; ... 219 ;; else: 220 ;; ... # point here 221 ;; ... 222 ;; def ... 223 (if (< (point) orig) 224 (goto-char (point-max))))) 225 226(defun cython-current-defun () 227 "`add-log-current-defun-function' for Cython." 228 (save-excursion 229 ;; Move up the tree of nested `class' and `def' blocks until we 230 ;; get to zero indentation, accumulating the defined names. 231 (let ((start t) 232 accum) 233 (while (or start (> (current-indentation) 0)) 234 (setq start nil) 235 (cython-beginning-of-block) 236 (end-of-line) 237 (beginning-of-defun) 238 (if (looking-at (rx (0+ space) (or "def" "cdef" "cpdef" "class") (1+ space) 239 (group (1+ (or word (syntax symbol)))))) 240 (push (match-string 1) accum))) 241 (if accum (mapconcat 'identity accum "."))))) 242 243;;;###autoload 244(define-derived-mode cython-mode python-mode "Cython" 245 "Major mode for Cython development, derived from Python mode. 246 247\\{cython-mode-map}" 248 (setcar font-lock-defaults 249 (append python-font-lock-keywords cython-font-lock-keywords)) 250 (set (make-local-variable 'outline-regexp) 251 (rx (* space) (or "class" "def" "cdef" "cpdef" "elif" "else" "except" "finally" 252 "for" "if" "try" "while" "with") 253 symbol-end)) 254 (set (make-local-variable 'beginning-of-defun-function) 255 #'cython-beginning-of-defun) 256 (set (make-local-variable 'end-of-defun-function) 257 #'cython-end-of-defun) 258 (set (make-local-variable 'compile-command) 259 (format cython-default-compile-format (shell-quote-argument buffer-file-name))) 260 (set (make-local-variable 'add-log-current-defun-function) 261 #'cython-current-defun) 262 (add-hook 'which-func-functions #'cython-current-defun nil t) 263 (add-to-list (make-local-variable 'compilation-finish-functions) 264 'cython-compilation-finish)) 265 266(provide 'cython-mode) 267 268;;; cython-mode.el ends here 269