|
|
;;; clojure-mode.el --- Major mode for Clojure code -*- lexical-binding: t; -*- |
|
|
|
|
|
;; Copyright © 2007-2015 Jeffrey Chu, Lennart Staflin, Phil Hagelberg |
|
|
;; Copyright © 2013-2015 Bozhidar Batsov |
|
|
;; |
|
|
;; Authors: Jeffrey Chu <jochu0@gmail.com> |
|
|
;; Lennart Staflin <lenst@lysator.liu.se> |
|
|
;; Phil Hagelberg <technomancy@gmail.com> |
|
|
;; Bozhidar Batsov <bozhidar@batsov.com> |
|
|
;; URL: http://github.com/clojure-emacs/clojure-mode |
|
|
;; Package-Version: 20151022.27 |
|
|
;; Keywords: languages clojure clojurescript lisp |
|
|
;; Version: 5.0.0-cvs |
|
|
;; Package-Requires: ((emacs "24.3")) |
|
|
|
|
|
;; This file is not part of GNU Emacs. |
|
|
|
|
|
;;; Commentary: |
|
|
|
|
|
;; Provides font-lock, indentation, and navigation for the Clojure |
|
|
;; programming language (http://clojure.org). |
|
|
|
|
|
;; Using clojure-mode with paredit or smartparens is highly recommended. |
|
|
|
|
|
;; Here are some example configurations: |
|
|
|
|
|
;; ;; require or autoload paredit-mode |
|
|
;; (add-hook 'clojure-mode-hook #'paredit-mode) |
|
|
|
|
|
;; ;; require or autoload smartparens |
|
|
;; (add-hook 'clojure-mode-hook #'smartparens-strict-mode) |
|
|
|
|
|
;; See inf-clojure (http://github.com/clojure-emacs/inf-clojure) for |
|
|
;; basic interaction with Clojure subprocesses. |
|
|
|
|
|
;; See CIDER (http://github.com/clojure-emacs/cider) for |
|
|
;; better interaction with subprocesses via nREPL. |
|
|
|
|
|
;;; License: |
|
|
|
|
|
;; This program is free software; you can redistribute it and/or |
|
|
;; modify it under the terms of the GNU General Public License |
|
|
;; as published by the Free Software Foundation; either version 3 |
|
|
;; of the License, or (at your option) any later version. |
|
|
;; |
|
|
;; This program is distributed in the hope that it will be useful, |
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
;; GNU General Public License for more details. |
|
|
;; |
|
|
;; You should have received a copy of the GNU General Public License |
|
|
;; along with GNU Emacs; see the file COPYING. If not, write to the |
|
|
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
|
|
;; Boston, MA 02110-1301, USA. |
|
|
|
|
|
;;; Code: |
|
|
|
|
|
|
|
|
|
|
|
(eval-when-compile |
|
|
(defvar calculate-lisp-indent-last-sexp) |
|
|
(defvar font-lock-beg) |
|
|
(defvar font-lock-end) |
|
|
(defvar paredit-space-for-delimiter-predicates) |
|
|
(defvar paredit-version) |
|
|
(defvar paredit-mode)) |
|
|
|
|
|
(require 'cl-lib) |
|
|
(require 'imenu) |
|
|
(require 'newcomment) |
|
|
|
|
|
(declare-function lisp-fill-paragraph "lisp-mode" (&optional justify)) |
|
|
|
|
|
(defgroup clojure nil |
|
|
"Major mode for editing Clojure code." |
|
|
:prefix "clojure-" |
|
|
:group 'languages |
|
|
:link '(url-link :tag "Github" "https://github.com/clojure-emacs/clojure-mode") |
|
|
:link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) |
|
|
|
|
|
(defconst clojure-mode-version "5.0.0-snapshot" |
|
|
"The current version of `clojure-mode'.") |
|
|
|
|
|
(defface clojure-keyword-face |
|
|
'((t (:inherit font-lock-constant-face))) |
|
|
"Face used to font-lock Clojure keywords (:something)." |
|
|
:group 'clojure |
|
|
:package-version '(clojure-mode . "3.0.0")) |
|
|
|
|
|
(defface clojure-character-face |
|
|
'((t (:inherit font-lock-string-face))) |
|
|
"Face used to font-lock Clojure character literals." |
|
|
:group 'clojure |
|
|
:package-version '(clojure-mode . "3.0.0")) |
|
|
|
|
|
(defface clojure-interop-method-face |
|
|
'((t (:inherit font-lock-preprocessor-face))) |
|
|
"Face used to font-lock interop method names (camelCase)." |
|
|
:group 'clojure |
|
|
:package-version '(clojure-mode . "3.0.0")) |
|
|
|
|
|
(defcustom clojure-defun-style-default-indent nil |
|
|
"When non-nil, use default indenting for functions and macros. |
|
|
Otherwise check `define-clojure-indent' and `put-clojure-indent'." |
|
|
:type 'boolean |
|
|
:group 'clojure |
|
|
:safe 'booleanp) |
|
|
|
|
|
(defcustom clojure-use-backtracking-indent t |
|
|
"When non-nil, enable context sensitive indentation." |
|
|
:type 'boolean |
|
|
:group 'clojure |
|
|
:safe 'booleanp) |
|
|
|
|
|
(defcustom clojure-max-backtracking 3 |
|
|
"Maximum amount to backtrack up a list to check for context." |
|
|
:type 'integer |
|
|
:group 'clojure |
|
|
:safe 'integerp) |
|
|
|
|
|
(defcustom clojure-docstring-fill-column fill-column |
|
|
"Value of `fill-column' to use when filling a docstring." |
|
|
:type 'integer |
|
|
:group 'clojure |
|
|
:safe 'integerp) |
|
|
|
|
|
(defcustom clojure-docstring-fill-prefix-width 2 |
|
|
"Width of `fill-prefix' when filling a docstring. |
|
|
The default value conforms with the de facto convention for |
|
|
Clojure docstrings, aligning the second line with the opening |
|
|
double quotes on the third column." |
|
|
:type 'integer |
|
|
:group 'clojure |
|
|
:safe 'integerp) |
|
|
|
|
|
(defcustom clojure-omit-space-between-tag-and-delimiters '(?\[ ?\{) |
|
|
"Allowed opening delimiter characters after a reader literal tag. |
|
|
For example, \[ is allowed in :db/id[:db.part/user]." |
|
|
:type '(set (const :tag "[" ?\[) |
|
|
(const :tag "{" ?\{) |
|
|
(const :tag "(" ?\() |
|
|
(const :tag "\"" ?\")) |
|
|
:group 'clojure |
|
|
:safe (lambda (value) |
|
|
(and (listp value) |
|
|
(cl-every 'characterp value)))) |
|
|
|
|
|
(defvar clojure-mode-map |
|
|
(let ((map (make-sparse-keymap))) |
|
|
(define-key map (kbd "C-:") #'clojure-toggle-keyword-string) |
|
|
(easy-menu-define clojure-mode-menu map "Clojure Mode Menu" |
|
|
'("Clojure" |
|
|
["Toggle between string & keyword" clojure-toggle-keyword-string] |
|
|
"--" |
|
|
["Insert ns form at point" clojure-insert-ns-form-at-point] |
|
|
["Insert ns form at beginning" clojure-insert-ns-form] |
|
|
["Update ns form" clojure-update-ns] |
|
|
"--" |
|
|
["Version" clojure-mode-display-version])) |
|
|
map) |
|
|
"Keymap for Clojure mode.") |
|
|
|
|
|
(defvar clojure-mode-syntax-table |
|
|
(let ((table (copy-syntax-table emacs-lisp-mode-syntax-table))) |
|
|
(modify-syntax-entry ?~ "' " table) |
|
|
(modify-syntax-entry ?\{ "(}" table) |
|
|
(modify-syntax-entry ?\} "){" table) |
|
|
(modify-syntax-entry ?\[ "(]" table) |
|
|
(modify-syntax-entry ?\] ")[" table) |
|
|
(modify-syntax-entry ?^ "'" table) |
|
|
(modify-syntax-entry ?@ "'" table) |
|
|
;; Make hash a usual word character |
|
|
(modify-syntax-entry ?# "_ p" table) |
|
|
table) |
|
|
"Syntax table for Clojure mode. |
|
|
Inherits from `emacs-lisp-mode-syntax-table'.") |
|
|
|
|
|
(defconst clojure--prettify-symbols-alist |
|
|
'(("fn" . ?λ))) |
|
|
|
|
|
(defun clojure-mode-display-version () |
|
|
"Display the current `clojure-mode-version' in the minibuffer." |
|
|
(interactive) |
|
|
(message "clojure-mode (version %s)" clojure-mode-version)) |
|
|
|
|
|
(defun clojure-space-for-delimiter-p (endp delim) |
|
|
"Prevent paredit from inserting useless spaces. |
|
|
See `paredit-space-for-delimiter-predicates' for the meaning of |
|
|
ENDP and DELIM." |
|
|
(if (or (derived-mode-p 'clojure-mode) |
|
|
(derived-mode-p 'cider-repl-mode)) |
|
|
(save-excursion |
|
|
(backward-char) |
|
|
(if (and (or (char-equal delim ?\() |
|
|
(char-equal delim ?\") |
|
|
(char-equal delim ?{)) |
|
|
(not endp)) |
|
|
(if (char-equal (char-after) ?#) |
|
|
(and (not (bobp)) |
|
|
(or (char-equal ?w (char-syntax (char-before))) |
|
|
(char-equal ?_ (char-syntax (char-before))))) |
|
|
t) |
|
|
t)) |
|
|
t)) |
|
|
|
|
|
(defun clojure-no-space-after-tag (endp delimiter) |
|
|
"Prevent inserting a space after a reader-literal tag? |
|
|
|
|
|
When a reader-literal tag is followed be an opening delimiter |
|
|
listed in `clojure-omit-space-between-tag-and-delimiters', this |
|
|
function returns t. |
|
|
|
|
|
This allows you to write things like #db/id[:db.part/user] |
|
|
without inserting a space between the tag and the opening |
|
|
bracket. |
|
|
|
|
|
See `paredit-space-for-delimiter-predicates' for the meaning of |
|
|
ENDP and DELIMITER." |
|
|
(if endp |
|
|
t |
|
|
(or (not (member delimiter clojure-omit-space-between-tag-and-delimiters)) |
|
|
(save-excursion |
|
|
(let ((orig-point (point))) |
|
|
(not (and (re-search-backward |
|
|
"#\\([a-zA-Z0-9._-]+/\\)?[a-zA-Z0-9._-]+" |
|
|
(line-beginning-position) |
|
|
t) |
|
|
(= orig-point (match-end 0))))))))) |
|
|
|
|
|
(declare-function paredit-open-curly "ext:paredit") |
|
|
(declare-function paredit-close-curly "ext:paredit") |
|
|
|
|
|
(defun clojure-paredit-setup () |
|
|
"Make \"paredit-mode\" play nice with `clojure-mode'." |
|
|
(when (>= paredit-version 21) |
|
|
(define-key clojure-mode-map "{" #'paredit-open-curly) |
|
|
(define-key clojure-mode-map "}" #'paredit-close-curly) |
|
|
(add-to-list 'paredit-space-for-delimiter-predicates |
|
|
#'clojure-space-for-delimiter-p) |
|
|
(add-to-list 'paredit-space-for-delimiter-predicates |
|
|
#'clojure-no-space-after-tag))) |
|
|
|
|
|
(defun clojure-mode-variables () |
|
|
"Set up initial buffer-local variables for Clojure mode." |
|
|
(setq-local imenu-create-index-function |
|
|
(lambda () |
|
|
(imenu--generic-function '((nil clojure-match-next-def 0))))) |
|
|
(setq-local indent-tabs-mode nil) |
|
|
(setq-local paragraph-ignore-fill-prefix t) |
|
|
(setq-local outline-regexp ";;;\\(;* [^ \t\n]\\)\\|(") |
|
|
(setq-local outline-level 'lisp-outline-level) |
|
|
(setq-local comment-start ";") |
|
|
(setq-local comment-start-skip ";+ *") |
|
|
(setq-local comment-add 1) ; default to `;;' in comment-region |
|
|
(setq-local comment-column 40) |
|
|
(setq-local comment-use-syntax t) |
|
|
(setq-local multibyte-syntax-as-symbol t) |
|
|
(setq-local electric-pair-skip-whitespace 'chomp) |
|
|
(setq-local electric-pair-open-newline-between-pairs nil) |
|
|
(setq-local fill-paragraph-function #'clojure-fill-paragraph) |
|
|
(setq-local adaptive-fill-function #'clojure-adaptive-fill-function) |
|
|
(setq-local normal-auto-fill-function #'clojure-auto-fill-function) |
|
|
(setq-local comment-start-skip |
|
|
"\\(\\(^\\|[^\\\\\n]\\)\\(\\\\\\\\\\)*\\)\\(;+\\|#|\\) *") |
|
|
(setq-local indent-line-function #'clojure-indent-line) |
|
|
(setq-local lisp-indent-function #'clojure-indent-function) |
|
|
(setq-local lisp-doc-string-elt-property 'clojure-doc-string-elt) |
|
|
(setq-local parse-sexp-ignore-comments t) |
|
|
(setq-local prettify-symbols-alist clojure--prettify-symbols-alist) |
|
|
(setq-local open-paren-in-column-0-is-defun-start nil)) |
|
|
|
|
|
;;;###autoload |
|
|
(define-derived-mode clojure-mode prog-mode "Clojure" |
|
|
"Major mode for editing Clojure code. |
|
|
|
|
|
\\{clojure-mode-map}" |
|
|
(clojure-mode-variables) |
|
|
(clojure-font-lock-setup) |
|
|
(add-hook 'paredit-mode-hook #'clojure-paredit-setup)) |
|
|
|
|
|
(defsubst clojure-in-docstring-p () |
|
|
"Check whether point is in a docstring." |
|
|
(eq (get-text-property (point) 'face) 'font-lock-doc-face)) |
|
|
|
|
|
(defsubst clojure-docstring-fill-prefix () |
|
|
"The prefix string used by `clojure-fill-paragraph'. |
|
|
|
|
|
It is simply `clojure-docstring-fill-prefix-width' number of spaces." |
|
|
(make-string clojure-docstring-fill-prefix-width ? )) |
|
|
|
|
|
(defun clojure-adaptive-fill-function () |
|
|
"Clojure adaptive fill function. |
|
|
This only takes care of filling docstring correctly." |
|
|
(when (clojure-in-docstring-p) |
|
|
(clojure-docstring-fill-prefix))) |
|
|
|
|
|
(defun clojure-fill-paragraph (&optional justify) |
|
|
"Like `fill-paragraph', but can handle Clojure docstrings. |
|
|
|
|
|
If JUSTIFY is non-nil, justify as well as fill the paragraph." |
|
|
(if (clojure-in-docstring-p) |
|
|
(let ((paragraph-start |
|
|
(concat paragraph-start |
|
|
"\\|\\s-*\\([(;:\"[]\\|~@\\|`(\\|#'(\\)")) |
|
|
(paragraph-separate |
|
|
(concat paragraph-separate "\\|\\s-*\".*[,\\.]$")) |
|
|
(fill-column (or clojure-docstring-fill-column fill-column)) |
|
|
(fill-prefix (clojure-docstring-fill-prefix))) |
|
|
(fill-paragraph justify)) |
|
|
(let ((paragraph-start (concat paragraph-start |
|
|
"\\|\\s-*\\([(;:\"[]\\|`(\\|#'(\\)")) |
|
|
(paragraph-separate |
|
|
(concat paragraph-separate "\\|\\s-*\".*[,\\.[]$"))) |
|
|
(or (fill-comment-paragraph justify) |
|
|
(fill-paragraph justify)) |
|
|
;; Always return `t' |
|
|
t))) |
|
|
|
|
|
(defun clojure-auto-fill-function () |
|
|
"Clojure auto-fill function." |
|
|
;; Check if auto-filling is meaningful. |
|
|
(let ((fc (current-fill-column))) |
|
|
(when (and fc (> (current-column) fc)) |
|
|
(let ((fill-column (if (clojure-in-docstring-p) |
|
|
clojure-docstring-fill-column |
|
|
fill-column)) |
|
|
(fill-prefix (clojure-adaptive-fill-function))) |
|
|
(do-auto-fill))))) |
|
|
|
|
|
|
|
|
|
|
|
(defun clojure-match-next-def () |
|
|
"Scans the buffer backwards for the next \"top-level\" definition. |
|
|
Called by `imenu--generic-function'." |
|
|
;; we have to take into account namespace-definition forms |
|
|
;; e.g. s/defn |
|
|
(when (re-search-backward "^(\\([a-z0-9.-]+/\\)?def\\sw*" nil t) |
|
|
(save-excursion |
|
|
(let (found? |
|
|
(start (point))) |
|
|
(down-list) |
|
|
(forward-sexp) |
|
|
(while (not found?) |
|
|
(forward-sexp) |
|
|
(or (if (char-equal ?[ (char-after (point))) |
|
|
(backward-sexp)) |
|
|
(if (char-equal ?) (char-after (point))) |
|
|
(backward-sexp))) |
|
|
(cl-destructuring-bind (def-beg . def-end) (bounds-of-thing-at-point 'sexp) |
|
|
(if (char-equal ?^ (char-after def-beg)) |
|
|
(progn (forward-sexp) (backward-sexp)) |
|
|
(setq found? t) |
|
|
(set-match-data (list def-beg def-end))))) |
|
|
(goto-char start))))) |
|
|
|
|
|
(defconst clojure-font-lock-keywords |
|
|
(eval-when-compile |
|
|
`(;; Top-level variable definition |
|
|
(,(concat "(\\(?:clojure.core/\\)?\\(" |
|
|
(regexp-opt '("def" "defonce")) |
|
|
;; variable declarations |
|
|
"\\)\\>" |
|
|
;; Any whitespace |
|
|
"[ \r\n\t]*" |
|
|
;; Possibly type or metadata |
|
|
"\\(?:#?^\\(?:{[^}]*}\\|\\sw+\\)[ \r\n\t]*\\)*" |
|
|
"\\(\\sw+\\)?") |
|
|
(1 font-lock-keyword-face) |
|
|
(2 font-lock-variable-name-face nil t)) |
|
|
;; Type definition |
|
|
(,(concat "(\\(?:clojure.core/\\)?\\(" |
|
|
(regexp-opt '("defstruct" "deftype" "defprotocol" |
|
|
"defrecord")) |
|
|
;; type declarations |
|
|
"\\)\\>" |
|
|
;; Any whitespace |
|
|
"[ \r\n\t]*" |
|
|
;; Possibly type or metadata |
|
|
"\\(?:#?^\\(?:{[^}]*}\\|\\sw+\\)[ \r\n\t]*\\)*" |
|
|
"\\(\\sw+\\)?") |
|
|
(1 font-lock-keyword-face) |
|
|
(2 font-lock-type-face nil t)) |
|
|
;; Function definition (anything that starts with def and is not |
|
|
;; listed above) |
|
|
(,(concat "(\\(?:[a-z\.-]+/\\)?\\(def[^ \r\n\t]*\\)" |
|
|
;; Function declarations |
|
|
"\\>" |
|
|
;; Any whitespace |
|
|
"[ \r\n\t]*" |
|
|
;; Possibly type or metadata |
|
|
"\\(?:#?^\\(?:{[^}]*}\\|\\sw+\\)[ \r\n\t]*\\)*" |
|
|
"\\(\\sw+\\)?") |
|
|
(1 font-lock-keyword-face) |
|
|
(2 font-lock-function-name-face nil t)) |
|
|
;; (fn name? args ...) |
|
|
(,(concat "(\\(?:clojure.core/\\)?\\(fn\\)[ \t]+" |
|
|
;; Possibly type |
|
|
"\\(?:#?^\\sw+[ \t]*\\)?" |
|
|
;; Possibly name |
|
|
"\\(t\\sw+\\)?" ) |
|
|
(1 font-lock-keyword-face) |
|
|
(2 font-lock-function-name-face nil t)) |
|
|
;; lambda arguments - %, %1, %2, etc |
|
|
("\\<%[1-9]?" (0 font-lock-variable-name-face)) |
|
|
;; Special forms |
|
|
(,(concat |
|
|
"(" |
|
|
(regexp-opt |
|
|
'("def" "do" "if" "let" "var" "fn" "loop" |
|
|
"recur" "throw" "try" "catch" "finally" |
|
|
"set!" "new" "." |
|
|
"monitor-enter" "monitor-exit" "quote") t) |
|
|
"\\>") |
|
|
1 font-lock-keyword-face) |
|
|
;; Built-in binding and flow of control forms |
|
|
(,(concat |
|
|
"(\\(?:clojure.core/\\)?" |
|
|
(regexp-opt |
|
|
'("letfn" "case" "cond" "cond->" "cond->>" "condp" |
|
|
"for" "when" "when-not" "when-let" "when-first" "when-some" |
|
|
"if-let" "if-not" "if-some" |
|
|
".." "->" "->>" "as->" "doto" "and" "or" |
|
|
"dosync" "doseq" "dotimes" "dorun" "doall" |
|
|
"ns" "in-ns" |
|
|
"with-open" "with-local-vars" "binding" |
|
|
"with-redefs" "with-redefs-fn" |
|
|
"declare") t) |
|
|
"\\>") |
|
|
1 font-lock-keyword-face) |
|
|
(,(concat |
|
|
"\\<" |
|
|
(regexp-opt |
|
|
'("*1" "*2" "*3" "*agent*" |
|
|
"*allow-unresolved-vars*" "*assert*" "*clojure-version*" |
|
|
"*command-line-args*" "*compile-files*" |
|
|
"*compile-path*" "*data-readers*" "*default-data-reader-fn*" |
|
|
"*e" "*err*" "*file*" "*flush-on-newline*" |
|
|
"*in*" "*macro-meta*" "*math-context*" "*ns*" "*out*" |
|
|
"*print-dup*" "*print-length*" "*print-level*" |
|
|
"*print-meta*" "*print-readably*" |
|
|
"*read-eval*" "*source-path*" |
|
|
"*unchecked-math*" |
|
|
"*use-context-classloader*" "*warn-on-reflection*") |
|
|
t) |
|
|
"\\>") |
|
|
0 font-lock-builtin-face) |
|
|
;; Dynamic variables - *something* or @*something* |
|
|
("\\(?:\\<\\|/\\)@?\\(\\*[a-z-]*\\*\\)\\>" 1 font-lock-variable-name-face) |
|
|
;; Global constants - nil, true, false |
|
|
(,(concat |
|
|
"\\<" |
|
|
(regexp-opt |
|
|
'("true" "false" "nil") t) |
|
|
"\\>") |
|
|
0 font-lock-constant-face) |
|
|
;; Character literals - \1, \a, \newline, \u0000 |
|
|
("\\\\\\([[:punct:]]\\|[a-z0-9]+\\>\\)" 0 'clojure-character-face) |
|
|
;; foo/ Foo/ @Foo/ /FooBar |
|
|
("\\(?:\\<:?\\|\\.\\)@?\\([a-zA-Z][.a-zA-Z0-9$_-]*\\)/" 1 font-lock-type-face) |
|
|
;; Constant values (keywords), including as metadata e.g. ^:static |
|
|
("\\<^?\\(:\\(\\sw\\|\\s_\\)+\\(\\>\\|\\_>\\)\\)" 1 'clojure-keyword-face append) |
|
|
;; Java interop highlighting |
|
|
;; CONST SOME_CONST (optionally prefixed by /) |
|
|
("\\(?:\\<\\|/\\)\\([A-Z]+\\|\\([A-Z]+_[A-Z1-9_]+\\)\\)\\>" 1 font-lock-constant-face) |
|
|
;; .foo .barBaz .qux01 .-flibble .-flibbleWobble |
|
|
("\\<\\.-?[a-z][a-zA-Z0-9]*\\>" 0 'clojure-interop-method-face) |
|
|
;; Foo Bar$Baz Qux_ World_OpenUDP Foo. Babylon15. |
|
|
("\\(?:\\<\\|\\.\\|/\\|#?^\\)\\([A-Z][a-zA-Z0-9_]*[a-zA-Z0-9$_]+\\.?\\>\\)" 1 font-lock-type-face) |
|
|
;; foo.bar.baz |
|
|
("\\<^?\\([a-z][a-z0-9_-]+\\.\\([a-z][a-z0-9_-]*\\.?\\)+\\)" 1 font-lock-type-face) |
|
|
;; (ns namespace) - special handling for single segment namespaces |
|
|
(,(concat "(\\<ns\\>[ \r\n\t]*" |
|
|
;; Possibly metadata |
|
|
"\\(?:\\^?{[^}]+}[ \r\n\t]*\\)*" |
|
|
;; namespace |
|
|
"\\([a-z0-9-]+\\)") |
|
|
(1 font-lock-type-face nil t)) |
|
|
;; fooBar |
|
|
("\\(?:\\<\\|/\\)\\([a-z]+[A-Z]+[a-zA-Z0-9$]*\\>\\)" 1 'clojure-interop-method-face) |
|
|
;; Highlight `code` marks, just like `elisp'. |
|
|
(,(rx "`" (group-n 1 (+ (or (syntax symbol) (syntax word)))) "`") |
|
|
(1 'font-lock-constant-face prepend)) |
|
|
;; Highlight grouping constructs in regular expressions |
|
|
(clojure-font-lock-regexp-groups |
|
|
(1 'font-lock-regexp-grouping-construct prepend)))) |
|
|
"Default expressions to highlight in Clojure mode.") |
|
|
|
|
|
(defun clojure-font-lock-syntactic-face-function (state) |
|
|
"Find and highlight text with a Clojure-friendly syntax table. |
|
|
|
|
|
This function is passed to `font-lock-syntactic-face-function', |
|
|
which is called with a single parameter, STATE (which is, in |
|
|
turn, returned by `parse-partial-sexp' at the beginning of the |
|
|
highlighted region)." |
|
|
(if (nth 3 state) |
|
|
;; This might be a (doc)string or a |...| symbol. |
|
|
(let ((startpos (nth 8 state))) |
|
|
(if (eq (char-after startpos) ?|) |
|
|
;; This is not a string, but a |...| symbol. |
|
|
nil |
|
|
(let* ((listbeg (nth 1 state)) |
|
|
(firstsym (and listbeg |
|
|
(save-excursion |
|
|
(goto-char listbeg) |
|
|
(and (looking-at "([ \t\n]*\\(\\(\\sw\\|\\s_\\)+\\)") |
|
|
(match-string 1))))) |
|
|
(docelt (and firstsym |
|
|
(function-get (intern-soft firstsym) |
|
|
lisp-doc-string-elt-property)))) |
|
|
(if (and docelt |
|
|
;; It's a string in a form that can have a docstring. |
|
|
;; Check whether it's in docstring position. |
|
|
(save-excursion |
|
|
(when (functionp docelt) |
|
|
(goto-char (match-end 1)) |
|
|
(setq docelt (funcall docelt))) |
|
|
(goto-char listbeg) |
|
|
(forward-char 1) |
|
|
(condition-case nil |
|
|
(while (and (> docelt 0) (< (point) startpos) |
|
|
(progn (forward-sexp 1) t)) |
|
|
;; ignore metadata and type hints |
|
|
(unless (looking-at "[ \n\t]*\\(\\^[A-Z:].+\\|\\^?{.+\\)") |
|
|
(setq docelt (1- docelt)))) |
|
|
(error nil)) |
|
|
(and (zerop docelt) (<= (point) startpos) |
|
|
(progn (forward-comment (point-max)) t) |
|
|
(= (point) (nth 8 state))))) |
|
|
font-lock-doc-face |
|
|
font-lock-string-face)))) |
|
|
font-lock-comment-face)) |
|
|
|
|
|
(defun clojure-font-lock-setup () |
|
|
"Configures font-lock for editing Clojure code." |
|
|
(setq-local font-lock-multiline t) |
|
|
(add-to-list 'font-lock-extend-region-functions |
|
|
#'clojure-font-lock-extend-region-def t) |
|
|
(setq font-lock-defaults |
|
|
'(clojure-font-lock-keywords ; keywords |
|
|
nil nil |
|
|
(("+-*/.<>=!?$%_&~^:@" . "w")) ; syntax alist |
|
|
nil |
|
|
(font-lock-mark-block-function . mark-defun) |
|
|
(font-lock-syntactic-face-function |
|
|
. clojure-font-lock-syntactic-face-function)))) |
|
|
|
|
|
(defun clojure-font-lock-def-at-point (point) |
|
|
"Range between the top-most def* and the fourth element after POINT. |
|
|
Note that this means that there is no guarantee of proper font |
|
|
locking in def* forms that are not at top level." |
|
|
(goto-char point) |
|
|
(condition-case nil |
|
|
(beginning-of-defun) |
|
|
(error nil)) |
|
|
|
|
|
(let ((beg-def (point))) |
|
|
(when (and (not (= point beg-def)) |
|
|
(looking-at "(def")) |
|
|
(condition-case nil |
|
|
(progn |
|
|
;; move forward as much as possible until failure (or success) |
|
|
(forward-char) |
|
|
(dotimes (_ 4) |
|
|
(forward-sexp))) |
|
|
(error nil)) |
|
|
(cons beg-def (point))))) |
|
|
|
|
|
(defun clojure-font-lock-extend-region-def () |
|
|
"Set region boundaries to include the first four elements of def* forms." |
|
|
(let ((changed nil)) |
|
|
(let ((def (clojure-font-lock-def-at-point font-lock-beg))) |
|
|
(when def |
|
|
(cl-destructuring-bind (def-beg . def-end) def |
|
|
(when (and (< def-beg font-lock-beg) |
|
|
(< font-lock-beg def-end)) |
|
|
(setq font-lock-beg def-beg |
|
|
changed t))))) |
|
|
(let ((def (clojure-font-lock-def-at-point font-lock-end))) |
|
|
(when def |
|
|
(cl-destructuring-bind (def-beg . def-end) def |
|
|
(when (and (< def-beg font-lock-end) |
|
|
(< font-lock-end def-end)) |
|
|
(setq font-lock-end def-end |
|
|
changed t))))) |
|
|
changed)) |
|
|
|
|
|
(defun clojure-font-lock-regexp-groups (bound) |
|
|
"Highlight grouping constructs in regular expression. |
|
|
|
|
|
BOUND denotes the maximum number of characters (relative to the |
|
|
point) to check." |
|
|
(catch 'found |
|
|
(while (re-search-forward (concat |
|
|
;; A group may start using several alternatives: |
|
|
"\\(\\(?:" |
|
|
;; 1. (? special groups |
|
|
"(\\?\\(?:" |
|
|
;; a) non-capturing group (?:X) |
|
|
;; b) independent non-capturing group (?>X) |
|
|
;; c) zero-width positive lookahead (?=X) |
|
|
;; d) zero-width negative lookahead (?!X) |
|
|
"[:=!>]\\|" |
|
|
;; e) zero-width positive lookbehind (?<=X) |
|
|
;; f) zero-width negative lookbehind (?<!X) |
|
|
"<[=!]\\|" |
|
|
;; g) named capturing group (?<name>X) |
|
|
"<[[:alnum:]]+>" |
|
|
"\\)\\|" ;; end of special groups |
|
|
;; 2. normal capturing groups ( |
|
|
;; 3. we also highlight alternative |
|
|
;; separarators |, and closing parens ) |
|
|
"[|()]" |
|
|
"\\)\\)") |
|
|
bound t) |
|
|
(let ((face (get-text-property (1- (point)) 'face))) |
|
|
(when (and (or (and (listp face) |
|
|
(memq 'font-lock-string-face face)) |
|
|
(eq 'font-lock-string-face face)) |
|
|
(clojure-string-start t)) |
|
|
(throw 'found t)))))) |
|
|
|
|
|
;; Docstring positions |
|
|
(put 'ns 'clojure-doc-string-elt 2) |
|
|
(put 'def 'clojure-doc-string-elt 2) |
|
|
(put 'defn 'clojure-doc-string-elt 2) |
|
|
(put 'defn- 'clojure-doc-string-elt 2) |
|
|
(put 'defmulti 'clojure-doc-string-elt 2) |
|
|
(put 'defmacro 'clojure-doc-string-elt 2) |
|
|
(put 'definline 'clojure-doc-string-elt 2) |
|
|
(put 'defprotocol 'clojure-doc-string-elt 2) |
|
|
|
|
|
(defun clojure-indent-line () |
|
|
"Indent current line as Clojure code." |
|
|
(if (clojure-in-docstring-p) |
|
|
(save-excursion |
|
|
(beginning-of-line) |
|
|
(when (and (looking-at "^\\s-*") |
|
|
(<= (string-width (match-string-no-properties 0)) |
|
|
(string-width (clojure-docstring-fill-prefix)))) |
|
|
(replace-match (clojure-docstring-fill-prefix)))) |
|
|
(lisp-indent-line))) |
|
|
|
|
|
(defvar clojure-get-indent-function nil |
|
|
"Function to get the indent spec of a symbol. |
|
|
This function should take one argument, the name of the symbol as |
|
|
a string. This name will be exactly as it appears in the buffer, |
|
|
so it might start with a namespace alias. |
|
|
|
|
|
This function is analogous to the `clojure-indent-function' |
|
|
symbol property, and its return value should match one of the |
|
|
allowed values of this property. See `clojure-indent-function' |
|
|
for more information.") |
|
|
|
|
|
(defun clojure--get-indent-method (function-name) |
|
|
"Return the indent spec for the symbol named FUNCTION-NAME. |
|
|
FUNCTION-NAME is a string. If it contains a `/', also try only |
|
|
the part after the `/'. |
|
|
|
|
|
Look for a spec using `clojure-get-indent-function', then try the |
|
|
`clojure-indent-function' and `clojure-backtracking-indent' |
|
|
symbol properties." |
|
|
(or (when (functionp clojure-get-indent-function) |
|
|
(funcall clojure-get-indent-function function-name)) |
|
|
(get (intern-soft function-name) 'clojure-indent-function) |
|
|
(get (intern-soft function-name) 'clojure-backtracking-indent) |
|
|
(when (string-match "/\\([^/]+\\)\\'" function-name) |
|
|
(or (get (intern-soft (match-string 1 function-name)) |
|
|
'clojure-indent-function) |
|
|
(get (intern-soft (match-string 1 function-name)) |
|
|
'clojure-backtracking-indent))))) |
|
|
|
|
|
(defvar clojure--current-backtracking-depth 0) |
|
|
|
|
|
(defun clojure--find-indent-spec-backtracking () |
|
|
"Return the indent sexp that applies to the sexp at point. |
|
|
Implementation function for `clojure--find-indent-spec'." |
|
|
(when (and (>= clojure-max-backtracking clojure--current-backtracking-depth) |
|
|
(not (looking-at "^"))) |
|
|
(let ((clojure--current-backtracking-depth (1+ clojure--current-backtracking-depth)) |
|
|
(pos 0)) |
|
|
;; Count how far we are from the start of the sexp. |
|
|
(while (ignore-errors (clojure-backward-logical-sexp 1) |
|
|
(not (or (bobp) |
|
|
(eq (char-before) ?\n)))) |
|
|
(cl-incf pos)) |
|
|
(let* ((function (thing-at-point 'symbol)) |
|
|
(method (or (when function ;; Is there a spec here? |
|
|
(clojure--get-indent-method function)) |
|
|
;; `up-list' errors on unbalanced sexps. |
|
|
(ignore-errors |
|
|
(up-list) ;; Otherwise look higher up. |
|
|
(clojure-backward-logical-sexp 1) |
|
|
(clojure--find-indent-spec-backtracking))))) |
|
|
(when (numberp method) |
|
|
(setq method (list method))) |
|
|
(pcase method |
|
|
((pred sequencep) |
|
|
(pcase (length method) |
|
|
(`0 nil) |
|
|
(`1 (let ((head (elt method 0))) |
|
|
(when (or (= pos 0) (sequencep head)) |
|
|
head))) |
|
|
(l (if (>= pos l) |
|
|
(elt method (1- l)) |
|
|
(elt method pos))))) |
|
|
((or `defun `:defn) |
|
|
(when (= pos 0) |
|
|
:defn)) |
|
|
((pred functionp) |
|
|
(when (= pos 0) |
|
|
method)) |
|
|
(_ |
|
|
(message "Invalid indent spec for `%s': %s" function method) |
|
|
nil)))))) |
|
|
|
|
|
(defun clojure--find-indent-spec () |
|
|
"Return the indent spec that applies to current sexp. |
|
|
If `clojure-use-backtracking-indent' is non-nil, also do |
|
|
backtracking up to a higher-level sexp in order to find the |
|
|
spec." |
|
|
(if clojure-use-backtracking-indent |
|
|
(save-excursion |
|
|
(clojure--find-indent-spec-backtracking)) |
|
|
(let ((function (thing-at-point 'symbol))) |
|
|
(clojure--get-indent-method function)))) |
|
|
|
|
|
(defun clojure--normal-indent (last-sexp) |
|
|
"Return the normal indentation column for a sexp. |
|
|
LAST-SEXP is the start of the previous sexp." |
|
|
(goto-char last-sexp) |
|
|
(forward-sexp 1) |
|
|
(clojure-backward-logical-sexp 1) |
|
|
(let ((last-sexp-start nil)) |
|
|
(unless (ignore-errors |
|
|
(while (string-match |
|
|
"[^[:blank:]]" |
|
|
(buffer-substring (line-beginning-position) (point))) |
|
|
(setq last-sexp-start (prog1 (point) |
|
|
(forward-sexp -1)))) |
|
|
t) |
|
|
;; If the last sexp was on the same line. |
|
|
(when (and last-sexp-start |
|
|
(> (line-end-position) last-sexp-start)) |
|
|
(goto-char last-sexp-start))) |
|
|
(current-column))) |
|
|
|
|
|
(defun clojure--not-function-form-p () |
|
|
"Non-nil if form at point doesn't represent a function call." |
|
|
(or (member (char-after) '(?\[ ?\{)) |
|
|
(save-excursion ;; Catch #?@ (:cljs ...) |
|
|
(skip-chars-backward "\r\n[:blank:]") |
|
|
(when (eq (char-before) ?@) |
|
|
(forward-char -1)) |
|
|
(and (eq (char-before) ?\?) |
|
|
(eq (char-before (1- (point))) ?\#))) |
|
|
;; Car of form is not a symbol. |
|
|
(not (looking-at ".\\(?:\\sw\\|\\s_\\)")))) |
|
|
|
|
|
(defun clojure-indent-function (indent-point state) |
|
|
"When indenting a line within a function call, indent properly. |
|
|
|
|
|
INDENT-POINT is the position where the user typed TAB, or equivalent. |
|
|
Point is located at the point to indent under (for default indentation); |
|
|
STATE is the `parse-partial-sexp' state for that position. |
|
|
|
|
|
If the current line is in a call to a Clojure function with a |
|
|
non-nil property `clojure-indent-function', that specifies how to do |
|
|
the indentation. |
|
|
|
|
|
The property value can be |
|
|
|
|
|
- `defun', meaning indent `defun'-style; |
|
|
- an integer N, meaning indent the first N arguments specially |
|
|
like ordinary function arguments and then indent any further |
|
|
arguments like a body; |
|
|
- a function to call just as this function was called. |
|
|
If that function returns nil, that means it doesn't specify |
|
|
the indentation. |
|
|
- a list, which is used by `clojure-backtracking-indent'. |
|
|
|
|
|
This function also returns nil meaning don't specify the indentation." |
|
|
;; Goto to the open-paren. |
|
|
(goto-char (elt state 1)) |
|
|
;; Maps, sets, vectors and reader conditionals. |
|
|
(if (clojure--not-function-form-p) |
|
|
(1+ (current-column)) |
|
|
;; Function or macro call. |
|
|
(forward-char 1) |
|
|
(let ((method (clojure--find-indent-spec)) |
|
|
(containing-form-column (1- (current-column)))) |
|
|
(pcase method |
|
|
((or (pred integerp) `(,method)) |
|
|
(let ((pos -1)) |
|
|
(condition-case nil |
|
|
(while (and (<= (point) indent-point) |
|
|
(not (eobp))) |
|
|
(clojure-forward-logical-sexp 1) |
|
|
(cl-incf pos)) |
|
|
;; If indent-point is _after_ the last sexp in the |
|
|
;; current sexp, we detect that by catching the |
|
|
;; `scan-error'. In that case, we should return the |
|
|
;; indentation as if there were an extra sexp at point. |
|
|
(scan-error (cl-incf pos))) |
|
|
(cond |
|
|
((= pos (1+ method)) |
|
|
(+ lisp-body-indent containing-form-column)) |
|
|
((> pos (1+ method)) |
|
|
(clojure--normal-indent calculate-lisp-indent-last-sexp)) |
|
|
(t |
|
|
(+ (* 2 lisp-body-indent) containing-form-column))))) |
|
|
(`:defn |
|
|
(+ lisp-body-indent containing-form-column)) |
|
|
((pred functionp) |
|
|
(funcall method indent-point state)) |
|
|
((and `nil |
|
|
(guard (let ((function (thing-at-point 'sexp))) |
|
|
(or (and clojure-defun-style-default-indent |
|
|
;; largely to preserve useful alignment of :require, etc in ns |
|
|
(not (string-match "^:" function))) |
|
|
(string-match "\\`\\(?:\\S +/\\)?\\(def\\|with-\\)" |
|
|
function))))) |
|
|
(+ lisp-body-indent containing-form-column)) |
|
|
(_ (clojure--normal-indent calculate-lisp-indent-last-sexp)))))) |
|
|
|
|
|
;;; Setting indentation |
|
|
(defun put-clojure-indent (sym indent) |
|
|
"Instruct `clojure-indent-function' to indent the body of SYM by INDENT." |
|
|
(put sym 'clojure-indent-function indent)) |
|
|
|
|
|
(defmacro define-clojure-indent (&rest kvs) |
|
|
"Call `put-clojure-indent' on a series, KVS." |
|
|
`(progn |
|
|
,@(mapcar (lambda (x) `(put-clojure-indent |
|
|
(quote ,(car x)) ,(cadr x))) |
|
|
kvs))) |
|
|
|
|
|
(defun add-custom-clojure-indents (name value) |
|
|
"Allow `clojure-defun-indents' to indent user-specified macros. |
|
|
|
|
|
Requires the macro's NAME and a VALUE." |
|
|
(custom-set-default name value) |
|
|
(mapcar (lambda (x) |
|
|
(put-clojure-indent x 'defun)) |
|
|
value)) |
|
|
|
|
|
(defcustom clojure-defun-indents nil |
|
|
"List of additional symbols with defun-style indentation in Clojure. |
|
|
|
|
|
You can use this to let Emacs indent your own macros the same way |
|
|
that it indents built-in macros like with-open. To manually set |
|
|
it from Lisp code, use (put-clojure-indent 'some-symbol :defn)." |
|
|
:type '(repeat symbol) |
|
|
:group 'clojure |
|
|
:set 'add-custom-clojure-indents) |
|
|
|
|
|
(define-clojure-indent |
|
|
;; built-ins |
|
|
(ns 1) |
|
|
(fn :defn) |
|
|
(def :defn) |
|
|
(defn :defn) |
|
|
(bound-fn :defn) |
|
|
(if 1) |
|
|
(if-not 1) |
|
|
(case 1) |
|
|
(cond 0) |
|
|
(condp 2) |
|
|
(cond-> 1) |
|
|
(cond->> 1) |
|
|
(when 1) |
|
|
(while 1) |
|
|
(when-not 1) |
|
|
(when-first 1) |
|
|
(do 0) |
|
|
(future 0) |
|
|
(comment 0) |
|
|
(doto 1) |
|
|
(locking 1) |
|
|
(proxy '(2 nil nil (1))) |
|
|
(as-> 2) |
|
|
|
|
|
(reify '(1 nil (1))) |
|
|
(deftype '(2 nil nil (1))) |
|
|
(defrecord '(2 nil nil (1))) |
|
|
(defprotocol '(1)) |
|
|
(extend 1) |
|
|
(extend-protocol '(1 (1))) |
|
|
(extend-type '(1 (1))) |
|
|
(specify '(1 (1))) |
|
|
(specify! '(1 (1))) |
|
|
(implement '(1 (1))) |
|
|
(try 0) |
|
|
(catch 2) |
|
|
(finally 0) |
|
|
|
|
|
;; binding forms |
|
|
(let 1) |
|
|
(letfn '(1 ((1)) nil)) |
|
|
(binding 1) |
|
|
(loop 1) |
|
|
(for 1) |
|
|
(doseq 1) |
|
|
(dotimes 1) |
|
|
(when-let 1) |
|
|
(if-let 1) |
|
|
(when-some 1) |
|
|
(if-some 1) |
|
|
|
|
|
(defmethod :defn) |
|
|
|
|
|
;; clojure.test |
|
|
(testing 1) |
|
|
(deftest :defn) |
|
|
(are 2) |
|
|
(use-fixtures :defn) |
|
|
|
|
|
;; core.logic |
|
|
(run :defn) |
|
|
(run* :defn) |
|
|
(fresh :defn) |
|
|
|
|
|
;; core.async |
|
|
(alt! 0) |
|
|
(alt!! 0) |
|
|
(go 0) |
|
|
(go-loop 1) |
|
|
(thread 0)) |
|
|
|
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
|
;; |
|
|
;; Better docstring filling for clojure-mode |
|
|
;; |
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
|
|
|
|
(defun clojure-string-start (&optional regex) |
|
|
"Return the position of the \" that begins the string at point. |
|
|
If REGEX is non-nil, return the position of the # that begins the |
|
|
regex at point. If point is not inside a string or regex, return |
|
|
nil." |
|
|
(when (nth 3 (syntax-ppss)) ;; Are we really in a string? |
|
|
(save-excursion |
|
|
(save-match-data |
|
|
;; Find a quote that appears immediately after whitespace, |
|
|
;; beginning of line, hash, or an open paren, brace, or bracket |
|
|
(re-search-backward "\\(\\s-\\|^\\|#\\|(\\|\\[\\|{\\)\\(\"\\)") |
|
|
(let ((beg (match-beginning 2))) |
|
|
(when beg |
|
|
(if regex |
|
|
(and (char-before beg) (char-equal ?# (char-before beg)) (1- beg)) |
|
|
(when (not (char-equal ?# (char-before beg))) |
|
|
beg)))))))) |
|
|
|
|
|
(defun clojure-char-at-point () |
|
|
"Return the char at point or nil if at buffer end." |
|
|
(when (not (= (point) (point-max))) |
|
|
(buffer-substring-no-properties (point) (1+ (point))))) |
|
|
|
|
|
(defun clojure-char-before-point () |
|
|
"Return the char before point or nil if at buffer beginning." |
|
|
(when (not (= (point) (point-min))) |
|
|
(buffer-substring-no-properties (point) (1- (point))))) |
|
|
|
|
|
(defun clojure-toggle-keyword-string () |
|
|
"Convert the string or keyword at point to keyword or string." |
|
|
(interactive) |
|
|
(let ((original-point (point))) |
|
|
(while (and (> (point) 1) |
|
|
(not (equal "\"" (buffer-substring-no-properties (point) (+ 1 (point))))) |
|
|
(not (equal ":" (buffer-substring-no-properties (point) (+ 1 (point)))))) |
|
|
(backward-char)) |
|
|
(cond |
|
|
((equal 1 (point)) |
|
|
(error "Beginning of file reached, this was probably a mistake")) |
|
|
((equal "\"" (buffer-substring-no-properties (point) (+ 1 (point)))) |
|
|
(insert ":" (substring (clojure-delete-and-extract-sexp) 1 -1))) |
|
|
((equal ":" (buffer-substring-no-properties (point) (+ 1 (point)))) |
|
|
(insert "\"" (substring (clojure-delete-and-extract-sexp) 1) "\""))) |
|
|
(goto-char original-point))) |
|
|
|
|
|
(defun clojure-delete-and-extract-sexp () |
|
|
"Delete the sexp and return it." |
|
|
(interactive) |
|
|
(let ((begin (point))) |
|
|
(forward-sexp) |
|
|
(let ((result (buffer-substring-no-properties begin (point)))) |
|
|
(delete-region begin (point)) |
|
|
result))) |
|
|
|
|
|
|
|
|
|
|
|
(defconst clojure-namespace-name-regex |
|
|
(rx line-start |
|
|
(zero-or-more whitespace) |
|
|
"(" |
|
|
(zero-or-one (group (regexp "clojure.core/"))) |
|
|
(zero-or-one (submatch "in-")) |
|
|
"ns" |
|
|
(zero-or-one "+") |
|
|
(one-or-more (any whitespace "\n")) |
|
|
(zero-or-more (or (submatch (zero-or-one "#") |
|
|
"^{" |
|
|
(zero-or-more (not (any "}"))) |
|
|
"}") |
|
|
(zero-or-more "^:" |
|
|
(one-or-more (not (any whitespace))))) |
|
|
(one-or-more (any whitespace "\n"))) |
|
|
;; why is this here? oh (in-ns 'foo) or (ns+ :user) |
|
|
(zero-or-one (any ":'")) |
|
|
(group (one-or-more (not (any "()\"" whitespace))) word-end))) |
|
|
|
|
|
;; for testing clojure-namespace-name-regex, you can evaluate this code and make |
|
|
;; sure foo (or whatever the namespace name is) shows up in results. some of |
|
|
;; these currently fail. |
|
|
;; (mapcar (lambda (s) (let ((n (string-match clojure-namespace-name-regex s))) |
|
|
;; (if n (match-string 4 s)))) |
|
|
;; '("(ns foo)" |
|
|
;; "(ns |
|
|
;; foo)" |
|
|
;; "(ns foo.baz)" |
|
|
;; "(ns ^:bar foo)" |
|
|
;; "(ns ^:bar ^:baz foo)" |
|
|
;; "(ns ^{:bar true} foo)" |
|
|
;; "(ns #^{:bar true} foo)" |
|
|
;; "(ns #^{:fail {}} foo)" |
|
|
;; "(ns ^{:fail2 {}} foo.baz)" |
|
|
;; "(ns ^{} foo)" |
|
|
;; "(ns ^{:skip-wiki true} |
|
|
;; aleph.netty |
|
|
;; " |
|
|
;; "(ns |
|
|
;; foo)" |
|
|
;; "foo")) |
|
|
|
|
|
|
|
|
|
|
|
(defun clojure-project-dir (&optional dir-name) |
|
|
"Return the absolute path to the project's root directory. |
|
|
|
|
|
Use `default-directory' if DIR-NAME is nil. |
|
|
Return nil if not inside a project." |
|
|
(let ((dir-name (or dir-name default-directory))) |
|
|
(let ((lein-project-dir (locate-dominating-file dir-name "project.clj")) |
|
|
(boot-project-dir (locate-dominating-file dir-name "build.boot"))) |
|
|
(when (or lein-project-dir boot-project-dir) |
|
|
(file-truename |
|
|
(cond ((not lein-project-dir) boot-project-dir) |
|
|
((not boot-project-dir) lein-project-dir) |
|
|
(t (if (file-in-directory-p lein-project-dir boot-project-dir) |
|
|
lein-project-dir |
|
|
boot-project-dir)))))))) |
|
|
|
|
|
(defun clojure-project-relative-path (path) |
|
|
"Denormalize PATH by making it relative to the project root." |
|
|
(file-relative-name path (clojure-project-dir))) |
|
|
|
|
|
(defun clojure-expected-ns (&optional path) |
|
|
"Return the namespace matching PATH. |
|
|
|
|
|
PATH is expected to be an absolute file path. |
|
|
|
|
|
If PATH is nil, use the path to the file backing the current buffer." |
|
|
(let* ((path (or path (file-truename (buffer-file-name)))) |
|
|
(relative (clojure-project-relative-path path)) |
|
|
(sans-file-type (substring relative 0 (- (length (file-name-extension path t))))) |
|
|
(sans-file-sep (mapconcat 'identity (cdr (split-string sans-file-type "/")) ".")) |
|
|
(sans-underscores (replace-regexp-in-string "_" "-" sans-file-sep))) |
|
|
;; Drop prefix from ns for projects with structure src/{clj,cljs,cljc} |
|
|
(replace-regexp-in-string "\\`clj[scx]?\\." "" sans-underscores))) |
|
|
|
|
|
(defun clojure-insert-ns-form-at-point () |
|
|
"Insert a namespace form at point." |
|
|
(interactive) |
|
|
(insert (format "(ns %s)" (clojure-expected-ns)))) |
|
|
|
|
|
(defun clojure-insert-ns-form () |
|
|
"Insert a namespace form at the beginning of the buffer." |
|
|
(interactive) |
|
|
(widen) |
|
|
(goto-char (point-min)) |
|
|
(clojure-insert-ns-form-at-point)) |
|
|
|
|
|
(defun clojure-update-ns () |
|
|
"Update the namespace of the current buffer. |
|
|
Useful if a file has been renamed." |
|
|
(interactive) |
|
|
(let ((nsname (clojure-expected-ns))) |
|
|
(when nsname |
|
|
(save-excursion |
|
|
(save-match-data |
|
|
(if (clojure-find-ns) |
|
|
(replace-match nsname nil nil nil 4) |
|
|
(error "Namespace not found"))))))) |
|
|
|
|
|
(defun clojure-find-ns () |
|
|
"Find the namespace of the current Clojure buffer." |
|
|
(let ((regexp clojure-namespace-name-regex)) |
|
|
(save-excursion |
|
|
(save-restriction |
|
|
(widen) |
|
|
(goto-char (point-min)) |
|
|
(when (re-search-forward regexp nil t) |
|
|
(match-string-no-properties 4)))))) |
|
|
|
|
|
(defun clojure-find-def () |
|
|
"Find the var declaration macro and symbol name of the current form. |
|
|
Returns a list pair, e.g. (\"defn\" \"abc\") or (\"deftest\" \"some-test\")." |
|
|
(let ((re (concat "(\\(?:\\(?:\\sw\\|\\s_\\)+/\\)?" |
|
|
;; Declaration |
|
|
"\\(def\\sw*\\)\\>" |
|
|
;; Any whitespace |
|
|
"[ \r\n\t]*" |
|
|
;; Possibly type or metadata |
|
|
"\\(?:#?^\\(?:{[^}]*}\\|\\(?:\\sw\\|\\s_\\)+\\)[ \r\n\t]*\\)*" |
|
|
;; Symbol name |
|
|
"\\(\\(?:\\sw\\|\\s_\\)+\\)"))) |
|
|
(save-excursion |
|
|
(unless (looking-at re) |
|
|
(beginning-of-defun)) |
|
|
(when (search-forward-regexp re nil t) |
|
|
(list (match-string-no-properties 1) |
|
|
(match-string-no-properties 2)))))) |
|
|
|
|
|
|
|
|
;;; Sexp navigation |
|
|
(defun clojure--looking-at-non-logical-sexp () |
|
|
"Return non-nil if sexp after point represents code. |
|
|
Sexps that don't represent code are ^metadata or #reader.macros." |
|
|
(comment-normalize-vars) |
|
|
(comment-forward (point-max)) |
|
|
(looking-at-p "\\^\\|#[?[:alpha:]]")) |
|
|
|
|
|
(defun clojure-forward-logical-sexp (&optional n) |
|
|
"Move forward N logical sexps. |
|
|
This will skip over sexps that don't represent objects, so that ^hints and |
|
|
#reader.macros are considered part of the following sexp." |
|
|
(interactive "p") |
|
|
(if (< n 0) |
|
|
(clojure-backward-logical-sexp (- n)) |
|
|
(let ((forward-sexp-function nil)) |
|
|
(while (> n 0) |
|
|
(while (clojure--looking-at-non-logical-sexp) |
|
|
(forward-sexp 1)) |
|
|
;; The actual sexp |
|
|
(forward-sexp 1) |
|
|
(setq n (1- n)))))) |
|
|
|
|
|
(defun clojure-backward-logical-sexp (&optional n) |
|
|
"Move backward N logical sexps. |
|
|
This will skip over sexps that don't represent objects, so that ^hints and |
|
|
#reader.macros are considered part of the following sexp." |
|
|
(interactive "p") |
|
|
(if (< n 0) |
|
|
(clojure-forward-logical-sexp (- n)) |
|
|
(let ((forward-sexp-function nil)) |
|
|
(while (> n 0) |
|
|
;; The actual sexp |
|
|
(backward-sexp 1) |
|
|
;; Non-logical sexps. |
|
|
(while (and (not (bobp)) |
|
|
(ignore-errors |
|
|
(save-excursion |
|
|
(backward-sexp 1) |
|
|
(clojure--looking-at-non-logical-sexp)))) |
|
|
(backward-sexp 1)) |
|
|
(setq n (1- n)))))) |
|
|
|
|
|
(defconst clojurescript-font-lock-keywords |
|
|
(eval-when-compile |
|
|
`(;; ClojureScript built-ins |
|
|
(,(concat "(\\(?:\.*/\\)?" |
|
|
(regexp-opt '("js-obj" "js-delete" "clj->js" "js->clj")) |
|
|
"\\>") |
|
|
0 font-lock-builtin-face))) |
|
|
"Additional font-locking for `clojurescrip-mode'.") |
|
|
|
|
|
;;;###autoload |
|
|
(define-derived-mode clojurescript-mode clojure-mode "ClojureScript" |
|
|
"Major mode for editing ClojureScript code. |
|
|
|
|
|
\\{clojurescript-mode-map}" |
|
|
(font-lock-add-keywords nil clojurescript-font-lock-keywords)) |
|
|
|
|
|
;;;###autoload |
|
|
(define-derived-mode clojurec-mode clojure-mode "ClojureC" |
|
|
"Major mode for editing ClojureC code. |
|
|
|
|
|
\\{clojurec-mode-map}") |
|
|
|
|
|
(defconst clojurex-font-lock-keywords |
|
|
;; cljx annotations (#+clj and #+cljs) |
|
|
'(("#\\+cljs?\\>" 0 font-lock-preprocessor-face)) |
|
|
"Additional font-locking for `clojurex-mode'.") |
|
|
|
|
|
;;;###autoload |
|
|
(define-derived-mode clojurex-mode clojure-mode "ClojureX" |
|
|
"Major mode for editing ClojureX code. |
|
|
|
|
|
\\{clojurex-mode-map}" |
|
|
(font-lock-add-keywords nil clojurex-font-lock-keywords)) |
|
|
|
|
|
;;;###autoload |
|
|
(progn |
|
|
(add-to-list 'auto-mode-alist |
|
|
'("\\.\\(clj\\|dtm\\|edn\\)\\'" . clojure-mode)) |
|
|
(add-to-list 'auto-mode-alist '("\\.cljc\\'" . clojurec-mode)) |
|
|
(add-to-list 'auto-mode-alist '("\\.cljx\\'" . clojurex-mode)) |
|
|
(add-to-list 'auto-mode-alist '("\\.cljs\\'" . clojurescript-mode)) |
|
|
;; boot build scripts are Clojure source files |
|
|
(add-to-list 'auto-mode-alist '("\\(?:build\\|profile\\)\\.boot\\'" . clojure-mode))) |
|
|
|
|
|
(provide 'clojure-mode) |
|
|
|
|
|
;; Local Variables: |
|
|
;; coding: utf-8 |
|
|
;; indent-tabs-mode: nil |
|
|
;; End: |
|
|
|
|
|
;;; clojure-mode.el ends here
|
|
|
|