|
|
|
|
;;; 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
|