From 099933b892a57d02c63c4509a7485bfce09a6f7f Mon Sep 17 00:00:00 2001 From: Eero Helenius Date: Tue, 20 Feb 2024 11:13:04 +0200 Subject: [PATCH] Improve require/import completion support Tutkain now also offers completions in these cases: - (require 'c) - (import 'j) --- Clojure (Tutkain).sublime-syntax | 2 +- clojure/repl/completions.repl | 30 ++++++++++++++++++++++++++++ clojure/src/tutkain/completions.cljc | 18 +++++++++++++++-- src/completions.py | 12 +++++++++-- 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/Clojure (Tutkain).sublime-syntax b/Clojure (Tutkain).sublime-syntax index ebaf4fd3..d84c6fa3 100644 --- a/Clojure (Tutkain).sublime-syntax +++ b/Clojure (Tutkain).sublime-syntax @@ -53,7 +53,7 @@ contexts: quote: - match: '(''){{whitespace}}(?![\(\[\{])' captures: - 1: keyword.operator.macro.clojure + 1: keyword.operator.macro.clojure meta.quote.clojure push: - include: forms - match: '' diff --git a/clojure/repl/completions.repl b/clojure/repl/completions.repl index 549033f3..f6a0187a 100644 --- a/clojure/repl/completions.repl +++ b/clojure/repl/completions.repl @@ -407,3 +407,33 @@ (xr/check! (partial set/subset? #{{:trigger "Date" :type :class} {:trigger "Deque" :type :class}})) + +;; require without braces +(completions + {:prefix "c" + :ns "user" + :enclosing-sexp "(require )" + :start-line 1 + :start-column 1 + :line 1 + :column 10}) + +(xr/check! + (spec/and + (prefixed-candidates ::specs/ns-completions "c") + (partial not-any? (comp #{:snippet} :completion-type)))) + +;; import without parens +(completions + {:prefix "j" + :ns "user" + :enclosing-sexp "(import )" + :start-line 1 + :start-column 1 + :line 1 + :column 10}) + +(xr/check! + (spec/and + (prefixed-candidates ::specs/class-completions "j") + (partial not-any? (comp #{:snippet} :completion-type)))) diff --git a/clojure/src/tutkain/completions.cljc b/clojure/src/tutkain/completions.cljc index e2cc6782..e3ea2e99 100644 --- a/clojure/src/tutkain/completions.cljc +++ b/clojure/src/tutkain/completions.cljc @@ -551,8 +551,15 @@ [loc prefix] ;; When e.g. [clojure.set :refer []], suggest vars in clojure.set. (let [node (some-> loc zip/node)] - (if (= :refer (second node)) + (cond + (and (sequential? node) (= :refer (second node))) (candidates-for-prefix prefix (ns-public-var-candidates (first node))) + + ;; require without braces (e.g. (require 'foo.bar)) + (= '(require) node) + (candidates-for-prefix prefix (ns-candidates)) + + :else (map (fn [{:keys [trigger] :as candidate}] (case trigger "clojure.test" @@ -569,10 +576,17 @@ (defn import-completions [loc prefix] (let [head (some-> loc zip/node first)] - (if (symbol? head) + (cond + ;; import without parens (e.g. (import 'foo.bar.Baz)) + (= 'import head) + (class-candidates prefix) + + (symbol? head) (map (fn [candidate] (update candidate :trigger (fn [trigger] (-> trigger (string/split #"\.") (last))))) (class-candidates (str (name head) "." prefix))) + + :else (map (fn [{:keys [trigger] :as candidate}] (let [parts (string/split trigger #"\.") diff --git a/src/completions.py b/src/completions.py index ede870d3..fca08b9a 100644 --- a/src/completions.py +++ b/src/completions.py @@ -90,7 +90,15 @@ def enclosing_sexp_sans_prefix(view, expr, prefix): The prefix is unlikely to resolve, so we must remove it from the S-expression to be able to analyze it on the server. """ - before = sublime.Region(expr.open.region.begin(), prefix.begin()) + + # If the character preceding the prefix is a quote, strip it, because e.g. + # (require ') is a syntax error. + if view.match_selector(prefix.begin() - 1, "meta.quote.clojure"): + begin = prefix.begin() - 1 + else: + begin = prefix.begin() + + before = sublime.Region(expr.open.region.begin(), begin) after = sublime.Region(prefix.end(), expr.close.region.end()) return view.substr(before) + view.substr(after) @@ -122,7 +130,7 @@ def get_completions(view, prefix, point): if ( view.match_selector( preceding_point, - "source.clojure & (meta.symbol - meta.function.parameters - entity.name) | constant.other.keyword", + "source.clojure & (meta.symbol - meta.function.parameters - entity.name) | constant.other.keyword | keyword.operator.macro", ) and (dialect := dialects.for_point(view, preceding_point)) and (client := state.get_client(view.window(), dialect))