-
-
Notifications
You must be signed in to change notification settings - Fork 645
/
cider-find.el
285 lines (253 loc) · 12 KB
/
cider-find.el
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
;;; cider-find.el --- Functionality for finding things -*- lexical-binding: t -*-
;; Copyright © 2013-2024 Bozhidar Batsov, Artur Malabarba and CIDER contributors
;;
;; Author: Bozhidar Batsov <[email protected]>
;; Artur Malabarba <[email protected]>
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;; This file is not part of GNU Emacs.
;;; Commentary:
;; A bunch of commands for finding resources and definitions.
;;; Code:
(require 'cider-client)
(require 'cider-common)
(require 'cider-resolve)
(require 'thingatpt)
(defun cider--find-var-other-window (var &optional line)
"Find the definition of VAR, optionally at a specific LINE.
Display the results in a different window."
(if-let* ((info (cider-var-info var)))
(progn
(if line (setq info (nrepl-dict-put info "line" line)))
(cider--jump-to-loc-from-info info t))
(user-error "Symbol `%s' not resolved" var)))
(defun cider--find-var (var &optional line)
"Find the definition of VAR, optionally at a specific LINE."
(if-let* ((info (cider-var-info var)))
(progn
(if line (setq info (nrepl-dict-put info "line" line)))
(cider--jump-to-loc-from-info info))
(user-error "Symbol `%s' not resolved" var)))
;;;###autoload
(defun cider-find-var (&optional arg var line)
"Find definition for VAR at LINE.
Prompt according to prefix ARG and `cider-prompt-for-symbol'.
A single or double prefix argument inverts the meaning of
`cider-prompt-for-symbol'. A prefix of `-` or a double prefix argument causes
the results to be displayed in a different window. The default value is
thing at point."
(interactive "P")
(if var
(cider--find-var var line)
(funcall (cider-prompt-for-symbol-function arg)
"Symbol"
(if (cider--open-other-window-p arg)
#'cider--find-var-other-window
#'cider--find-var))))
;;;###autoload
(defun cider-find-dwim-at-mouse (event)
"Find and display variable or resource at mouse EVENT."
(interactive "e")
(if-let* ((symbol-file (save-excursion
(mouse-set-point event)
(cider-symbol-at-point 'look-back))))
(cider-find-dwim symbol-file)
(user-error "No variable or resource here")))
(defun cider--find-dwim (symbol-file callback &optional other-window)
"Find the SYMBOL-FILE at point.
CALLBACK upon failure to invoke prompt if not prompted previously.
Show results in a different window if OTHER-WINDOW is true."
(if-let* ((info (cider-var-info symbol-file)))
(cider--jump-to-loc-from-info info other-window)
(progn
(cider-ensure-op-supported "resource")
(if-let* ((resource (cider-sync-request:resource symbol-file))
(buffer (cider-find-file resource)))
(cider-jump-to buffer 0 other-window)
(if (cider--prompt-for-symbol-p current-prefix-arg)
(error "Resource or var %s not resolved" symbol-file)
(let ((current-prefix-arg (if current-prefix-arg nil '(4))))
(call-interactively callback)))))))
(defun cider--find-dwim-interactive (prompt)
"Get interactive arguments for jump-to functions using PROMPT as needed."
(if (cider--prompt-for-symbol-p current-prefix-arg)
(list
(cider-read-from-minibuffer prompt (thing-at-point 'filename)))
(list (or (thing-at-point 'filename) "")))) ; No prompt.
(defun cider-find-dwim-other-window (symbol-file)
"Jump to SYMBOL-FILE at point, place results in other window."
(interactive (cider--find-dwim-interactive "Jump to: "))
(cider--find-dwim symbol-file 'cider-find-dwim-other-window t))
;;;###autoload
(defun cider-find-dwim (symbol-file)
"Find and display the SYMBOL-FILE at point.
SYMBOL-FILE could be a var or a resource. If thing at point is empty then
show Dired on project. If var is not found, try to jump to resource of the
same name. When called interactively, a prompt is given according to the
variable `cider-prompt-for-symbol'. A single or double prefix argument
inverts the meaning. A prefix of `-' or a double prefix argument causes
the results to be displayed in a different window. A default value of thing
at point is given when prompted."
(interactive (cider--find-dwim-interactive "Jump to: "))
(cider--find-dwim symbol-file `cider-find-dwim
(cider--open-other-window-p current-prefix-arg)))
;;;###autoload
(defun cider-find-resource (path)
"Find the resource at PATH.
Prompt for input as indicated by the variable `cider-prompt-for-symbol'.
A single or double prefix argument inverts the meaning of
`cider-prompt-for-symbol'. A prefix argument of `-` or a double prefix
argument causes the results to be displayed in other window. The default
value is thing at point."
(interactive
(list
(if (cider--prompt-for-symbol-p current-prefix-arg)
(completing-read "Resource: "
(cider-sync-request:resources-list)
nil nil
(thing-at-point 'filename))
(or (thing-at-point 'filename) ""))))
(cider-ensure-op-supported "resource")
(when (= (length path) 0)
(error "Cannot find resource for empty path"))
(if-let* ((resource (cider-sync-request:resource path))
(buffer (cider-find-file resource)))
(cider-jump-to buffer nil (cider--open-other-window-p current-prefix-arg))
(if (cider--prompt-for-symbol-p current-prefix-arg)
(error "Cannot find resource %s" path)
(let ((current-prefix-arg (cider--invert-prefix-arg current-prefix-arg)))
(call-interactively 'cider-find-resource)))))
(defun cider--invert-prefix-arg (arg)
"Invert the effect of prefix value ARG on `cider-prompt-for-symbol'.
This function preserves the `other-window' meaning of ARG."
(let ((narg (prefix-numeric-value arg)))
(pcase narg
(16 -1) ; empty empty -> -
(-1 16) ; - -> empty empty
(4 nil) ; empty -> no-prefix
(_ 4)))) ; no-prefix -> empty
(defun cider--prefix-invert-prompt-p (arg)
"Test prefix value ARG for its effect on `cider-prompt-for-symbol`."
(let ((narg (prefix-numeric-value arg)))
(pcase narg
(16 t) ; empty empty
(4 t) ; empty
(_ nil))))
(defun cider--prompt-for-symbol-p (&optional prefix)
"Check if cider should prompt for symbol.
Tests againsts PREFIX and the value of `cider-prompt-for-symbol'.
Invert meaning of `cider-prompt-for-symbol' if PREFIX indicates it should be."
(if (cider--prefix-invert-prompt-p prefix)
(not cider-prompt-for-symbol) cider-prompt-for-symbol))
(defun cider--find-ns (ns &optional other-window)
"Find the file containing NS's definition.
Optionally open it in a different window if OTHER-WINDOW is truthy."
(if-let* ((path (cider-sync-request:ns-path ns)))
(cider-jump-to (cider-find-file path) nil other-window)
(user-error "Can't find namespace `%s'" ns)))
;;;###autoload
(defun cider-find-ns (&optional arg ns)
"Find the file containing NS.
A prefix ARG of `-` or a double prefix argument causes
the results to be displayed in a different window."
(interactive "P")
(cider-ensure-connected)
(cider-ensure-op-supported "ns-path")
(if ns
(cider--find-ns ns)
(let* ((namespaces (cider-sync-request:ns-list))
(ns (completing-read "Find namespace: " namespaces)))
(cider--find-ns ns (cider--open-other-window-p arg)))))
(defun cider--find-keyword-in-buffer (buffer kw)
"Returns the point where `KW' is found in `BUFFER'.
Returns nil of no matching keyword is found.
Occurrences of `KW' as (or within) strings, comments, #_ forms, symbols, etc
are disregarded."
(with-current-buffer buffer
(save-excursion
(goto-char (point-min))
(font-lock-ensure) ;; make the forthcoming `text-properties-at` call useful
(let ((found nil)
(continue t)
(current-point (point)))
(while continue
(setq found (and (search-forward-regexp kw nil 'noerror)
(member 'clojure-keyword-face (text-properties-at (1- (point))))))
(setq continue (and (not found)
;; if we haven't moved, there's nothing left to search:
(not (equal current-point (point)))))
(setq current-point (point)))
(when found
current-point)))))
(defun cider--find-keyword-loc (kw)
"Given `KW', returns an nrepl-dict with url, dest, dest-point.
Returns the dict in all cases. `dest-point' indicates success:
integer on successful finds, nil otherwise."
(let* ((simple-ns-qualifier (and
(string-match "^:\\(.+\\)/.+$" kw)
(match-string 1 kw)))
(auto-resolved-ns-qualifier (and
(string-match "^::\\(.+\\)/.+$" kw)
(match-string 1 kw)))
(kw-ns (or (and auto-resolved-ns-qualifier
(or (cider-resolve-alias (cider-current-ns) auto-resolved-ns-qualifier)
(user-error "Could not resolve alias: %S" auto-resolved-ns-qualifier)))
(and (string-match "^::" kw)
(cider-current-ns :no-default))
simple-ns-qualifier
(user-error "Not a ns-qualified keyword: %S" kw)))
(kw-name (replace-regexp-in-string "^:+\\(.+/\\)?" "" kw))
(beginning-of-symbol "\\_<")
(end-of-symbol "\\_>") ;; important: if searching for foo, we don't want to match foobar (a larger symbol)
(kw-to-find (concat beginning-of-symbol
"\\("
(concat "::" kw-name)
"\\|"
(concat ":" kw-ns "/" kw-name)
"\\)"
end-of-symbol)))
(let* ((url (when kw-ns
(cider-sync-request:ns-path kw-ns t)))
(dest (when url
(cider-find-file url)))
(dest-point (when dest
(cider--find-keyword-in-buffer dest kw-to-find))))
(nrepl-dict "url" url "dest" dest "dest-point" dest-point))))
;;;###autoload
(defun cider-find-keyword (&optional arg)
"Find the namespace of the keyword at point and its primary occurrence there.
For instance - if the keyword at point is \":cider.demo/keyword\", this command
would find the namespace \"cider.demo\" and afterwards find the primary (most relevant or first)
mention of \"::keyword\" there.
Prompt according to prefix ARG and `cider-prompt-for-symbol'.
A single or double prefix argument inverts the meaning of
`cider-prompt-for-symbol'. A prefix of `-` or a double prefix argument causes
the results to be displayed in a different window. The default value is
thing at point."
(interactive "P")
(cider-ensure-connected)
(let* ((kw (let ((kw-at-point (cider-symbol-at-point 'look-back)))
(if (or cider-prompt-for-symbol arg)
(read-string
(format "Keyword (default %s): " kw-at-point)
nil nil kw-at-point)
kw-at-point)))
(before (buffer-list))
(result (cider--find-keyword-loc kw)))
(nrepl-dbind-response result (dest dest-point)
(if dest-point
(cider-jump-to dest dest-point arg)
(progn
(unless (memq dest before)
(kill-buffer dest))
(user-error "Couldn't find a definition for %S" kw))))))
(provide 'cider-find)
;;; cider-find.el ends here