Skip to content

Commit

Permalink
[context] Try to complete unfinished forms when parsing context
Browse files Browse the repository at this point in the history
Addresses #53.
  • Loading branch information
alexander-yakushev committed Feb 22, 2019
1 parent 5273ff5 commit b3b52af
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 15 deletions.
57 changes: 42 additions & 15 deletions src/compliment/context.clj
@@ -1,24 +1,51 @@
(ns compliment.context
"Utilities for parsing and storing the current completion context."
(:require [clojure.walk :refer [walk]]))
(:require [clojure.string :as str]
[clojure.walk :as walk]))

(defn- restore-map-literals [context]
(clojure.walk/postwalk (fn [el]
(if (and (sequential? el)
(= (first el) 'compliment-hashmap))
(apply hash-map
(if (even? (count el))
(concat (rest el) [nil])
(rest el)))
el)) context))
(walk/postwalk (fn [el]
(if (and (sequential? el)
(= (first el) 'compliment-hashmap))
(apply hash-map
(if (even? (count el))
(concat (rest el) [nil])
(rest el)))
el)) context))

(defn- try-read-replacing-maps [s]
(try (binding [*read-eval* false]
(-> s
(str/replace "{" "(compliment-hashmap ")
(str/replace "}" ")")
read-string
restore-map-literals))
(catch Exception ex)))

(defn- dumb-read-form
"Take a presumably unfinished Clojure form and try to \"complete\" it so that it
can be read. The algorithm is incredibly stupid, but is better than nothing."
[unfinished-form-str]
(let [open->close {\( \), \[ \], \{ \}},
close->open {\) \(, \] \[, \} \{}]
(loop [[c & r] (reverse (filter (set "([{}])") unfinished-form-str))
to-append []]
(if c
(cond (open->close c)
(recur r (conj to-append (open->close c)))

(close->open c)
(if (= c (open->close (first r)))
(recur (rest r) to-append)
;; Everything is bad - just give up
nil))
(try-read-replacing-maps (apply str unfinished-form-str to-append))))))

#_(dumb-read-form "(let [a {:b 1}, c {__prefix__")

(defn- safe-read-context-string [^String context]
(try (-> context
(.replace "{" "(compliment-hashmap ")
(.replace "}" ")")
read-string
restore-map-literals)
(catch Exception ex nil)))
(or (try-read-replacing-maps context)
(dumb-read-form context)))

(def ^{:doc "Stores the last completion context."
:private true}
Expand Down
6 changes: 6 additions & 0 deletions test/compliment/sources/t_local_bindings.clj
Expand Up @@ -85,6 +85,12 @@
__prefix__))))))
=> (just ["afunction" "arg1" "arg2" "foo"] :in-any-order))

(fact "bindings will be completed even if the form is unfinished"
(strip-tags (src/candidates "" *ns* (ctx/parse-context
(#'ctx/safe-read-context-string
"(let [foo 42, [bar baz] 17, qux __prefix__"))))
=> (just ["foo" "bar" "baz" "qux"] :in-any-order))

(fact "source silently fails if context is malformed"
(src/candidates "" *ns* "(let __prefix__)") => []
(src/candidates "" *ns* "(let [() 1]__prefix__)") => []
Expand Down
6 changes: 6 additions & 0 deletions test/compliment/t_context.clj
Expand Up @@ -50,6 +50,12 @@
(ctx/parse-context (#'ctx/safe-read-context-string "{:foo __prefix__ :bar}"))
=> '({:idx :foo, :map-role :value, :form {:foo __prefix__, :bar nil}}))

(fact "failing to parse an unfinished form will try to recover by completing it"
(ctx/parse-context (#'ctx/safe-read-context-string "(let [a {:b 1}, c {__prefix__"))
=> '({:idx nil, :map-role :key, :form {__prefix__ nil}}
{:idx 3, :form [a {:b 1} c {__prefix__ nil}]}
{:idx 1, :form (let [a {:b 1} c {__prefix__ nil}])}))

(fact "in maps :map-role shows which role in key-value pair the
__prefix__ (or the form with it) has, and :idx shows the opposite
element of its key-value pair."
Expand Down

0 comments on commit b3b52af

Please sign in to comment.