/
hover.clj
130 lines (118 loc) · 5 KB
/
hover.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
(ns clojure-lsp.feature.hover
(:require
[clojure-lsp.feature.clojuredocs :as f.clojuredocs]
[clojure-lsp.queries :as q]
[clojure-lsp.settings :as settings]
[clojure-lsp.shared :as shared]
[clojure.string :as string]))
(set! *warn-on-reflection* true)
(def line-break "\n\n----\n\n")
(def opening-code "```clojure\n")
(def closing-code "\n```")
(defn ^:private drop-whitespace [n s]
(if (> n (count s))
s
(let [fully-trimmed (string/triml s)
dropped (subs s n)]
(last (sort-by count [fully-trimmed dropped])))))
(defn ^:private count-whitespace [s]
(- (count s) (count (string/triml s))))
(defn ^:private docstring->formatted-markdown [doc]
(let [lines (string/split-lines doc)
other-lines (filter (comp not string/blank?) (rest lines))
multi-line? (> (count other-lines) 0)]
(if-not multi-line?
doc
(let [indentation (apply min (map count-whitespace other-lines))
unindented-lines (cons (first lines)
(map #(drop-whitespace indentation %) (rest lines)))]
(string/join "\n" unindented-lines)))))
(defn ^:private clojuredocs->hover-docs
[{:keys [doc examples see-alsos notes]}
doc-line]
(string/join
"\n\n"
(cond-> []
doc-line (conj doc-line)
(and (not doc-line)
doc) (conj doc)
(seq examples) (conj "__Examples:__"
(->> examples
(map #(str opening-code % closing-code))
(string/join "\n---\n")))
(seq see-alsos) (conj "__See also:__"
(->> see-alsos
(map (fn [see-also]
(let [name (name see-also)
ns (namespace see-also)]
(format "[%s](https://clojuredocs.org/%s/%s)"
(str ns "/" name)
ns
name))))
(string/join "\n\n")))
(seq notes) (conj "__Notes:__"
(->> notes
(map #(str %))
(string/join "\n---\n"))))))
(defn hover-documentation
[{sym-ns :ns sym-name :name :keys [doc filename arglist-strs] :as _definition} db*]
(let [db @db*
content-formats (get-in db [:client-capabilities :text-document :hover :content-format])
arity-on-same-line? (or (settings/get db [:hover :arity-on-same-line?])
(settings/get db [:show-docs-arity-on-same-line?]))
hide-filename? (settings/get db [:hover :hide-file-location?])
join-char (if arity-on-same-line? " " "\n")
signatures (some->> arglist-strs
(remove nil?)
(string/join join-char))
sym (cond->> sym-name
sym-ns (str sym-ns "/"))
sym-line (str sym (when signatures
(str join-char signatures)))
markdown? (some #{"markdown"} content-formats)
doc-line (when (seq doc)
(if markdown?
(docstring->formatted-markdown doc)
doc))
clojuredocs (f.clojuredocs/find-hover-docs-for sym-name sym-ns db*)]
(if markdown?
{:kind "markdown"
:value (cond-> (str opening-code sym-line closing-code)
clojuredocs
(str "\n\n" (clojuredocs->hover-docs clojuredocs doc-line))
(and (not clojuredocs)
doc-line)
(str "\n\n" doc-line)
(and filename (not hide-filename?))
(str (format "%s*[%s](%s)*"
line-break
(string/replace filename #"\\" "\\\\")
(shared/filename->uri filename db))))}
;; Default to plaintext
(cond->> []
(and filename (not hide-filename?)) (cons filename)
doc-line (cons doc-line)
(and signatures
(not arity-on-same-line?))
(cons {:language "clojure"
:value (str signatures)})
sym (cons {:language "clojure"
:value (str (if arity-on-same-line? sym-line sym))})))))
(defn hover [filename line column db*]
(let [db @db*
analysis (:analysis db)
element (loop [try-column column]
(if-let [usage (q/find-element-under-cursor analysis filename line try-column)]
usage
(when (pos? try-column)
(recur (dec try-column)))))
definition (when element (q/find-definition analysis element))]
(cond
definition
{:range (shared/->range element)
:contents (hover-documentation definition db*)}
element
{:range (shared/->range element)
:contents (hover-documentation element db*)}
:else
{:contents []})))