1 ;;; cython-mode.el --- Major mode for editing Cython files
5 ;; This should work with python-mode.el as well as either the new
6 ;; python.el or the old.
10 ;; Load python-mode if available, otherwise use builtin emacs python package
11 (when (not (require 'python-mode nil t
))
13 (eval-when-compile (require 'rx
))
16 (add-to-list 'auto-mode-alist
'("\\.pyx\\'" . cython-mode
))
18 (add-to-list 'auto-mode-alist
'("\\.pxd\\'" . cython-mode
))
20 (add-to-list 'auto-mode-alist
'("\\.pxi\\'" . cython-mode
))
23 (defvar cython-buffer nil
24 "Variable pointing to the cython buffer which was compiled.")
26 (defun cython-compile ()
27 "Compile the file via Cython."
29 (let ((cy-buffer (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
))))
36 (defun cython-compilation-finish (buffer how
)
37 "Called when Cython compilation finishes."
38 ;; XXX could annotate source here
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
)
46 "Keymap used in `cython-mode'.")
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_]*\\)"
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.")
67 (defgroup cython nil
"Major mode for editing and compiling Cython files"
70 :link
'(url-link :tag
"Homepage" "http://cython.org"))
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."
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."
84 (eq ?
# (char-after (point)))))
86 (defun cython-in-string/comment
()
87 "Return non-nil if point is in a comment or string."
88 (nth 8 (syntax-ppss)))
90 (defalias 'cython-beginning-of-statement
93 ((fboundp 'py-beginning-of-statement
)
94 'py-beginning-of-statement
)
96 ((fboundp 'python-beginning-of-statement
)
97 'python-beginning-of-statement
)
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'"))))
103 (defalias 'cython-beginning-of-block
106 ((fboundp 'py-beginning-of-block
)
107 'py-beginning-of-block
)
109 ((fboundp 'python-beginning-of-block
)
110 'python-beginning-of-block
)
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'"))))
116 (defalias 'cython-end-of-statement
119 ((fboundp 'py-end-of-statement
)
120 'py-end-of-statement
)
122 ((fboundp 'python-end-of-statement
)
123 'python-end-of-statement
)
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'"))))
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."
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:")
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))
168 (not (cython-in-string/comment
)))
171 (defun cython-end-of-defun ()
172 "`end-of-defun-function' for Cython.
173 Finds end of innermost nested class or method definition."
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)
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
189 (beginning-of-line)))
190 ;; Don't move before top-level statement that would end defun.
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
202 (let ((block-indentation (current-indentation)))
203 (python-nav-end-of-statement)
204 (while (and (forward-line 1)
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:
224 (goto-char (point-max)))))
226 (defun cython-current-defun ()
227 "`add-log-current-defun-function' for Cython."
229 ;; Move up the tree of nested `class' and `def' blocks until we
230 ;; get to zero indentation, accumulating the defined names.
233 (while (or start
(> (current-indentation) 0))
235 (cython-beginning-of-block)
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
".")))))
244 (define-derived-mode cython-mode python-mode
"Cython"
245 "Major mode for Cython development, derived from Python mode.
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")
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
))
266 (provide 'cython-mode
)
268 ;;; cython-mode.el ends here