-
Notifications
You must be signed in to change notification settings - Fork 175
/
nrepl.clj
863 lines (789 loc) · 50.3 KB
/
nrepl.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
(ns cider.nrepl
"Middleware descriptors and related utility functions.
While normally middleware descriptors live alongside middleware
definitions, cider-nrepl separates those. The rationale behind this
is to avoid loading each middleware definition until its first usage.
For this purpose we're defining each middleware as a wrapper that
defers the loading of the actual middleware."
(:require
[cider.nrepl.version :as version]
[cider.nrepl.middleware :as mw]
[cider.nrepl.middleware.util.cljs :as cljs]
[cider.nrepl.print-method] ;; we load this namespace, so it's directly available to clients
[haystack.analyzer :as analyzer]
[nrepl.middleware :refer [set-descriptor!]]
[nrepl.middleware.caught :refer [wrap-caught]]
[nrepl.middleware.print :refer [wrap-print wrap-print-optional-arguments]]
[nrepl.middleware.session :refer [session]]
[nrepl.misc :as misc :refer [with-session-classloader]]
[nrepl.server :as nrepl-server]
[orchard.info]
[orchard.namespace]
[orchard.java]))
(def min-clojure-verion
"The minimum Clojure version needed by cider-nrepl to work properly.
Having an enforced minimum version can help users and maintainers alike diagnose issues more quickly,
avoiding problematic code paths in our middleware, and in clients like cider.el."
{:major 1
:minor 9})
;; Make sure we're running a supported Clojure version
(when (or (< (-> *clojure-version* :major long)
(-> min-clojure-verion :major long))
(and (= (-> *clojure-version* :major long)
(-> min-clojure-verion :major long))
(< (-> *clojure-version* :minor long)
(-> min-clojure-verion :minor long))))
(try
(.println System/err (format "cider-nrepl requires a newer Clojure version (found: %s, minimum required: %s). Exiting."
*clojure-version*
min-clojure-verion))
(finally
(System/exit 1))))
;; Perform the underlying dynamic `require`s asap, and also not within a separate thread
;; (note the `future` used in `#'initializer`),
;; since `require` is not thread-safe:
@@orchard.java/parser-next-available?
@analyzer/spec-abbrev
(defn warmup-orchard-caches!
"Warms up `orchard.java/cache`, drastically improving the completion and info UX performance for first hits.
(This was our behavior for many years,
then had to be disabled for test suite reasons in Orchard 0.15.0 to 0.17.0 / cider-nrepl 0.38.0 to 0.41.0, and now it's restored again)
Note that this can only be done cider-nrepl side, unlike before when it was done in Orchard itself."
[]
;; 1.- Warmup the overall cache for core Java and Clojure stuff
@orchard.java/cache-initializer
;; 2.- Also cache classes that are `:import`ed throughout the project.
;; The class list is obtained through `ns` form analysis,
;; so that we don't depend on whether the namespaces have been loaded yet:
(doseq [ns-form (vals (orchard.namespace/project-ns-forms))
class-sym (orchard.namespace/ns-form-imports ns-form)
:when (try
(Class/forName (str class-sym)
false ;; Don't initialize this class, avoiding side-effects (including static class initializers; with the exception of static fields with an initial value)
(.getContextClassLoader (Thread/currentThread)))
(catch Throwable _))]
(orchard.java/class-info class-sym))
;; 3.- Leave an indicator that can help up assess the cache size in a future:
(-> orchard.java/cache .keySet .size))
(def initializer
(future
(warmup-orchard-caches!)))
;;; Functionality for deferred middleware loading
;;
;; cider-nrepl depends on many libraries and loading all middleware at
;; startup time causes significant delays. That's why we've developed
;; a simple approach to delay loading the actual definition of a middleware
;; until a request handled by this middleware is made.
(def delayed-handlers
"Map of `delay`s holding deferred middleware handlers."
(atom {}))
(def require-lock
"Lock used to inhibit concurrent `require` of the middleware namespaces.
Clojure seem to have issues with concurrent loading of transitive
dependencies. The issue is extremely hard to reproduce. For the context see
https://github.com/clojure-emacs/cider/issues/2092 and
https://github.com/clojure-emacs/cider/pull/2078."
(Object.))
(defn- resolve-or-fail [sym]
(or (resolve sym)
(throw (IllegalArgumentException. (format "Cannot resolve %s" sym)))))
(defn- handler-future
"Check whether a delay exists in the `delayed-handlers`. Otherwise make a delay
out of `fn-name` and place it in the atom. "
[sym ns fn-name session]
(or (get @delayed-handlers sym)
(get (swap! delayed-handlers assoc sym
(delay
(locking require-lock
(with-session-classloader session
(require ns))
(resolve-or-fail fn-name))))
sym)))
(defmacro run-deferred-handler
"Require and invoke the handler delay at run-time with arguments `handler` and `msg`.
`fn-name` must be a namespaced symbol (unquoted)."
[fn-name handler msg]
(let [ns (symbol (namespace `~fn-name))
sym (symbol (name `~fn-name))]
`(@(handler-future '~sym '~ns '~fn-name (:session ~msg))
~handler ~msg)))
(defmacro ^{:arglists '([name handler-fn descriptor]
[name handler-fn trigger-it descriptor])}
def-wrapper
"Define delayed middleware (e.g. wrap-xyz).
`handler-fn` is an unquoted name of a function that takes two arguments -
`handler` and `message`. It is called only when certain conditions are met as
expressed by the optional `trigger-it` argument. `trigger-it` can be either a
function or a set of ops (strings). When a function, it must take a `msg` and
return truthy value when `handler-fn` should run. When `trigger-it` is missing,
`handle-fn` is called when :op of `msg` is one of keys of the :handles slot of
the `descriptor`. When `trigger-it` is a set it should contain extra ops,
besides those in :handles slot, on which `handle-fn` is
triggered. `descriptor` is passed directly to the nREPLs `set-descriptor!`."
[name handler-fn & [trigger-it descriptor]]
(let [[descriptor trigger-it] (if descriptor
[descriptor trigger-it]
[trigger-it descriptor])
trigger-it (eval trigger-it)
descriptor (update (eval descriptor) :requires (fnil conj #{}) #'nrepl.middleware.session/session)
cond (if (or (nil? trigger-it) (set? trigger-it))
(let [ops-set (into (-> descriptor :handles keys set) trigger-it)]
`(~ops-set (:op ~'msg)))
`(~trigger-it ~'msg))
doc (or (:doc descriptor) "")]
(assert descriptor)
`(do
(defn ~name ~doc [~'h]
(fn [~'msg]
(if (and ~cond (not (:inhibit-cider-middleware ~'msg)))
(run-deferred-handler ~handler-fn ~'h ~'msg)
(~'h ~'msg))))
(set-descriptor! #'~name ~descriptor))))
;;; Deferred Middleware Wrappers
;;
;; Each middleware is defined in its own namespace, but here we're defining
;; deferred versions of the middleware handlers, that load the actual middleware
;; handlers on demand (the first time some middleware op gets invoked). This
;; makes the code a bit more complex, but improves a lot the startup time
;; as almost nothing gets loaded during REPL boot time.
(def-wrapper wrap-content-type cider.nrepl.middleware.content-type/handle-content-type
#{"eval"}
{:doc "Middleware that adds `content-type` annotations to the result of the the eval op."
:requires #{#'wrap-print}
:expects #{"eval" "load-file"}
:handles {"content-type"
{:doc "Enhances the `eval` op by adding `content-type` and `body` to certain `eval` responses. Not an op in itself.
Depending on the type of the return value of the evaluation this middleware may kick in and include a representation of the result in the response, together with a MIME/Media type to indicate how it should be handled by the client. Comes with implementations for `URI`, `URL`, `File`, and `java.awt.Image`. More type handlers can be provided by the user by extending the `cider.nrepl.middleware.content-type/content-type-response` multimethod. This dispatches using `clojure.core/type`, so `:type` metadata on plain Clojure values can be used to provide custom handling."
:returns {"body" "The rich response document, if applicable."
"content-type" "The Media type (MIME type) of the reponse, structured as a pair, `[type {:as attrs}]`."
"content-transfer-encoding" "The encoding of the response body (Optional, currently only one possible value: `\"base64\"`)."}
:optional {"content-type" "If present and non-nil, try to detect and handle content-types."}}}})
(def-wrapper wrap-slurp cider.nrepl.middleware.slurp/handle-slurp
{:doc "Middleware that handles slurp requests."
:handles {"slurp"
{:doc "Slurps a URL from the nREPL server, returning MIME data."
:returns {"content-type" "A MIME type for the response, if one can be detected."
"content-transfer-encoding" "The encoding (if any) for the content."
"body" "The slurped content body."}}}})
(def-wrapper wrap-apropos cider.nrepl.middleware.apropos/handle-apropos
{:doc "Middleware that handles apropos requests"
:handles {"apropos"
{:doc "Return a sequence of vars whose name matches the query pattern, or if specified, having the pattern in their docstring."
:requires {"query" "The search query."}
:optional {"filter-regexps" "All vars from namespaces matching any regexp from this list would be dropped from the result."}
:returns {"apropos-matches" "A list of matching symbols."}}}})
(def-wrapper wrap-classpath cider.nrepl.middleware.classpath/handle-classpath
{:doc "Middleware that provides the java classpath."
:handles {"classpath"
{:doc "Obtain a list of entries in the Java classpath."
:returns {"classpath" "A list of the Java classpath entries."}}}})
(def-wrapper wrap-complete cider.nrepl.middleware.complete/handle-complete
(cljs/requires-piggieback
{:doc "Middleware providing completion support."
:requires #{#'session}
:handles {"complete"
{:doc "Return a list of symbols matching the specified (partial) symbol."
:requires {"ns" "The namespace is which to look for completions (falls back to *ns* if not specified)"
"prefix" "The prefix for completion candidates"
"session" "The current session"}
:optional {"context" "Completion context for compliment."
"extra-metadata" "List of extra-metadata fields. Possible values: arglists, doc."}
:returns {"completions" "A list of possible completions"}}
"complete-doc"
{:doc "Retrieve documentation suitable for display in completion popup"
:requires {"ns" "The symbol's namespace"
"sym" "The symbol to lookup"}
:returns {"completion-doc" "Symbol's documentation"}}
"complete-flush-caches"
{:doc "Forces the completion backend to repopulate all its caches"}}}))
(def-wrapper wrap-debug cider.nrepl.middleware.debug/handle-debug
#{"eval"}
(cljs/requires-piggieback
{:doc "Provide instrumentation and debugging functionality."
:expects #{"eval"}
:requires #{#'wrap-print #'session}
:handles {"debug-input"
{:doc "Read client input on debug action."
:requires {"input" "The user's reply to the input request."
"key" "The corresponding input request key."}
:returns {"status" "done"}}
"init-debugger"
{:doc "Initialize the debugger so that `breakpoint` works correctly. This usually does not respond immediately. It sends a response when a breakpoint is reached or when the message is discarded."
:requires {"id" "A message id that will be responded to when a breakpoint is reached."}}
"debug-instrumented-defs"
{:doc "Return an alist of definitions currently thought to be instrumented on each namespace. Due to Clojure's versatility, this could include false postives, but there will not be false negatives. Instrumentations inside protocols are not listed."
:returns {"status" "done"
"list" "The alist of (NAMESPACE . VARS) that are thought to be instrumented."}}
"debug-middleware"
{:doc "Debug a code form or fall back on regular eval."
:requires {"id" "A message id that will be responded to when a breakpoint is reached."
"code" "Code to debug, there must be a #dbg or a #break reader macro in it, or nothing will happen."
"file" "File where the code is located."
"ns" "Passed to \"eval\"."
"point" "Position in the file where the provided code begins."}
:returns {"status" "\"done\" if the message will no longer be used, or \"need-debug-input\" during debugging sessions"}}}}))
(def-wrapper wrap-enlighten cider.nrepl.middleware.enlighten/handle-enlighten
:enlighten
{:expects #{"eval" #'wrap-debug}})
(def-wrapper wrap-format cider.nrepl.middleware.format/handle-format
{:doc "Middleware providing support for formatting Clojure code and EDN data."
:requires #{#'wrap-print}
:handles {"format-code"
{:doc "Reformats the given Clojure code, returning the result as a string."
:requires {"code" "The code to format."}
:optional {"options" "Configuration map for cljfmt."}
:returns {"formatted-code" "The formatted code."}}
"format-edn"
{:doc "Reformats the given EDN data, returning the result as a string."
:requires {"edn" "The data to format."}
:optional wrap-print-optional-arguments
:returns {"formatted-edn" "The formatted data."}}}})
(def fragments-desc
"It's a vector of fragments, where fragment is a map with `:type` ('text' or 'html') and `:content` plain text or html markup, respectively")
(def fragments-doc
{"doc-fragments" (str "May be absent. Represents the body of a Java doc comment, including the first sentence and excluding any block tags. " fragments-desc)
"doc-first-sentence-fragments" (str "May be absent. Represents the first sentence of a Java doc comment. " fragments-desc)
"doc-block-tags-fragments" (str "May be absent. Represent the 'param', 'returns' and 'throws' sections a Java doc comment. " fragments-desc)})
(def info-params
{"sym" "The symbol to lookup"
"ns" "The current namespace"
"context" "A Compliment completion context, just like the ones already passed for the \"complete\" op,
with the difference that the symbol at point should be entirely replaced by \"__prefix__\".
For Java interop queries, it helps inferring the precise type of the object the `:sym` or `:member` refers to,
making the results more accurate (and less numerous)."
"class" "A Java class. If `:ns` is passed, it will be used for fully-qualifiying the class, if necessary."
"member" "A Java class member."
"var-meta-allowlist" "The metadata keys from vars to be returned. Currently only affects `:clj`.
Defaults to the value of `orchard.meta/var-meta-allowlist`.
If specified, the value will be concatenated to that of `orchard.meta/var-meta-allowlist`."})
(def-wrapper wrap-info cider.nrepl.middleware.info/handle-info
(cljs/requires-piggieback
{:requires #{#'session}
:handles {"info"
{:doc "Return a map of information about the specified symbol."
:optional info-params
:returns (merge {"status" "done"} fragments-doc)}
"eldoc"
{:doc "Return a map of information about the specified symbol."
:optional info-params
:returns (merge {"status" "done"} fragments-doc)}
"eldoc-datomic-query"
{:doc "Return a map containing the inputs of the datomic query."
:requires {"sym" "The symbol to lookup"
"ns" "The current namespace"}
:returns {"status" "done"}}}}))
(def inspector-returns (merge {"status" "\"done\""
"value" "The inspector result. Contains a specially-formatted string that can be `read` and then rendered client-side."}
fragments-doc))
(def-wrapper wrap-inspect cider.nrepl.middleware.inspect/handle-inspect
#{"eval"}
(cljs/expects-piggieback
{:doc "Add a value inspector option to the eval op. Passing a non-nil value
in the `:inspect` slot will cause the last value returned by eval to
be inspected. Returns a string representation of the resulting
inspector's state in the `:value` slot."
:requires #{"clone" #'wrap-caught #'wrap-print}
:expects #{"eval"}
:handles {"inspect-pop"
{:doc "Moves one level up in the inspector stack."
:requires {"session" "The current session"}
:returns inspector-returns}
"inspect-push"
{:doc "Inspects the inside value specified by index."
:requires {"idx" "Index of the internal value currently rendered."
"session" "The current session"}
:returns inspector-returns}
"inspect-next-sibling"
{:doc "Increment the index of the last 'nth in the path by 1,
if applicable, and re-render the updated value."
:requires {"session" "The current session"}
:returns inspector-returns}
"inspect-previous-sibling"
{:doc "Decrement the index of the last 'nth in the path by 1,
if applicable, and re-render the updated value."
:requires {"session" "The current session"}
:returns inspector-returns}
"inspect-refresh"
{:doc "Re-renders the currently inspected value."
:requires {"session" "The current session"}
:returns inspector-returns}
"inspect-get-path"
{:doc "Returns the path to the current position in the inspected value."
:requires {"session" "The current session"}
:returns inspector-returns}
"inspect-next-page"
{:doc "Jumps to the next page in paginated collection view."
:requires {"session" "The current session"}
:returns inspector-returns}
"inspect-prev-page"
{:doc "Jumps to the previous page in paginated collection view."
:requires {"session" "The current session"}
:returns inspector-returns}
"inspect-set-page-size"
{:doc "Sets the page size in paginated view to specified value."
:requires {"page-size" "New page size."
"session" "The current session"}
:returns inspector-returns}
"inspect-set-max-atom-length"
{:doc "Set the max length of nested atoms to specified value."
:requires {"max-atom-length" "New max length."
"session" "The current session"}
:returns inspector-returns}
"inspect-set-max-coll-size"
{:doc "Set the number of nested collection members to display before truncating."
:requires {"max-coll-size" "New collection size."
"session" "The current session"}
:returns inspector-returns}
"inspect-clear"
{:doc "Clears the state state of the inspector."
:requires {"session" "The current session"}
:returns inspector-returns}
"inspect-def-current-value"
{:doc "Define the currently inspected value as a var with the given var-name in the provided namespace."
:requires {"session" "The current session"
"ns" "Namespace to define var on"
"var-name" "The var name"}
:returns inspector-returns}
"inspect-tap-current-value"
{:doc "Send the currently inspected value to the Clojure tap>."
:requires {"session" "The current session"}
:returns inspector-returns}
"inspect-tap-indexed"
{:doc "Send the currently inspected sub-value at `idx` to the Clojure tap>."
:requires {"session" "The current session"
"idx" "Index of the internal value to be tapped"}
:returns inspector-returns}}}))
(def-wrapper wrap-log cider.nrepl.middleware.log/handle-log
{:doc "Middleware that captures log events and makes them inspect-able."
:requires #{#'session #'wrap-print}
:handles
{"cider/log-add-appender"
{:doc "Add an appender to a log framework."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."
"filters" "A map from filter name to filter condition."
"size" "The number of events the appender keeps in memory."
"threshold" "The threshold in percent used to cleanup events."}
:optional {"logger" "The name of the logger to attach to."}
:returns {"status" "done"
"cider/log-add-appender" "The appender that was added."}}
"cider/log-add-consumer"
{:doc "Add a consumer to an appender of a log framework."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."
"filters" "A map from filter name to filter condition."}
:returns {"status" "done"
"cider/log-add-consumer" "The consumer that was added."}}
"cider/log-analyze-stacktrace"
{:doc "Analyze the stacktrace of a log event."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."
"event" "The id of the event to inspect."}
:returns {"status" "done"}}
"cider/log-clear-appender"
{:doc "Clear all events of a log appender."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."}
:returns {"status" "done"
"cider/log-clear-appender" "The appender that was cleared."}}
"cider/log-exceptions"
{:doc "Return the exceptions and their frequencies for the given framework and appender."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."}
:returns {"status" "done"
"cider/log-exceptions" "A map from exception name to event frequency."}}
"cider/log-frameworks"
{:doc "Return the available log frameworks."
:returns {"status" "done"
"cider/log-frameworks" "A list of log frameworks."}}
"cider/log-format-event"
{:doc "Format a log event."
:requires {"framework" "The id of the log framework."
"appender" "The name of the log appender."
"event" "The id of the log event."}
:optional wrap-print-optional-arguments
:returns {"status" "done"
"cider/log-format-event" "The formatted log event."}}
"cider/log-inspect-event"
{:doc "Inspect a log event."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."
"event" "The id of the event to inspect."}
:returns {"status" "done"
"value" "The inspection result."}}
"cider/log-levels"
{:doc "Return the log levels and their frequencies for the given framework and appender."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."}
:returns {"status" "done"
"cider/log-levels" "A map from log level to event frequency."}}
"cider/log-loggers"
{:doc "Return the loggers and their frequencies for the given framework and appender."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."}
:returns {"status" "done"
"cider/log-loggers" "A map from logger name to event frequency."}}
"cider/log-remove-appender"
{:doc "Remove an appender from a log framework."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."}
:returns {"status" "done"
"cider/log-remove-appender" "The removed appender."}}
"cider/log-remove-consumer"
{:doc "Remove a consumer from the appender of a log framework."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."
"consumer" "The name of the consumer."}
:returns {"status" "done"
"cider/log-add-consumer" "The removed consumer."}}
"cider/log-update-appender"
{:doc "Update the appender of a log framework."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."
"filters" "A map from filter name to filter condition."
"size" "The number of events the appender keeps in memory."
"threshold" "The threshold in percent used to cleanup events."}
:returns {"status" "done"
"cider/log-update-appender" "The updated appender."}}
"cider/log-update-consumer"
{:doc "Update the consumer of a log appender."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."
"consumer" "The name of the consumer."
"filters" "A map from filter name to filter condition."}
:returns {"status" "done"
"cider/log-update-consumer" "The consumer that was updated."}}
"cider/log-search"
{:doc "Search the log events of an appender."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."}
:optional {"filters" "A map from filter name to filter condition."
"limit" "Number of log events to return."}
:returns {"status" "done"
"cider/log-search" "The list of log events matching the search."}}
"cider/log-threads"
{:doc "Return the threads and their frequencies for the given framework and appender."
:requires {"framework" "The id of the log framework."
"appender" "The name of the appender."}
:returns {"status" "done"
"cider/log-threads" "A map from thread name to event frequency."}}}})
(def-wrapper wrap-macroexpand cider.nrepl.middleware.macroexpand/handle-macroexpand
(cljs/requires-piggieback
{:doc "Macroexpansion middleware."
:requires #{#'session}
:handles {"macroexpand"
{:doc "Produces macroexpansion of some form using the given expander."
:requires {"code" "The form to macroexpand."}
:optional {"ns" "The namespace in which to perform the macroexpansion. Defaults to 'user for Clojure and 'cljs.user for ClojureScript."
"expander" "The macroexpansion function to use. Possible values are \"macroexpand-1\", \"macroexpand\", or \"macroexpand-all\". Defaults to \"macroexpand\"."
"display-namespaces" "How to print namespace-qualified symbols in the result. Possible values are \"qualified\" to leave all namespaces qualified, \"none\" to elide all namespaces, or \"tidy\" to replace namespaces with their aliases in the given namespace. Defaults to \"qualified\"."
"print-meta" "If truthy, also print metadata of forms."}
:returns {"expansion" "The macroexpanded form."}}}}))
(def-wrapper wrap-ns cider.nrepl.middleware.ns/handle-ns
(cljs/requires-piggieback
{:doc "Provide ns listing and browsing functionality."
:requires #{#'session}
:handles {"ns-list"
{:doc "Return a sorted list of all namespaces."
:returns {"status" "done" "ns-list" "The sorted list of all namespaces."}
:optional {"filter-regexps" "All namespaces matching any regexp from this list would be dropped from the result."}}
"ns-list-vars-by-name"
{:doc "Return a list of vars named `name` amongst all namespaces."
:requires {"name" "The name to use."}
:returns {"status" "done" "var-list" "The list obtained."}}
"ns-vars"
{:doc "Returns a sorted list of public vars in a namespace."
:requires {"ns" "The namespace to browse."}
:optional {"var-query" "The search query for vars. Only \"private?\" is supported for ClojureScript."}
:returns {"status" "done" "ns-vars" "The sorted list of public vars in a namespace."}}
"ns-vars-with-meta"
{:doc "Returns a map of [var-name] to [var-metadata] for public vars in a namespace."
:requires {"ns" "The namespace to use."}
:optional {"var-query" "The search query for vars. Only \"private?\" is supported for ClojureScript."}
:returns {"status" "done" "ns-vars-with-meta" "The map of [var-name] to [var-metadata] for public vars in a namespace."}}
"ns-path"
{:doc "Returns the path to the file containing ns."
:requires {"ns" "The namespace to find."}
:returns {"status" "done"
"path" "The path to the file containing ns. Please favor `:url` in ClojureScript, but fall back to `:path`."
"url" "The Java URL indicating the file containing ns. Please favor this attribute over `:path` when possible. If this value is nil, you can fall back to `:path`."}}
"ns-load-all"
{:doc "Loads all project namespaces."
:returns {"status" "done" "loaded-ns" "The list of ns that were loaded."}}
"ns-aliases"
{:doc "Returns a map of [ns-alias] to [ns-name] in a namespace."
:requires {"ns" "The namespace to use."}
:returns {"status" "done" "ns-aliases" "The map of [ns-alias] to [ns-name] in a namespace."}}}}))
(def-wrapper wrap-out cider.nrepl.middleware.out/handle-out
(cljs/expects-piggieback
{:requires #{#'session}
:expects #{"eval"}
:handles {"out-subscribe"
{:doc "Change #'*out* so that it also prints to active sessions, even outside an eval scope."}
"out-unsubscribe"
{:doc "Change #'*out* so that it no longer prints to active sessions outside an eval scope."}}}))
(def-wrapper wrap-profile cider.nrepl.middleware.profile/handle-profile
{:doc "Middleware that provides supports Profiling based on https://github.com/thunknyc/profile"
:handles {"toggle-profile-ns" {:doc "Toggle profiling of given namespace."
:requires {"ns" "The current namespace"}
:returns {"status" "Done"
"value" "'profiled' if profiling enabled, 'unprofiled' if disabled"}}
"is-var-profiled" {:doc "Reports wheth symbol is currently profiled."
:requires {"sym" "The symbol to check"
"ns" "The current namespace"}
:returns {"status" "Done"
"value" "'profiled' if profiling enabled, 'unprofiled' if disabled"}}
"get-max-samples" {:doc "Returns maximum number of samples to be collected for any var."
:requires {}
:returns {"status" "Done"
"value" "String representing number of max-sample-count"}}
"set-max-samples" {:doc "Sets maximum sample count. Returns new max-sample-count."
:requires {"max-samples" "Maxiumum samples to collect for any single var."}
:returns {"status" "Done"
"value" "String representing number of max-sample-count"}}
"toggle-profile" {:doc "Toggle profiling of a given var."
:requires {"sym" "The symbol to profile"
"ns" "The current namespace"}
:returns {"status" "Done"
"value" "'profiled' if profiling enabled, 'unprofiled' if disabled, 'unbound' if ns/sym not bound"}}
"profile-var-summary" {:doc "Return profiling data summary for a single var."
:requires {"sym" "The symbol to profile"
"ns" "The current namespace"}
:returns {"status" "Done"
"err" "Content of profile summary report"}}
"profile-summary" {:doc "Return profiling data summary."
:requires {}
:returns {"status" "Done"
"err" "Content of profile summary report"}}
"clear-profile" {:doc "Clears profile of samples."
:requires {}
:returns {"status" "Done"}}}})
(def code-reloading-before-after-opts
{"before" "The namespace-qualified name of a zero-arity function to call before reloading."
"after" "The namespace-qualified name of a zero-arity function to call after reloading."})
(def-wrapper wrap-refresh cider.nrepl.middleware.refresh/handle-refresh
{:doc "Refresh middleware."
:requires #{"clone" #'wrap-print}
:handles {"refresh"
{:doc "Reloads all changed files in dependency order."
:optional (merge wrap-print-optional-arguments
{"dirs" "List of directories to scan. If no directories given, defaults to all directories on the classpath."}
code-reloading-before-after-opts)
:returns {"reloading" "List of namespaces that will be reloaded."
"status" "`:ok` if reloading was successful; otherwise `:error`."
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."
"error-ns" "The namespace that caused reloading to fail when `status` is `:error`."}}
"refresh-all"
{:doc "Reloads all files in dependency order."
:optional (merge wrap-print-optional-arguments
{"dirs" "List of directories to scan. If no directories given, defaults to all directories on the classpath."
"before" "The namespace-qualified name of a zero-arity function to call before reloading."
"after" "The namespace-qualified name of a zero-arity function to call after reloading."})
:returns {"reloading" "List of namespaces that will be reloaded."
"status" "`:ok` if reloading was successful; otherwise `:error`."
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."
"error-ns" "The namespace that caused reloading to fail when `status` is `:error`."}}
"refresh-clear"
{:doc "Clears the state of the refresh middleware. This can help recover from a failed load or a circular dependency error."}}})
(def-wrapper wrap-reload cider.nrepl.middleware.reload/handle-reload
{:doc "Reload middleware."
:requires #{"clone" #'wrap-print}
:handles {"cider.clj-reload/reload"
{:doc "Reloads all changed files in dependency order,
using the io.github.tonsky/clj-reload library. It is bundled with cider-nrepl.
If that dependency is already in present your project and clj-reload.core/init has been invoked beforehand,
those configured directories will be honored."
:optional code-reloading-before-after-opts
:returns {"progress" "Description of current namespace being unloaded/loaded."
"status" "`:ok` if reloading was successful; otherwise `:error`."
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."}}
"cider.clj-reload/reload-all"
{:doc "Reloads all files in dependency order."
:optional code-reloading-before-after-opts
:returns {"reloading" "Description of current namespace being unloaded/loaded."
"status" "`:ok` if reloading was successful; otherwise `:error`."
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."}}
"cider.clj-reload/reload-clear"
{:doc "Clears the state of clj-reload. This can help recover from a failed load or a circular dependency error."}}})
(def-wrapper wrap-resource cider.nrepl.middleware.resource/handle-resource
{:doc "Middleware that provides the path to resource."
:handles {"resource"
{:doc "Obtain the path to a resource."
:requires {"name" "The name of the resource in question."}
:returns {"resource-path" "The file path to a resource."}}
"resources-list"
{:doc "Obtain a list of all resources on the classpath."
:returns {"resources-list" "The list of resources."}}}})
(def-wrapper wrap-spec cider.nrepl.middleware.spec/handle-spec
(cljs/requires-piggieback
{:doc "Middleware that provides `clojure.spec` browsing functionality."
:handles {"spec-list" {:doc "Return a sorted list of all specs in the registry"
:returns {"status" "done"
"spec-list" "The sorted list of all specs in the registry with their descriptions"}
:optional {"filter-regex" "Only the specs that matches filter prefix regex will be returned "}}
"spec-form" {:doc "Return the form of a given spec"
:requires {"spec-name" "The spec namespaced keyword we are looking for"}
:returns {"status" "done"
"spec-form" "The spec form"}}
"spec-example" {:doc "Return a string with a pretty printed example for a spec"
:requires {"spec-name" "The spec namespaced keyword we want the example for"}
:returns {"status" "done"
"example" "The pretty printed spec example string"}}}}))
(def-wrapper wrap-stacktrace cider.nrepl.middleware.stacktrace/handle-stacktrace
(cljs/requires-piggieback
{:doc "Middleware that handles stacktrace requests, sending
cause and stack frame info for the most recent exception."
:requires #{#'session #'wrap-print}
:expects #{}
:handles {"analyze-last-stacktrace" {:doc "Return messages describing each cause and stack frame of the most recent exception."
:optional wrap-print-optional-arguments
:returns {"status" "\"done\", or \"no-error\" if `*e` is nil"}}
"inspect-last-exception" {:doc "Returns an Inspector response for the last exception that has been processed through `analyze-last-stacktrace` for the current nrepl session.
Assumes that `analyze-last-stacktrace` has been called first, returning \"no-error\" otherwise."
:requires {"index" "0 for inspecting the top-level exception, 1 for its ex-cause, 2 for its ex-cause's ex-cause, and so on."}
:returns {"status" "\"done\", or \"no-error\" if `analyze-last-stacktrace` wasn't called beforehand (or the `index` was out of bounds)."
"value" "A value, as produced by the Inspector middleware."}}
"analyze-stacktrace" {:doc "Parse and analyze the `:stacktrace`
parameter and return messages describing each cause and stack frame. The
stacktrace must be a string formatted in one of the following formats:
* `:aviso` Stacktraces printed with the
https://ioavisopretty.readthedocs.io/en/latest/exceptions.html[write-exception]
function of the https://github.com/AvisoNovate/pretty[Aviso] library.
* `:clojure.tagged-literal` Stacktraces printed as a tagged literal, like a
https://docs.oracle.com/javase/8/docs/api/java/lang/Throwable.html[java.lang.Throwable]
printed with the
https://clojure.github.io/clojure/branch-master/clojure.core-api.html#clojure.core/pr[pr]
function.
* `:clojure.stacktrace` Stacktraces printed with the
https://clojure.github.io/clojure/branch-master/clojure.stacktrace-api.html#clojure.stacktrace/print-cause-trace[print-cause-trace]
function of the
https://clojure.github.io/clojure/branch-master/clojure.stacktrace-api.html[clojure.stacktrace]
namespace.
* `:clojure.repl` Stacktraces printed with the
https://clojure.github.io/clojure/branch-master/clojure.repl-api.html#clojure.repl/pst[pst]
function of the
https://clojure.github.io/clojure/branch-master/clojure.repl-api.html[clojure.repl]
namespace.
* `:java` Stacktraces printed with the
link:++https://docs.oracle.com/javase/8/docs/api/java/lang/Throwable.html#printStackTrace--++[printStackTrace]
method of
https://docs.oracle.com/javase/8/docs/api/java/lang/Throwable.html[java.lang.Throwable]."
:requires {"stacktrace" "The stacktrace to be parsed and analyzed as a string."}
:optional wrap-print-optional-arguments
:returns {"status" "\"done\", or \"no-error\" if `stracktrace` is not recognized"}}
"stacktrace" {:doc "Return messages describing each cause and
stack frame of the most recent exception. This op is deprecated, please use the
`analyze-last-stacktrace` op instead."
:optional wrap-print-optional-arguments
:returns {"status" "\"done\", or \"no-error\" if `*e` is nil"}}}}))
(def timing-info-return-doc {"status" "Either done or indication of an error"
"elapsed-time" "a report of the elapsed time spent running all the given namespaces. The structure is `:elapsed-time {:ms <integer> :humanized <string>}`."
"ns-elapsed-time" "a report of the elapsed time spent running each namespace. The structure is `:ns-elapsed-time {<ns as keyword> {:ms <integer> :humanized <string>}}`."
"var-elapsed-time" "a report of the elapsed time spent running each var. The structure is `:var-elapsed-time {<ns as keyword> {<var as keyword> {:ms <integer> :humanized <string>}}}`."
"results" "Misc information about the test result. The structure is `:results {<ns as keyword> {<test var as keyword> [{,,, :elapsed-time {:ms <integer> :humanized <string>}}]}}`"})
(def fail-fast-doc {"fail-fast" "If equals to the string \"true\", the tests will be considered complete after the first test has failed or errored."})
(def-wrapper wrap-test cider.nrepl.middleware.test/handle-test
{:doc "Middleware that handles testing requests."
:requires #{#'session #'wrap-print}
:handles {"test-var-query"
{:doc "Run tests specified by the `var-query` and return results. Results are cached for exception retrieval and to enable re-running of failed/erring tests."
:requires {"var-query" "A search query specifying the test vars to execute. See Orchard's var query documentation for more details."}
:optional (merge wrap-print-optional-arguments)
:returns (merge fail-fast-doc timing-info-return-doc)}
"test"
{:doc "[DEPRECATED - `use test-var-query` instead] Run tests in the specified namespace and return results. This accepts a set of `tests` to be run; if nil, runs all tests. Results are cached for exception retrieval and to enable re-running of failed/erring tests."
:optional wrap-print-optional-arguments
:returns (merge fail-fast-doc timing-info-return-doc)}
"test-all"
{:doc "Return exception cause and stack frame info for an erring test via the `stacktrace` middleware. The error to be retrieved is referenced by namespace, var name, and assertion index within the var."
:optional wrap-print-optional-arguments
:returns (merge fail-fast-doc timing-info-return-doc)}
"test-stacktrace"
{:doc "Rerun all tests that did not pass when last run. Results are cached for exception retrieval and to enable re-running of failed/erring tests."
:optional wrap-print-optional-arguments}
"retest"
{:doc "[DEPRECATED - `use test-var-query` instead] Run all tests in the project. If `load?` is truthy, all project namespaces are loaded; otherwise, only tests in presently loaded namespaces are run. Results are cached for exception retrieval and to enable re-running of failed/erring tests."
:optional wrap-print-optional-arguments
:returns (merge fail-fast-doc timing-info-return-doc)}}})
(def-wrapper wrap-trace cider.nrepl.middleware.trace/handle-trace
{:doc "Toggle tracing of a given var."
:handles {"toggle-trace-var"
{:doc "Toggle tracing of a given var."
:requires {"sym" "The symbol to trace"
"ns" "The current namespace"}
:returns {"var-status" "The result of tracing operation"
"var-name" "The fully-qualified name of the traced/untraced var"}}
"toggle-trace-ns"
{:doc "Toggle tracing of a given ns."
:requires {"ns" "The namespace to trace"}
:returns {"ns-status" "The result of tracing operation"}}}})
(def ops-that-can-eval
"Set of nREPL ops that can lead to code being evaluated."
#{"eval" "load-file" "refresh" "refresh-all" "refresh-clear"
"toggle-trace-var" "toggle-trace-ns" "undef" "undef-all"})
(def-wrapper wrap-tracker cider.nrepl.middleware.track-state/handle-tracker
ops-that-can-eval
(cljs/expects-piggieback
{:doc "Under its normal operation mode, enhances the `eval` op by notifying the client of the current REPL state.
You can also request to compute the info directly by requesting the \"cider/get-state\" op."
:requires #{#'session}
:expects ops-that-can-eval
:handles {"cider/get-state" {}}
:returns {"repl-type" "`:clj` or `:cljs`."
"changed-namespaces" "A map of namespaces to `{:aliases ,,, :interns ,,,}`"}}))
(def-wrapper wrap-undef cider.nrepl.middleware.undef/handle-undef
{:doc "Middleware to undefine a symbol in a namespace."
:handles
{"undef" {:doc "Undefine a symbol"
:requires {"sym" "The symbol to undefine"
"ns" "The namespace is which to resolve sym (falls back to *ns* if not specified)"}
:returns {"status" "done"}}
"undef-all" {:doc "Undefine all aliases and symbols in a namespace"
:requires {"ns" "The namespace to operate on"}
:returns {"status" "done"}}}})
(def-wrapper wrap-version cider.nrepl.middleware.version/handle-version
{:doc "Provides CIDER-nREPL version information."
:describe-fn (fn [_] {:cider-version version/version}) ;; For the "describe" op. Merged into `:aux`.
:handles
{"cider-version"
{:doc "Returns the version of the CIDER-nREPL middleware."
:requires {}
:returns {"cider-version" "CIDER-nREPL's version map."
"status" "done"}}}})
(def-wrapper wrap-xref cider.nrepl.middleware.xref/handle-xref
{:doc "Middleware that provides find references functionality."
:handles {"fn-refs"
{:doc "Look up functions that reference a particular function."
:requires {"sym" "The symbol to lookup"
"ns" "The current namespace"}
:returns {"fn-refs" "A list of function references, with a `:name :doc :file :file-url :line :column` structure."
"status" "done"}}
"fn-deps"
{:doc "Look up the function dependencies of particular function."
:requires {"sym" "The symbol to lookup"
"ns" "The current namespace"}
:returns {"fn-deps" "A list of function deps, with a `:name :doc :file :file-url :line :column` structure."
"status" "done"}}}})
(def-wrapper wrap-clojuredocs cider.nrepl.middleware.clojuredocs/handle-clojuredocs
{:doc "Middleware to find a documents from ClojureDocs."
:handles {"clojuredocs-refresh-cache"
{:doc "Reload exported documents file from ClojureDocs, and store it as a cache."
:requires {}
:optional {"export-edn-url" "EDN file URL exported from ClojureDocs. Defaults to \"https://github.com/clojure-emacs/clojuredocs-export-edn/raw/master/exports/export.compact.edn\"."}
:returns {"status" "\"ok\" if reloading was successful"}}
"clojuredocs-lookup"
{:doc "Return a map of information in ClojureDocs."
:requires {"ns" "The namespace where `sym` will be resolved."
"sym" "The symbol to lookup."}
:returns {"clojuredocs" "A map of information in ClojureDocs."
"status" "\"no-doc\" if there is no document matching to `ns` and `symbol`."}}}})
;;; CIDER's nREPL Handler
;;
;; Here everything comes together. We define an nREPL handler
;; that includes all of CIDER's middleware. Note that
;; end users might opt to build custom handlers that don't
;; include every middleware available.
(def cider-middleware mw/cider-middleware) ;; for backwards compatibility
(def cider-nrepl-handler
"CIDER's nREPL handler."
(apply nrepl-server/default-handler (map resolve-or-fail mw/cider-middleware)))