Skip to content

Commit

Permalink
Show completion candidates when hitting tab
Browse files Browse the repository at this point in the history
Fixes #527
  • Loading branch information
mfikes committed Nov 13, 2017
1 parent 68b46fd commit 863b7b1
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 113 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. This change
## [Unreleased]
### Changed
- transit-cljs 0.8.243
- Show completion candidates when hitting tab ([#527](https://github.com/mfikes/planck/issues/527))
- Remove `planck.repl/get-arglists` spec (it is non-user supplied and present by default)

### Fixed
Expand Down
70 changes: 30 additions & 40 deletions planck-c/linenoise.c
Expand Up @@ -372,6 +372,8 @@ static void freeCompletions(linenoiseCompletions *lc) {
free(lc->cvec);
}

int linenoiseEditInsert(struct linenoiseState *l, char c);

/* This is an helper function for linenoiseEdit() and is called when the
* user types the <tab> key in order to complete the string currently in the
* input.
Expand All @@ -384,54 +386,42 @@ static int completeLine(struct linenoiseState *ls) {
char c = 0;

completionCallback(ls->buf, &lc);
if (lc.len == 0) {
if (lc.len == 1) {
linenoiseBeep();
} else if (lc.len == 2) {
size_t i;
size_t prefix_len = strlen(lc.cvec[0]);
size_t count = strlen(lc.cvec[1]) - prefix_len;
for (i = 0; i < count; i++) {
linenoiseEditInsert(ls, lc.cvec[1][prefix_len + i]);
}
refreshLine(ls);
} else {
size_t stop = 0, i = 0;

while (!stop) {
/* Show completion or original buffer */
if (i < lc.len) {
struct linenoiseState saved = *ls;

ls->len = ls->pos = strlen(lc.cvec[i]);
ls->buf = lc.cvec[i];
refreshLine(ls);
ls->len = saved.len;
ls->pos = saved.pos;
ls->buf = saved.buf;
} else {
refreshLine(ls);
size_t i;
size_t max_completion_length = 0;
for (i = 1; i < lc.len; i++) {
if (strlen(lc.cvec[i]) > max_completion_length) {
max_completion_length = strlen(lc.cvec[i]);
}
}
size_t column_width = max_completion_length + 1;

nread = read(ls->ifd, &c, 1);
if (nread <= 0) {
freeCompletions(&lc);
return -1;
}
size_t columns = (getColumns(STDIN_FILENO, STDOUT_FILENO) - 4) / column_width;

switch (c) {
case 9: /* tab */
i = (i + 1) % (lc.len + 1);
if (i == lc.len) linenoiseBeep();
break;
case 27: /* escape */
/* Re-show original buffer */
if (i < lc.len) refreshLine(ls);
stop = 1;
break;
default:
/* Update buffer and return */
if (i < lc.len) {
nwritten = snprintf(ls->buf, ls->buflen, "%s", lc.cvec[i]);
ls->len = ls->pos = nwritten;
}
stop = 1;
break;
char format[100];
sprintf(format, "%%-%zus", column_width);

for (i = 1; i < lc.len; i++) {
if ((i - 1) % columns == 0) {
printf("\r\n");
}
printf(format, lc.cvec[i]);
printf(" ");
}
printf("\n");
printf("\n");
refreshLine(ls);
}

freeCompletions(&lc);
return c; /* Return last read character */
}
Expand Down
46 changes: 32 additions & 14 deletions planck-cljs/src/planck/repl.cljs
Expand Up @@ -559,27 +559,45 @@
(second (re-find #"::([a-zA-Z-]*)$" buffer)))

(defn- local-keyword-completions
[buffer kw-name]
(let [buffer-prefix (subs buffer 0 (- (count buffer) (count kw-name) 2))]
(clj->js (sequence
(comp
(map local-keyword-str)
(filter #(string/starts-with? % (str "::" kw-name)))
(map #(str buffer-prefix %)))
(spec-registered-keywords @current-ns)))))
[kw-name]
(let [kw-source (str "::" kw-name)]
(clj->js (into [kw-source]
(sequence
(comp
(map local-keyword-str)
(filter #(string/starts-with? % kw-source)))
(spec-registered-keywords @current-ns))))))

(defn- longest-common-prefix
[strings]
(let [minl (apply min (map count strings))]
(loop [l minl]
(if (> l 0)
(if (every? #{(subs (first strings) 0 l)}
(map #(subs % 0 l) (rest strings)))
(subs (first strings) 0 l)
(recur (dec l)))
""))))

(defn- ^:export get-completions
"Returns an array of the buffer-match-suffix, along with completions for the
entered text. If one completion is returned the line should be completed to
match it (in which the completion may actually only be a longest prefix from
the list of candiates), otherwise the list of completions should be
displayed."
[buffer]
(if-let [kw-name (local-keyword buffer)]
(local-keyword-completions buffer kw-name)
(local-keyword-completions kw-name)
(let [top-form? (re-find #"^\s*\(\s*[^()\s]*$" buffer)
typed-ns (second (re-find #"\(*(\b[a-zA-Z-.<>*=&?]+)/[a-zA-Z-]*$" buffer))]
(let [buffer-match-suffix (first (re-find #":?([a-zA-Z-.<>*=&?]*|^\(/)$" buffer))
buffer-prefix (subs buffer 0 (- (count buffer) (count buffer-match-suffix)))]
(clj->js (map #(str buffer-prefix %)
(sort
(filter (partial is-completion? buffer-match-suffix)
(completion-candidates top-form? typed-ns)))))))))
completions (sort (filter (partial is-completion? buffer-match-suffix)
(completion-candidates top-form? typed-ns)))
common-prefix (longest-common-prefix completions)]
(if (or (empty? common-prefix)
(= common-prefix buffer-match-suffix))
(clj->js (into [buffer-match-suffix] completions))
#js [buffer-match-suffix common-prefix])))))

(defn- is-completely-readable?
[source]
Expand Down
59 changes: 0 additions & 59 deletions planck-cljs/test/planck/repl_test.cljs
Expand Up @@ -57,65 +57,6 @@
(is (= '([x] [x y] [x y & more]) (planck.repl/get-arglists "max")))
(is (nil? (planck.repl/get-arglists "bogus-undefined"))))

(defn is-completion [i o]
(let [completions (planck.repl/get-completions i)]
(is (= (js->clj completions) (sort (into [] (map str) o))))))

(defn is-contains-completion
([i o]
(is-contains-completion i o identity))
([i o f]
(let [completions (planck.repl/get-completions i)]
(is (f (contains? (set completions) o))))))

(deftest test-get-completions
(testing "keyword completions"
(is-completion ":" planck.repl/keyword-completions)
(is-completion ":a" [":args" ":as"])
(is-completion ":ref" [":refer" ":refer-clojure" ":refer-macros"]))
(testing "aliased namespaces completions"
(with-redefs [planck.repl/current-alias-map (fn []
'{string clojure.string})]
(is-contains-completion "str" "string/")
(is-contains-completion "(str" "(string/")
(is-contains-completion "(set" "(set/" not))
(with-redefs [planck.repl/all-ns (fn [] '(clojure.set clojure.string))]
(is-contains-completion "(clojure.s" "(clojure.set")))
(testing "cljs.core function completions"
(is-contains-completion "sub" "subs")
(is-contains-completion "mer" "merge")
(is-contains-completion "clojure.core/mer" "clojure.core/merge"))
#_(testing "referred vars completions"
(with-redefs [planck.repl/get-namespace (fn [_]
'{:uses {foo foo.core}
:requires {foo.core foo.core}
:use-macros {longer-var bar.core}})]
(is-contains-completion "fo" "foo")
(is-contains-completion "lon" "longer-var")
(is-contains-completion "(lon" "(longer-var")))
(testing "completions after slash"
(is-contains-completion "clojure.core/" "clojure.core/merge")
(is-contains-completion "" "merge")
(with-redefs [planck.repl/current-alias-map (fn []
'{string clojure.string})]
(is-contains-completion "(string/" "(string/merge" not)))
#_(testing "JS Completions"
(is-contains-completion "js/con" "js/console"))
(testing "Auto-complete after arrow and other special chars"
(is-contains-completion "(-" "(->")
(is-contains-completion "(-" "(->>")
(is-contains-completion "(->" "(->>")
(is-contains-completion "(->" "(->merge" not)
(is-contains-completion "*" "*clojurescript-version*")
(is-contains-completion "*" "*merge" not)
(is-contains-completion "(<" "(<merge" not)
(is-contains-completion "(=" "(=merge" not)
(is-contains-completion "(&" "(&merge" not)
(is-contains-completion "(?" "(?merge" not)
(is-contains-completion "(/" "(/merge" not)
(is-contains-completion "(?" "(?merge" not)
(is-contains-completion "(MER" "(merge")))

(deftest completions-for-goog-ns
(is (some #{"isArrayLike"} (planck.repl/completion-candidates-for-ns 'goog false)))
(is (some #{"trimLeft"} (planck.repl/completion-candidates-for-ns 'goog.string false))))
Expand Down

0 comments on commit 863b7b1

Please sign in to comment.