pax_global_header 0000666 0000000 0000000 00000000064 15015275167 0014523 g ustar 00root root 0000000 0000000 52 comment=d336db623e7ae8cffff50aaaea3f1b05cc4ccecb
clojure-mode-5.20.0/ 0000775 0000000 0000000 00000000000 15015275167 0014174 5 ustar 00root root 0000000 0000000 clojure-mode-5.20.0/.circleci/ 0000775 0000000 0000000 00000000000 15015275167 0016027 5 ustar 00root root 0000000 0000000 clojure-mode-5.20.0/.circleci/config.yml 0000664 0000000 0000000 00000005713 15015275167 0020025 0 ustar 00root root 0000000 0000000 version: 2.1
orbs:
win: circleci/windows@2.2.0
commands:
setup:
steps:
- checkout
- run:
name: Install Eldev
command: curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/circle-eldev > x.sh && source ./x.sh
setup-macos:
steps:
- checkout
- run:
name: Install Emacs latest
command: |
brew install homebrew/cask/emacs
- run:
name: Install Eldev
command: curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/circle-eldev > x.sh && source ./x.sh
setup-windows:
steps:
- checkout
- run:
name: Install Eldev
command: |
# Remove expired DST Root CA X3 certificate. Workaround
# for https://debbugs.gnu.org/cgi/bugreport.cgi?bug=51038
# bug on Emacs 27.2.
gci cert:\LocalMachine\Root\DAC9024F54D8F6DF94935FB1732638CA6AD77C13
gci cert:\LocalMachine\Root\DAC9024F54D8F6DF94935FB1732638CA6AD77C13 | Remove-Item
(iwr https://raw.github.com/doublep/eldev/master/webinstall/circle-eldev.ps1).Content | powershell -command -
test:
steps:
- run:
name: Run regression tests
command: eldev -dtT -p test
lint:
steps:
- run:
name: Lint
command: eldev lint -c
compile:
steps:
- run:
name: Check for byte-compilation errors
command: eldev -dtT compile --warnings-as-errors
jobs:
test-ubuntu-emacs-26:
docker:
- image: silex/emacs:26-ci
entrypoint: bash
steps:
- setup
- test
- lint
- compile
test-ubuntu-emacs-27:
docker:
- image: silex/emacs:27-ci
entrypoint: bash
steps:
- setup
- test
- lint
- compile
test-ubuntu-emacs-28:
docker:
- image: silex/emacs:28-ci
entrypoint: bash
steps:
- setup
- test
- lint
- compile
test-ubuntu-emacs-29:
docker:
- image: silex/emacs:29-ci
entrypoint: bash
steps:
- setup
- test
- lint
- compile
test-ubuntu-emacs-master:
docker:
- image: silex/emacs:master-ci
entrypoint: bash
steps:
- setup
- test
- lint
- compile
test-macos-emacs-latest:
macos:
xcode: "14.2.0"
steps:
- setup-macos
- test
- lint
- compile
test-windows-emacs-latest:
executor: win/default
steps:
- run:
name: Install Emacs latest
command: |
choco install emacs -y
- setup-windows
- test
- lint
- compile
workflows:
version: 2.1
ci-test-matrix:
jobs:
- test-ubuntu-emacs-26
- test-ubuntu-emacs-27
- test-ubuntu-emacs-28
- test-ubuntu-emacs-29
- test-ubuntu-emacs-master
- test-windows-emacs-latest
- test-macos-emacs-latest:
requires:
- test-ubuntu-emacs-29
clojure-mode-5.20.0/.dir-locals.el 0000664 0000000 0000000 00000000401 15015275167 0016620 0 ustar 00root root 0000000 0000000 ((nil
(bug-reference-url-format . "https://github.com/clojure-emacs/clojure-mode/issues/%s")
(indent-tabs-mode . nil)
(fill-column . 80)
(checkdoc-arguments-in-order-flag))
(emacs-lisp-mode
(bug-reference-bug-regexp . "#\\(?2:[[:digit:]]+\\)")))
clojure-mode-5.20.0/.github/ 0000775 0000000 0000000 00000000000 15015275167 0015534 5 ustar 00root root 0000000 0000000 clojure-mode-5.20.0/.github/FUNDING.yml 0000664 0000000 0000000 00000000257 15015275167 0017355 0 ustar 00root root 0000000 0000000 # These are supported funding model platforms
github: bbatsov
patreon: bbatsov
ko_fi: bbatsov
open_collective: cider
liberapay: bbatsov
custom: https://www.paypal.me/bbatsov
clojure-mode-5.20.0/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15015275167 0017717 5 ustar 00root root 0000000 0000000 clojure-mode-5.20.0/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000001610 15015275167 0022407 0 ustar 00root root 0000000 0000000 ---
name: Bug Report
about: Report an issue with clojure-mode you've discovered.
labels: [bug]
---
*Use the template below when reporting bugs. Please, make sure that
you're running the latest stable clojure-mode and that the problem you're reporting
hasn't been reported (and potentially fixed) already.*
**Please, remove all of the placeholder text (the one in italics) in your final report!**
## Expected behavior
## Actual behavior
## Steps to reproduce the problem
*This is extremely important! Providing us with a reliable way to reproduce
a problem will expedite its solution.*
## Environment & Version information
### clojure-mode version
*Include here the version string displayed by `M-x
clojure-mode-display-version`. Here's an example:*
```
clojure-mode (version 5.2.0)
```
### Emacs version
*E.g. 24.5* (use C-h C-a to see it)
### Operating system
*E.g. Windows 10*
clojure-mode-5.20.0/.github/PULL_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000001404 15015275167 0021334 0 ustar 00root root 0000000 0000000 **Replace this placeholder text with a summary of the changes in your PR.**
-----------------
Before submitting a PR mark the checkboxes for the items you've done (if you
think a checkbox does not apply, then leave it unchecked):
- [ ] The commits are consistent with our [contribution guidelines][1].
- [ ] You've added tests (if possible) to cover your change(s). Bugfix, indentation, and font-lock tests are extremely important!
- [ ] You've run `M-x checkdoc` and fixed any warnings in the code you've written.
- [ ] You've updated the changelog (if adding/changing user-visible functionality).
- [ ] You've updated the readme (if adding/changing user-visible functionality).
Thanks!
[1]: https://github.com/clojure-emacs/clojure-mode/blob/master/CONTRIBUTING.md
clojure-mode-5.20.0/.gitignore 0000664 0000000 0000000 00000000227 15015275167 0016165 0 ustar 00root root 0000000 0000000 # temporary files
*~
*\#*\#
*.\#*
# Emacs byte-compiled files
*.elc
.cask
elpa*
/clojure-mode-autoloads.el
/clojure-mode-pkg.el
/.eldev
/Eldev-local
clojure-mode-5.20.0/CHANGELOG.md 0000664 0000000 0000000 00000053424 15015275167 0016015 0 ustar 00root root 0000000 0000000 # Changelog
## master (unreleased)
## 5.20.0 (2025-05-27)
### New features
* Add `clojuredart-mode`, `joker-mode` and `jank-mode`, derived from `clojure-mode`.
### Bugs fixed
* [cider#3758](https://github.com/clojure-emacs/cider/issues/3758): Improve regexp for `clojure-find-def` to recognize more complex metadata on vars.
* [#684](https://github.com/clojure-emacs/clojure-mode/issues/684): Restore `outline-regexp` pattern to permit outline handling of top-level forms.
* Improve regexp for `clojure-find-def` to recognize `defn-` and other declarations on the form `def...-`.
## 5.19.0 (2024-05-26)
### Bugs fixed
* Fix `clojure-align` when called from `clojure-ts-mode` major mode buffers.
* [#671](https://github.com/clojure-emacs/clojure-mode/issues/671): Syntax highlighting for digits after the first in `%` args. (e.g. `%10`)
* [#680](https://github.com/clojure-emacs/clojure-mode/issues/680): Change syntax class of ASCII control characters to punctuation, fixing situations where carriage returns were being interpreted as symbols.
# Changes
* [#675](https://github.com/clojure-emacs/clojure-mode/issues/675): Add `.lpy` to the list of known Clojure file extensions.
## 5.18.1 (2023-11-24)
### Bugs fixed
* [#653](https://github.com/clojure-emacs/clojure-mode/issues/653): Don't highlight vars with colons as keywords.
## 5.18.0 (2023-10-18)
### Changes
* [cider#2903](https://github.com/clojure-emacs/cider/issues/2903): Avoid `No comment syntax is defined` prompts.
## 5.17.1 (2023-09-12)
### Changes
* Declare indentation for the `async` ClojureScript macro.
## 5.17.0 (2023-09-11)
### Changes
* Improve support for multiple forms in the same line by replacing `beginning-of-defun` fn.
### Bugs fixed
* [#656](https://github.com/clojure-emacs/clojure-mode/issues/656): Fix `clojure-find-ns` when ns form is preceded by other forms.
* [#593](https://github.com/clojure-emacs/clojure-mode/issues/593): Fix `clojure-find-ns` when ns form is preceded by whitespace or inside comment form.
## 5.16.2 (2023-08-23)
### Changes
* `clojure-find-ns`: add an option to never raise errors, returning `nil` instead on unparseable ns forms.
## 5.16.1 (2023-06-26)
### Changes
* Font-lock Lein's `defproject` as a keyword.
### Bugs fixed
* [#645](https://github.com/clojure-emacs/clojure-mode/issues/645): Fix infinite loop when sorting a ns with comments in the end.
* [#586](https://github.com/clojure-emacs/clojure-mode/issues/586): Fix infinite loop when opening file containing `comment` with `clojure-toplevel-inside-comment-form` set to `t`.
## 5.16.0 (2022-12-14)
### Changes
* [#641](https://github.com/clojure-emacs/clojure-mode/issues/641): Recognize nbb projects (identified by the presence of `nbb.edn`).
* [#629](https://github.com/clojure-emacs/clojure-mode/pull/629): Set `add-log-current-defun-function` to new function `clojure-current-defun-name` (this is used by `which-function-mode` and `easy-kill`).
### Bugs fixed
* [#581](https://github.com/clojure-emacs/clojure-mode/issues/581): Fix font locking not working for keywords starting with a number.
* [#377](https://github.com/clojure-emacs/clojure-mode/issues/377): Fix everything starting with the prefix `def` being highlighted as a definition form. Now definition forms are enumerated explicitly in the font-locking code, like all other forms.
* [#638](https://github.com/clojure-emacs/clojure-mode/pull/638): Fix `imenu` with Clojure code in string or comment.
## 5.15.1 (2022-07-30)
### Bugs fixed
* [#625](https://github.com/clojure-emacs/clojure-mode/issues/625): Fix metadata being displayed in `imenu` instead of var name.
## 5.15.0 (2022-07-19)
### Changes
* [#622](https://github.com/clojure-emacs/clojure-mode/issues/622): Add font locking for missing `clojure.core` macros.
* [#615](https://github.com/clojure-emacs/clojure-mode/issues/615): Support clojure-dart files.
### Bugs fixed
* [#595](https://github.com/clojure-emacs/clojure-mode/issues/595), [#612](https://github.com/clojure-emacs/clojure-mode/issues/612): Fix buffer freezing when typing metadata for a definition.
## 5.14.0 (2022-03-07)
### New features
* Allow additional directories, beyond the default `clj[sc]`, to be correctly formulated by `clojure-expected-ns` via new `defcustom` entitled `clojure-directory-prefixes`
* Recognize babashka projects (identified by the presence of `bb.edn`).
* [#601](https://github.com/clojure-emacs/clojure-mode/pull/601): Add new command `clojure-promote-fn-literal` for converting `#()` function literals to `fn` form.
### Changes
* [#604](https://github.com/clojure-emacs/clojure-mode/pull/604): Add `bb` (babashka) to `interpreter-mode-alist`.
### Bugs fixed
* [#608](https://github.com/clojure-emacs/clojure-mode/issues/608) Fix alignment issue involving margin comments at the end of nested forms.
## 5.13.0 (2021-05-05)
### New features
* [#590](https://github.com/clojure-emacs/clojure-mode/pull/590): Extend `clojure-rename-ns-alias` to work on selected regions.
* [#567](https://github.com/clojure-emacs/clojure-mode/issues/567): Add new commands `clojure-toggle-ignore`, `clojure-toggle-ignore-surrounding-form`, and `clojure-toggle-defun` for inserting/deleting `#_` ignore forms.
* [#582](https://github.com/clojure-emacs/clojure-mode/pull/582): Add `clojure-special-arg-indent-factor` to control special argument indentation.
### Bugs fixed
* [#588](https://github.com/clojure-emacs/clojure-mode/pull/588): Fix font-lock for character literals.
* Stop `clojure-sort-ns` from calling `redisplay`.
### Changes
* [#589](https://github.com/clojure-emacs/clojure-mode/issues/589): Improve font-locking performance on strings with escaped characters.
* [#571](https://github.com/clojure-emacs/clojure-mode/issues/571): Remove `project.el` integration.
* [#574](https://github.com/clojure-emacs/clojure-mode/issues/574): Remove `clojure-view-grimoire` command.
* [#584](https://github.com/clojure-emacs/clojure-mode/issues/584): Align to recent `pcase` changes on Emacs master.
## 5.12.0 (2020-08-13)
### New features
* [#556](https://github.com/clojure-emacs/clojure-mode/issues/556): `clojure-rename-ns-alias` picks up existing aliases for minibuffer completion.
### Bugs fixed
* [#565](https://github.com/clojure-emacs/clojure-mode/issues/565): Fix extra spaces being inserted after quote in paredit-mode.
* [#544](https://github.com/clojure-emacs/clojure-mode/issues/544): Fix docstring detection when string contains backslash.
* [#547](https://github.com/clojure-emacs/clojure-mode/issues/547): Fix font-lock regex for character literals for uppercase chars and other symbols.
* [#556](https://github.com/clojure-emacs/clojure-mode/issues/556): Fix renaming of ns aliases containing regex characters.
* [#555](https://github.com/clojure-emacs/clojure-mode/issues/555): Fix ns detection for ns forms with complex metadata.
* [#550](https://github.com/clojure-emacs/clojure-mode/issues/550): Fix `outline-regexp` so `outline-insert-heading` behaves correctly.
* [#551](https://github.com/clojure-emacs/clojure-mode/issues/551): Indent `clojure-align` region before aligning.
* [#520](https://github.com/clojure-emacs/clojure-mode/issues/508): Fix allow `clojure-align-cond-forms` to recognize qualified forms.
* [#404](https://github.com/clojure-emacs/clojure-mode/issues/404)/[#528]((https://github.com/clojure-emacs/clojure-mode/issues/528)): Fix syntax highlighting for multiple consecutive comment reader macros (`#_#_`).
### Changes
* Inline definition of `clojure-mode-syntax-table` and support `'` quotes in symbols.
* Enhance add arity refactoring to support a `defn` inside a reader conditional.
* Enhance add arity refactoring to support new forms: `letfn`, `fn`, `defmacro`, `defmethod`, `defprotocol`, `reify` and `proxy`.
## 5.11.0 (2019-07-16)
### New features
* [#496](https://github.com/clojure-emacs/clojure-mode/issues/496): Highlight `[[wikilinks]]` in comments.
* [#366](https://github.com/clojure-emacs/clj-refactor.el/issues/366): Add support for renaming ns aliases (`clojure-rename-ns-alias`, default binding `C-c C-r n r`).
* [#410](https://github.com/clojure-emacs/clojure-mode/issues/410): Add support for adding an arity to a function (`clojure-add-arity`, default binding `C-c C-r a`).
### Bugs fixed
* Dynamic vars whose names contain non-alphanumeric characters are now font-locked correctly.
* [#445 (comment)](https://github.com/clojure-emacs/clojure-mode/issues/445#issuecomment-340460753): Proper font-locking for namespaced keywords like for example `(s/def ::keyword)`.
* [#508](https://github.com/clojure-emacs/clojure-mode/issues/508): Fix font-locking for namespaces with metadata.
* [#506](https://github.com/clojure-emacs/clojure-mode/issues/506): `clojure-mode-display-version` correctly displays the package's version.
* [#445](https://github.com/clojure-emacs/clojure-mode/issues/445), [#405](https://github.com/clojure-emacs/clojure-mode/issues/405), [#469](https://github.com/clojure-emacs/clojure-mode/issues/469): Correct font-locking on string definitions with docstrings, e.g: `(def foo "doc" "value")`. Correct indentation as well.
* [#518](https://github.com/clojure-emacs/clojure-mode/issues/518): Fix `clojure-find-ns` when there's an `ns` form inside a string.
* [#530](https://github.com/clojure-emacs/clojure-mode/pull/530): Prevent electric indentation within inlined docstrings.
### Changes
* [#524](https://github.com/clojure-emacs/clojure-mode/issues/524): Add proper indentation rule for `delay` (same as for `future`).
* [#538](https://github.com/clojure-emacs/clojure-mode/pull/538): Refactor `clojure-unwind` to take numeric prefix argument for unwinding N steps, and universal argument for unwinding completely. The dedicated `C-c C-r a` binding for `clojure-unwind-all`is now removed and replaced with the universal arg convention `C-u C-c C-r u`.
## 5.10.0 (2019-01-05)
### New features
* Recognize Gradle projects using the new Kotlin DSL (`build.gradle.kts`).
* [#481](https://github.com/clojure-emacs/clojure-mode/issues/481): Support vertical alignment even in the presence of blank lines, with the new `clojure-align-separator` user option.
* [#483](https://github.com/clojure-emacs/clojure-mode/issues/483): Support alignment for reader conditionals, with the new `clojure-align-reader-conditionals` user option.
* [#497](https://github.com/clojure-emacs/clojure-mode/pull/497): Indent "let", "when" and "while" as function form if not at start of a symbol.
### Bugs fixed
* [#489](https://github.com/clojure-emacs/clojure-mode/issues/489): Inserting parens before comment form doesn't move point.
* [#500](https://github.com/clojure-emacs/clojure-mode/pull/500): Fix project.el integration.
* [#513](https://github.com/clojure-emacs/clojure-mode/pull/513): Fix incorrect indentation of namespaced map.
### Changes
* Change the accepted values of `clojure-indent-style` from keywords to symbols.
* [#503](https://github.com/clojure-emacs/clojure-mode/pull/503): Fix Makefile so that we can compile again.
## 5.9.1 (2018-08-27)
* [#485](https://github.com/clojure-emacs/clojure-mode/issues/485): Fix a regression in `end-f-defun`.
## 5.9.0 (2018-08-18)
### Changes
* Add `clojure-toplevel-inside-comment-form` to make forms inside of `(comment ...)` forms appear as top level forms for evaluation and navigation.
* Require Emacs 25.1+.
## 5.8.2 (2018-08-09)
### Changes
* Disable ns caching by default.
## 5.8.1 (2018-07-03)
### Bugs fixed
* Fix the project.el integration.
## 5.8.0 (2018-06-26)
### New features
* New interactive commands `clojure-show-cache` and `clojure-clear-cache`.
* Add basic integration with `project.el`.
* The results of `clojure-project-dir` are cached by default to optimize performance.
* [#478](https://github.com/clojure-emacs/clojure-mode/issues/478): Cache the result of `clojure-find-ns` to optimize performance.
### Changes
* Indent `fdef` (clojure.spec) like a `def`.
* Add `shadow-cljs.edn` to the default list of build tool files.
## 5.7.0 (2018-04-29)
### New features
* Add imenu support for multimethods.
* Make imenu recognize indented def-forms.
* New interactive command `clojure-cycle-when`.
* New interactive command `clojure-cycle-not`.
* New defcustom `clojure-comment-regexp` for font-locking `#_` or `#_` AND `(comment)` sexps.
* [#459](https://github.com/clojure-emacs/clojure-mode/issues/459): Add font-locking for new built-ins added in Clojure 1.9.
* [#471](https://github.com/clojure-emacs/clojure-mode/issues/471): Support tagged maps (new in Clojure 1.9) in paredit integration.
* Consider `deps.edn` a project root.
* [#467](https://github.com/clojure-emacs/clojure-mode/issues/467): Make `prog-mode-map` the parent keymap for `clojure-mode-map`.
### Changes
* Drop support for CLJX.
* Remove special font-locking of Java interop methods & constants: There is no semantic distinction between interop methods, constants and global vars in Clojure.
### Bugs fixed
* [#458](https://github.com/clojure-emacs/clojure-mode/pull/458): Get correct ns when in middle of ns form with `clojure-find-ns`
* [#447](https://github.com/clojure-emacs/clojure-mode/issues/241): When `electric-indent-mode` is on, force indentation from within docstrings.
* [#438](https://github.com/clojure-emacs/clojure-mode/issues/438): Filling within a doc-string doesn't affect surrounding code.
* Fix fill-paragraph in multi-line comments.
* [#443](https://github.com/clojure-emacs/clojure-mode/issues/443): Fix behavior of `clojure-forward-logical-sexp` and `clojure-backward-logical-sexp` with conditional macros.
* [#429](https://github.com/clojure-emacs/clojure-mode/issues/429): Fix a bug causing last occurrence of expression sometimes is not replaced when using `move-to-let`.
* [#423](https://github.com/clojure-emacs/clojure-mode/issues/423): Make `clojure-match-next-def` more robust against zero-arity def-like forms.
* [#451](https://github.com/clojure-emacs/clojure-mode/issues/451): Make project root directory calculation customized by `clojure-project-root-function`.
* Fix namespace font-locking: namespaces may also contain non alphanumeric chars.
## 5.6.1 (2016-12-21)
### Bugs fixed
* Make `clojure--read-let-bindings` more robust so `let` related refactorings do not bail on an incorrectly formatted binding form.
## 5.6.0 (2016-11-18)
### New features
* New interactive command `clojure-mode-report-bug`.
* New interactive command `clojure-view-guide`.
* New interactive command `clojure-view-reference-section`.
* New interactive command `clojure-view-cheatsheet`.
* New interactive command `clojure-view-grimoire`.
* New interactive command `clojure-view-style-guide`.
* Make the refactoring keymap prefix customizable via `clojure-refactor-map-prefix`.
* Port and rework `let`-related features from `clj-refactor`. Available features: introduce `let`, move to `let`, forward slurp form into `let`, backward slurp form into `let`.
### Changes
* `clojure-mode` now requires Emacs 24.4.
## 5.5.2 (2016-08-03)
### Bugs fixed
* [#399](https://github.com/clojure-emacs/clojure-mode/issues/399): Fix fontification of prefix characters inside keywords.
## 5.5.1 (2016-07-25)
### Bugs fixed
* [#394](https://github.com/clojure-emacs/clojure-mode/issues/394): `?` character is now treated as prefix when outside symbols.
* [#394](https://github.com/clojure-emacs/clojure-mode/issues/394): `#` character now has prefix syntax class.
* Fixed indentation of `definterface` to match that of `defprotocol`.
* [#389](https://github.com/clojure-emacs/clojure-mode/issues/389): Fixed the indentation of `defrecord` and `deftype` multiple airity protocol forms.
* [#393](https://github.com/clojure-emacs/clojure-mode/issues/393): `imenu-generic-expression` is no longer hard-coded and its global value is respected.
## 5.5.0 (2016-06-25)
### New features
* Port cycle privacy, cycle collection type and cycle if/if-not from clj-refactor.el.
* Rework cycle collection type into convert collection to list, quoted list, map, vector, set.
## 5.4.0 (2016-05-21)
### New features
* When aligning forms with `clojure-align` (or with the automatic align feature), blank lines will divide alignment regions.
* [#378](https://github.com/clojure-emacs/clojure-mode/issues/378): Font-lock escape characters in strings.
* Port threading macros related features from clj-refactor.el. Available refactorings: thread, unwind, thread first all, thread last all, unwind all.
* New command: `clojure-sort-ns`.
* All ns manipulation commands have keybindings under `C-c C-r n`.
## 5.3.0 (2016-04-04)
### Bugs fixed
* [#371](https://github.com/clojure-emacs/clojure-mode/issues/371): Don't font-lock `:foo/def` like a `def` form.
* [#367](https://github.com/clojure-emacs/clojure-mode/issues/367): `clojure-align` no longer gets confused with commas. In fact, now it even removes extra commas.
### New features
* [#370](https://github.com/clojure-emacs/clojure-mode/issues/370): Warn the user if they seem to have activated the wrong major-mode.
* Make the expected ns function configurable via `clojure-expected-ns-function`.
## 5.2.0 (2016-02-04)
### Bugs fixed
* [#361](https://github.com/clojure-emacs/clojure-mode/issues/361): Fixed a typo preventing the highlighting of fn names that don't start with `t`.
* [#360](https://github.com/clojure-emacs/clojure-mode/issues/360): `clojure-align` now reindents after aligning, which also fixes an issue with nested alignings.
### New features
* [#362](https://github.com/clojure-emacs/clojure-mode/issues/362): New custom option `clojure-indent-style` offers 3 different ways to indent code.
## 5.1.0 (2016-01-04)
### New features
* Vertically align sexps with `C-c SPC`. This can also be done automatically (as part of indentation) by turning on `clojure-align-forms-automatically`.
* Indent and font-lock forms that start with `let-`, `while-` or `when-` like their counterparts.
* Apply the `font-lock-comment-face` to code commented out with `#_`.
* Add indentation config for ClojureScript's `this-as`.
### Bugs fixed
* Namespaces can now use the full palette of legal symbol characters.
* Namespace font-locking according to `clojure.lang.LispReader`.
* Fixed the indentation for `specify` and `specify!`.
* Fixed the docstring indentation for `defprotocol`.
## 5.0.1 (2015-11-15)
### Bugs fixed
* Don't treat the symbol `default-(something)` as def* macro.
* `cider-find-ns` now returns the closest `ns` instead of the first one.
* [#344](https://github.com/clojure-emacs/clojure-mode/issues/344): Fixed the indentation of `extend-type`.
## 5.0.0 (2015-10-30)
### New features
* [#302](https://github.com/clojure-emacs/clojure-mode/pull/302): Add new sexp navigation commands. `clojure-forward-logical-sexp` and `clojure-backward-logical-sexp` consider `^hints` and `#reader.macros` to be part of the sexp that follows them.
* [#303](https://github.com/clojure-emacs/clojure-mode/issues/303): Handle `boot` projects in `clojure-expected-ns`.
* Added dedicated modes for ClojureScript, ClojureC and ClojureX. All of them are derived from `clojure-mode`.
* Added support for Gradle projects.
* Vastly improved indentation engine.
* Added support for reader conditionals.
* Improved font-locking of namespaced symbols.
### Bugs fixed
* [#310](https://github.com/clojure-emacs/clojure-mode/issues/310) and [#311](https://github.com/clojure-emacs/clojure-mode/issues/311) Fix `clojure-expected-ns` in multi-source projects.
* [#307](https://github.com/clojure-emacs/clojure-mode/issues/307): Don't highlight `handle` and `handler-case` as keywords.
* Fix font-locking for def with special chars such as: `defn*`, `defspecial!`.
* Numerous indentation issues.
## 4.1.0 (2015-06-20)
### Changes
* Add `.cljc` to `auto-mode-alist`.
* [#281](https://github.com/clojure-emacs/clojure-mode/pull/281): Add support for namespace-prefixed definition forms.
* Remove `clojure-mark-string`.
* [#283](https://github.com/clojure-emacs/clojure-mode/pull/283): You can now specify different indentation settings for ns-prefixed symbols.
* [#285](https://github.com/clojure-emacs/clojure-mode/issues/285): Require Emacs 24.3+.
### Bugs fixed
* Prevent error when calling `indent-for-tab-command` at the start of
the buffer at end of line.
* [#274](https://github.com/clojure-emacs/clojure-mode/issues/274): Correct font-locking of certain punctuation character literals.
* Fix font-locking of namespace-prefixed dynamic vars (e.g. `some.ns/*var*`).
* [#284](https://github.com/clojure-emacs/clojure-mode/issues/284): Fix the indentation of the `are` macro.
## 4.0.1 (2014-12-19)
### Bugs fixed
* Indent properly `as->`.
* Revert the indentation settings for `->`, `->>`, `some->` and `some->>`.
## 4.0.0 (2014-12-12)
### Changes
* Removed `inferior-lisp` integration in favor of `inf-clojure`.
* Indented the body of `cond` with 2 spaces.
* Removed special indentation settings for `defstruct`, `struct-map` and `assoc`.
* Added special indentation settings for `->`, `->>`, `cond->`, `cond->>`, `some->` and `some->>`.
## 3.0.1 (2014-11-24)
### Bugs fixed
* Numerous font-lock bug fixes.
* [#260](https://github.com/clojure-emacs/clojure-mode/pull/260): Don't treat `@` as a word character.
* [#239](https://github.com/clojure-emacs/clojure-mode/issues/239): Indent properly multi-arity definitions.
## 3.0.0 (2014-09-02)
### New features
* Added font-locking for namespaces and namespace aliases.
* Added font-locking for character literals.
* Added font-locking for constants.
* Added font-locking for dynamic vars.
* Added font-locking for `cljx`.
* Various docstring filling improvements.
* Introduced additional faces for keyword literals, character literals and
interop method invocations.
* Added support for `prettify-symbols-mode`.
### Changes
* Emacs 24.1 is required.
* Removed deprecated `clojure-font-lock-comment-sexp`.
* Renamed `clojure-mode-font-lock-setup` to `clojure-font-lock-setup`.
* Some font-locking was extracted to a separate package. ([clojure-mode-extra-font-locking](https://github.com/clojure-emacs/clojure-mode/blob/master/clojure-mode-extra-font-locking.el)).
### Bugs fixed
* Properly font-lock docstrings regardless of the presence of metadata or type hints.
clojure-mode-5.20.0/CONTRIBUTING.md 0000664 0000000 0000000 00000004604 15015275167 0016431 0 ustar 00root root 0000000 0000000 # Contributing
If you discover issues, have ideas for improvements or new features,
please report them to the [issue tracker][1] of the repository or
submit a pull request. Please, try to follow these guidelines when you
do so.
## Issue reporting
* Check that the issue has not already been reported.
* Check that the issue has not already been fixed in the latest code
(a.k.a. `master`).
* Be clear, concise and precise in your description of the problem.
* Open an issue with a descriptive title and a summary in grammatically correct,
complete sentences.
* Mention your Emacs version and operating system.
* Mention `clojure-mode`'s version info (`M-x clojure-mode-version-info`), e.g.:
```el
clojure-mode (version 2.1.1)
```
* Include any relevant code to the issue summary.
## Pull requests
* Read [how to properly contribute to open source projects on Github][2].
* Use a topic branch to easily amend a pull request later, if necessary.
* Write [good commit messages][3].
* Mention related tickets in the commit messages (e.g. `[Fix #N] Font-lock properly ...`)
* Update the [changelog][6].
* Use the same coding conventions as the rest of the project.
* Verify your Emacs Lisp code with `checkdoc` (C-c ? d).
* [Squash related commits together][5].
* Open a [pull request][4] that relates to *only* one subject with a clear title
and description in grammatically correct, complete sentences.
* When applicable, attach ERT unit tests. See below for instructions on running the tests.
## Development setup
1. Fork and clone the repository.
1. Install [Eldev][7].
1. Run `eldev build` in the repository folder.
1. Run tests with `make test`.
**Note:** macOS users should make sure that the `emacs` command resolves the version of Emacs they've installed
manually (e.g. via `homebrew`), instead of the ancient Emacs 22 that comes bundled with macOS.
See [this article][8] for more details.
[1]: https://github.com/clojure-emacs/clojure-mode/issues
[2]: https://gun.io/blog/how-to-github-fork-branch-and-pull-request
[3]: https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[4]: https://help.github.com/articles/using-pull-requests
[5]: https://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html
[6]: https://github.com/clojure-emacs/clojure-mode/blob/master/CHANGELOG.md
[7]: https://github.com/emacs-eldev/eldev
[8]: https://emacsredux.com/blog/2015/05/09/emacs-on-os-x/
clojure-mode-5.20.0/Eldev 0000664 0000000 0000000 00000001501 15015275167 0015153 0 ustar 00root root 0000000 0000000 ; -*- mode: emacs-lisp; lexical-binding: t -*-
(eldev-require-version "1.6")
(eldev-use-package-archive 'gnu-elpa)
(eldev-use-package-archive 'nongnu-elpa)
(eldev-use-package-archive 'melpa)
(eldev-use-plugin 'autoloads)
(eldev-add-extra-dependencies 'test 'paredit 's 'buttercup)
(setq byte-compile-docstring-max-column 240)
(setq checkdoc-force-docstrings-flag nil)
(setq checkdoc-permit-comma-termination-flag t)
(setq checkdoc--interactive-docstring-flag nil)
(setf eldev-lint-default '(elisp))
(with-eval-after-load 'elisp-lint
;; We will byte-compile with Eldev.
(setf elisp-lint-ignored-validators '("package-lint" "fill-column" "byte-compile" "checkdoc")
enable-local-variables :safe
elisp-lint-indent-specs '((define-clojure-indent . 0))))
(setq eldev-project-main-file "clojure-mode.el")
clojure-mode-5.20.0/Makefile 0000664 0000000 0000000 00000000413 15015275167 0015632 0 ustar 00root root 0000000 0000000 .PHONY: clean compile lint test all
.DEFAULT_GOAL := all
clean:
eldev clean
lint: clean
eldev lint -c
# Checks for byte-compilation warnings.
compile: clean
eldev -dtT compile --warnings-as-errors
test: clean
eldev -dtT -p test
all: clean compile lint test
clojure-mode-5.20.0/README.md 0000664 0000000 0000000 00000053653 15015275167 0015467 0 ustar 00root root 0000000 0000000 [![circleci][badge-circleci]][circleci]
[![MELPA][melpa-badge]][melpa-package]
[![MELPA Stable][melpa-stable-badge]][melpa-stable-package]
[](https://elpa.nongnu.org/nongnu/clojure-mode.html)
[](https://discord.com/invite/nFPpynQPME)
[![License GPL 3][badge-license]][copying]
# Clojure Mode
`clojure-mode` is an Emacs major mode that provides font-lock (syntax
highlighting), indentation, navigation and refactoring support for the
[Clojure(Script) programming language](https://clojure.org).
-----------
**This documentation tracks the `master` branch of `clojure-mode`. Some of
the features and settings discussed here might not be available in
older releases (including the current stable release). Please, consult
the relevant git tag (e.g. [5.19.0](https://github.com/clojure-emacs/clojure-mode/tree/v5.19.0)) if you need documentation for a
specific `clojure-mode` release.**
## Installation
Available on the major `package.el` community maintained repos -
[MELPA Stable][] and [MELPA][] repos.
MELPA Stable is the recommended repo as it has the latest stable
version. MELPA has a development snapshot for users who don't mind
(infrequent) breakage but don't want to run from a git checkout.
You can install `clojure-mode` using the following command:
M-x `package-install` [RET] `clojure-mode` [RET]
or if you'd rather keep it in your dotfiles:
```el
(unless (package-installed-p 'clojure-mode)
(package-install 'clojure-mode))
```
If the installation doesn't work try refreshing the package list:
M-x `package-refresh-contents`
## Bundled major modes
The `clojure-mode` package actually bundles together several major modes:
* `clojure-mode` is a major mode for editing Clojure code
* `clojurescript-mode` is a major mode for editing ClojureScript code
* `clojurec-mode` is a major mode for editing `.cljc` source files
* `clojuredart-mode` is a major mode for editing ClojureDart `.cljd` source files
* `jank-mode` is a major mode for editing Jank `.jank` source files
* `joker-mode` is a major mode for editing Joker `.joke` source files
All the major modes derive from `clojure-mode` and provide more or less the same
functionality. Differences can be found mostly in the font-locking -
e.g. ClojureScript has some built-in constructs that are not present in Clojure.
The proper major mode is selected automatically based on the extension of the
file you're editing.
Having separate major modes gives you the flexibility to attach different hooks
to them and to alter their behavior individually (e.g. add extra font-locking
just to `clojurescript-mode`) .
Note that all modes derive from `clojure-mode`, so things you add to
`clojure-mode-hook` and `clojure-mode-map` will affect all the derived modes as
well.
## Configuration
In the spirit of Emacs, pretty much everything you can think of in `clojure-mode` is configurable.
To see a list of available configuration options do `M-x customize-group RET clojure`.
### Indentation options
The default indentation rules in `clojure-mode` are derived from the
[community Clojure Style Guide](https://guide.clojure.style).
Please, refer to the guide for the general Clojure indentation rules.
If you'd like to use the alternative "fixed/tonsky" indentation style you should
update your configuration accordingly:
``` el
(setq clojure-indent-style 'always-indent
clojure-indent-keyword-style 'always-indent
clojure-enable-indent-specs nil)
```
Read on for more details on the available indentation-related configuration options.
#### Indentation of docstrings
By default multi-line docstrings are indented with 2 spaces, as this is a
somewhat common standard in the Clojure community. You can however adjust this
by modifying `clojure-docstring-fill-prefix-width`. Set it to 0 if you don't
want multi-line docstrings to be indented at all (which is pretty common in most lisps).
#### Indentation of function forms
The indentation of function forms is configured by the variable
`clojure-indent-style`. It takes three possible values:
- `always-align` (the default)
```clj
(some-function
10
1
2)
(some-function 10
1
2)
```
- `always-indent`
```clj
(some-function
10
1
2)
(some-function 10
1
2)
```
- `align-arguments`
```clj
(some-function
10
1
2)
(some-function 10
1
2)
```
**Note:** Prior to clojure-mode 5.10, the configuration options for `clojure-indent-style` used to be
keywords, but now they are symbols. Keywords will still be supported at least until clojure-mode 6.
#### Indentation of keywords
Similarly we have the `clojure-indent-keyword-style`, which works in the following way:
* `always-align` (default) - All
args are vertically aligned with the first arg in case (A),
and vertically aligned with the function name in case (B).
``` clojure
(:require [foo.bar]
[bar.baz])
(:require
[foo.bar]
[bar.baz])
```
* `always-indent` - All args are indented like a macro body.
``` clojure
(:require [foo.bar]
[bar.baz])
(:x
location
0)
```
* `align-arguments` - Case (A) is indented like `always-align`, and
case (B) is indented like a macro body.
``` clojure
(:require [foo.bar]
[bar.baz])
(:x
location
0)
```
#### Indentation of macro forms
The indentation of special forms and macros with bodies is controlled via
`put-clojure-indent`, `define-clojure-indent` and `clojure-backtracking-indent`.
Nearly all special forms and built-in macros with bodies have special indentation
settings in `clojure-mode`. You can add/alter the indentation settings in your
personal config. Let's assume you want to indent `->>` and `->` like this:
```clojure
(->> something
ala
bala
portokala)
```
You can do so by putting the following in your config:
```el
(put-clojure-indent '-> 1)
(put-clojure-indent '->> 1)
```
This means that the body of the `->/->>` is after the first argument.
A more compact way to do the same thing is:
```el
(define-clojure-indent
(-> 1)
(->> 1))
```
To indent something like a definition (`defn`) you can do something like:
``` el
(put-clojure-indent '>defn :defn)
```
You can also specify different indentation settings for symbols
prefixed with some ns (or ns alias):
```el
(put-clojure-indent 'do 0)
(put-clojure-indent 'my-ns/do 1)
```
The bodies of certain more complicated macros and special forms
(e.g. `letfn`, `deftype`, `extend-protocol`, etc) are indented using
a contextual backtracking indentation method, require more sophisticated
indent specifications. Here are a few examples:
```el
(define-clojure-indent
(implement '(1 (1)))
(letfn '(1 ((:defn)) nil))
(proxy '(2 nil nil (1)))
(reify '(:defn (1)))
(deftype '(2 nil nil (1)))
(defrecord '(2 nil nil (1)))
(specify '(1 (1)))
(specify '(1 (1))))
```
These follow the same rules as the `:style/indent` metadata specified by [cider-nrepl][].
For instructions on how to write these specifications, see
[this document](https://docs.cider.mx/cider/indent_spec.html).
The only difference is that you're allowed to use lists instead of vectors.
The indentation of [special arguments](https://docs.cider.mx/cider/indent_spec.html#special-arguments) is controlled by
`clojure-special-arg-indent-factor`, which by default indents special arguments
a further `lisp-body-indent` when compared to ordinary arguments.
An example of the default formatting is:
```clojure
(defrecord MyRecord
[my-field])
```
Note that `defrecord` has two special arguments, followed by the form's body -
namely the record's name and its fields vector.
Setting `clojure-special-arg-indent-factor` to 1, results in:
```clojure
(defrecord MyRecord
[my-field])
```
You can completely disable the effect of indentation specs like this:
``` el
(setq clojure-enable-indent-specs nil)
```
#### Indentation of Comments
`clojure-mode` differentiates between comments like `;`, `;;`, etc.
By default `clojure-mode` treats `;` as inline comments and *always* indents those.
You can change this behaviour like this:
```emacs-lisp
(add-hook 'clojure-mode-hook (lambda () (setq-local comment-column 0)))
```
You might also want to change `comment-add` to 0 in that way, so that Emacs comment
functions (e.g. `comment-region`) would use `;` by default instead of `;;`.
**Note:** Check out [this section](https://guide.clojure.style/#comments) of the Clojure style guide to understand better the semantics of the different comment levels and why `clojure-mode` treats them differently by default.
### Vertical alignment
You can vertically align sexps with `C-c SPC`. For instance, typing
this combo on the following form:
```clj
(def my-map
{:a-key 1
:other-key 2})
```
Leads to the following:
```clj
(def my-map
{:a-key 1
:other-key 2})
```
This can also be done automatically (as part of indentation) by
turning on `clojure-align-forms-automatically`. This way it will
happen whenever you select some code and hit `TAB`.
### Font-locking
`clojure-mode` features static font-locking (syntax highlighting) that you can extend yourself
if needed. As typical for Emacs, it's based on regular expressions. You can find
the default font-locking rules in `clojure-font-lock-keywords`. Here's how you can add font-locking for built-in Clojure functions and vars:
``` el
(defvar clojure-built-in-vars
'(;; clojure.core
"accessor" "aclone"
"agent" "agent-errors" "aget" "alength" "alias"
"all-ns" "alter" "alter-meta!" "alter-var-root" "amap"
;; omitted for brevity
))
(defvar clojure-built-in-dynamic-vars
'(;; clojure.test
"*initial-report-counters*" "*load-tests*" "*report-counters*"
"*stack-trace-depth*" "*test-out*" "*testing-contexts*" "*testing-vars*"
;; clojure.xml
"*current*" "*sb*" "*stack*" "*state*"
))
(font-lock-add-keywords 'clojure-mode
`((,(concat "(\\(?:\.*/\\)?"
(regexp-opt clojure-built-in-vars t)
"\\>")
1 font-lock-builtin-face)))
(font-lock-add-keywords 'clojure-mode
`((,(concat "\\<"
(regexp-opt clojure-built-in-dynamic-vars t)
"\\>")
0 font-lock-builtin-face)))
```
**Note:** The package `clojure-mode-extra-font-locking` provides such additional
font-locking for Clojure built-ins.
As you might imagine one problem with this font-locking approach is that because
it's based on regular expressions you'll get some false positives here and there
(there's no namespace information, and no way for `clojure-mode` to know what
var a symbol resolves to). That's why `clojure-mode`'s font-locking defaults are
conservative and minimalistic.
Precise font-locking requires additional data that can obtained from a running
REPL (that's how CIDER's [dynamic font-locking](https://docs.cider.mx/cider/config/syntax_highlighting.html) works) or from static code analysis.
When it comes to non built-in definitions, `clojure-mode` needs to be manually instructed how to handle the docstrings and highlighting. Here's an example:
``` emacs-lisp
(put '>defn 'clojure-doc-string-elt 2)
(font-lock-add-keywords 'clojure-mode
`((,(concat "(\\(?:" clojure--sym-regexp "/\\)?"
"\\(>defn\\)\\>")
1 font-lock-keyword-face)))
```
**Note:** The `clojure-doc-string-elt` attribute is processed by the function `clojure-font-lock-syntactic-face-function`.
## Refactoring support
The available refactorings were originally created and maintained by the
`clj-refactor.el` team. The ones implemented in Elisp only are gradually migrated
to `clojure-mode`.
### Threading macros related features
`clojure-thread`: Thread another form into the surrounding thread. Both `->>`
and `->` variants are supported.
`clojure-unwind`: Unwind a threaded expression. Supports both `->>` and `->`.
`clojure-thread-first-all`: Introduce the thread first macro (`->`) and rewrite
the entire form. With a prefix argument do not thread the last form.
`clojure-thread-last-all`: Introduce the thread last macro and rewrite the
entire form. With a prefix argument do not thread the last form.
`clojure-unwind-all`: Fully unwind a threaded expression removing the threading
macro.
### Cycling things
`clojure-cycle-privacy`: Cycle privacy of `def`s or `defn`s. Use metadata
explicitly with setting `clojure-use-metadata-for-privacy` to `t` for `defn`s
too.
`clojure-cycle-not`: Add or remove a `not` form around the current form.
`clojure-cycle-when`: Find the closest `when` or `when-not` up the syntax tree
and toggle it.
`clojure-cycle-if`: Find the closest `if` or `if-not` up the syntax tree and
toggle it. Also transpose the `else` and `then` branches, keeping the semantics
the same as before.
### Convert collection
Convert any given collection at point to list, quoted list, map, vector or set.
### Let expression
`clojure-introduce-let`: Introduce a new `let` form. Put the current form into
its binding form with a name provided by the user as a bound name. If called
with a numeric prefix put the let form Nth level up in the form hierarchy.
`clojure-move-to-let`: Move the current form to the closest `let`'s binding
form. Replace all occurrences of the form in the body of the let.
`clojure-let-forward-slurp-sexp`: Slurp the next form after the `let` into the
`let`. Replace all occurrences of the bound forms in the form added to the `let`
form. If called with a prefix argument slurp the next n forms.
`clojure-let-backward-slurp-sexp`: Slurp the form before the `let` into the
`let`. Replace all occurrences of the bound forms in the form added to the `let`
form. If called with a prefix argument slurp the previous n forms.
`paredit-convolute-sexp` is advised to replace occurrences of bound forms with their bound names when convolute is used on a let form.
### Rename ns alias
`clojure-rename-ns-alias`: Rename an alias inside a namespace declaration,
and all of its usages in the buffer
If there is an active selected region, only rename usages of aliases within the region,
without affecting the namespace declaration.
### Add arity to a function
`clojure-add-arity`: Add a new arity to an existing single-arity or multi-arity function.
## Related packages
* [clojure-mode-extra-font-locking][] provides additional font-locking
for built-in methods and macros. The font-locking is pretty
imprecise, because it doesn't take namespaces into account and it
won't font-lock a function at all possible positions in a sexp, but
if you don't mind its imperfections you can easily enable it:
```el
(require 'clojure-mode-extra-font-locking)
```
The code in `clojure-mode-font-locking` used to be bundled with
`clojure-mode` before version 3.0.
You can also use the code in this package as a basis for extending the
font-locking further (e.g. functions/macros from more
namespaces). Generally you should avoid adding special font-locking
for things that don't have fairly unique names, as this will result in
plenty of incorrect font-locking. CIDER users should avoid this package,
as CIDER does its own dynamic font-locking, which is namespace-aware
and doesn't produce almost any false positives.
* [clj-refactor][] provides refactoring support.
* Enabling `CamelCase` support for editing commands(like
`forward-word`, `backward-word`, etc) in `clojure-mode` is quite
useful since we often have to deal with Java class and method
names. The built-in Emacs minor mode `subword-mode` provides such
functionality:
```el
(add-hook 'clojure-mode-hook #'subword-mode)
```
* The use of [paredit][] when editing Clojure (or any other Lisp) code
is highly recommended. It helps ensure the structure of your forms is
not compromised and offers a number of operations that work on code
structure at a higher level than just characters and words. To enable
it for Clojure buffers:
```el
(add-hook 'clojure-mode-hook #'paredit-mode)
```
* [smartparens][] is an excellent
(newer) alternative to paredit. Many Clojure hackers have adopted it
recently and you might want to give it a try as well. To enable
`smartparens` use the following code:
```el
(add-hook 'clojure-mode-hook #'smartparens-strict-mode)
```
* [RainbowDelimiters][] is a
minor mode which highlights parentheses, brackets, and braces
according to their depth. Each successive level is highlighted in a
different color. This makes it easy to spot matching delimiters,
orient yourself in the code, and tell which statements are at a
given depth. Assuming you've already installed `RainbowDelimiters` you can
enable it like this:
```el
(add-hook 'clojure-mode-hook #'rainbow-delimiters-mode)
```
* [aggressive-indent-mode][] automatically adjust the indentation of your code,
while you're writing it. Using it together with `clojure-mode` is highly
recommended. Provided you've already installed `aggressive-indent-mode` you can
enable it like this:
```el
(add-hook 'clojure-mode-hook #'aggressive-indent-mode)
```
## REPL Interaction
One of the fundamental aspects of Lisps in general, and Clojure in
particular, is the notion of interactive programming - building your
programs by continuously changing the state of the running Lisp
program (as opposed to doing something more traditional like making a
change and re-running the program afterwards to see the changes in
action). To get the most of clojure-mode you'll have to combine it
with some tool which will allow you to interact with your Clojure program
(a.k.a. process/REPL).
A number of options exist for connecting to a
running Clojure process and evaluating code interactively.
### Basic REPL
[inf-clojure][] provides basic interaction with a Clojure REPL process.
It's very similar in nature and supported functionality to `inferior-lisp-mode`
for Common Lisp.
### CIDER
[CIDER][] is a powerful Clojure interactive development environment,
similar to SLIME for Common Lisp.
If you're into Clojure and Emacs you should definitely check it out.
## Tutorials
Tutorials,
targeting Emacs beginners, are available at
[clojure-doc.org](https://clojure-doc.org/articles/tutorials/editors/) and
[Clojure for the Brave and the True](https://www.braveclojure.com/basic-emacs/).
Keep in mind, however, that they might be out-of-date.
## Caveats
`clojure-mode` is a capable tool, but it's certainly not perfect. This section
lists a couple of general design problems/limitations that might affect your
experience negatively.
### General Issues
`clojure-mode` derives a lot of functionality directly from `lisp-mode` (an Emacs major mode for Common Lisp), which
simplified the initial implementation, but also made it harder to implement
certain functionality. Down the road it'd be nice to fully decouple `clojure-mode`
from `lisp-mode`.
See [this ticket](https://github.com/clojure-emacs/clojure-mode/issues/270) for a bit more details.
### Indentation Performance
`clojure-mode`'s indentation engine is a bit slow. You can speed things up significantly by disabling `clojure-use-backtracking-indent`, but this will break the indentation of complex forms like `deftype`, `defprotocol`, `reify`, `letfn`, etc.
We should look into ways to optimize the performance of the backtracking indentation logic. See [this ticket](https://github.com/clojure-emacs/clojure-mode/issues/606) for more details.
### Font-locking Implementation
As mentioned [above](https://github.com/clojure-emacs/clojure-mode#font-locking), the font-locking is implemented in terms of regular expressions which makes it both slow and inaccurate.
## Changelog
An extensive changelog is available [here](CHANGELOG.md).
## License
Copyright © 2007-2025 Jeffrey Chu, Lennart Staflin, Phil Hagelberg, Bozhidar
Batsov, Artur Malabarba, Magnar Sveen and [contributors][].
Distributed under the GNU General Public License; type C-h C-c to view it.
[badge-license]: https://img.shields.io/badge/license-GPL_3-green.svg
[melpa-badge]: https://melpa.org/packages/clojure-mode-badge.svg
[melpa-stable-badge]: https://stable.melpa.org/packages/clojure-mode-badge.svg
[melpa-package]: https://melpa.org/#/clojure-mode
[melpa-stable-package]: https://stable.melpa.org/#/clojure-mode
[COPYING]: https://www.gnu.org/copyleft/gpl.html
[badge-circleci]: https://circleci.com/gh/clojure-emacs/clojure-mode.svg?style=svg
[circleci]: https://circleci.com/gh/clojure-emacs/clojure-mode
[CIDER]: https://github.com/clojure-emacs/cider
[cider-nrepl]: https://github.com/clojure-emacs/cider-nrepl
[inf-clojure]: https://github.com/clojure-emacs/inf-clojure
[contributors]: https://github.com/clojure-emacs/clojure-mode/contributors
[melpa]: https://melpa.org
[melpa stable]: https://stable.melpa.org
[clojure-mode-extra-font-locking]: https://github.com/clojure-emacs/clojure-mode/blob/master/clojure-mode-extra-font-locking.el
[clj-refactor]: https://github.com/clojure-emacs/clj-refactor.el
[paredit]: https://mumble.net/~campbell/emacs/paredit.html
[smartparens]: https://github.com/Fuco1/smartparens
[RainbowDelimiters]: https://github.com/Fanael/rainbow-delimiters
[aggressive-indent-mode]: https://github.com/Malabarba/aggressive-indent-mode
clojure-mode-5.20.0/clojure-mode-extra-font-locking.el 0000664 0000000 0000000 00000027554 15015275167 0022631 0 ustar 00root root 0000000 0000000 ;;; clojure-mode-extra-font-locking.el --- Extra font-locking for Clojure mode -*- lexical-binding: t; -*-
;; Copyright © 2014-2021 Bozhidar Batsov
;;
;; Author: Bozhidar Batsov
;; URL: https://github.com/clojure-emacs/clojure-mode
;; Version: 3.0.0
;; Keywords: languages, lisp
;; Package-Requires: ((clojure-mode "3.0"))
;; This file is not part of GNU Emacs.
;;; Commentary:
;; Provides additional font-locking for clojure-mode. This font-locking
;; used to be part of clojure-mode up to version 3.0, but it was removed
;; due to its unreliable nature (the implementation is quite primitive
;; and font-locks symbols without any regard for what they resolve to).
;; CIDER provides much more reliable font-locking, that's based on the runtime
;; state of your Clojure application.
;;; 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:
(require 'clojure-mode)
(defvar clojure-built-in-vars
'(
;; clojure.core
"accessor"
"aclone"
"agent"
"agent-errors"
"aget"
"alength"
"alias"
"all-ns"
"alter"
"alter-meta!"
"alter-var-root"
"ancestors"
"any?"
"apply"
"array-map"
"aset"
"aset-boolean"
"aset-byte"
"aset-char"
"aset-double"
"aset-float"
"aset-int"
"aset-long"
"aset-short"
"assoc"
"assoc!"
"assoc-in"
"associative?"
"atom"
"await"
"await-for"
"await1"
"bases"
"bean"
"bigdec"
"bigint"
"bit-and"
"bit-and-not"
"bit-clear"
"bit-flip"
"bit-not"
"bit-or"
"bit-set"
"bit-shift-left"
"bit-shift-right"
"bit-test"
"bit-xor"
"boolean"
"boolean?"
"boolean-array"
"booleans"
"bounded-count"
"bound-fn*"
"bound?"
"butlast"
"byte"
"byte-array"
"bytes"
"bytes?"
"cast"
"char"
"char-array"
"char-escape-string"
"char-name-string"
"char?"
"chars"
"chunk"
"chunk-append"
"chunk-buffer"
"chunk-cons"
"chunk-first"
"chunk-next"
"chunk-rest"
"chunked-seq?"
"class"
"class?"
"clear-agent-errors"
"clojure-version"
"coll?"
"commute"
"comp"
"comparator"
"compare"
"compare-and-set!"
"compile"
"complement"
"concat"
"conj"
"conj!"
"cons"
"constantly"
"construct-proxy"
"contains?"
"count"
"counted?"
"create-ns"
"create-struct"
"cycle"
"dec"
"decimal?"
"delay?"
"deliver"
"denominator"
"deref"
"derive"
"descendants"
"destructure"
"disj"
"disj!"
"dissoc"
"dissoc!"
"distinct"
"distinct?"
"doc"
"double"
"double?"
"double-array"
"doubles"
"drop"
"drop-last"
"drop-while"
"empty"
"empty?"
"ensure"
"enumeration-seq"
"error-handler"
"error-mode"
"eval"
"even?"
"every?"
"every-pred"
"extend"
"extends?"
"extenders"
"ex-info"
"ex-data"
"false?"
"ffirst"
"file-seq"
"filter"
"filterv"
"find"
"find-doc"
"find-ns"
"find-keyword"
"find-var"
"first"
"flatten"
"float"
"float-array"
"float?"
"floats"
"flush"
"fn?"
"fnext"
"force"
"format"
"frequencies"
"future-call"
"future-cancel"
"future-cancelled?"
"future-done?"
"future?"
"gensym"
"get"
"get-in"
"get-method"
"get-proxy-class"
"get-thread-bindings"
"get-validator"
"group-by"
"halt-when?"
"hash"
"hash-map"
"hash-ordered-coll"
"hash-set"
"hash-unordered-coll"
"ident?"
"identical?"
"identity"
"indexed?"
"ifn?"
"inc"
"init-proxy"
"instance?"
"inst-ms"
"inst?"
"int"
"int?"
"int-array"
"integer?"
"interleave"
"intern"
"interpose"
"into"
"into-array"
"ints"
"isa?"
"iterate"
"iterator-seq"
"juxt"
"keep"
"keep-indexed"
"key"
"keys"
"keyword"
"keyword?"
"last"
"line-seq"
"list"
"list*"
"list?"
"load-file"
"load-reader"
"load-string"
"loaded-libs"
"long"
"long-array"
"longs"
"macroexpand"
"macroexpand-1"
"make-array"
"make-hierarchy"
"map"
"mapv"
"map?"
"map-indexed"
"mapcat"
"max"
"max-key"
"memoize"
"merge"
"merge-with"
"meta"
"method-sig"
"methods"
"min"
"min-key"
"mix-collection-hash"
"mod"
"name"
"namespace"
"nat-int?"
"neg-int?"
"neg?"
"newline"
"next"
"nfirst"
"nil?"
"nnext"
"not"
"not-any?"
"not-empty"
"not-every?"
"not="
"ns-aliases"
"ns-imports"
"ns-interns"
"ns-map"
"ns-name"
"ns-publics"
"ns-refers"
"ns-resolve"
"ns-unalias"
"ns-unmap"
"nth"
"nthnext"
"nthrest"
"num"
"number?"
"numerator"
"object-array"
"odd?"
"parents"
"partial"
"partition"
"partition-all"
"partition-by"
"pcalls"
"peek"
"persistent!"
"pmap"
"pop"
"pop!"
"pop-thread-bindings"
"pos?"
"pos-int?"
"pr"
"pr-str"
"prefer-method"
"prefers"
"primitives-classnames"
"print"
"print-ctor"
"print-doc"
"print-dup"
"print-method"
"print-namespace-doc"
"print-simple"
"print-special-doc"
"print-str"
"printf"
"println"
"println-str"
"prn"
"prn-str"
"promise"
"proxy-call-with-super"
"proxy-mappings"
"proxy-name"
"push-thread-bindings"
"qualified-ident?"
"qualified-keyword?"
"qualified-symbol?"
"quot"
"rand"
"rand-int"
"rand-nth"
"range"
"ratio?"
"rational?"
"rationalize"
"re-find"
"re-groups"
"re-matcher"
"re-matches"
"re-pattern"
"re-seq"
"read"
"read-line"
"read-string"
"realized?"
"record?"
"reduce"
"reduce-kv"
"reduced"
"reduced?"
"reductions"
"ref"
"ref-history-count"
"ref-max-history"
"ref-min-history"
"ref-set"
"release-pending-sends"
"rem"
"remove"
"remove-all-methods"
"remove-method"
"remove-ns"
"remove-watch"
"repeat"
"repeatedly"
"replace"
"replicate"
"require"
"restart-agent"
"reset!"
"reset-meta!"
"reset-vals!"
"resolve"
"rest"
"resultset-seq"
"reverse"
"reversible?"
"rseq"
"rsubseq"
"satisfies?"
"second"
"select-keys"
"send"
"send-off"
"send-via"
"seq"
"seq?"
"seqable?"
"seque"
"sequence"
"sequential?"
"set"
"set-agent-send-executor!"
"set-agent-send-off-executor!"
"set-error-handler!"
"set-error-mode!"
"set-validator!"
"set?"
"short"
"short-array"
"shorts"
"shuffle"
"shutdown-agents"
"simple-indent?"
"simple-keyword?"
"simple-symbol?"
"slurp"
"some"
"some-fn"
"some?"
"sort"
"sort-by"
"sorted-map"
"sorted-map-by"
"sorted-set"
"sorted-set-by"
"sorted?"
"special-form-anchor"
"special-symbol?"
"specify"
"specify!"
"spit"
"split-at"
"split-with"
"str"
"stream?"
"string?"
"struct"
"struct-map"
"subs"
"subseq"
"subvec"
"supers"
"swap!"
"swap-vals!"
"symbol"
"symbol?"
"syntax-symbol-anchor"
"take"
"take-last"
"take-nth"
"take-while"
"test"
"the-ns"
"thread-bound?"
"to-array"
"to-array-2d"
"trampoline"
"transient"
"tree-seq"
"true?"
"type"
"unchecked-add"
"unchecked-add-int"
"unchecked-byte"
"unchecked-char"
"unchecked-dec"
"unchecked-dec-int"
"unchecked-divide"
"unchecked-divide-int"
"unchecked-double"
"unchecked-float"
"unchecked-inc"
"unchecked-inc-int"
"unchecked-long"
"unchecked-multiply"
"unchecked-multiply-int"
"unchecked-negate"
"unchecked-negate-int"
"unchecked-remainder"
"unchecked-remainder-int"
"unchecked-short"
"unchecked-subtract-int"
"unchecked-subtract"
"underive"
"unsigned-bit-shift-right"
"unquote"
"unquote-splicing"
"update"
"update-in"
"update-proxy"
"uri?"
"use"
"uuid?"
"val"
"vals"
"var-get"
"var-set"
"var?"
"vary-meta"
"vec"
"vector"
"vector?"
"vector-of"
"with-bindings*"
"with-meta"
"xml-seq"
"zero?"
"zipmap"
;; clojure.inspector
"atom?"
"collection-tag"
"get-child"
"get-child-count"
"inspect"
"inspect-table"
"inspect-tree"
"is-leaf"
"list-model"
"list-provider"
;; clojure.main
"load-script"
"main"
"repl"
"repl-caught"
"repl-exception"
"repl-prompt"
"repl-read"
"skip-if-eol"
"skip-whitespace"
;; clojure.set
"difference"
"index"
"intersection"
"join"
"map-invert"
"project"
"rename"
"rename-keys"
"select"
"union"
;; clojure.stacktrace
"e"
"print-cause-trace"
"print-stack-trace"
"print-throwable"
"print-trace-element"
;; clojure.template
"do-template"
"apply-template"
;; clojure.test
"are"
"assert-any"
"assert-expr"
"assert-predicate"
"compose-fixtures"
"deftest"
"deftest-"
"file-position"
"function?"
"get-possibly-unbound-var"
"inc-report-counter"
"is"
"join-fixtures"
"report"
"run-all-tests"
"run-tests"
"set-test"
"successful?"
"test-all-vars"
"test-ns"
"test-var"
"test-vars"
"testing"
"testing-contexts-str"
"testing-vars-str"
"try-expr"
"use-fixtures"
"with-test"
"with-test-out"
;; clojure.walk
"keywordize-keys"
"macroexpand-all"
"postwalk"
"postwalk-demo"
"postwalk-replace"
"prewalk"
"prewalk-demo"
"prewalk-replace"
"stringify-keys"
"walk"
;; clojure.xml
"attrs"
"content"
"content-handler"
"element"
"emit"
"emit-element"
;; clojure.zip
"append-child"
"branch?"
"children"
"down"
"edit"
"end?"
"insert-child"
"insert-left"
"insert-right"
"left"
"leftmost"
"lefts"
"make-node"
"next"
"node"
"path"
"prev"
"remove"
"replace"
"right"
"rightmost"
"rights"
"root"
"seq-zip"
"up"
)
)
(defvar clojure-built-in-dynamic-vars
'(;; clojure.test
"*initial-report-counters*" "*load-tests*" "*report-counters*"
"*stack-trace-depth*" "*test-out*" "*testing-contexts*" "*testing-vars*"
;; clojure.xml
"*current*" "*sb*" "*stack*" "*state*"
))
(font-lock-add-keywords 'clojure-mode
`((,(concat "(\\(?:\.*/\\)?"
(regexp-opt clojure-built-in-vars t)
"\\>")
1 font-lock-builtin-face)))
(font-lock-add-keywords 'clojure-mode
`((,(concat "\\<"
(regexp-opt clojure-built-in-dynamic-vars t)
"\\>")
0 font-lock-builtin-face)))
(provide 'clojure-mode-extra-font-locking)
;;; clojure-mode-extra-font-locking.el ends here
clojure-mode-5.20.0/clojure-mode.el 0000664 0000000 0000000 00000374457 15015275167 0017127 0 ustar 00root root 0000000 0000000 ;;; clojure-mode.el --- Major mode for Clojure code -*- lexical-binding: t; -*-
;; Copyright © 2007-2013 Jeffrey Chu, Lennart Staflin, Phil Hagelberg
;; Copyright © 2013-2025 Bozhidar Batsov, Artur Malabarba, Magnar Sveen
;;
;; Authors: Jeffrey Chu
;; Lennart Staflin
;; Phil Hagelberg
;; Bozhidar Batsov
;; Artur Malabarba
;; Magnar Sveen
;; Maintainer: Bozhidar Batsov
;; URL: https://github.com/clojure-emacs/clojure-mode
;; Keywords: languages clojure clojurescript lisp
;; Version: 5.20.0
;; Package-Requires: ((emacs "25.1"))
;; This file is not part of GNU Emacs.
;;; Commentary:
;; Provides font-lock, indentation, navigation and basic refactoring for the
;; Clojure programming language (https://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 (https://github.com/clojure-emacs/inf-clojure) for
;; basic interaction with Clojure subprocesses.
;; See CIDER (https://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:
(defvar calculate-lisp-indent-last-sexp)
(defvar delete-pair-blink-delay)
(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)
(require 'thingatpt)
(require 'align)
(require 'subr-x)
(require 'lisp-mnt)
(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
(eval-when-compile
(lm-version (or load-file-name buffer-file-name)))
"The current version of `clojure-mode'.")
(defface clojure-keyword-face
'((t (:inherit font-lock-constant-face)))
"Face used to font-lock Clojure keywords (:something)."
: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."
:package-version '(clojure-mode . "3.0.0"))
(defcustom clojure-indent-style 'always-align
"Indentation style to use for function forms and macro forms.
For forms that start with a keyword see `clojure-indent-keyword-style'.
There are two cases of interest configured by this variable.
- Case (A) is when at least one function argument is on the same
line as the function name.
- Case (B) is the opposite (no arguments are on the same line as
the function name). Note that the body of macros is not
affected by this variable, it is always indented by
`lisp-body-indent' (default 2) spaces.
Note that this variable configures the indentation of function
forms (and function-like macros), it does not affect macros that
already use special indentation rules.
The possible values for this variable are keywords indicating how
to indent function forms.
`always-align' - Follow the same rules as `lisp-mode'. All
args are vertically aligned with the first arg in case (A),
and vertically aligned with the function name in case (B).
For instance:
(reduce merge
some-coll)
(reduce
merge
some-coll)
`always-indent' - All args are indented like a macro body.
(reduce merge
some-coll)
(reduce
merge
some-coll)
`align-arguments' - Case (A) is indented like `lisp', and
case (B) is indented like a macro body.
(reduce merge
some-coll)
(reduce
merge
some-coll)"
:safe #'symbolp
:type '(choice (const :tag "Same as `lisp-mode'" always-align)
(const :tag "Indent like a macro body" always-indent)
(const :tag "Indent like a macro body unless first arg is on the same line"
align-arguments))
:package-version '(clojure-mode . "5.2.0"))
(defcustom clojure-indent-keyword-style 'always-align
"Indentation style to use for forms that start with a keyword.
For function/macro forms, see `clojure-indent-style'.
There are two cases of interest configured by this variable.
- Case (A) is when at least one argument following the keyword is
on the same line as the keyword.
- Case (B) is the opposite (no arguments are on the same line as
the keyword).
The possible values for this variable are keywords indicating how
to indent keyword invocation forms.
`always-align' - Follow the same rules as `lisp-mode'. All
args are vertically aligned with the first arg in case (A),
and vertically aligned with the function name in case (B).
For instance:
(:require [foo.bar]
[bar.baz])
(:require
[foo.bar]
[bar.baz])
`always-indent' - All args are indented like a macro body.
(:require [foo.bar]
[bar.baz])
(:x
location
0)
`align-arguments' - Case (A) is indented like `lisp', and
case (B) is indented like a macro body.
(:require [foo.bar]
[bar.baz])
(:x
location
0)"
:safe #'symbolp
:type '(choice (const :tag "Same as `lisp-mode'" always-align)
(const :tag "Indent like a macro body" always-indent)
(const :tag "Indent like a macro body unless first arg is on the same line"
align-arguments))
:package-version '(clojure-mode . "5.19.0"))
(defcustom clojure-use-backtracking-indent t
"When non-nil, enable context sensitive indentation."
:type 'boolean
:safe 'booleanp)
(defcustom clojure-max-backtracking 3
"Maximum amount to backtrack up a list to check for context."
:type 'integer
:safe 'integerp)
(defcustom clojure-docstring-fill-column fill-column
"Value of `fill-column' to use when filling a docstring."
:type 'integer
: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
: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 "\"" ?\"))
:safe (lambda (value)
(and (listp value)
(cl-every 'characterp value))))
(defcustom clojure-build-tool-files
'("project.clj" ; Leiningen
"build.boot" ; Boot
"build.gradle" ; Gradle
"build.gradle.kts" ; Gradle
"deps.edn" ; Clojure CLI (a.k.a. tools.deps)
"shadow-cljs.edn" ; shadow-cljs
"bb.edn" ; babashka
"nbb.edn" ; nbb
"basilisp.edn" ; Basilisp (Python)
)
"A list of files, which identify a Clojure project's root.
Out-of-the box `clojure-mode' understands lein, boot, gradle,
shadow-cljs, tools.deps, babashka and nbb."
:type '(repeat string)
:package-version '(clojure-mode . "5.0.0")
:safe (lambda (value)
(and (listp value)
(cl-every 'stringp value))))
(defcustom clojure-directory-prefixes
'("\\`clj[scxd]?\\.")
"A list of directory prefixes used by `clojure-expected-ns'.
The prefixes are used to generate the correct namespace."
:type '(repeat string)
:package-version '(clojure-mode . "5.14.0")
:safe (lambda (value)
(and (listp value)
(cl-every 'stringp value))))
(defcustom clojure-project-root-function #'clojure-project-root-path
"Function to locate clojure project root directory."
:type 'function
:risky t
:package-version '(clojure-mode . "5.7.0"))
(defcustom clojure-refactor-map-prefix (kbd "C-c C-r")
"Clojure refactor keymap prefix."
:type 'string
:package-version '(clojure-mode . "5.6.0"))
(defvar clojure-refactor-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-t") #'clojure-thread)
(define-key map (kbd "t") #'clojure-thread)
(define-key map (kbd "C-u") #'clojure-unwind)
(define-key map (kbd "u") #'clojure-unwind)
(define-key map (kbd "C-f") #'clojure-thread-first-all)
(define-key map (kbd "f") #'clojure-thread-first-all)
(define-key map (kbd "C-l") #'clojure-thread-last-all)
(define-key map (kbd "l") #'clojure-thread-last-all)
(define-key map (kbd "C-p") #'clojure-cycle-privacy)
(define-key map (kbd "p") #'clojure-cycle-privacy)
(define-key map (kbd "C-(") #'clojure-convert-collection-to-list)
(define-key map (kbd "(") #'clojure-convert-collection-to-list)
(define-key map (kbd "C-'") #'clojure-convert-collection-to-quoted-list)
(define-key map (kbd "'") #'clojure-convert-collection-to-quoted-list)
(define-key map (kbd "C-{") #'clojure-convert-collection-to-map)
(define-key map (kbd "{") #'clojure-convert-collection-to-map)
(define-key map (kbd "C-[") #'clojure-convert-collection-to-vector)
(define-key map (kbd "[") #'clojure-convert-collection-to-vector)
(define-key map (kbd "C-#") #'clojure-convert-collection-to-set)
(define-key map (kbd "#") #'clojure-convert-collection-to-set)
(define-key map (kbd "C-i") #'clojure-cycle-if)
(define-key map (kbd "i") #'clojure-cycle-if)
(define-key map (kbd "C-w") #'clojure-cycle-when)
(define-key map (kbd "w") #'clojure-cycle-when)
(define-key map (kbd "C-o") #'clojure-cycle-not)
(define-key map (kbd "o") #'clojure-cycle-not)
(define-key map (kbd "n i") #'clojure-insert-ns-form)
(define-key map (kbd "n h") #'clojure-insert-ns-form-at-point)
(define-key map (kbd "n u") #'clojure-update-ns)
(define-key map (kbd "n s") #'clojure-sort-ns)
(define-key map (kbd "n r") #'clojure-rename-ns-alias)
(define-key map (kbd "s i") #'clojure-introduce-let)
(define-key map (kbd "s m") #'clojure-move-to-let)
(define-key map (kbd "s f") #'clojure-let-forward-slurp-sexp)
(define-key map (kbd "s b") #'clojure-let-backward-slurp-sexp)
(define-key map (kbd "C-a") #'clojure-add-arity)
(define-key map (kbd "a") #'clojure-add-arity)
(define-key map (kbd "-") #'clojure-toggle-ignore)
(define-key map (kbd "C--") #'clojure-toggle-ignore)
(define-key map (kbd "_") #'clojure-toggle-ignore-surrounding-form)
(define-key map (kbd "C-_") #'clojure-toggle-ignore-surrounding-form)
(define-key map (kbd "P") #'clojure-promote-fn-literal)
(define-key map (kbd "C-P") #'clojure-promote-fn-literal)
map)
"Keymap for Clojure refactoring commands.")
(fset 'clojure-refactor-map clojure-refactor-map)
(defvar clojure-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map prog-mode-map)
(define-key map (kbd "C-:") #'clojure-toggle-keyword-string)
(define-key map (kbd "C-c SPC") #'clojure-align)
(define-key map clojure-refactor-map-prefix 'clojure-refactor-map)
(easy-menu-define clojure-mode-menu map "Clojure Mode Menu"
'("Clojure"
["Toggle between string & keyword" clojure-toggle-keyword-string]
["Align expression" clojure-align]
["Cycle privacy" clojure-cycle-privacy]
["Cycle if, if-not" clojure-cycle-if]
["Cycle when, when-not" clojure-cycle-when]
["Cycle not" clojure-cycle-not]
["Toggle #_ ignore form" clojure-toggle-ignore]
["Toggle #_ ignore of surrounding form" clojure-toggle-ignore-surrounding-form]
["Add function arity" clojure-add-arity]
["Promote #() fn literal" clojure-promote-fn-literal]
("ns forms"
["Insert ns form at the top" clojure-insert-ns-form]
["Insert ns form here" clojure-insert-ns-form-at-point]
["Update ns form" clojure-update-ns]
["Sort ns form" clojure-sort-ns]
["Rename ns alias" clojure-rename-ns-alias])
("Convert collection"
["Convert to list" clojure-convert-collection-to-list]
["Convert to quoted list" clojure-convert-collection-to-quoted-list]
["Convert to map" clojure-convert-collection-to-map]
["Convert to vector" clojure-convert-collection-to-vector]
["Convert to set" clojure-convert-collection-to-set])
("Refactor -> and ->>"
["Thread once more" clojure-thread]
["Fully thread a form with ->" clojure-thread-first-all]
["Fully thread a form with ->>" clojure-thread-last-all]
"--"
["Unwind once" clojure-unwind]
["Fully unwind a threading macro" clojure-unwind-all])
("Let expression"
["Introduce let" clojure-introduce-let]
["Move to let" clojure-move-to-let]
["Forward slurp form into let" clojure-let-forward-slurp-sexp]
["Backward slurp form into let" clojure-let-backward-slurp-sexp])
("Documentation"
["View a Clojure guide" clojure-view-guide]
["View a Clojure reference section" clojure-view-reference-section]
["View the Clojure cheatsheet" clojure-view-cheatsheet]
["View the Clojure style guide" clojure-view-style-guide])
"--"
["Report a clojure-mode bug" clojure-mode-report-bug]
["Clojure-mode version" clojure-mode-display-version]))
map)
"Keymap for Clojure mode.")
(defvar clojure-mode-syntax-table
(let ((table (make-syntax-table)))
;; Initialize ASCII charset as symbol syntax
;; Control characters from 0-31 default to the punctuation syntax class
(modify-syntax-entry '(32 . 127) "_" table)
;; Word syntax
(modify-syntax-entry '(?0 . ?9) "w" table)
(modify-syntax-entry '(?a . ?z) "w" table)
(modify-syntax-entry '(?A . ?Z) "w" table)
;; Whitespace
(modify-syntax-entry ?\s " " table)
(modify-syntax-entry ?\xa0 " " table) ; non-breaking space
(modify-syntax-entry ?\t " " table)
(modify-syntax-entry ?\f " " table)
(modify-syntax-entry ?\r " " table)
;; Setting commas as whitespace makes functions like `delete-trailing-whitespace' behave unexpectedly (#561)
(modify-syntax-entry ?, "." table)
;; Delimiters
(modify-syntax-entry ?\( "()" table)
(modify-syntax-entry ?\) ")(" table)
(modify-syntax-entry ?\[ "(]" table)
(modify-syntax-entry ?\] ")[" table)
(modify-syntax-entry ?\{ "(}" table)
(modify-syntax-entry ?\} "){" table)
;; Prefix chars
(modify-syntax-entry ?` "'" table)
(modify-syntax-entry ?~ "'" table)
(modify-syntax-entry ?^ "'" table)
(modify-syntax-entry ?@ "'" table)
(modify-syntax-entry ?? "_ p" table) ; ? is a prefix outside symbols
(modify-syntax-entry ?# "_ p" table) ; # is allowed inside keywords (#399)
(modify-syntax-entry ?' "_ p" table) ; ' is allowed anywhere but the start of symbols
;; Others
(modify-syntax-entry ?\; "<" table) ; comment start
(modify-syntax-entry ?\n ">" table) ; comment end
(modify-syntax-entry ?\" "\"" table) ; string
(modify-syntax-entry ?\\ "\\" table) ; escape
table)
"Syntax table for Clojure mode.")
(defconst clojure--prettify-symbols-alist
'(("fn" . ?λ)))
(defvar-local clojure-expected-ns-function nil
"The function used to determine the expected namespace of a file.
`clojure-mode' ships a basic function named `clojure-expected-ns'
that does basic heuristics to figure this out.
CIDER provides a more complex version which does classpath analysis.")
(defun clojure-mode-display-version ()
"Display the current `clojure-mode-version' in the minibuffer."
(interactive)
(message "clojure-mode (version %s)" clojure-mode-version))
(defconst clojure-mode-report-bug-url "https://github.com/clojure-emacs/clojure-mode/issues/new"
"The URL to report a `clojure-mode' issue.")
(defun clojure-mode-report-bug ()
"Report a bug in your default browser."
(interactive)
(browse-url clojure-mode-report-bug-url))
(defconst clojure-guides-base-url "https://clojure.org/guides/"
"The base URL for official Clojure guides.")
(defconst clojure-guides '(("Getting Started" . "getting_started")
("Install Clojure" . "install_clojure")
("Editors" . "editors")
("Structural Editing" . "structural_editing")
("REPL Programming" . "repl/introduction")
("Learn Clojure" . "learn/clojure")
("FAQ" . "faq")
("spec" . "spec")
("Reading Clojure Characters" . "weird_characters")
("Destructuring" . "destructuring")
("Threading Macros" . "threading_macros")
("Equality" . "equality")
("Comparators" . "comparators")
("Reader Conditionals" . "reader_conditionals")
("Higher Order Functions" . "higher_order_functions")
("Dev Startup Time" . "dev_startup_time")
("Deps and CLI" . "deps_and_cli")
("tools.build" . "tools_build")
("core.async Walkthrough" . "async_walkthrough")
("Go Block Best Practices" . "core_async_go")
("test.check" . "test_check_beginner"))
"A list of all official Clojure guides.")
(defun clojure-view-guide ()
"Open a Clojure guide in your default browser.
The command will prompt you to select one of the available guides."
(interactive)
(let ((guide (completing-read "Select a guide: " (mapcar #'car clojure-guides))))
(when guide
(let ((guide-url (concat clojure-guides-base-url (cdr (assoc guide clojure-guides)))))
(browse-url guide-url)))))
(defconst clojure-reference-base-url "https://clojure.org/reference/"
"The base URL for the official Clojure reference.")
(defconst clojure-reference-sections '(("The Reader" . "reader")
("The REPL and main" . "repl_and_main")
("Evaluation" . "evaluation")
("Special Forms" . "special_forms")
("Macros" . "macros")
("Other Functions" . "other_functions")
("Data Structures" . "data_structures")
("Datatypes" . "datatypes")
("Sequences" . "sequences")
("Transients" . "transients")
("Transducers" . "transducers")
("Multimethods and Hierarchies" . "multimethods")
("Protocols" . "protocols")
("Metadata" . "metadata")
("Namespaces" . "namespaces")
("Libs" . "libs")
("Vars and Environments" . "vars")
("Refs and Transactions" . "refs")
("Agents" . "agents")
("Atoms" . "atoms")
("Reducers" . "reducers")
("Java Interop" . "java_interop")
("Compilation and Class Generation" . "compilation")
("Other Libraries" . "other_libraries")
("Differences with Lisps" . "lisps")
("Deps and CLI" . "deps_and_cli")))
(defun clojure-view-reference-section ()
"Open a Clojure reference section in your default browser.
The command will prompt you to select one of the available sections."
(interactive)
(let ((section (completing-read "Select a reference section: " (mapcar #'car clojure-reference-sections))))
(when section
(let ((section-url (concat clojure-reference-base-url (cdr (assoc section clojure-reference-sections)))))
(browse-url section-url)))))
(defconst clojure-cheatsheet-url "https://clojure.org/api/cheatsheet"
"The URL of the official Clojure cheatsheet.")
(defun clojure-view-cheatsheet ()
"Open the Clojure cheatsheet in your default browser."
(interactive)
(browse-url clojure-cheatsheet-url))
(defconst clojure-style-guide-url "https://guide.clojure.style"
"The URL of the Clojure style guide.")
(defun clojure-view-style-guide ()
"Open the Clojure style guide in your default browser."
(interactive)
(browse-url clojure-style-guide-url))
(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."
(and (not endp)
;; don't insert after opening quotes, auto-gensym syntax, or reader tags
(not (looking-back
(if (member delim clojure-omit-space-between-tag-and-delimiters)
"\\_<\\(?:'+\\|#.*\\)"
"\\_<\\(?:'+\\|#\\)")
(line-beginning-position)))))
(declare-function paredit-open-curly "ext:paredit" t t)
(declare-function paredit-close-curly "ext:paredit" t t)
(declare-function paredit-convolute-sexp "ext:paredit")
(defvar clojure--let-regexp
"\(\\(when-let\\|if-let\\|let\\)\\(\\s-*\\|\\[\\)"
"Regexp matching let like expressions, i.e. \"let\", \"when-let\", \"if-let\".
The first match-group is the let expression.
The second match-group is the whitespace or the opening square
bracket if no whitespace between the let expression and the
bracket.")
(defun clojure--replace-let-bindings-and-indent (&rest _)
"Replace let bindings and indent."
(save-excursion
(backward-sexp)
(when (looking-back clojure--let-regexp nil)
(clojure--replace-sexps-with-bindings-and-indent))))
(defun clojure-paredit-setup (&optional keymap)
"Make \"paredit-mode\" play nice with `clojure-mode'.
If an optional KEYMAP is passed the changes are applied to it,
instead of to `clojure-mode-map'.
Also advice `paredit-convolute-sexp' when used on a let form as drop in
replacement for `cljr-expand-let`."
(when (>= paredit-version 21)
(let ((keymap (or keymap clojure-mode-map)))
(define-key keymap "{" #'paredit-open-curly)
(define-key keymap "}" #'paredit-close-curly))
(make-local-variable 'paredit-space-for-delimiter-predicates)
(add-to-list 'paredit-space-for-delimiter-predicates
#'clojure-space-for-delimiter-p)
(advice-add 'paredit-convolute-sexp :after #'clojure--replace-let-bindings-and-indent)))
(defun clojure-current-defun-name ()
"Return the name of the defun at point, or nil.
`add-log-current-defun-function' is set to this, for use by `which-func'."
(save-excursion
(let ((location (point)))
;; If we are now precisely at the beginning of a defun, make sure
;; beginning-of-defun finds that one rather than the previous one.
(or (eobp) (forward-char 1))
(beginning-of-defun-raw)
;; Make sure we are really inside the defun found, not after it.
(when (and (looking-at "\\s(")
(progn (end-of-defun)
(< location (point)))
(progn (forward-sexp -1)
(>= location (point))))
(if (looking-at "\\s(")
(forward-char 1))
;; Skip the defining construct name, e.g. "defn" or "def".
(forward-sexp 1)
;; The second element is usually a symbol being defined. If it
;; is not, use the first symbol in it.
(skip-chars-forward " \t\n'(")
;; Skip metadata
(while (looking-at "\\^")
(forward-sexp 1)
(skip-chars-forward " \t\n'("))
(buffer-substring-no-properties (point)
(progn (forward-sexp 1)
(point)))))))
(defun clojure-mode-variables ()
"Set up initial buffer-local variables for Clojure mode."
(add-to-list 'imenu-generic-expression '(nil clojure-match-next-def 0))
(setq-local indent-tabs-mode nil)
(setq-local paragraph-ignore-fill-prefix t)
(setq-local outline-regexp ";;;;* \\|(") ; comments and top-level forms
(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 indent-region-function #'clojure-indent-region)
(setq-local lisp-indent-function #'clojure-indent-function)
(setq-local lisp-doc-string-elt-property 'clojure-doc-string-elt)
(setq-local clojure-expected-ns-function #'clojure-expected-ns)
(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)
(setq-local add-log-current-defun-function #'clojure-current-defun-name)
(setq-local beginning-of-defun-function #'clojure-beginning-of-defun-function))
(defsubst clojure-in-docstring-p ()
"Check whether point is in a docstring."
(let ((ppss (syntax-ppss)))
;; are we in a string?
(when (nth 3 ppss)
;; check font lock at the start of the string
(eq (get-text-property (nth 8 ppss) 'face)
'font-lock-doc-face))))
;;;###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)
;; `electric-layout-post-self-insert-function' prevents indentation in strings
;; and comments, force indentation of non-inlined docstrings:
(add-hook 'electric-indent-functions
(lambda (_char) (if (and (clojure-in-docstring-p)
;; make sure we're not dealing with an inline docstring
;; e.g. (def foo "inline docstring" bar)
(save-excursion
(beginning-of-line-text)
(eq (get-text-property (point) 'face)
'font-lock-doc-face)))
'do-indent))))
(defcustom clojure-verify-major-mode t
"If non-nil, warn when activating the wrong `major-mode'."
:type 'boolean
:safe #'booleanp
:package-version '(clojure-mode "5.3.0"))
(defun clojure--check-wrong-major-mode ()
"Check if the current `major-mode' matches the file extension.
If it doesn't, issue a warning if `clojure-verify-major-mode' is
non-nil."
(when (and clojure-verify-major-mode
(stringp (buffer-file-name)))
(let* ((case-fold-search t)
(problem (cond ((and (string-match "\\.clj\\'" (buffer-file-name))
(not (eq major-mode 'clojure-mode)))
'clojure-mode)
((and (string-match "\\.cljs\\'" (buffer-file-name))
(not (eq major-mode 'clojurescript-mode)))
'clojurescript-mode)
((and (string-match "\\.cljc\\'" (buffer-file-name))
(not (eq major-mode 'clojurec-mode)))
'clojurec-mode))))
(when problem
(message "[WARNING] %s activated `%s' instead of `%s' in this buffer.
This could cause problems.
\(See `clojure-verify-major-mode' to disable this message.)"
(if (eq major-mode real-this-command)
"You have"
"Something in your configuration")
major-mode
problem)))))
(add-hook 'clojure-mode-hook #'clojure--check-wrong-major-mode)
(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)))
;; we are in a string and string start pos (8th element) is non-nil
(let* ((beg-doc (nth 8 (syntax-ppss)))
(end-doc (save-excursion
(goto-char beg-doc)
(or (ignore-errors (forward-sexp) (point))
(point-max)))))
(save-restriction
(narrow-to-region beg-doc end-doc)
(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)))))
;;; #_ comments font-locking
;; Code heavily borrowed from Slime.
;; https://github.com/slime/slime/blob/master/contrib/slime-fontifying-fu.el#L186
(defvar clojure--comment-macro-regexp
(rx (seq (+ (seq "#_" (* " ")))) (group-n 1 (not (any " "))))
"Regexp matching the start of a comment sexp.
The beginning of match-group 1 should be before the sexp to be
marked as a comment. The end of sexp is found with
`clojure-forward-logical-sexp'.")
(defvar clojure--reader-and-comment-regexp
(rx (or (seq (+ (seq "#_" (* " ")))
(group-n 1 (not (any " "))))
(seq (group-n 1 "(comment" symbol-end))))
"Regexp matching both `#_' macro and a comment sexp." )
(defcustom clojure-comment-regexp clojure--comment-macro-regexp
"Comment mode.
The possible values for this variable are keywords indicating
what is considered a comment (affecting font locking).
- Reader macro `#_' only - the default
- Reader macro `#_' and `(comment)'"
:type '(choice (const :tag "Reader macro `#_' and `(comment)'" clojure--reader-and-comment-regexp)
(other :tag "Reader macro `#_' only" clojure--comment-macro-regexp))
:package-version '(clojure-mode . "5.7.0"))
(defun clojure--search-comment-macro-internal (limit)
"Search for a comment forward stopping at LIMIT."
(when (search-forward-regexp clojure-comment-regexp limit t)
(let* ((md (match-data))
(start (match-beginning 1))
(state (syntax-ppss start)))
;; inside string or comment?
(if (or (nth 3 state)
(nth 4 state))
(clojure--search-comment-macro-internal limit)
(goto-char start)
;; Count how many #_ we got and step by that many sexps
;; For (comment ...), step at least 1 sexp
(clojure-forward-logical-sexp
(max (count-matches (rx "#_") (elt md 0) (elt md 1))
1))
;; Data for (match-end 1).
(setf (elt md 3) (point))
(set-match-data md)
t))))
(defun clojure--search-comment-macro (limit)
"Find comment macros and set the match data.
Search from point up to LIMIT. The region that should be
considered a comment is between `(match-beginning 1)'
and `(match-end 1)'."
(let ((result 'retry))
(while (and (eq result 'retry) (<= (point) limit))
(condition-case nil
(setq result (clojure--search-comment-macro-internal limit))
(end-of-file (setq result nil))
(scan-error (setq result 'retry))))
result))
;;; General font-locking
(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 "^[ \t]*(\\([a-z0-9.-]+/\\)?\\(def\\sw*\\)" nil t)
(save-excursion
(let (found?
(deftype (match-string 2))
(start (point)))
;; ignore user-error from down-list when called from inside a string or comment
;; TODO: a better workaround would be to wrap it in
;; unless (ppss-comment-or-string-start (syntax-ppss)) instead of ignore-errors,
;; but ppss-comment-or-string-start is only available since Emacs 27
(ignore-errors
(down-list))
(forward-sexp)
(while (not found?)
(ignore-errors
(forward-sexp))
(or (when (char-equal ?\[ (char-after (point)))
(backward-sexp))
(when (char-equal ?\) (char-after (point)))
(backward-sexp)))
(cl-destructuring-bind (def-beg . def-end) (bounds-of-thing-at-point 'sexp)
(when (char-equal ?^ (char-after def-beg))
;; move to the beginning of next sexp
(progn (forward-sexp) (backward-sexp)))
(when (or (not (char-equal ?^ (char-after def-beg)))
(and (char-equal ?^ (char-after (point))) (= def-beg (point))))
(setq found? t)
(when (string= deftype "defmethod")
(setq def-end (progn (goto-char def-end)
(forward-sexp)
(point))))
(set-match-data (list def-beg def-end)))))
(goto-char start)))))
(eval-and-compile
(defconst clojure--sym-forbidden-rest-chars "][\";@\\^`~\(\)\{\}\\,\s\t\n\r"
"A list of chars that a Clojure symbol cannot contain.
See definition of `macros': URL `https://git.io/vRGLD'.")
(defconst clojure--sym-forbidden-1st-chars (concat clojure--sym-forbidden-rest-chars "0-9:'")
"A list of chars that a Clojure symbol cannot start with.
See the for-loop: URL `https://git.io/vRGTj' lines: URL
`https://git.io/vRGIh', URL `https://git.io/vRGLE' and value
definition of `macros': URL `https://git.io/vRGLD'.")
(defconst clojure--sym-regexp
(concat "[^" clojure--sym-forbidden-1st-chars "][^" clojure--sym-forbidden-rest-chars "]*")
"A regexp matching a Clojure symbol or namespace alias.
Matches the rule `clojure--sym-forbidden-1st-chars' followed by
any number of matches of `clojure--sym-forbidden-rest-chars'.")
(defconst clojure--keyword-sym-forbidden-1st-chars
(concat clojure--sym-forbidden-rest-chars ":'")
"A list of chars that a Clojure keyword symbol cannot start with.")
(defconst clojure--keyword-sym-regexp
(concat "[^" clojure--keyword-sym-forbidden-1st-chars "]"
"[^" clojure--sym-forbidden-rest-chars "]*")
"A regexp matching a Clojure keyword name or keyword namespace.
Matches the rule `clojure--keyword-sym-forbidden-1st-chars' followed by
any number of matches of `clojure--sym-forbidden-rest-chars'."))
(defconst clojure-font-lock-keywords
(eval-when-compile
`(;; Any def form
(,(concat "(\\(?:" clojure--sym-regexp "/\\)?"
"\\("
(regexp-opt '("def"
"defonce"
"defn"
"defn-"
"defmacro"
"definline"
"defmulti"
"defmethod"
"defprotocol"
"definterface"
"defrecord"
"deftype"
"defstruct"
;; clojure.test
"deftest"
"deftest-"
;; clojure.logic
"defne"
"defnm"
"defnu"
"defnc"
"defna"
;; Third party
"deftask"
"defstate"
"defproject"))
"\\)\\>")
(1 font-lock-keyword-face))
;; 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+\\)?")
(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+\\)?")
(2 font-lock-type-face nil t))
;; Function definition
(,(concat "(\\(?:clojure.core/\\)?\\("
(regexp-opt '("defn"
"defn-"
"defmulti"
"defmethod"
"deftest"
"deftest-"
"defmacro"
"definline"))
"\\)"
;; Function declarations
"\\>"
;; Any whitespace
"[ \r\n\t]*"
;; Possibly type or metadata
"\\(?:#?^\\(?:{[^}]*}\\|\\sw+\\)[ \r\n\t]*\\)*"
(concat "\\(" clojure--sym-regexp "\\)?"))
(2 font-lock-function-name-face nil t))
;; (fn name? args ...)
(,(concat "(\\(?:clojure.core/\\)?\\(fn\\)[ \t]+"
;; Possibly type
"\\(?:#?^\\sw+[ \t]*\\)?"
;; Possibly name
"\\(\\sw+\\)?" )
(2 font-lock-function-name-face nil t))
;; Special forms
(,(concat
"("
(regexp-opt
'("do" "if" "let*" "var" "fn" "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
'(
"->"
"->>"
".."
"amap"
"and"
"areduce"
"as->"
"assert"
"binding"
"bound-fn"
"case"
"comment"
"cond"
"cond->"
"cond->>"
"condp"
"declare"
"delay"
"doall"
"dorun"
"doseq"
"dosync"
"dotimes"
"doto"
"extend-protocol"
"extend-type"
"for"
"future"
"gen-class"
"gen-interface"
"if-let"
"if-not"
"if-some"
"import"
"in-ns"
"io!"
"lazy-cat"
"lazy-seq"
"let"
"letfn"
"locking"
"loop"
"memfn"
"ns"
"or"
"proxy"
"proxy-super"
"pvalues"
"refer-clojure"
"reify"
"some->"
"some->>"
"sync"
"time"
"vswap!"
"when"
"when-first"
"when-let"
"when-not"
"when-some"
"while"
"with-bindings"
"with-in-str"
"with-loading-context"
"with-local-vars"
"with-open"
"with-out-str"
"with-precision"
"with-redefs"
"with-redefs-fn"
)
t)
"\\>")
1 font-lock-keyword-face)
;; Macros similar to let, when, and while
(,(rx symbol-start
(or "let" "when" "while") "-"
(1+ (or (syntax word) (syntax symbol)))
symbol-end)
0 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*
(,(concat "\\(?:\\<\\|/\\)@?\\(\\*" clojure--sym-regexp "\\*\\)\\>")
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
(,(rx (group "\\" (or any
"newline" "space" "tab" "formfeed" "backspace"
"return"
(: "u" (= 4 (char "0-9a-fA-F")))
(: "o" (repeat 1 3 (char "0-7")))))
(or (not word) word-boundary))
1 'clojure-character-face)
;; lambda arguments - %, %&, %1, %2, etc
;; must come after character literals for \% to be handled properly
("\\<%[&1-9]*" (0 font-lock-variable-name-face))
;; namespace definitions: (ns foo.bar)
(,(concat "(\\[ \r\n\t]*"
;; Possibly metadata, shorthand and/or longhand
"\\(?:\\^?\\(?:{[^}]+}\\|:[^ \r\n\t]+[ \r\n\t]\\)[ \r\n\t]*\\)*"
;; namespace
"\\(" clojure--sym-regexp "\\)")
(1 font-lock-type-face))
;; TODO dedupe the code for matching of keywords, type-hints and unmatched symbols
;; keywords: {:oneword/ve/yCom|pLex.stu-ff 0}
(,(concat "\\(:\\{1,2\\}\\)\\(" clojure--keyword-sym-regexp "?\\)\\(/\\)"
"\\(" clojure--keyword-sym-regexp "\\)")
;; with ns
(1 'clojure-keyword-face)
(2 font-lock-type-face)
(3 'default)
(4 'clojure-keyword-face))
(,(concat "\\<\\(:\\{1,2\\}\\)\\(" clojure--keyword-sym-regexp "\\)")
;; without ns
(1 'clojure-keyword-face)
(2 'clojure-keyword-face))
;; type-hints: #^oneword
(,(concat "\\(#?\\^\\)\\(" clojure--sym-regexp "?\\)\\(/\\)\\(" clojure--sym-regexp "\\)")
(1 'default)
(2 font-lock-type-face)
(3 'default)
(4 'default))
(,(concat "\\(#?\\^\\)\\(" clojure--sym-regexp "\\)")
(1 'default)
(2 font-lock-type-face))
;; clojure symbols not matched by the previous regexps; influences CIDER's
;; dynamic syntax highlighting (CDSH). See https://git.io/vxEEA:
(,(concat "\\(" clojure--sym-regexp "?\\)\\(/\\)\\(" clojure--sym-regexp "\\)")
(1 font-lock-type-face)
;; 2nd and 3th matching groups can be font-locked to `nil' or `default'.
;; CDSH seems to kick in only for functions and variables referenced w/o
;; writing their namespaces.
(2 nil)
(3 nil))
(,(concat "\\(" clojure--sym-regexp "\\)")
;; this matching group must be font-locked to `nil' otherwise CDSH breaks.
(1 nil))
;; #_ and (comment ...) macros.
(clojure--search-comment-macro 1 font-lock-comment-face t)
;; Highlight `code` marks, just like `elisp'.
(,(rx "`" (group-n 1 (optional "#'")
(+ (or (syntax symbol) (syntax word)))) "`")
(1 'font-lock-constant-face prepend))
;; Highlight [[var]] comments
(,(rx "[[" (group-n 1 (optional "#'")
(+ (or (syntax symbol) (syntax word)))) "]]")
(1 'font-lock-constant-face prepend))
;; Highlight escaped characters in strings.
(clojure-font-lock-escaped-chars 0 'bold 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 is a (doc)string
(let* ((startpos (nth 8 state))
(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)
(ignore-errors
(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)))))
(and (zerop docelt) (<= (point) startpos)
(progn (forward-comment (point-max)) t)
(= (point) (nth 8 state))))
;; In a def, at last position is not a docstring
(not (and (string= "def" firstsym)
(save-excursion
(goto-char startpos)
(goto-char (end-of-thing 'sexp))
(looking-at "[ \r\n\t]*\)")))))
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)
(ignore-errors
(beginning-of-defun-raw))
(let ((beg-def (point)))
(when (and (not (= point beg-def))
(looking-at "(def"))
(ignore-errors
;; move forward as much as possible until failure (or success)
(forward-char)
(dotimes (_ 4)
(forward-sexp)))
(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-locked-as-string-p (&optional regexp)
"Non-nil if the char before point is font-locked as a string.
If REGEXP is non-nil, also check whether current string is
preceeded by a #."
(let ((face (get-text-property (1- (point)) 'face)))
(and (or (and (listp face)
(memq 'font-lock-string-face face))
(eq 'font-lock-string-face face))
(or (clojure-string-start t)
(unless regexp
(clojure-string-start nil))))))
(defun clojure-font-lock-escaped-chars (bound)
"Highlight \\escaped chars in strings.
BOUND denotes a buffer position to limit the search."
(let ((found nil))
(while (and (not found)
(re-search-forward "\\\\." bound t))
(setq found (clojure--font-locked-as-string-p)))
found))
(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."
(let ((found nil))
(while (and (not found)
(re-search-forward (eval-when-compile
(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)
"<[[:alnum:]]+>"
"\\)\\|" ;; end of special groups
;; 2. normal capturing groups (
;; 3. we also highlight alternative
;; separarators |, and closing parens )
"[|()]"
"\\)\\)"))
bound t))
(setq found (clojure--font-locked-as-string-p 'regexp)))
found))
;; 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)
(put 'deftask 'clojure-doc-string-elt 2) ;; common Boot macro
;;; Vertical alignment
(defcustom clojure-align-forms-automatically nil
"If non-nil, vertically align some forms automatically.
Automatically means it is done as part of indenting code. This
applies to binding forms (`clojure-align-binding-forms'), to cond
forms (`clojure-align-cond-forms') and to map literals. For
instance, selecting a map a hitting \\`\\[indent-for-tab-command]'
will align the values like this:
{:some-key 10
:key2 20}"
:package-version '(clojure-mode . "5.1")
:safe #'booleanp
:type 'boolean)
(defconst clojure--align-separator-newline-regexp "^ *$")
(defcustom clojure-align-separator clojure--align-separator-newline-regexp
"Separator passed to `align-region' when performing vertical alignment."
:package-version '(clojure-mode . "5.10")
:type `(choice (const :tag "Make blank lines prevent vertical alignment from happening."
,clojure--align-separator-newline-regexp)
(other :tag "Allow blank lines to happen within a vertically-aligned expression."
entire)))
(defcustom clojure-align-reader-conditionals nil
"Whether to align reader conditionals, as if they were maps."
:package-version '(clojure-mode . "5.10")
:safe #'booleanp
:type 'boolean)
(defcustom clojure-align-binding-forms
'("let" "when-let" "when-some" "if-let" "if-some" "binding" "loop"
"doseq" "for" "with-open" "with-local-vars" "with-redefs")
"List of strings matching forms that have binding forms."
:package-version '(clojure-mode . "5.1")
:safe #'listp
:type '(repeat string))
(defcustom clojure-align-cond-forms
'("condp" "cond" "cond->" "cond->>" "case" "are"
"clojure.core/condp" "clojure.core/cond" "clojure.core/cond->"
"clojure.core/cond->>" "clojure.core/case" "clojure.test/are")
"List of strings identifying cond-like forms."
:package-version '(clojure-mode . "5.1")
:safe #'listp
:type '(repeat string))
(defcustom clojure-special-arg-indent-factor
2
"Factor of the `lisp-body-indent' used to indent special arguments."
:package-version '(clojure-mode . "5.13")
:type 'integer
:safe 'integerp)
(defvar clojure--beginning-of-reader-conditional-regexp
"#\\?@(\\|#\\?("
"Regexp denoting the beginning of a reader conditional.")
(defun clojure--position-for-alignment ()
"Non-nil if the sexp around point should be automatically aligned.
This function expects to be called immediately after an
open-brace or after the function symbol in a function call.
First check if the sexp around point is a map literal, or is a
call to one of the vars listed in `clojure-align-cond-forms'. If
it isn't, return nil. If it is, return non-nil and place point
immediately before the forms that should be aligned.
For instance, in a map literal point is left immediately before
the first key; while, in a let-binding, point is left inside the
binding vector and immediately before the first binding
construct."
(let ((point (point)))
;; Are we in a map?
(or (and (eq (char-before) ?{)
(not (eq (char-before (1- point)) ?\#)))
;; Are we in a reader conditional?
(and clojure-align-reader-conditionals
(looking-back clojure--beginning-of-reader-conditional-regexp (- (point) 4)))
;; Are we in a cond form?
(let* ((fun (car (member (thing-at-point 'symbol) clojure-align-cond-forms)))
(method (and fun (clojure--get-indent-method fun)))
;; The number of special arguments in the cond form is
;; the number of sexps we skip before aligning.
(skip (cond ((numberp method) method)
((null method) 0)
((sequencep method) (elt method 0)))))
(when (and fun (numberp skip))
(clojure-forward-logical-sexp skip)
(comment-forward (point-max))
fun)) ; Return non-nil (the var name).
;; Are we in a let-like form?
(when (member (thing-at-point 'symbol)
clojure-align-binding-forms)
;; Position inside the binding vector.
(clojure-forward-logical-sexp)
(backward-sexp)
(when (eq (char-after) ?\[)
(forward-char 1)
(comment-forward (point-max))
;; Return non-nil.
t)))))
(defun clojure--find-sexp-to-align (end)
"Non-nil if there's a sexp ahead to be aligned before END.
Place point as in `clojure--position-for-alignment'."
;; Look for a relevant sexp.
(let ((found))
(while (and (not found)
(search-forward-regexp
(concat (when clojure-align-reader-conditionals
(concat clojure--beginning-of-reader-conditional-regexp
"\\|"))
"{\\|("
(regexp-opt
(append clojure-align-binding-forms
clojure-align-cond-forms)
'symbols))
end 'noerror))
(let ((ppss (syntax-ppss)))
;; If we're in a string or comment.
(unless (or (elt ppss 3)
(elt ppss 4))
;; Only stop looking if we successfully position
;; the point.
(setq found (clojure--position-for-alignment)))))
found))
(defun clojure--search-whitespace-after-next-sexp (&optional bound _noerror)
"Move point after all whitespace after the next sexp.
Additionally, move past a comment if one exists (this is only
possible when the end of the sexp coincides with the end of a
line).
Set the match data group 1 to be this region of whitespace and
return point.
BOUND is bounds the whitespace search."
(unwind-protect
(ignore-errors
(clojure-forward-logical-sexp 1)
;; Move past any whitespace or comment.
(search-forward-regexp "\\([,\s\t]*\\)\\(;+.*\\)?" bound)
(pcase (syntax-after (point))
;; End-of-line, try again on next line.
(`(12) (clojure--search-whitespace-after-next-sexp bound))
;; Closing paren, stop here.
(`(5 . ,_) nil)
;; Anything else is something to align.
(_ (point))))
(when (and bound (> (point) bound))
(goto-char bound))))
(defun clojure-align (beg end)
"Vertically align the contents of the sexp around point.
If region is active, align it. Otherwise, align everything in the
current \"top-level\" sexp.
When called from lisp code align everything between BEG and END."
(interactive (if (use-region-p)
(list (region-beginning) (region-end))
(save-excursion
(let ((end (progn (end-of-defun)
(point))))
(clojure-backward-logical-sexp)
(list (point) end)))))
(setq end (copy-marker end))
(save-excursion
(goto-char beg)
(while (clojure--find-sexp-to-align end)
(let ((sexp-end (save-excursion
(backward-up-list)
(forward-sexp 1)
(point-marker)))
(clojure-align-forms-automatically nil)
(count 1))
;; For some bizarre reason, we need to `align-region' once for each
;; group.
(save-excursion
(while (search-forward-regexp "^ *\n" sexp-end 'noerror)
(cl-incf count)))
;; Pre-indent the region to avoid aligning to improperly indented
;; contents (#551). Also fixes #360.
(indent-region (point) (marker-position sexp-end))
(dotimes (_ count)
(align-region (point) sexp-end nil
`((clojure-align (regexp . clojure--search-whitespace-after-next-sexp)
(group . 1)
(separate . ,clojure-align-separator)
(repeat . t)))
nil))))))
;;; Indentation
(defun clojure-indent-region (beg end)
"Like `indent-region', but also maybe align forms.
Forms between BEG and END are aligned according to
`clojure-align-forms-automatically'."
(prog1 (let ((indent-region-function nil))
(indent-region beg end))
(when clojure-align-forms-automatically
(condition-case nil
(clojure-align beg end)
(scan-error nil)))))
(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)))
;; indent symbols starting with if, when, ...
;; such as if-let, when-let, ...
;; like if, when, ...
(when (string-match (rx string-start (or "if" "when" "let" "while") (syntax symbol))
function-name)
(clojure--get-indent-method (substring (match-string 0 function-name) 0 -1)))))
(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))
(ignore-errors
;; Otherwise look higher up.
(pcase (syntax-ppss)
(`(,(pred (< 0)) ,start . ,_)
(goto-char start)
(clojure--find-indent-spec-backtracking)))))))
(when (numberp method)
(setq method (list method)))
(pcase method
((pred functionp)
(when (= pos 0)
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))
(_
(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--keyword-to-symbol (keyword)
"Convert KEYWORD to symbol."
(intern (substring (symbol-name keyword) 1)))
(defun clojure--normal-indent (last-sexp indent-mode)
"Return the normal indentation column for a sexp.
Point should be after the open paren of the _enclosing_ sexp, and
LAST-SEXP is the start of the previous sexp (immediately before
the sexp being indented). INDENT-MODE is any of the values
accepted by `clojure-indent-style'."
(goto-char last-sexp)
(forward-sexp 1)
(clojure-backward-logical-sexp 1)
(let ((last-sexp-start nil))
(if (ignore-errors
;; `backward-sexp' until we reach the start of a sexp that is the
;; first of its line (the start of the enclosing sexp).
(while (string-match
"[^[:blank:]]"
(buffer-substring (line-beginning-position) (point)))
(setq last-sexp-start (prog1 (point)
(forward-sexp -1))))
t)
;; Here we have found an arg before the arg we're indenting which is at
;; the start of a line. Every mode simply aligns on this case.
(current-column)
;; Here we have reached the start of the enclosing sexp (point is now at
;; the function name), so the behaviour depends on INDENT-MODE and on
;; whether there's also an argument on this line (case A or B).
(let ((indent-mode (if (keywordp indent-mode)
;; needed for backwards compatibility
;; as before clojure-mode 5.10 indent-mode was a keyword
(clojure--keyword-to-symbol indent-mode)
indent-mode))
(case-a ; The meaning of case-a is explained in `clojure-indent-style'.
(and last-sexp-start
(< last-sexp-start (line-end-position)))))
(cond
((eq indent-mode 'always-indent)
(+ (current-column) lisp-body-indent -1))
;; There's an arg after the function name, so align with it.
(case-a (goto-char last-sexp-start)
(current-column))
;; Not same line.
((eq indent-mode 'align-arguments)
(+ (current-column) lisp-body-indent -1))
;; Finally, just align with the function name.
(t (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_\\)"))))
(defcustom clojure-enable-indent-specs t
"Control whether to honor indent specs.
They can be either set via metadata on the function/macro, or via
`define-clojure-indent'. Set this to nil to get uniform
formatting of all forms."
:type 'boolean
:safe #'booleanp
:package-version '(clojure-mode . "5.19.0"))
;; Check the general context, and provide indentation for data structures and
;; special macros. If current form is a function (or non-special macro),
;; delegate indentation to `clojure--normal-indent'.
(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
- `:defn', meaning indent `defn'-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 (and clojure-enable-indent-specs
(clojure--find-indent-spec)))
(last-sexp calculate-lisp-indent-last-sexp)
(containing-form-column (1- (current-column))))
(pcase method
((or (and (pred integerp) method) `(,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
;; The first non-special arg. Rigidly reduce indentation.
((= pos (1+ method))
(+ lisp-body-indent containing-form-column))
;; Further non-special args, align with the arg above.
((> pos (1+ method))
(clojure--normal-indent last-sexp 'always-align))
;; Special arg. Rigidly indent with a large indentation.
(t
(+ (* clojure-special-arg-indent-factor lisp-body-indent)
containing-form-column)))))
(`:defn
(+ lisp-body-indent containing-form-column))
((pred functionp)
(funcall method indent-point state))
;; No indent spec, do the default.
(`nil
(let ((function (thing-at-point 'symbol)))
(cond
;; Preserve useful alignment of :require (and friends) in `ns' forms.
((and function (string-match "^:" function))
(clojure--normal-indent last-sexp clojure-indent-keyword-style))
;; This should be identical to the :defn above.
((and function
(string-match "\\`\\(?:\\S +/\\)?\\(def[a-z]*\\|with-\\)"
function)
(not (string-match "\\`default" (match-string 1 function))))
(+ lisp-body-indent containing-form-column))
;; Finally, nothing special here, just respect the user's
;; preference.
(t (clojure--normal-indent last-sexp clojure-indent-style)))))))))
;;; 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))
(defun clojure--maybe-quoted-symbol-p (x)
"Check that X is either a symbol or a quoted symbol like :foo or \\='foo."
(or (symbolp x)
(and (listp x)
(= 2 (length x))
(eq 'quote (car x))
(symbolp (cadr x)))))
(defun clojure--valid-unquoted-indent-spec-p (spec)
"Check that the indentation SPEC is valid.
Validate it with respect to
https://docs.cider.mx/cider/indent_spec.html e.g. (2 :form
:form (1)))."
(or (integerp spec)
(memq spec '(:form :defn))
(and (listp spec)
(not (null spec))
(or (integerp (car spec))
(memq (car spec) '(:form :defn)))
(cl-every 'clojure--valid-unquoted-indent-spec-p (cdr spec)))))
(defun clojure--valid-indent-spec-p (spec)
"Check that the indentation SPEC (quoted if a list) is valid.
Validate it with respect to
https://docs.cider.mx/cider/indent_spec.html e.g. (2 :form
:form (1)))."
(or (integerp spec)
(and (keywordp spec) (memq spec '(:form :defn)))
(and (listp spec)
(= 2 (length spec))
(eq 'quote (car spec))
(clojure--valid-unquoted-indent-spec-p (cadr spec)))))
(defun clojure--valid-put-clojure-indent-call-p (exp)
"Check that EXP is a valid `put-clojure-indent' expression.
For example: (put-clojure-indent \\='defrecord \\='(2 :form :form (1))."
(unless (and (listp exp)
(= 3 (length exp))
(eq 'put-clojure-indent (nth 0 exp))
(clojure--maybe-quoted-symbol-p (nth 1 exp))
(clojure--valid-indent-spec-p (nth 2 exp)))
(error "Unrecognized put-clojure-indent call: %s" exp))
t)
(put 'put-clojure-indent 'safe-local-eval-function
'clojure--valid-put-clojure-indent-call-p)
(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. This variable
only works when set via the customize interface (`setq' won't
work). To set it from Lisp code, use
(put-clojure-indent \\='some-symbol :defn)."
:type '(repeat symbol)
: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)
(delay 0)
(future 0)
(comment 0)
(doto 1)
(locking 1)
(proxy '(2 nil nil (:defn)))
(as-> 2)
(fdef 1)
(reify '(:defn (1)))
(deftype '(2 nil nil (:defn)))
(defrecord '(2 nil nil (:defn)))
(defprotocol '(1 (:defn)))
(definterface '(1 (:defn)))
(extend 1)
(extend-protocol '(1 :defn))
(extend-type '(1 :defn))
;; specify and specify! are from ClojureScript
(specify '(1 :defn))
(specify! '(1 :defn))
(try 0)
(catch 2)
(finally 0)
;; binding forms
(let 1)
(letfn '(1 ((:defn)) nil))
(binding 1)
(loop 1)
(for 1)
(doseq 1)
(dotimes 1)
(when-let 1)
(if-let 1)
(when-some 1)
(if-some 1)
(this-as 1) ; ClojureScript
(defmethod :defn)
;; clojure.test
(testing 1)
(deftest :defn)
(are 2)
(use-fixtures :defn)
(async 1)
;; 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?
(let* ((beg (nth 8 (syntax-ppss)))
(hash (eq ?# (char-before beg))))
(if regex
(and hash (1- beg))
(and (not hash) 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 surrounding sexp and return it."
(let ((begin (point)))
(forward-sexp)
(let ((result (buffer-substring begin (point))))
(delete-region begin (point))
result)))
(defcustom clojure-cache-project-dir t
"Whether to cache the results of `clojure-project-dir'."
:type 'boolean
:safe #'booleanp
:package-version '(clojure-mode . "5.8.0"))
(defvar-local clojure-cached-project-dir nil
"A project dir cache used to speed up related operations.")
(defun clojure-project-dir (&optional dir-name)
"Return the absolute path to the project's root directory.
Call is delegated down to `clojure-project-root-function' with
optional DIR-NAME as argument.
When `clojure-cache-project-dir' is t the results of the command
are cached in a buffer local variable (`clojure-cached-project-dir')."
(let ((project-dir (or clojure-cached-project-dir
(funcall clojure-project-root-function dir-name))))
(when (and clojure-cache-project-dir
(derived-mode-p 'clojure-mode)
(not clojure-cached-project-dir))
(setq clojure-cached-project-dir project-dir))
project-dir))
(defun clojure-project-root-path (&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))
(choices (delq nil
(mapcar (lambda (fname)
(locate-dominating-file dir-name fname))
clojure-build-tool-files))))
(when (> (length choices) 0)
(car (sort choices #'file-in-directory-p)))))
(defun clojure-project-relative-path (path)
"Denormalize PATH by making it relative to the project root."
(file-relative-name path (clojure-project-dir)))
;;; ns manipulation
(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}
(cl-reduce (lambda (a x) (replace-regexp-in-string x "" a))
clojure-directory-prefixes
:initial-value sans-underscores)))
(defun clojure-insert-ns-form-at-point ()
"Insert a namespace form at point."
(interactive)
(insert (format "(ns %s)" (funcall clojure-expected-ns-function))))
(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))
(defvar-local clojure-cached-ns nil
"A buffer ns cache used to speed up ns-related operations.")
(defun clojure-update-ns ()
"Update the namespace of the current buffer.
Useful if a file has been renamed."
(interactive)
(let ((nsname (funcall clojure-expected-ns-function)))
(when nsname
(save-excursion
(save-match-data
(if (clojure-find-ns)
(progn
(replace-match nsname nil nil nil 4)
(message "ns form updated to `%s'" nsname)
(setq clojure-cached-ns nsname))
(user-error "Can't find ns form")))))))
(defun clojure--sort-following-sexps ()
"Sort sexps between point and end of current sexp.
Comments at the start of a line are considered part of the
following sexp. Comments at the end of a line (after some other
content) are considered part of the preceding sexp."
;; Here we're after the :require/:import symbol.
(save-restriction
(narrow-to-region (point) (save-excursion
(up-list)
;; Ignore any comments in the end before sorting
(backward-char)
(forward-sexp -1)
(clojure-forward-logical-sexp)
(unless (looking-at-p ")")
(search-forward-regexp "$"))
(point)))
(skip-chars-forward "\r\n[:blank:]")
(sort-subr nil
(lambda () (skip-chars-forward "\r\n[:blank:]"))
;; Move to end of current top-level thing.
(lambda ()
(condition-case nil
(while t (up-list))
(scan-error nil))
;; We could be inside a symbol instead of a sexp.
(unless (looking-at "\\s-\\|$")
(clojure-forward-logical-sexp))
;; move past comments at the end of the line.
(search-forward-regexp "$"))
;; Move to start of ns name.
(lambda ()
(comment-forward)
(skip-chars-forward "[:blank:]\n\r[(")
(clojure-forward-logical-sexp)
(forward-sexp -1)
nil)
;; Move to end of ns name.
(lambda ()
(clojure-forward-logical-sexp)))
(goto-char (point-max))
;; Does the last line now end in a comment?
(when (nth 4 (parse-partial-sexp (point-min) (point)))
(insert "\n"))))
(defun clojure-sort-ns ()
"Internally sort each sexp inside the ns form."
(interactive)
(comment-normalize-vars t) ;; `t`: avoid prompts
(if (clojure-find-ns)
(save-excursion
(goto-char (match-beginning 0))
(let ((beg (point))
(ns))
(forward-sexp 1)
(setq ns (buffer-substring beg (point)))
(forward-char -1)
(while (progn (forward-sexp -1)
(looking-at "(:[a-z]"))
(save-excursion
(forward-char 1)
(forward-sexp 1)
(clojure--sort-following-sexps)))
(goto-char beg)
(if (looking-at (regexp-quote ns))
(message "ns form is already sorted")
(message "ns form has been sorted"))))
(user-error "Can't find ns form")))
(defconst clojure-namespace-name-regex
(rx line-start
"("
(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")))
(zero-or-one (any ":'")) ;; (in-ns 'foo) or (ns+ :user)
(group (one-or-more (not (any "()\"" whitespace))) symbol-end)))
(make-obsolete-variable 'clojure-namespace-name-regex 'clojure-namespace-regexp "5.12.0")
(defconst clojure-namespace-regexp
(rx "(" (? "clojure.core/") (or "in-ns" "ns" "ns+") symbol-end))
(defcustom clojure-cache-ns nil
"Whether to cache the results of `clojure-find-ns'.
Note that this won't work well in buffers with multiple namespace
declarations (which rarely occur in practice) and you'll
have to invalidate this manually after changing the ns for
a buffer. If you update the ns using `clojure-update-ns'
the cached value will be updated automatically."
:type 'boolean
:safe #'booleanp
:package-version '(clojure-mode . "5.8.0"))
(defun clojure--find-ns-in-direction (direction)
"Return the nearest namespace in a specific DIRECTION.
DIRECTION is `forward' or `backward'."
(let ((candidate)
(fn (if (eq direction 'forward)
#'search-forward-regexp
#'search-backward-regexp)))
(while (and (not candidate)
(funcall fn clojure-namespace-regexp nil t))
(let ((start (match-beginning 0))
(end (match-end 0)))
(save-excursion
(when (clojure--looking-at-top-level-form start)
(save-match-data
(goto-char end)
(clojure-forward-logical-sexp)
(setq candidate (string-remove-prefix "'" (thing-at-point 'symbol))))))))
candidate))
(defun clojure-find-ns (&optional suppress-errors)
"Return the namespace of the current Clojure buffer, honor `SUPPRESS-ERRORS'.
Return the namespace closest to point and above it. If there are
no namespaces above point, return the first one in the buffer.
If `SUPPRESS-ERRORS' is t, errors during ns form parsing will be swallowed,
and nil will be returned instead of letting this function fail.
The results will be cached if `clojure-cache-ns' is set to t."
(if (and clojure-cache-ns clojure-cached-ns)
clojure-cached-ns
(let* ((f (lambda (direction)
(if suppress-errors
(ignore-errors (clojure--find-ns-in-direction direction))
(clojure--find-ns-in-direction direction))))
(ns (save-excursion
(save-restriction
(widen)
;; Move to top-level to avoid searching from inside ns
(ignore-errors (while t (up-list nil t t)))
(or (funcall f 'backward)
(funcall f 'forward))))))
(setq clojure-cached-ns ns)
ns)))
(defun clojure-show-cache ()
"Display cached values if present.
Useful for debugging."
(interactive)
(message "Cached Project: %s, Cached Namespace: %s" clojure-cached-project-dir clojure-cached-ns))
(defun clojure-clear-cache ()
"Clear all buffer-local cached values.
Normally you'd need to do this very infrequently - e.g.
after renaming the root folder of project or after
renaming a namespace."
(interactive)
(setq clojure-cached-project-dir nil
clojure-cached-ns nil)
(message "Buffer-local clojure-mode cache cleared"))
(defconst clojure-def-type-and-name-regex
(concat "(\\(?:\\(?:\\sw\\|\\s_\\)+/\\)?"
;; Declaration
"\\(def\\(?:\\sw\\|\\s_\\)*\\(?:-\\|\\>\\)\\)"
;; Any whitespace
"[ \r\n\t]*"
;; Possibly type or metadata
"\\(?:#?^\\(?:{[^}]*}+\\|\\(?:\\sw\\|\\s_\\)+\\)[ \r\n\t]*\\)*"
;; Symbol name
"\\(\\(?:\\sw\\|\\s_\\)+\\)"))
(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\")."
(save-excursion
(unless (looking-at clojure-def-type-and-name-regex)
(beginning-of-defun-raw))
(when (search-forward-regexp clojure-def-type-and-name-regex 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 text after point is \"non-logical\" sexp.
\"Non-logical\" sexp are ^metadata and #reader.macros."
(comment-normalize-vars t) ;; `t`: avoid prompts
(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")
(unless n (setq n 1))
(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)
(skip-chars-forward ",")
(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")
(unless n (setq n 1))
(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))))))
(defun clojure--looking-at-top-level-form (&optional point)
"Return truthy if form at POINT is a top level form."
(save-excursion
(when point (goto-char point))
(and (looking-at-p "(")
(= (point)
(progn (forward-char)
(beginning-of-defun-raw)
(point))))))
(defun clojure-top-level-form-p (first-form)
"Return truthy if the first form matches FIRST-FORM."
(condition-case nil
(save-excursion
(beginning-of-defun-raw)
(forward-char 1)
(clojure-forward-logical-sexp 1)
(clojure-backward-logical-sexp 1)
(looking-at-p first-form))
(scan-error nil)
(end-of-buffer nil)))
(defun clojure-sexp-starts-until-position (position)
"Return the starting points for forms before POSITION.
Positions are in descending order to aide in finding the first starting
position before the current position."
(save-excursion
(let (sexp-positions)
(condition-case nil
(while (< (point) position)
(clojure-forward-logical-sexp 1)
(clojure-backward-logical-sexp 1)
;; Needed to prevent infinite recursion when there's only 1 form in buffer.
(if (eq (point) (car sexp-positions))
(goto-char position)
(push (point) sexp-positions)
(clojure-forward-logical-sexp 1)))
(scan-error nil))
sexp-positions)))
(defcustom clojure-toplevel-inside-comment-form nil
"Eval top level forms inside comment forms instead of the comment form itself.
Experimental. Function `cider-defun-at-point' is used extensively so if we
change this heuristic it needs to be bullet-proof and desired. While
testing, give an easy way to turn this new behavior off."
:type 'boolean
:safe #'booleanp
:package-version '(clojure-mode . "5.9.0"))
(defun clojure-find-first (pred coll)
"Find first element of COLL for which PRED return truthy."
(let ((found)
(haystack coll))
(while (and (not found)
haystack)
(if (funcall pred (car haystack))
(setq found (car haystack))
(setq haystack (cdr haystack))))
found))
(defun clojure-beginning-of-defun-function (&optional n)
"Go to top level form.
Set as `beginning-of-defun-function' so that these generic
operators can be used. Given a positive N it will do it that
many times."
(let ((beginning-of-defun-function nil))
(if (and clojure-toplevel-inside-comment-form
(clojure-top-level-form-p "comment"))
(condition-case nil
(save-match-data
(let ((original-position (point))
clojure-comment-end)
(beginning-of-defun-raw)
(end-of-defun)
(setq clojure-comment-end (point))
(beginning-of-defun-raw)
(forward-char 1) ;; skip paren so we start at comment
(clojure-forward-logical-sexp) ;; skip past the comment form itself
(if-let ((sexp-start (clojure-find-first (lambda (beg-pos)
(< beg-pos original-position))
(clojure-sexp-starts-until-position
clojure-comment-end))))
(progn (goto-char sexp-start) t)
(beginning-of-defun-raw n))))
(scan-error (beginning-of-defun-raw n)))
(beginning-of-defun-raw n))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Refactoring support
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Threading macros related
(defcustom clojure-thread-all-but-last nil
"Non-nil means do not thread the last expression.
This means that `clojure-thread-first-all' and
`clojure-thread-last-all' not thread the deepest sexp inside the
current sexp."
:package-version '(clojure-mode . "5.4.0")
:safe #'booleanp
:type 'boolean)
(defun clojure--point-after (&rest actions)
"Return POINT after performing ACTIONS.
An action is either the symbol of a function or a two element
list of (fn args) to pass to `apply''"
(save-excursion
(dolist (fn-and-args actions)
(let ((f (if (listp fn-and-args) (car fn-and-args) fn-and-args))
(args (if (listp fn-and-args) (cdr fn-and-args) nil)))
(apply f args)))
(point)))
(defun clojure--maybe-unjoin-line ()
"Undo a `join-line' done by a threading command."
(when (get-text-property (point) 'clojure-thread-line-joined)
(remove-text-properties (point) (1+ (point)) '(clojure-thread-line-joined t))
(insert "\n")))
(defun clojure--unwind-last ()
"Unwind a thread last macro once.
Point must be between the opening paren and the ->> symbol."
(forward-sexp)
(save-excursion
(let ((contents (clojure-delete-and-extract-sexp)))
(when (looking-at " *\n")
(join-line 'following))
(clojure--ensure-parens-around-function-names)
(let* ((sexp-beg-line (line-number-at-pos))
(sexp-end-line (progn (forward-sexp)
(line-number-at-pos)))
(multiline-sexp-p (not (= sexp-beg-line sexp-end-line))))
(down-list -1)
(if multiline-sexp-p
(insert "\n")
;; `clojure--maybe-unjoin-line' only works when unwinding sexps that were
;; threaded in the same Emacs session, but it also catches cases that
;; `multiline-sexp-p' doesn't.
(clojure--maybe-unjoin-line))
(insert contents))))
(forward-char))
(defun clojure--ensure-parens-around-function-names ()
"Insert parens around function names if necessary."
(clojure--looking-at-non-logical-sexp)
(unless (looking-at "(")
(insert-parentheses 1)
(backward-up-list)))
(defun clojure--unwind-first ()
"Unwind a thread first macro once.
Point must be between the opening paren and the -> symbol."
(forward-sexp)
(save-excursion
(let ((contents (clojure-delete-and-extract-sexp)))
(when (looking-at " *\n")
(join-line 'following))
(clojure--ensure-parens-around-function-names)
(down-list)
(forward-sexp)
(insert contents)
(forward-sexp -1)
(clojure--maybe-unjoin-line)))
(forward-char))
(defun clojure--pop-out-of-threading ()
"Raise a sexp up a level to unwind a threading form."
(save-excursion
(down-list 2)
(backward-up-list)
(raise-sexp)))
(defun clojure--nothing-more-to-unwind ()
"Return non-nil if a threaded form cannot be unwound further."
(save-excursion
(let ((beg (point)))
(forward-sexp)
(down-list -1)
(backward-sexp 2) ;; the last sexp, the threading macro
(when (looking-back "(\\s-*" (line-beginning-position))
(backward-up-list)) ;; and the paren
(= beg (point)))))
(defun clojure--fix-sexp-whitespace (&optional move-out)
"Fix whitespace after unwinding a threading form.
Optional argument MOVE-OUT, if non-nil, means moves up a list
before fixing whitespace."
(save-excursion
(when move-out (backward-up-list))
(let ((sexp (bounds-of-thing-at-point 'sexp)))
(clojure-indent-region (car sexp) (cdr sexp))
(delete-trailing-whitespace (car sexp) (cdr sexp)))))
;;;###autoload
(defun clojure-unwind (&optional n)
"Unwind thread at point or above point by N levels.
With universal argument \\[universal-argument], fully unwind thread."
(interactive "P")
(setq n (cond ((equal n '(4)) 999)
(n) (1)))
(save-excursion
(let ((limit (save-excursion
(beginning-of-defun-raw)
(point))))
(ignore-errors
(when (looking-at "(")
(forward-char 1)
(forward-sexp 1)))
(while (> n 0)
(search-backward-regexp "([^-]*->" limit)
(if (clojure--nothing-more-to-unwind)
(progn (clojure--pop-out-of-threading)
(clojure--fix-sexp-whitespace)
(setq n 0)) ;; break out of loop
(down-list)
(cond
((looking-at "[^-]*->\\_>") (clojure--unwind-first))
((looking-at "[^-]*->>\\_>") (clojure--unwind-last)))
(clojure--fix-sexp-whitespace 'move-out)
(setq n (1- n)))))))
;;;###autoload
(defun clojure-unwind-all ()
"Fully unwind thread at point or above point."
(interactive)
(clojure-unwind '(4)))
(defun clojure--remove-superfluous-parens ()
"Remove extra parens from a form."
(when (looking-at "([^ )]+)")
(let ((delete-pair-blink-delay 0))
(delete-pair))))
(defun clojure--thread-first ()
"Thread a nested sexp using ->."
(down-list)
(forward-symbol 1)
(unless (looking-at ")")
(let ((contents (clojure-delete-and-extract-sexp)))
(backward-up-list)
(just-one-space 0)
(save-excursion
(insert contents "\n")
(clojure--remove-superfluous-parens))
(when (looking-at "\\s-*\n")
(join-line 'following)
(forward-char 1)
(put-text-property (point) (1+ (point))
'clojure-thread-line-joined t))
t)))
(defun clojure--thread-last ()
"Thread a nested sexp using ->>."
(forward-sexp 2)
(down-list -1)
(backward-sexp)
(unless (eq (char-before) ?\()
(let ((contents (clojure-delete-and-extract-sexp)))
(just-one-space 0)
(backward-up-list)
(insert contents "\n")
(clojure--remove-superfluous-parens)
;; cljr #255 Fix dangling parens
(forward-sexp)
(when (looking-back "^\\s-*\\()+\\)\\s-*" (line-beginning-position))
(let ((pos (match-beginning 1)))
(put-text-property pos (1+ pos) 'clojure-thread-line-joined t))
(join-line))
t)))
(defun clojure--threadable-p ()
"Return non-nil if a form can be threaded."
(save-excursion
(forward-symbol 1)
(looking-at "[\n\r\t ]*(")))
;;;###autoload
(defun clojure-thread ()
"Thread by one more level an existing threading macro."
(interactive)
(ignore-errors
(when (looking-at "(")
(forward-char 1)
(forward-sexp 1)))
(search-backward-regexp "([^-]*->")
(down-list)
(when (clojure--threadable-p)
(prog1 (cond
((looking-at "[^-]*->\\_>") (clojure--thread-first))
((looking-at "[^-]*->>\\_>") (clojure--thread-last)))
(clojure--fix-sexp-whitespace 'move-out))))
(defun clojure--thread-all (first-or-last-thread but-last)
"Fully thread the form at point.
FIRST-OR-LAST-THREAD is \"->\" or \"->>\".
When BUT-LAST is non-nil, the last expression is not threaded.
Default value is `clojure-thread-all-but-last'."
(save-excursion
(insert-parentheses 1)
(insert first-or-last-thread))
(while (save-excursion (clojure-thread)))
(when (or but-last clojure-thread-all-but-last)
(clojure-unwind)))
;;;###autoload
(defun clojure-thread-first-all (but-last)
"Fully thread the form at point using ->.
When BUT-LAST is non-nil, the last expression is not threaded.
Default value is `clojure-thread-all-but-last'."
(interactive "P")
(clojure--thread-all "-> " but-last))
;;;###autoload
(defun clojure-thread-last-all (but-last)
"Fully thread the form at point using ->>.
When BUT-LAST is non-nil, the last expression is not threaded.
Default value is `clojure-thread-all-but-last'."
(interactive "P")
(clojure--thread-all "->> " but-last))
;;; Cycling stuff
(defcustom clojure-use-metadata-for-privacy nil
"If nil, `clojure-cycle-privacy' will use (defn- f []).
If t, it will use (defn ^:private f [])."
:package-version '(clojure-mode . "5.5.0")
:safe #'booleanp
:type 'boolean)
;;;###autoload
(defun clojure-cycle-privacy ()
"Make public the current private def, or vice-versa.
See: https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-cycle-privacy"
(interactive)
(save-excursion
(ignore-errors (forward-char 7))
(search-backward-regexp "(defn?\\(-\\| ^:private\\)?\\_>")
(if (match-string 1)
(replace-match "" nil nil nil 1)
(goto-char (match-end 0))
(insert (if (or clojure-use-metadata-for-privacy
(equal (match-string 0) "(def"))
" ^:private"
"-")))))
(defun clojure--convert-collection (coll-open coll-close)
"Convert the collection at (point)
by unwrapping it an wrapping it between COLL-OPEN and COLL-CLOSE."
(save-excursion
(while (and
(not (bobp))
(not (looking-at "(\\|{\\|\\[")))
(backward-char))
(when (or (eq ?\# (char-before))
(eq ?\' (char-before)))
(delete-char -1))
(when (and (bobp)
(not (memq (char-after) '(?\{ ?\( ?\[))))
(user-error "Beginning of file reached, collection is not found"))
(insert coll-open (substring (clojure-delete-and-extract-sexp) 1 -1) coll-close)))
;;;###autoload
(defun clojure-convert-collection-to-list ()
"Convert collection at (point) to list."
(interactive)
(clojure--convert-collection "(" ")"))
;;;###autoload
(defun clojure-convert-collection-to-quoted-list ()
"Convert collection at (point) to quoted list."
(interactive)
(clojure--convert-collection "'(" ")"))
;;;###autoload
(defun clojure-convert-collection-to-map ()
"Convert collection at (point) to map."
(interactive)
(clojure--convert-collection "{" "}"))
;;;###autoload
(defun clojure-convert-collection-to-vector ()
"Convert collection at (point) to vector."
(interactive)
(clojure--convert-collection "[" "]"))
;;;###autoload
(defun clojure-convert-collection-to-set ()
"Convert collection at (point) to set."
(interactive)
(clojure--convert-collection "#{" "}"))
(defun clojure--in-string-p ()
"Check whether the point is currently in a string."
(nth 3 (syntax-ppss)))
(defun clojure--in-comment-p ()
"Check whether the point is currently in a comment."
(nth 4 (syntax-ppss)))
(defun clojure--goto-if ()
"Find the first surrounding if or if-not expression."
(when (clojure--in-string-p)
(while (or (not (looking-at "("))
(clojure--in-string-p))
(backward-char)))
(while (not (looking-at "\\((if \\)\\|\\((if-not \\)"))
(condition-case nil
(backward-up-list)
(scan-error (user-error "No if or if-not found")))))
;;;###autoload
(defun clojure-cycle-if ()
"Change a surrounding if to if-not, or vice-versa.
See: https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-cycle-if"
(interactive)
(save-excursion
(clojure--goto-if)
(cond
((looking-at "(if-not")
(forward-char 3)
(delete-char 4)
(forward-sexp 2)
(transpose-sexps 1))
((looking-at "(if")
(forward-char 3)
(insert "-not")
(forward-sexp 2)
(transpose-sexps 1)))))
;; TODO: Remove code duplication with `clojure--goto-if'.
(defun clojure--goto-when ()
"Find the first surrounding when or when-not expression."
(when (clojure--in-string-p)
(while (or (not (looking-at "("))
(clojure--in-string-p))
(backward-char)))
(while (not (looking-at "\\((when \\)\\|\\((when-not \\)"))
(condition-case nil
(backward-up-list)
(scan-error (user-error "No when or when-not found")))))
;;;###autoload
(defun clojure-cycle-when ()
"Change a surrounding when to when-not, or vice-versa."
(interactive)
(save-excursion
(clojure--goto-when)
(cond
((looking-at "(when-not")
(forward-char 9)
(delete-char -4))
((looking-at "(when")
(forward-char 5)
(insert "-not")))))
(defun clojure-cycle-not ()
"Add or remove a not form around the current form."
(interactive)
(save-excursion
(condition-case nil
(backward-up-list)
(scan-error (user-error "`clojure-cycle-not' must be invoked inside a list")))
(if (looking-back "(not " nil)
(progn
(delete-char -5)
(forward-sexp)
(delete-char 1))
(insert "(not ")
(forward-sexp)
(insert ")"))))
;;; let related stuff
(defun clojure--goto-let ()
"Go to the beginning of the nearest let form."
(when (clojure--in-string-p)
(while (or (not (looking-at "("))
(clojure--in-string-p))
(backward-char)))
(ignore-errors
(while (not (looking-at clojure--let-regexp))
(backward-up-list)))
(looking-at clojure--let-regexp))
(defun clojure--inside-let-binding-p ()
"Return non-nil if point is inside a let binding."
(ignore-errors
(save-excursion
(let ((pos (point)))
(clojure--goto-let)
(re-search-forward "\\[")
(if (< pos (point))
nil
(forward-sexp)
(up-list)
(< pos (point)))))))
(defun clojure--beginning-of-current-let-binding ()
"Move before the bound name of the current binding.
Assume that point is in the binding form of a let."
(let ((current-point (point)))
(clojure--goto-let)
(search-forward "[")
(forward-char)
(while (> current-point (point))
(forward-sexp))
(backward-sexp 2)))
(defun clojure--previous-line ()
"Keep the column position while go the previous line."
(let ((col (current-column)))
(forward-line -1)
(move-to-column col)))
(defun clojure--prepare-to-insert-new-let-binding ()
"Move to right place in the let form to insert a new binding and indent."
(if (clojure--inside-let-binding-p)
(progn
(clojure--beginning-of-current-let-binding)
(newline-and-indent)
(clojure--previous-line)
(indent-for-tab-command))
(clojure--goto-let)
(search-forward "[")
(backward-up-list)
(forward-sexp)
(down-list -1)
(backward-char)
(if (looking-at "\\[\\s-*\\]")
(forward-char)
(forward-char)
(newline-and-indent))))
(defun clojure--sexp-regexp (sexp)
"Return a regexp for matching SEXP."
(concat "\\([^[:word:]^-]\\)"
(mapconcat #'identity (mapcar 'regexp-quote (split-string sexp))
"[[:space:]\n\r]+")
"\\([^[:word:]^-]\\)"))
(defun clojure--replace-sexp-with-binding (bound-name init-expr)
"Replace a binding with its bound name in the let form.
BOUND-NAME is the name (left-hand side) of a binding.
INIT-EXPR is the value (right-hand side) of a binding."
(save-excursion
(while (re-search-forward
(clojure--sexp-regexp init-expr)
(clojure--point-after 'clojure--goto-let 'forward-sexp)
t)
(replace-match (concat "\\1" bound-name "\\2")))))
(defun clojure--replace-sexps-with-bindings (bindings)
"Replace bindings with their respective bound names in the let form.
BINDINGS is the list of bound names and init expressions."
(let ((bound-name (pop bindings))
(init-expr (pop bindings)))
(when bound-name
(clojure--replace-sexp-with-binding bound-name init-expr)
(clojure--replace-sexps-with-bindings bindings))))
(defun clojure--replace-sexps-with-bindings-and-indent ()
"Replace sexps with bindings."
(clojure--replace-sexps-with-bindings
(clojure--read-let-bindings))
(clojure-indent-region
(clojure--point-after 'clojure--goto-let)
(clojure--point-after 'clojure--goto-let 'forward-sexp)))
(defun clojure--read-let-bindings ()
"Read the bound-name and init expression pairs in the binding form.
Return a list: odd elements are bound names, even elements init expressions."
(clojure--goto-let)
(down-list 2)
(let* ((start (point))
(sexp-start start)
(end (save-excursion
(backward-char)
(forward-sexp)
(down-list -1)
(point)))
bindings)
(while (/= sexp-start end)
(forward-sexp)
(push
(string-trim (buffer-substring-no-properties sexp-start (point)))
bindings)
(skip-chars-forward "\r\n\t[:blank:]")
(setq sexp-start (point)))
(nreverse bindings)))
(defun clojure--introduce-let-internal (name &optional n)
"Create a let form, binding the form at point with NAME.
Optional numeric argument N, if non-nil, introduces the let N
lists up."
(if (numberp n)
(let ((init-expr-sexp (clojure-delete-and-extract-sexp)))
(insert name)
(ignore-errors (backward-up-list n))
(insert "(let" (clojure-delete-and-extract-sexp) ")")
(backward-sexp)
(down-list)
(forward-sexp)
(insert " [" name " " init-expr-sexp "]\n")
(clojure--replace-sexps-with-bindings-and-indent))
(insert "[ " (clojure-delete-and-extract-sexp) "]")
(backward-sexp)
(insert "(let " (clojure-delete-and-extract-sexp) ")")
(backward-sexp)
(down-list 2)
(insert name)
(forward-sexp)
(up-list)
(newline-and-indent)
(insert name)))
(defun clojure--move-to-let-internal (name)
"Bind the form at point to NAME in the nearest let."
(if (not (save-excursion (clojure--goto-let)))
(clojure--introduce-let-internal name)
(let ((contents (clojure-delete-and-extract-sexp)))
(insert name)
(clojure--prepare-to-insert-new-let-binding)
(insert contents)
(backward-sexp)
(insert " ")
(backward-char)
(insert name)
(clojure--replace-sexps-with-bindings-and-indent))))
(defun clojure--let-backward-slurp-sexp-internal ()
"Slurp the s-expression before the let form into the let form."
(clojure--goto-let)
(backward-sexp)
(let ((sexp (string-trim (clojure-delete-and-extract-sexp))))
(delete-blank-lines)
(down-list)
(forward-sexp 2)
(newline-and-indent)
(insert sexp)
(clojure--replace-sexps-with-bindings-and-indent)))
;;;###autoload
(defun clojure-let-backward-slurp-sexp (&optional n)
"Slurp the s-expression before the let form into the let form.
With a numeric prefix argument slurp the previous N s-expressions
into the let form."
(interactive "p")
(let ((n (or n 1)))
(dotimes (_ n)
(save-excursion (clojure--let-backward-slurp-sexp-internal)))))
(defun clojure--let-forward-slurp-sexp-internal ()
"Slurp the next s-expression after the let form into the let form."
(clojure--goto-let)
(forward-sexp)
(let ((sexp (string-trim (clojure-delete-and-extract-sexp))))
(down-list -1)
(newline-and-indent)
(insert sexp)
(clojure--replace-sexps-with-bindings-and-indent)))
;;;###autoload
(defun clojure-let-forward-slurp-sexp (&optional n)
"Slurp the next s-expression after the let form into the let form.
With a numeric prefix argument slurp the next N s-expressions
into the let form."
(interactive "p")
(unless n (setq n 1))
(dotimes (_ n)
(save-excursion (clojure--let-forward-slurp-sexp-internal))))
;;;###autoload
(defun clojure-introduce-let (&optional n)
"Create a let form, binding the form at point.
With a numeric prefix argument the let is introduced N lists up."
(interactive "P")
(clojure--introduce-let-internal (read-from-minibuffer "Name of bound symbol: ") n))
;;;###autoload
(defun clojure-move-to-let ()
"Move the form at point to a binding in the nearest let."
(interactive)
(clojure--move-to-let-internal (read-from-minibuffer "Name of bound symbol: ")))
;;; Promoting #() function literals
(defun clojure--gather-fn-literal-args ()
"Return a cons cell (ARITY . VARARG)
ARITY is number of arguments in the function,
VARARG is a boolean of whether it takes a variable argument %&."
(save-excursion
(let ((end (save-excursion (clojure-forward-logical-sexp) (point)))
(rgx (rx symbol-start "%" (group (? (or "&" (+ (in "0-9"))))) symbol-end))
(arity 0)
(vararg nil))
(while (re-search-forward rgx end 'noerror)
(when (not (or (clojure--in-comment-p) (clojure--in-string-p)))
(let ((s (match-string 1)))
(if (string= s "&")
(setq vararg t)
(setq arity
(max arity
(if (string= s "") 1
(string-to-number s))))))))
(cons arity vararg))))
(defun clojure--substitute-fn-literal-arg (arg sub end)
"ARG is either a number or the symbol '&.
SUB is a string to substitute with, and
END marks the end of the fn expression"
(save-excursion
(let ((rgx (format "\\_<%%%s\\_>" (if (eq arg 1) "1?" arg))))
(while (re-search-forward rgx end 'noerror)
(when (and (not (clojure--in-comment-p))
(not (clojure--in-string-p)))
(replace-match sub))))))
(defun clojure-promote-fn-literal ()
"Convert a #(...) function into (fn [...] ...), prompting for the argument names."
(interactive)
(when-let (beg (clojure-string-start))
(goto-char beg))
(if (or (looking-at-p "#(")
(ignore-errors (forward-char 1))
(re-search-backward "#(" (save-excursion (beginning-of-defun-raw) (backward-char) (point)) 'noerror))
(let* ((end (save-excursion (clojure-forward-logical-sexp) (point-marker)))
(argspec (clojure--gather-fn-literal-args))
(arity (car argspec))
(vararg (cdr argspec)))
(delete-char 1)
(save-excursion (forward-sexp 1) (insert ")"))
(save-excursion
(insert "(fn [] ")
(backward-char 2)
(mapc (lambda (n)
(let ((name (read-string (format "Name of argument %d: " n))))
(when (/= n 1) (insert " "))
(insert name)
(clojure--substitute-fn-literal-arg n name end)))
(number-sequence 1 arity))
(when vararg
(insert " & ")
(let ((name (read-string "Name of variadic argument: ")))
(insert name)
(clojure--substitute-fn-literal-arg '& name end)))))
(user-error "No #() literal at point!")))
;;; Renaming ns aliases
(defun clojure--alias-usage-regexp (alias)
"Regexp for matching usages of ALIAS in qualified symbols, keywords and maps.
When nil, match all namespace usages.
The first match-group is the alias."
(let ((alias (if alias (regexp-quote alias) clojure--sym-regexp)))
(concat "#::\\(?1:" alias "\\)[ ,\r\n\t]*{"
"\\|"
"\\_<\\(?1:" alias "\\)/")))
(defun clojure--rename-ns-alias-usages (current-alias new-alias beg end)
"Rename all usages of CURRENT-ALIAS in region BEG to END with NEW-ALIAS."
(let ((rgx (clojure--alias-usage-regexp current-alias)))
(save-mark-and-excursion
(goto-char end)
(setq end (point-marker))
(goto-char beg)
(while (re-search-forward rgx end 'noerror)
(when (not (clojure--in-string-p)) ;; replace in comments, but not strings
(goto-char (match-beginning 1))
(delete-region (point) (match-end 1))
(insert new-alias))))))
(defun clojure--collect-ns-aliases (beg end ns-form-p)
"Collect all aliases between BEG and END.
When NS-FORM-P is non-nil, treat the region as a ns form
and pick up aliases from [... :as alias] forms,
otherwise pick up alias usages from keywords / symbols."
(let ((res ()))
(save-excursion
(let ((rgx (if ns-form-p
(rx ":as" (+ space)
(group-n 1 (+ (not (in " ,]\n")))))
(clojure--alias-usage-regexp nil))))
(goto-char beg)
(while (re-search-forward rgx end 'noerror)
(unless (or (clojure--in-string-p) (clojure--in-comment-p))
(cl-pushnew (match-string-no-properties 1) res
:test #'equal)))
(reverse res)))))
(defun clojure--rename-ns-alias-internal (current-alias new-alias)
"Rename a namespace alias CURRENT-ALIAS to NEW-ALIAS.
Assume point is at the start of ns form."
(clojure--find-ns-in-direction 'backward)
(let ((rgx (concat ":as +" (regexp-quote current-alias) "\\_>"))
(bound (save-excursion (forward-list 1) (point-marker))))
(when (search-forward-regexp rgx bound t)
(replace-match (concat ":as " new-alias))
(clojure--rename-ns-alias-usages current-alias new-alias bound (point-max)))))
;;;###autoload
(defun clojure-rename-ns-alias ()
"Rename a namespace alias.
If a region is active, only pick up and rename aliases within the region."
(interactive)
(if (use-region-p)
(let ((beg (region-beginning))
(end (copy-marker (region-end)))
current-alias new-alias)
;; while loop for renaming multiple aliases in the region.
;; C-g or leave blank to break out of the loop
(while (not (string-empty-p
(setq current-alias
(completing-read "Current alias: "
(clojure--collect-ns-aliases beg end nil)))))
(setq new-alias (read-from-minibuffer (format "Replace %s with: " current-alias)))
(clojure--rename-ns-alias-usages current-alias new-alias beg end)))
(save-excursion
(clojure--find-ns-in-direction 'backward)
(let* ((bounds (bounds-of-thing-at-point 'list))
(current-alias (completing-read "Current alias: "
(clojure--collect-ns-aliases
(car bounds) (cdr bounds) t)))
(new-alias (read-from-minibuffer (format "Replace %s with: " current-alias))))
(clojure--rename-ns-alias-internal current-alias new-alias)))))
(defun clojure--add-arity-defprotocol-internal ()
"Add an arity to a signature inside a defprotocol.
Assumes cursor is at beginning of signature."
(re-search-forward "\\[")
(save-excursion (insert "] [")))
(defun clojure--add-arity-reify-internal ()
"Add an arity to a function inside a reify.
Assumes cursor is at beginning of function."
(re-search-forward "\\(\\w+ \\)")
(insert "[")
(save-excursion (insert "])\n(" (match-string 0))))
(defun clojure--add-arity-internal ()
"Add an arity to a function.
Assumes cursor is at beginning of function."
(let ((beg-line (line-number-at-pos))
(end (save-excursion (forward-sexp)
(point))))
(down-list 2)
(when (looking-back "{" 1) ;; skip metadata if present
(up-list)
(down-list))
(cond
((looking-back "(" 1) ;; multi-arity fn
(insert "[")
(save-excursion (insert "])\n(")))
((looking-back "\\[" 1) ;; single-arity fn
(let* ((same-line (= beg-line (line-number-at-pos)))
(new-arity-text (concat (when same-line "\n") "([")))
(save-excursion
(goto-char end)
(insert ")"))
(re-search-backward " +\\[")
(replace-match new-arity-text)
(save-excursion (insert "])\n([")))))))
;;;###autoload
(defun clojure-add-arity ()
"Add an arity to a function."
(interactive)
(let ((original-pos (point))
(n 0))
(while (not (looking-at-p "(\\(defn\\|letfn\\|fn\\|defmacro\\|defmethod\\|defprotocol\\|reify\\|proxy\\)"))
(setq n (1+ n))
(backward-up-list 1 t))
(let ((beg (point))
(end-marker (make-marker))
(end (save-excursion (forward-sexp)
(point)))
(jump-up (lambda (x)
(goto-char original-pos)
(backward-up-list x t))))
(set-marker end-marker end)
(cond
((looking-at-p "(\\(defn\\|fn\\|defmethod\\|defmacro\\)")
(clojure--add-arity-internal))
((looking-at-p "(letfn")
(funcall jump-up (- n 2))
(clojure--add-arity-internal))
((looking-at-p "(proxy")
(funcall jump-up (- n 1))
(clojure--add-arity-internal))
((looking-at-p "(defprotocol")
(funcall jump-up (- n 1))
(clojure--add-arity-defprotocol-internal))
((looking-at-p "(reify")
(funcall jump-up (- n 1))
(clojure--add-arity-reify-internal)))
(indent-region beg end-marker))))
;;; Toggle Ignore forms
(defun clojure--toggle-ignore-next-sexp (&optional n)
"Insert or delete N `#_' ignore macros at the current point.
Point must be directly before a sexp or the #_ characters.
When acting on a top level form, insert #_ on a new line
preceding the form to prevent indentation changes."
(let ((rgx (rx-to-string `(repeat ,(or n 1) (seq "#_" (* (in "\r\n" blank)))))))
(backward-prefix-chars)
(skip-chars-backward "#_ \r\n")
(skip-chars-forward " \r\n")
(if (looking-at rgx)
(delete-region (point) (match-end 0))
(dotimes (_ (or n 1)) (insert-before-markers "#_"))
(when (zerop (car (syntax-ppss)))
(insert-before-markers "\n")))))
(defun clojure-toggle-ignore (&optional n)
"Toggle the #_ ignore reader form for the sexp at point.
With numeric argument, toggle N number of #_ forms at the same point.
e.g. with N = 2:
|a b c => #_#_a b c"
(interactive "p")
(save-excursion
(ignore-errors
(goto-char (or (nth 8 (syntax-ppss)) ;; beginning of string
(beginning-of-thing 'sexp))))
(clojure--toggle-ignore-next-sexp n)))
(defun clojure-toggle-ignore-surrounding-form (&optional arg)
"Toggle the #_ ignore reader form for the surrounding form at point.
With optional ARG, move up by ARG surrounding forms first.
With universal argument \\[universal-argument], act on the \"top-level\" form."
(interactive "P")
(save-excursion
(if (consp arg)
(clojure-toggle-ignore-defun)
(condition-case nil
(backward-up-list arg t t)
(scan-error nil)))
(clojure--toggle-ignore-next-sexp)))
(defun clojure-toggle-ignore-defun ()
"Toggle the #_ ignore reader form for the \"top-level\" form at point."
(interactive)
(save-excursion
(beginning-of-defun-raw)
(clojure--toggle-ignore-next-sexp)))
;;; ClojureScript
(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 `clojurescript-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}")
;;;###autoload
(define-derived-mode clojuredart-mode clojure-mode "ClojureDart"
"Major mode for editing Clojure Dart code.
\\{clojuredart-mode-map}")
;;;###autoload
(define-derived-mode jank-mode clojure-mode "Jank"
"Major mode for editing Jank code.
\\{jank-mode-map}")
;;;###autoload
(define-derived-mode joker-mode clojure-mode "Joker"
"Major mode for editing Joker code.
\\{joker-mode-map}")
;;;###autoload
(progn
(add-to-list 'auto-mode-alist
'("\\.\\(clj\\|cljd\\|dtm\\|edn\\|lpy\\)\\'" . clojure-mode))
(add-to-list 'auto-mode-alist '("\\.cljc\\'" . clojurec-mode))
(add-to-list 'auto-mode-alist '("\\.cljs\\'" . clojurescript-mode))
(add-to-list 'auto-mode-alist '("\\.cljd\\'" . clojuredart-mode))
(add-to-list 'auto-mode-alist '("\\.jank\\'" . jank-mode))
(add-to-list 'auto-mode-alist '("\\.joke\\'" . joker-mode))
;; boot build scripts are Clojure source files
(add-to-list 'auto-mode-alist '("\\(?:build\\|profile\\)\\.boot\\'" . clojure-mode))
;; babashka scripts are Clojure source files
(add-to-list 'interpreter-mode-alist '("bb" . clojure-mode))
;; nbb scripts are ClojureScript source files
(add-to-list 'interpreter-mode-alist '("nbb" . clojurescript-mode)))
(provide 'clojure-mode)
;; Local Variables:
;; coding: utf-8
;; End:
;;; clojure-mode.el ends here
clojure-mode-5.20.0/doc/ 0000775 0000000 0000000 00000000000 15015275167 0014741 5 ustar 00root root 0000000 0000000 clojure-mode-5.20.0/doc/clojure-add-arity.gif 0000664 0000000 0000000 00006230253 15015275167 0020762 0 ustar 00root root 0000000 0000000 GIF89a1 # " &