From ed9562f26728ed21ac472a671c6f2584072ac5f6 Mon Sep 17 00:00:00 2001 From: Eric Dallo Date: Sun, 21 Feb 2021 16:10:15 -0300 Subject: [PATCH] Add parameters support to textDocument/signatureHelp Related to #324 --- src/clojure_lsp/feature/signature_help.clj | 54 ++++-- .../features/signature_help_test.clj | 158 +++++++++++++++--- 2 files changed, 170 insertions(+), 42 deletions(-) diff --git a/src/clojure_lsp/feature/signature_help.clj b/src/clojure_lsp/feature/signature_help.clj index 75634ffbf..6632eee66 100644 --- a/src/clojure_lsp/feature/signature_help.clj +++ b/src/clojure_lsp/feature/signature_help.clj @@ -6,6 +6,7 @@ [clojure-lsp.queries :as q] [clojure-lsp.refactor.edit :as edit] [clojure-lsp.shared :as shared] + [edamame.core :as edamame] [rewrite-clj.node :as n] [rewrite-clj.zip :as z] [taoensso.timbre :as log]) @@ -21,17 +22,25 @@ (remove n/whitespace-or-comment?) (drop 1))) -;; TODO Use it when we return parameters -;; (defn ^:private get-active-parameter-index [arglist-nodes cursor-row cursor-col] -;; (let [selected-arg (->> arglist-nodes -;; reverse -;; (filter (fn [node] -;; (let [{:keys [row col]} (meta node)] -;; (or (< row cursor-row) -;; (and (= row cursor-row) -;; (<= col cursor-col)))))) -;; first)] -;; (max (.indexOf ^PersistentVector arglist-nodes selected-arg) 0))) +(defn ^:private get-active-parameter-index + [signatures active-signature arglist-nodes cursor-row cursor-col] + (let [params-count (-> (nth signatures active-signature) + :parameters + count) + selected-arg (->> arglist-nodes + reverse + (filter (fn [node] + (let [{:keys [row col]} (meta node)] + (or (< row cursor-row) + (and (= row cursor-row) + (<= col cursor-col)))))) + first)] + (if selected-arg + (let [index (.indexOf ^PersistentVector (vec arglist-nodes) selected-arg)] + (if (> index (dec params-count)) + (dec params-count) + index)) + 0))) (defn ^:private get-active-signature-index [{:keys [fixed-arities arglist-strs]} arglist-nodes] (let [arities (vec (sort-by max (if fixed-arities @@ -47,9 +56,22 @@ (.indexOf ^PersistentVector arities (apply max arities)) (.indexOf ^PersistentVector arities (apply min arities)))))) +(defn ^:private arglist-str->parameters [arglist-str] + (let [parameters (edamame/parse-string arglist-str {:auto-resolve #(symbol (str ":" %))}) + rest-args? (some #(= '& %) parameters) + available-params (filter (complement #(= '& %)) parameters) + params-count (dec (count available-params))] + (->> available-params + (map-indexed (fn [index arg] + (let [last-arg? (= index params-count)] + (if (and rest-args? last-arg?) + {:label (format "& %s" arg)} + {:label (str arg)}))))))) + (defn ^:private definition->signature-informations [{:keys [arglist-strs] :as definition}] - (map (fn [arg] - (-> {:label (format "(%s %s)" (-> definition :name str) arg)} + (map (fn [arglist-str] + (-> {:label (format "(%s %s)" (-> definition :name str) arglist-str) + :parameters (arglist-str->parameters arglist-str)} (shared/assoc-some :documentation (:doc definition)))) arglist-strs)) @@ -62,7 +84,9 @@ (let [arglist-nodes (function-loc->arglist-nodes function-loc) function-meta (meta (z/node function-loc)) definition (q/find-definition-from-cursor (:analysis @db/db) filename (:row function-meta) (:col function-meta)) - signatures (definition->signature-informations definition)] + signatures (definition->signature-informations definition) + active-signature (get-active-signature-index definition arglist-nodes)] (when (seq signatures) {:signatures signatures - :active-signature (get-active-signature-index definition arglist-nodes)}))))) + :active-parameter (get-active-parameter-index signatures active-signature arglist-nodes row col) + :active-signature active-signature}))))) diff --git a/test/clojure_lsp/features/signature_help_test.clj b/test/clojure_lsp/features/signature_help_test.clj index 4b26fa280..0855ff3bf 100644 --- a/test/clojure_lsp/features/signature_help_test.clj +++ b/test/clojure_lsp/features/signature_help_test.clj @@ -26,19 +26,31 @@ (h/code "(defn bar [a b] 1)" "(defn foo [a b] (|bar| |1 |2) |)"))] (testing "before function name" - (is (= {:signatures [{:label "(bar [a b])"}] + (is (= {:signatures [{:label "(bar [a b])" + :parameters [{:label "a"} + {:label "b"}]}] + :active-parameter 0 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" before-r before-c)))) (testing "after function name" - (is (= {:signatures [{:label "(bar [a b])"}] + (is (= {:signatures [{:label "(bar [a b])" + :parameters [{:label "a"} + {:label "b"}]}] + :active-parameter 0 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" after-r after-c)))) (testing "on first arg" - (is (= {:signatures [{:label "(bar [a b])"}] + (is (= {:signatures [{:label "(bar [a b])" + :parameters [{:label "a"} + {:label "b"}]}] + :active-parameter 0 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" first-arg-r first-arg-c)))) (testing "on second arg" - (is (= {:signatures [{:label "(bar [a b])"}] + (is (= {:signatures [{:label "(bar [a b])" + :parameters [{:label "a"} + {:label "b"}]}] + :active-parameter 1 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" second-arg-r second-arg-c)))) (testing "outside function" @@ -59,28 +71,58 @@ "(bar| 1 2 3)" "(bar| 1 2 3 4)"))] (testing "zero arity" - (is (= {:signatures [{:label "(bar [a b])"} - {:label "(bar [a b c])"}] + (is (= {:signatures [{:label "(bar [a b])" + :parameters [{:label "a"} + {:label "b"}]} + {:label "(bar [a b c])" + :parameters [{:label "a"} + {:label "b"} + {:label "c"}]}] + :active-parameter 0 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" zero-r zero-c)))) (testing "one arity" - (is (= {:signatures [{:label "(bar [a b])"} - {:label "(bar [a b c])"}] + (is (= {:signatures [{:label "(bar [a b])" + :parameters [{:label "a"} + {:label "b"}]} + {:label "(bar [a b c])" + :parameters [{:label "a"} + {:label "b"} + {:label "c"}]}] + :active-parameter 0 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" one-r one-c)))) (testing "two arity" - (is (= {:signatures [{:label "(bar [a b])"} - {:label "(bar [a b c])"}] + (is (= {:signatures [{:label "(bar [a b])" + :parameters [{:label "a"} + {:label "b"}]} + {:label "(bar [a b c])" + :parameters [{:label "a"} + {:label "b"} + {:label "c"}]}] + :active-parameter 0 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" two-r two-c)))) (testing "three arity" - (is (= {:signatures [{:label "(bar [a b])"} - {:label "(bar [a b c])"}] + (is (= {:signatures [{:label "(bar [a b])" + :parameters [{:label "a"} + {:label "b"}]} + {:label "(bar [a b c])" + :parameters [{:label "a"} + {:label "b"} + {:label "c"}]}] + :active-parameter 0 :active-signature 1} (f.signature-help/signature-help "file:///a.clj" three-r three-c)))) (testing "four arity" - (is (= {:signatures [{:label "(bar [a b])"} - {:label "(bar [a b c])"}] + (is (= {:signatures [{:label "(bar [a b])" + :parameters [{:label "a"} + {:label "b"}]} + {:label "(bar [a b c])" + :parameters [{:label "a"} + {:label "b"} + {:label "c"}]}] + :active-parameter 0 :active-signature 1} (f.signature-help/signature-help "file:///a.clj" four-r four-c)))))) (testing "With & rest arity only" @@ -92,15 +134,21 @@ "(bar| 1)" "(bar| 1 2)"))] (testing "zero arity" - (is (= {:signatures [{:label "(bar [& rest])"}] + (is (= {:signatures [{:label "(bar [& rest])" + :parameters [{:label "& rest"}]}] + :active-parameter 0 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" zero-r zero-c)))) (testing "one arity" - (is (= {:signatures [{:label "(bar [& rest])"}] + (is (= {:signatures [{:label "(bar [& rest])" + :parameters [{:label "& rest"}]}] + :active-parameter 0 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" one-r one-c)))) (testing "two arity" - (is (= {:signatures [{:label "(bar [& rest])"}] + (is (= {:signatures [{:label "(bar [& rest])" + :parameters [{:label "& rest"}]}] + :active-parameter 0 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" two-r two-c)))))) (testing "With fixed arities and & rest arity" @@ -116,27 +164,83 @@ "(bar| 1 2 3)" "(bar| 1 2 3 4)"))] (testing "zero arity" - (is (= {:signatures [{:label "(bar [a])"} - {:label "(bar [a & rest])"}] + (is (= {:signatures [{:label "(bar [a])" + :parameters [{:label "a"}]} + {:label "(bar [a & rest])" + :parameters [{:label "a"} + {:label "& rest"}]}] + :active-parameter 0 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" zero-r zero-c)))) (testing "one arity" - (is (= {:signatures [{:label "(bar [a])"} - {:label "(bar [a & rest])"}] + (is (= {:signatures [{:label "(bar [a])" + :parameters [{:label "a"}]} + {:label "(bar [a & rest])" + :parameters [{:label "a"} + {:label "& rest"}]}] + :active-parameter 0 :active-signature 0} (f.signature-help/signature-help "file:///a.clj" one-r one-c)))) (testing "two arity" - (is (= {:signatures [{:label "(bar [a])"} - {:label "(bar [a & rest])"}] + (is (= {:signatures [{:label "(bar [a])" + :parameters [{:label "a"}]} + {:label "(bar [a & rest])" + :parameters [{:label "a"} + {:label "& rest"}]}] + :active-parameter 0 :active-signature 1} (f.signature-help/signature-help "file:///a.clj" two-r two-c)))) (testing "three arity" - (is (= {:signatures [{:label "(bar [a])"} - {:label "(bar [a & rest])"}] + (is (= {:signatures [{:label "(bar [a])" + :parameters [{:label "a"}]} + {:label "(bar [a & rest])" + :parameters [{:label "a"} + {:label "& rest"}]}] + :active-parameter 0 :active-signature 1} (f.signature-help/signature-help "file:///a.clj" three-r three-c)))) (testing "four arity" - (is (= {:signatures [{:label "(bar [a])"} - {:label "(bar [a & rest])"}] + (is (= {:signatures [{:label "(bar [a])" + :parameters [{:label "a"}]} + {:label "(bar [a & rest])" + :parameters [{:label "a"} + {:label "& rest"}]}] + :active-parameter 0 :active-signature 1} (f.signature-help/signature-help "file:///a.clj" four-r four-c))))))) + +(deftest signature-help-active-parameter + (let [[[first-arg-r first-arg-c] + [second-arg-r second-arg-c] + [third-arg-r third-arg-c] + [end-function-r end-function-c]] (h/load-code-and-locs + (h/code "(defn bar [a & more] 1)" + "(defn foo [a b] (bar |1 |2 {:a| 2} |))"))] + (testing "on first arg" + (is (= {:signatures [{:label "(bar [a & more])" + :parameters [{:label "a"} + {:label "& more"}]}] + :active-parameter 0 + :active-signature 0} + (f.signature-help/signature-help "file:///a.clj" first-arg-r first-arg-c)))) + (testing "on second arg" + (is (= {:signatures [{:label "(bar [a & more])" + :parameters [{:label "a"} + {:label "& more"}]}] + :active-parameter 1 + :active-signature 0} + (f.signature-help/signature-help "file:///a.clj" second-arg-r second-arg-c)))) + (testing "on third arg" + (is (= {:signatures [{:label "(bar [a & more])" + :parameters [{:label "a"} + {:label "& more"}]}] + :active-parameter 1 + :active-signature 0} + (f.signature-help/signature-help "file:///a.clj" third-arg-r third-arg-c)))) + (testing "on end of the function" + (is (= {:signatures [{:label "(bar [a & more])" + :parameters [{:label "a"} + {:label "& more"}]}] + :active-parameter 1 + :active-signature 0} + (f.signature-help/signature-help "file:///a.clj" end-function-r end-function-c))))))