1;;; cider-find.el --- Functionality for finding things -*- lexical-binding: t -*-
2
3;; Copyright © 2013-2021 Bozhidar Batsov, Artur Malabarba and CIDER contributors
4;;
5;; Author: Bozhidar Batsov <bozhidar@batsov.com>
6;;         Artur Malabarba <bruce.connor.am@gmail.com>
7
8;; This program is free software: you can redistribute it and/or modify
9;; it under the terms of the GNU General Public License as published by
10;; the Free Software Foundation, either version 3 of the License, or
11;; (at your option) any later version.
12
13;; This program is distributed in the hope that it will be useful,
14;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16;; GNU General Public License for more details.
17
18;; You should have received a copy of the GNU General Public License
19;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21;; This file is not part of GNU Emacs.
22
23;;; Commentary:
24
25;; A bunch of commands for finding resources and definitions.
26
27;;; Code:
28
29(require 'cider-client)
30(require 'cider-common)
31(require 'cider-resolve)
32
33(require 'thingatpt)
34
35(defun cider--find-var-other-window (var &optional line)
36  "Find the definition of VAR, optionally at a specific LINE.
37
38Display the results in a different window."
39  (if-let* ((info (cider-var-info var)))
40      (progn
41        (if line (setq info (nrepl-dict-put info "line" line)))
42        (cider--jump-to-loc-from-info info t))
43    (user-error "Symbol `%s' not resolved" var)))
44
45(defun cider--find-var (var &optional line)
46  "Find the definition of VAR, optionally at a specific LINE."
47  (if-let* ((info (cider-var-info var)))
48      (progn
49        (if line (setq info (nrepl-dict-put info "line" line)))
50        (cider--jump-to-loc-from-info info))
51    (user-error "Symbol `%s' not resolved" var)))
52
53;;;###autoload
54(defun cider-find-var (&optional arg var line)
55  "Find definition for VAR at LINE.
56Prompt according to prefix ARG and `cider-prompt-for-symbol'.
57A single or double prefix argument inverts the meaning of
58`cider-prompt-for-symbol'.  A prefix of `-` or a double prefix argument causes
59the results to be displayed in a different window.  The default value is
60thing at point."
61  (interactive "P")
62  (if var
63      (cider--find-var var line)
64    (funcall (cider-prompt-for-symbol-function arg)
65             "Symbol"
66             (if (cider--open-other-window-p arg)
67                 #'cider--find-var-other-window
68               #'cider--find-var))))
69
70;;;###autoload
71(defun cider-find-dwim-at-mouse (event)
72  "Find and display variable or resource at mouse EVENT."
73  (interactive "e")
74  (if-let* ((symbol-file (save-excursion
75                           (mouse-set-point event)
76                           (cider-symbol-at-point 'look-back))))
77      (cider-find-dwim symbol-file)
78    (user-error "No variable or resource here")))
79
80(defun cider--find-dwim (symbol-file callback &optional other-window)
81  "Find the SYMBOL-FILE at point.
82CALLBACK upon failure to invoke prompt if not prompted previously.
83Show results in a different window if OTHER-WINDOW is true."
84  (if-let* ((info (cider-var-info symbol-file)))
85      (cider--jump-to-loc-from-info info other-window)
86    (progn
87      (cider-ensure-op-supported "resource")
88      (if-let* ((resource (cider-sync-request:resource symbol-file))
89                (buffer (cider-find-file resource)))
90          (cider-jump-to buffer 0 other-window)
91        (if (cider--prompt-for-symbol-p current-prefix-arg)
92            (error "Resource or var %s not resolved" symbol-file)
93          (let ((current-prefix-arg (if current-prefix-arg nil '(4))))
94            (call-interactively callback)))))))
95
96(defun cider--find-dwim-interactive (prompt)
97  "Get interactive arguments for jump-to functions using PROMPT as needed."
98  (if (cider--prompt-for-symbol-p current-prefix-arg)
99      (list
100       (cider-read-from-minibuffer prompt (thing-at-point 'filename)))
101    (list (or (thing-at-point 'filename) ""))))  ; No prompt.
102
103(defun cider-find-dwim-other-window (symbol-file)
104  "Jump to SYMBOL-FILE at point, place results in other window."
105  (interactive (cider--find-dwim-interactive "Jump to: "))
106  (cider--find-dwim symbol-file 'cider-find-dwim-other-window t))
107
108;;;###autoload
109(defun cider-find-dwim (symbol-file)
110  "Find and display the SYMBOL-FILE at point.
111SYMBOL-FILE could be a var or a resource.  If thing at point is empty then
112show dired on project.  If var is not found, try to jump to resource of the
113same name.  When called interactively, a prompt is given according to the
114variable `cider-prompt-for-symbol'.  A single or double prefix argument
115inverts the meaning.  A prefix of `-' or a double prefix argument causes
116the results to be displayed in a different window.  A default value of thing
117at point is given when prompted."
118  (interactive (cider--find-dwim-interactive "Jump to: "))
119  (cider--find-dwim symbol-file `cider-find-dwim
120                    (cider--open-other-window-p current-prefix-arg)))
121
122;;;###autoload
123(defun cider-find-resource (path)
124  "Find the resource at PATH.
125Prompt for input as indicated by the variable `cider-prompt-for-symbol'.
126A single or double prefix argument inverts the meaning of
127`cider-prompt-for-symbol'.  A prefix argument of `-` or a double prefix
128argument causes the results to be displayed in other window.  The default
129value is thing at point."
130  (interactive
131   (list
132    (if (cider--prompt-for-symbol-p current-prefix-arg)
133        (completing-read "Resource: "
134                         (cider-sync-request:resources-list)
135                         nil nil
136                         (thing-at-point 'filename))
137      (or (thing-at-point 'filename) ""))))
138  (cider-ensure-op-supported "resource")
139  (when (= (length path) 0)
140    (error "Cannot find resource for empty path"))
141  (if-let* ((resource (cider-sync-request:resource path))
142            (buffer (cider-find-file resource)))
143      (cider-jump-to buffer nil (cider--open-other-window-p current-prefix-arg))
144    (if (cider--prompt-for-symbol-p current-prefix-arg)
145        (error "Cannot find resource %s" path)
146      (let ((current-prefix-arg (cider--invert-prefix-arg current-prefix-arg)))
147        (call-interactively 'cider-find-resource)))))
148
149(defun cider--invert-prefix-arg (arg)
150  "Invert the effect of prefix value ARG on `cider-prompt-for-symbol'.
151This function preserves the `other-window' meaning of ARG."
152  (let ((narg (prefix-numeric-value arg)))
153    (pcase narg
154      (16 -1)   ; empty empty -> -
155      (-1 16)   ; - -> empty empty
156      (4 nil)   ; empty -> no-prefix
157      (_ 4)))) ; no-prefix -> empty
158
159(defun cider--prefix-invert-prompt-p (arg)
160  "Test prefix value ARG for its effect on `cider-prompt-for-symbol`."
161  (let ((narg (prefix-numeric-value arg)))
162    (pcase narg
163      (16 t) ; empty empty
164      (4 t)  ; empty
165      (_ nil))))
166
167(defun cider--prompt-for-symbol-p (&optional prefix)
168  "Check if cider should prompt for symbol.
169Tests againsts PREFIX and the value of `cider-prompt-for-symbol'.
170Invert meaning of `cider-prompt-for-symbol' if PREFIX indicates it should be."
171  (if (cider--prefix-invert-prompt-p prefix)
172      (not cider-prompt-for-symbol) cider-prompt-for-symbol))
173
174(defun cider--find-ns (ns &optional other-window)
175  "Find the file containing NS's definition.
176Optionally open it in a different window if OTHER-WINDOW is truthy."
177  (if-let* ((path (cider-sync-request:ns-path ns)))
178      (cider-jump-to (cider-find-file path) nil other-window)
179    (user-error "Can't find namespace `%s'" ns)))
180
181;;;###autoload
182(defun cider-find-ns (&optional arg ns)
183  "Find the file containing NS.
184A prefix ARG of `-` or a double prefix argument causes
185the results to be displayed in a different window."
186  (interactive "P")
187  (cider-ensure-connected)
188  (cider-ensure-op-supported "ns-path")
189  (if ns
190      (cider--find-ns ns)
191    (let* ((namespaces (cider-sync-request:ns-list))
192           (ns (completing-read "Find namespace: " namespaces)))
193      (cider--find-ns ns (cider--open-other-window-p arg)))))
194
195;;;###autoload
196(defun cider-find-keyword (&optional arg)
197  "Find the namespace of the keyword at point and its first occurrence there.
198
199For instance - if the keyword at point is \":cider.demo/keyword\", this command
200would find the namespace \"cider.demo\" and afterwards find the first mention
201of \"::keyword\" there.
202
203Prompt according to prefix ARG and `cider-prompt-for-symbol'.
204A single or double prefix argument inverts the meaning of
205`cider-prompt-for-symbol'.  A prefix of `-` or a double prefix argument causes
206the results to be displayed in a different window.  The default value is
207thing at point."
208  (interactive "P")
209  (cider-ensure-connected)
210  (let* ((kw (let ((kw-at-point (cider-symbol-at-point 'look-back)))
211               (if (or cider-prompt-for-symbol arg)
212                   (read-string
213                    (format "Keyword (default %s): " kw-at-point)
214                    nil nil kw-at-point)
215                 kw-at-point)))
216         (ns-qualifier (and
217                        (string-match "^:+\\(.+\\)/.+$" kw)
218                        (match-string 1 kw)))
219         (kw-ns (if ns-qualifier
220                    (cider-resolve-alias (cider-current-ns) ns-qualifier)
221                  (cider-current-ns)))
222         (kw-to-find (concat "::" (replace-regexp-in-string "^:+\\(.+/\\)?" "" kw))))
223
224    (when (and ns-qualifier (string= kw-ns (cider-current-ns)))
225      (error "Could not resolve alias `%s' in `%s'" ns-qualifier (cider-current-ns)))
226    (cider--find-ns kw-ns arg)
227    (search-forward-regexp kw-to-find nil 'noerror)))
228
229(provide 'cider-find)
230;;; cider-find.el ends here
231