Skip to content

Commit b13a70c

Browse files
vspinubbatsov
authored andcommitted
Add support for interactive location references in REPL
1 parent af7c76f commit b13a70c

File tree

3 files changed

+132
-6
lines changed

3 files changed

+132
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
### New Features
66

77
* [#2050](https://github.com/clojure-emacs/cider/pull/2050) Use `view-mode` for `cider-grimoire` buffers
8+
* Make stacktraces and other location references in REPL clickable.
9+
* Highlight root namespace in REPL stacktraces.
810
* Filter stacktrace to just frames from your project.
911
* [#1918](https://github.com/clojure-emacs/cider/issues/1918): Add new commands `cider-browse-spec` and `cider-browse-spec-all` which start a spec browser.
1012
* [#2015](https://github.com/clojure-emacs/cider/pull/2015): Show symbols as special forms *and* macros in `cider-doc`

cider-interaction.el

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1125,7 +1125,7 @@ ADDITIONAL-PARAMS is a plist to be appended to the request message.
11251125
11261126
If `cider-interactive-eval-override' is a function, call it with the same
11271127
arguments and only proceed with evaluation if it returns nil."
1128-
(let ((form (or form (apply #'buffer-substring bounds)))
1128+
(let ((form (or form (apply #'buffer-substring-no-properties bounds)))
11291129
(start (car-safe bounds))
11301130
(end (car-safe (cdr-safe bounds))))
11311131
(when (and start end)

cider-repl.el

Lines changed: 129 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,33 @@ When there is a possible unfinished ansi control sequence,
550550
(insert-before-markers (cadr ansi-color-context))
551551
(setq ansi-color-context nil)))
552552

553+
(defvar cider-repl--root-ns-highlitht-template "\\<%s[^$/: \t\n]+"
554+
"Regexp used to highlight root ns in REPL buffers.")
555+
556+
(defvar-local cider-repl--root-ns-regexp nil
557+
"Cache of root ns regexp in REPLs")
558+
559+
(defun cider-repl--apply-current-project-color (string)
560+
"Fontify project's root namespace to make stacktraces more readable.
561+
Foreground of `cider-stacktrace-ns-face' is used to propertize matched
562+
namespaces. STRING is REPL's output."
563+
(if (null nrepl-project-dir)
564+
string
565+
(unless cider-repl--root-ns-regexp
566+
(let ((root (file-name-nondirectory (directory-file-name nrepl-project-dir))))
567+
(setq cider-repl--root-ns-regexp
568+
;; Replace _ or - with regexp patter to accommodate "raw" namespaces
569+
(format cider-repl--root-ns-highlitht-template
570+
(replace-regexp-in-string "[_-]+" "[_-]+" root)))))
571+
(let ((start 0)
572+
(end 0))
573+
(while (setq start (string-match cider-repl--root-ns-regexp string end))
574+
(setq end (match-end 0))
575+
(let ((face-spec (list (cons 'foreground-color
576+
(face-attribute 'cider-stacktrace-ns-face :foreground nil t)))))
577+
(font-lock-prepend-text-property start end 'face face-spec string)))
578+
string)))
579+
553580
(defun cider-repl--emit-output-at-pos (buffer string output-face position &optional bol)
554581
"Using BUFFER, insert STRING (applying to it OUTPUT-FACE) at POSITION.
555582
If BOL is non-nil insert at the beginning of line."
@@ -560,10 +587,14 @@ If BOL is non-nil insert at the beginning of line."
560587
(goto-char position)
561588
;; TODO: Review the need for bol
562589
(when (and bol (not (bolp))) (insert-before-markers "\n"))
563-
(insert-before-markers
564-
(ansi-color-apply (propertize string
565-
'font-lock-face output-face
566-
'rear-nonsticky '(font-lock-face))))
590+
(setq string
591+
(thread-first string
592+
(propertize 'font-lock-face output-face
593+
'rear-nonsticky '(font-lock-face))
594+
(ansi-color-apply)
595+
(cider-repl--apply-current-project-color)
596+
(propertize 'help-echo 'cider-locref-help-echo)))
597+
(insert-before-markers string)
567598
(cider-repl--flush-ansi-color-context)
568599
(when (and (= (point) cider-repl-prompt-start-mark)
569600
(not (bolp)))
@@ -930,7 +961,100 @@ namespace to switch to."
930961
:both))
931962

932963

933-
;;;;; History
964+
;;; Location References
965+
966+
(defcustom cider-locref-regexp-alist
967+
'((stdout-stacktrace "[ \t]\\(at \\([^$(]+\\).*(\\([^:()]+\\):\\([0-9]+\\))\\)" 1 2 3 4)
968+
(aviso-stacktrace "^[ \t]*\\(\\([^$/ \t]+\\).*? +\\([^:]+\\): +\\([0-9]+\\)\\)" 1 2 3 4)
969+
(print-stacktrace "\\[\\([^][$ \t]+\\).* +\\([^ \t]+\\) +\\([0-9]+\\)\\]" 0 1 2 3)
970+
(timbre-log "\\(TRACE\\|INFO\\|DEBUG\\|WARN\\|ERROR\\) +\\(\\[\\([^:]+\\):\\([0-9]+\\)\\]\\)" 2 3 nil 4))
971+
"Alist holding regular expressions for inline location references.
972+
Each element in the alist has the form (NAME REGEXP HIGHLIGHT VAR FILE
973+
LINE), where NAME is the identifier of the regexp, REGEXP - regexp matching
974+
a location, HIGHLIGHT - sub-expression matching region to highlight on
975+
mouse-over, VAR - sub-expression giving Clojure VAR to look up. FILE is
976+
currently only used when VAR is nil and must be full resource path in that
977+
case."
978+
:type '(alist :key-type sexp)
979+
:group 'cider-repl
980+
:package-version '(cider. "0.16.0"))
981+
982+
(defun cider--locref-at-point-1 (reg-list &optional pos)
983+
"Workhorse for getting locref at POS.
984+
REG-LIST is an entry in `cider-locref-regexp-alist'."
985+
(save-excursion
986+
(let ((pos (or pos (point))))
987+
(goto-char pos)
988+
(beginning-of-line)
989+
(when (re-search-forward (nth 1 reg-list) (point-at-eol) t)
990+
(let ((ix-highlight (or (nth 2 reg-list) 0))
991+
(ix-var (nth 3 reg-list))
992+
(ix-file (nth 4 reg-list))
993+
(ix-line (nth 5 reg-list)))
994+
(list
995+
:type (car reg-list)
996+
:highlight (cons (match-beginning ix-highlight) (match-end ix-highlight))
997+
:var (and ix-var
998+
(replace-regexp-in-string "_" "-"
999+
(match-string-no-properties ix-var)
1000+
nil t))
1001+
:file (and ix-file (match-string-no-properties ix-file))
1002+
:line (and ix-line (string-to-number (match-string-no-properties ix-line)))))))))
1003+
1004+
(defun cider-locref-at-point (&optional pos)
1005+
"Return a plist of components of the location reference at POS.
1006+
Limit search to current line only and return nil if no location has been
1007+
found. Returned keys are :type, :highlight, :var, :file, :line, where
1008+
:highlight is a cons of positions, :var and :file are strings or nil, :line
1009+
is a number. See `cider-locref-regexp-alist' for how to specify regexes
1010+
for locref look up."
1011+
(seq-some (lambda (rl) (cider--locref-at-point-1 rl pos))
1012+
cider-locref-regexp-alist))
1013+
1014+
(defun cider-jump-to-locref-at-point (&optional pos)
1015+
"Identify location reference at POS and navigate to it.
1016+
This function is used from help-echo property inside REPL buffers and uses
1017+
regexes from `cider-locref-regexp-alist' to infer locations at point."
1018+
(interactive)
1019+
(if-let ((loc (cider-locref-at-point pos)))
1020+
(let* ((var (plist-get loc :var))
1021+
(line (plist-get loc :line))
1022+
(file (if var
1023+
(or (cider-sync-request:ns-path var)
1024+
(nrepl-dict-get (cider-sync-request:info var) "file"))
1025+
(plist-get loc :file))))
1026+
(if file
1027+
(cider--jump-to-loc-from-info (nrepl-dict "file" file "line" line))
1028+
(error "No source location for %s" var)))
1029+
(user-error "No location reference at point")))
1030+
1031+
(defvar cider-locref-hoover-overlay
1032+
(let ((o (make-overlay 1 1)))
1033+
(overlay-put o 'category 'cider-error-hoover)
1034+
;; (overlay-put o 'face 'highlight)
1035+
(overlay-put o 'pointer 'hand)
1036+
(overlay-put o 'mouse-face 'highlight)
1037+
(overlay-put o 'follow-link 'mouse)
1038+
(overlay-put o 'keymap
1039+
(let ((map (make-sparse-keymap)))
1040+
(define-key map [return] 'cider-jump-to-locref-at-point)
1041+
(define-key map [mouse-2] 'cider-jump-to-locref-at-point)
1042+
map))
1043+
o)
1044+
"Overlay used during hoovering on location references in REPL buffers.
1045+
One for all REPLs.")
1046+
1047+
(defun cider-locref-help-echo (win buffer pos)
1048+
"Function for help-echo property in REPL buffers.
1049+
WIN, BUFFER and POS are the window, buffer and point under mouse position."
1050+
(with-current-buffer buffer
1051+
(if-let ((hl (plist-get (cider-locref-at-point pos) :highlight)))
1052+
(move-overlay cider-locref-hoover-overlay (car hl) (cdr hl))
1053+
(delete-overlay cider-locref-hoover-overlay))
1054+
nil))
1055+
1056+
1057+
;;; History
9341058

9351059
(defcustom cider-repl-wrap-history nil
9361060
"T to wrap history around when the end is reached."

0 commit comments

Comments
 (0)