Skip to content

Commit 9802bf0

Browse files
committed
Implement dynamic font-locking
Ditch instrumented defs overlay for the new font-locking. All macros are now font-locked. This is configurable via the cider-font-lock-dynamically variable.
1 parent c26c935 commit 9802bf0

File tree

7 files changed

+204
-48
lines changed

7 files changed

+204
-48
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### New features
66

7+
* [#1301](https://github.com/clojure-emacs/cider/issues/1301): CIDER can do dynamic font-locking of defined variables, functions, and macros. This is controlled by the `cider-font-lock-dynamically` custom option.
78
* [#1271](https://github.com/clojure-emacs/cider/issues/1271): New possible value (`always-save`) for `cider-prompt-save-file-on-load`.
89
* [#1197](https://github.com/clojure-emacs/cider/issues/1197): Display some indication that we're waiting for a result for long-running evaluations.
910
* [#1127](https://github.com/clojure-emacs/cider/issues/1127): Make it possible to associate a buffer with a connection (via `cider-assoc-buffer-with-connection`).

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,15 @@ font-locked as in `clojure-mode` use the following:
466466
(setq cider-repl-use-clojure-font-lock t)
467467
```
468468

469+
* CIDER can syntax highlight symbols that are known to be defined. By default,
470+
this is done on symbols from the `clojure.core` namespace as well as macros
471+
from any namespace. If you'd like CIDER to also colorize usages of functions
472+
and variables from any namespace, do:
473+
474+
```el
475+
(setq cider-font-lock-dynamically '(macro core function var))
476+
```
477+
469478
* You can control the <kbd>C-c C-z</kbd> key behavior of switching to the REPL buffer
470479
with the `cider-switch-to-repl-command` variable. While the default command
471480
`cider-switch-to-relevant-repl-buffer` should be an adequate choice for

cider-debug.el

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -104,28 +104,6 @@ This variable must be set before starting the repl connection."
104104

105105

106106
;;; Implementation
107-
(defun cider--update-instrumented-defs (defs)
108-
"Update which DEFS in current buffer are instrumented."
109-
(remove-overlays nil nil 'cider-type 'instrumented-defs)
110-
(save-excursion
111-
(dolist (name defs)
112-
(goto-char (point-min))
113-
(when (search-forward-regexp
114-
(format "(def.*\\s-\\(%s\\)" (regexp-quote name))
115-
nil 'noerror)
116-
(cider--make-overlay
117-
(match-beginning 1) (match-end 1) 'instrumented-defs
118-
'face 'cider-instrumented-face)))))
119-
120-
(defun cider--debug-handle-instrumented-defs (defs ns)
121-
"Update display of NS according to instrumented DEFS."
122-
(-when-let (buf (-first (lambda (b) (with-current-buffer b
123-
(and (derived-mode-p 'clojure-mode)
124-
(string= ns (cider-current-ns)))))
125-
(buffer-list)))
126-
(with-current-buffer buf
127-
(cider--update-instrumented-defs defs))))
128-
129107
(defun cider-browse-instrumented-defs ()
130108
"List all instrumented definitions."
131109
(interactive)
@@ -147,8 +125,6 @@ This variable must be set before starting the repl connection."
147125
(defun cider--debug-response-handler (response)
148126
"Handle responses from the cider.debug middleware."
149127
(nrepl-dbind-response response (status id instrumented-defs ns causes)
150-
(when (member "instrumented-defs" status)
151-
(cider--debug-handle-instrumented-defs instrumented-defs ns))
152128
(when (member "eval-error" status)
153129
(cider--render-stacktrace-causes causes))
154130
(when (member "need-debug-input" status)

cider-mode.el

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
(require 'cider-interaction)
3434
(require 'cider-eldoc)
3535
(require 'cider-repl)
36+
(require 'cider-resolve)
3637

3738
(defcustom cider-mode-line-show-connection t
3839
"If the mode-line lighter should detail the connection."
@@ -178,6 +179,97 @@ entirely."
178179
["Version info" cider-version]))
179180
map))
180181

182+
;;; Dynamic font locking
183+
(defcustom cider-font-lock-dynamically '(macro core)
184+
"Specifies how much dynamic font-locking CIDER should use.
185+
Dynamic font-locking this refers to applying syntax highlighting to vars
186+
defined in the currently active nREPL connection. This is done in addition
187+
to `clojure-mode's usual (static) font-lock, so even if you set this
188+
variable to nil you'll still see basic syntax highlighting.
189+
190+
The value is a list of symbols, each one indicates a different type of var
191+
that should be font-locked:
192+
`macro' (default): Any defined macro gets the `font-lock-builtin-face'.
193+
`function': Any defined function gets the `font-lock-function-face'.
194+
`var': Any non-local var gets the `font-lock-variable-face'.
195+
`core' (default): Any symbol from clojure.core (face depends on type).
196+
197+
The value can also be t, which means to font-lock as much as possible."
198+
:type '(choice (set :tag "Fine-tune font-locking"
199+
(const :tag "Any defined macro" macro)
200+
(const :tag "Any defined function" function)
201+
(const :tag "Any defined var" var)
202+
(const :tag "Any symbol from clojure.core" core))
203+
(const :tag "Font-lock as much as possible" t))
204+
:group 'cider
205+
:package-version '(cider . "0.10.0"))
206+
207+
(defvar cider-font-lock-keywords clojure-font-lock-keywords)
208+
209+
(defun cider--compile-font-lock-keywords (symbols-dict core-dict)
210+
"Return a list of font-lock rules for the symbols in SYMBOLS-DICT."
211+
(let ((cider-font-lock-dynamically (if (eq cider-font-lock-dynamically t)
212+
'(function var macro core)
213+
cider-font-lock-dynamically))
214+
macros functions vars instrumented)
215+
(when (memq 'core cider-font-lock-dynamically)
216+
(nrepl-dict-map (lambda (sym meta)
217+
(when (nrepl-dict-get meta "cider-instrumented")
218+
(push sym instrumented))
219+
(cond
220+
((nrepl-dict-get meta "macro")
221+
(push sym macros))
222+
((nrepl-dict-get meta "arglists")
223+
(push sym functions))
224+
(t
225+
(push sym vars))))
226+
core-dict))
227+
(nrepl-dict-map (lambda (sym meta)
228+
(when (nrepl-dict-get meta "cider-instrumented")
229+
(push sym instrumented))
230+
(cond
231+
((and (memq 'macro cider-font-lock-dynamically)
232+
(nrepl-dict-get meta "macro"))
233+
(push sym macros))
234+
((and (memq 'function cider-font-lock-dynamically)
235+
(nrepl-dict-get meta "arglists"))
236+
(push sym functions))
237+
((memq 'var cider-font-lock-dynamically)
238+
(push sym vars))))
239+
symbols-dict)
240+
`(;; Aliases
241+
("\\_<\\(?1:\\(\\s_\\|\\sw\\)+\\)/" 1 font-lock-type-face)
242+
243+
,@(when macros
244+
`((,(concat (rx (or "(" "#'")) ; Can't take the value of macros.
245+
"\\(" (regexp-opt macros 'symbols) "\\)")
246+
1 font-lock-keyword-face append)))
247+
,@(when functions
248+
`((,(regexp-opt functions 'symbols) 0 font-lock-function-name-face append)))
249+
,@(when vars
250+
`((,(regexp-opt vars 'symbols) 0 font-lock-variable-name-face append)))
251+
,@(when instrumented
252+
`((,(regexp-opt instrumented 'symbols) 0 'cider-instrumented-face prepend))))))
253+
254+
(defconst cider-static-font-lock-keywords
255+
(eval-when-compile
256+
`((,(regexp-opt '("#break" "#dbg") 'symbols) 0 font-lock-warning-face)))
257+
"Default expressions to highlight in CIDER mode.")
258+
259+
(defun cider-refresh-font-lock (&optional ns)
260+
"Ensure that the current buffer has up-to-date font-lock rules.
261+
NS defaults to `cider-current-ns', and it can also be a dict describing the
262+
namespace itself."
263+
(interactive)
264+
(when cider-font-lock-dynamically
265+
(-when-let (symbols (cider-resolve-ns-symbols (or ns (cider-current-ns))))
266+
(setq-local cider-font-lock-keywords
267+
(append clojure-font-lock-keywords
268+
cider-static-font-lock-keywords
269+
(cider--compile-font-lock-keywords
270+
symbols (cider-resolve-ns-symbols (cider-resolve-core-ns))))))
271+
(font-lock-refresh-defaults)))
272+
181273
;;;###autoload
182274
(define-minor-mode cider-mode
183275
"Minor mode for REPL interaction from a Clojure buffer.
@@ -190,6 +282,10 @@ entirely."
190282
(make-local-variable 'completion-at-point-functions)
191283
(add-to-list 'completion-at-point-functions
192284
#'cider-complete-at-point)
285+
(when (consp font-lock-defaults)
286+
(setq-local font-lock-defaults
287+
(cons 'cider-font-lock-keywords (cdr font-lock-defaults))))
288+
(cider-refresh-font-lock)
193289
(setq next-error-function #'cider-jump-to-compilation-error))
194290

195291
(provide 'cider-mode)

cider-repl.el

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,15 +169,24 @@ PROJECT-DIR, HOST and PORT are as in `nrepl-make-buffer-name'."
169169
"0.10.0")
170170

171171
(defvar-local cider-repl-ns-cache nil
172-
"A dict holding information about all currently loaded namespaces.")
172+
"A dict holding information about all currently loaded namespaces.
173+
This cache is stored in the connection buffer. Other buffer's access it
174+
via `cider-current-connection'.")
173175

174176
(defun cider-repl--state-handler (response)
175177
"Handle the server STATE.
176178
Currently, this is only used to keep `cider-repl-type' updated."
177-
(-when-let (state (nrepl-dict-get response "state"))
178-
(nrepl-dbind-response state (repl-type changed-namespaces)
179-
(setq cider-repl-type repl-type)
180-
(setq cider-repl-ns-cache (nrepl-dict-merge cider-repl-ns-cache changed-namespaces)))))
179+
(with-demoted-errors "Error in `cider-repl--state-handler': %s"
180+
(-when-let (state (nrepl-dict-get response "state"))
181+
(nrepl-dbind-response state (repl-type changed-namespaces)
182+
(setq cider-repl-type repl-type)
183+
(unless (nrepl-dict-empty-p changed-namespaces)
184+
(setq cider-repl-ns-cache (nrepl-dict-merge cider-repl-ns-cache changed-namespaces))
185+
(dolist (b (buffer-list))
186+
(with-current-buffer b
187+
(when cider-mode
188+
(-when-let (ns-dict (nrepl-dict-get changed-namespaces (cider-current-ns)))
189+
(cider-refresh-font-lock ns-dict))))))))))
181190

182191
(defun cider-repl-create (endpoint)
183192
"Create a REPL buffer and install `cider-repl-mode'.

cider-resolve.el

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,77 @@
1919

2020
;;; Commentary:
2121

22+
;; The ns cache is a dict of namespaces stored in the connection buffer. This
23+
;; file offers functions to easily get information about variables from this
24+
;; cache, given the variable's name and the file's namespace. This
25+
;; functionality is similar to that offered by the `cider-var-info' function
26+
;; (and others). The difference is that all functions in this file operate
27+
;; without contacting the server (they still rely on an active connection
28+
;; buffer, but no messages are actually exchanged).
29+
30+
;; For this reason, the functions here are well suited for very
31+
;; performance-sentitive operations, such as font-locking or
32+
;; indentation. Meanwhile, operations like code-jumping are better off
33+
;; communicating with the middleware, just in the off chance that the cache is
34+
;; outdated.
35+
36+
;; Below is a typical entry on this cache dict. Note that clojure.core symbols
37+
;; are excluded from the refers to save space.
38+
39+
;; "cider.nrepl.middleware.track-state"
40+
;; (dict "aliases"
41+
;; (dict "cljs" "cider.nrepl.middleware.util.cljs"
42+
;; "misc" "cider.nrepl.middleware.util.misc"
43+
;; "set" "clojure.set")
44+
;; "interns" (dict a
45+
;; "assoc-state" (dict "arglists"
46+
;; (("response"
47+
;; (dict "as" "msg" "keys"
48+
;; ("session")))))
49+
;; "filter-core" (dict "arglists"
50+
;; (("refers")))
51+
;; "make-transport" (dict "arglists"
52+
;; (((dict "as" "msg" "keys"
53+
;; ("transport")))))
54+
;; "ns-as-map" (dict "arglists"
55+
;; (("ns")))
56+
;; "ns-cache" (dict)
57+
;; "relevant-meta" (dict "arglists"
58+
;; (("var")))
59+
;; "update-vals" (dict "arglists"
60+
;; (("m" "f")))
61+
;; "wrap-tracker" (dict "arglists"
62+
;; (("handler"))))
63+
;; "refers" (dict "set-descriptor!" "#'clojure.tools.nrepl.middleware/set-descriptor!"))
64+
2265
;;; Code:
2366

2467
(require 'nrepl-client)
2568
(require 'cider-interaction)
26-
(require 'cider-repl)
69+
70+
(defvar cider-repl-ns-cache)
2771

2872
(defun cider-resolve--get-in (&rest keys)
29-
"Return (nrepl-dict-get-in cider-repl-ns-cache keys)."
73+
"Return (nrepl-dict-get-in cider-repl-ns-cache KEYS)."
3074
(when cider-connections
31-
(nrepl-dict-get-in
32-
(with-current-buffer (cider-current-connection)
33-
cider-repl-ns-cache)
34-
keys)))
75+
(with-current-buffer (cider-current-connection)
76+
(nrepl-dict-get-in cider-repl-ns-cache keys))))
3577

3678
(defun cider-resolve-alias (ns alias)
3779
"Return the namespace that ALIAS refers to in namespace NS.
3880
If it doesn't point anywhere, returns ALIAS."
3981
(or (cider-resolve--get-in ns "aliases" alias)
4082
alias))
4183

84+
(defconst cider-resolve--prefix-regexp "\\`\\(?:#'\\)?\\([^/]+\\)/")
85+
4286
(defun cider-resolve-var (ns var)
4387
"Return a dict of the metadata of a clojure var VAR in namespace NS.
4488
VAR is a string.
4589
Return nil only if VAR cannot be resolved."
46-
(let* ((prefix-regexp "\\`\\([^/]+\\)/")
47-
(var-ns (when (string-match prefix-regexp var)
90+
(let* ((var-ns (when (string-match cider-resolve--prefix-regexp var)
4891
(cider-resolve-alias ns (match-string 1 var))))
49-
(name (replace-regexp-in-string prefix-regexp "" var)))
92+
(name (replace-regexp-in-string cider-resolve--prefix-regexp "" var)))
5093
(or
5194
(cider-resolve--get-in (or var-ns ns) "interns" name)
5295
(unless var-ns
@@ -57,16 +100,31 @@ Return nil only if VAR cannot be resolved."
57100
(unless (equal ns "clojure.core")
58101
(cider-resolve-var "clojure.core" name)))))))
59102

60-
(defun cider-match-instrumented-symbol (n face)
61-
"Return a face specification for font-locking.
62-
If (match-string N) is an instrumented symbol, return
63-
(face cider-instrumented-face FACE)
64-
otherwise, return (face FACE)."
65-
(cons 'face
66-
(if (nrepl-dict-get (cider-resolve-var (cider-current-ns) (match-string n))
67-
"cider-instrumented")
68-
`((cider-instrumented-face ,face))
69-
(list face))))
103+
(defun cider-resolve-core-ns ()
104+
"Return a dict of the core namespace for current connection.
105+
This will be clojure.core or cljs.core depending on `cider-repl-type'."
106+
(when (cider-connected-p)
107+
(with-current-buffer (cider-current-connection)
108+
(cider-resolve--get-in (if (equal cider-repl-type "cljs")
109+
"cljs.core"
110+
"clojure.core")))))
111+
112+
(defun cider-resolve-ns-symbols (ns)
113+
"Return a dict of all valid symbols in NS.
114+
Each entry's value is the metadata of the var that the symbol refers to.
115+
NS can be the namespace name, or a dict of the namespace itself."
116+
(-when-let (dict (if (stringp ns)
117+
(cider-resolve--get-in ns)
118+
ns))
119+
(nrepl-dbind-response dict (interns refers aliases)
120+
(append interns
121+
(nrepl-dict-flat-map (lambda (sym var) (list sym (cider-resolve-var ns var)))
122+
refers)
123+
(nrepl-dict-flat-map (lambda (alias namespace)
124+
(nrepl-dict-flat-map (lambda (sym meta)
125+
(list (concat alias "/" sym) meta))
126+
(cider-resolve--get-in namespace "interns")))
127+
aliases)))))
70128

71129
(provide 'cider-resolve)
72130
;;; cider-resolve.el ends here

nrepl-client.el

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,13 @@ any of the values is nil."
348348
(setq out (nrepl-dict-get out (pop keys))))
349349
out))
350350

351+
(defun nrepl-dict-flat-map (function dict)
352+
"Map FUNCTION over DICT and flatten the result.
353+
FUNCTION follows the same restrictions as in `nrepl-dict-map', and it must
354+
also alway return a sequence (since the result will be flattened)."
355+
(when dict
356+
(apply #'append (nrepl-dict-map function dict))))
357+
351358
(defun nrepl--cons (car list-or-dict)
352359
"Generic cons of CAR to LIST-OR-DICT."
353360
(if (eq (car list-or-dict) 'dict)

0 commit comments

Comments
 (0)