OLD | NEW |
(Empty) | |
| 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. |
| 75 It 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. |
| 131 BOS 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" "cpde
f" |
| 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. |
| 141 Finds beginning of innermost nested class or method definition. |
| 142 Returns the name of the definition found at the end, or nil if |
| 143 reached 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. |
| 173 Finds end of innermost nested class or method definition." |
| 174 (let ((orig (point)) |
| 175 (pattern (rx line-start (0+ space) (or "def" "cdef" "cpdef" "class") spa
ce))) |
| 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+ spa
ce) |
| 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" "fi
nally" |
| 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-n
ame))) |
| 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 |
OLD | NEW |