;;; cython-mode.el --- Major mode for editing Cython files ;; License: Apache-2.0 ;;; Commentary: ;; This should work with python-mode.el as well as either the new ;; python.el or the old. ;;; Code: ;; Load python-mode if available, otherwise use builtin emacs python package (when (not (require 'python-mode nil t)) (require 'python)) (eval-when-compile (require 'rx)) ;;;###autoload (add-to-list 'auto-mode-alist '("\\.pyx\\'" . cython-mode)) ;;;###autoload (add-to-list 'auto-mode-alist '("\\.pxd\\'" . cython-mode)) ;;;###autoload (add-to-list 'auto-mode-alist '("\\.pxi\\'" . cython-mode)) (defvar cython-buffer nil "Variable pointing to the cython buffer which was compiled.") (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) 'cython-compilation-finish)))) (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 `(;; 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 (,(rx symbol-start (or "by" "cdef" "cimport" "cpdef" "extern" "gil" "include" "nogil" "property" "public" "readonly" "DEF" "IF" "ELIF" "ELSE" "new" "del" "cppclass" "namespace" "const" "__stdcall" "__cdecl" "__fastcall" "inline" "api") symbol-end) . 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) ;; C and Python types (highlight as builtins) (,(rx symbol-start (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") symbol-end) . font-lock-builtin-face) (,(rx symbol-start "NULL" symbol-end) . font-lock-constant-face) ;; cdef is used for more than functions, so simply highlighting the next ;; word is problematic. struct, enum and property work though. (,(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)) ("\\_ (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))))) (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 "."))))) ;;;###autoload (define-derived-mode cython-mode python-mode "Cython" "Major mode for Cython development, derived from Python mode. \\{cython-mode-map}" (font-lock-add-keywords nil cython-font-lock-keywords) (set (make-local-variable 'outline-regexp) (rx (* space) (or "class" "def" "cdef" "cpdef" "elif" "else" "except" "finally" "for" "if" "try" "while" "with") symbol-end)) (set (make-local-variable 'beginning-of-defun-function) #'cython-beginning-of-defun) (set (make-local-variable 'end-of-defun-function) #'cython-end-of-defun) (set (make-local-variable 'compile-command) (format cython-default-compile-format (shell-quote-argument (or buffer-file-name "")))) (set (make-local-variable 'add-log-current-defun-function) #'cython-current-defun) (add-hook 'which-func-functions #'cython-current-defun nil t) (add-to-list (make-local-variable 'compilation-finish-functions) 'cython-compilation-finish)) (provide 'cython-mode) ;;; cython-mode.el ends here