~technomancy/fennel

b0a597fae2e9773157ac8ffd894afeabf605a0e9 — Phil Hagelberg 2 months ago 77f8a57
Support repl completions on methods in tables.
5 files changed, 44 insertions(+), 22 deletions(-)

M Makefile
M changelog.md
M reference.md
M src/fennel/repl.fnl
M test/repl.fnl
M Makefile => Makefile +1 -1
@@ 10,7 10,7 @@ SRC=src/fennel.fnl $(wildcard src/fennel/*.fnl)
build: fennel fennel.lua

test: fennel.lua fennel
	$(LUA) test/init.lua
	$(LUA) test/init.lua $(TESTS)

testall: export FNL_TESTALL = 1
testall: export FNL_TEST_OUTPUT ?= text

M changelog.md => changelog.md +1 -1
@@ 4,7 4,7 @@ Backwards-incompatible changes are **marked in bold**.

## 0.9.3 / ???

* Fix a bug when the repl does completion on tables with a function metatable index
* Support repl completion on methods inside tables
* Change how auto-gensym prefixes are calculated in compilation output
* Add separate `FENNEL_MACRO_PATH` environment variable for `fennel.macro-path`
* **Add separate `fennel.macro-path` for searching for macro modules**

M reference.md => reference.md +0 -1
@@ 1247,7 1247,6 @@ can use `tostring` to get the name of a symbol.
* `gensym` - generates a unique symbol for use in macros.
* `varg?` - is this a `...` symbol which indicates var args?
* `multi-sym?` - a multi-sym is a dotted symbol which refers to a table's field
* `gensym` - generate a guaranteed-unique symbol
* `view` - `fennel.view` table serializer

* `assert-compile` - works like `assert` but takes a list/symbol as its third

M src/fennel/repl.fnl => src/fennel/repl.fnl +24 -13
@@ 58,27 58,38 @@
        input-fragment (text:gsub ".*[%s)(]+" "")]
    (var stop-looking? false)

    (fn add-partials [input tbl prefix] ; add partial key matches in tbl
    (fn add-partials [input tbl prefix method?] ; add partial key matches in tbl
      (each [k (utils.allpairs tbl)]
        (let [k (if (or (= tbl env) (= tbl env.___replLocals___))
                    (. scope.unmanglings k)
                    k)]
          (when (and (< (length matches) 2000)
                     ; stop explosion on too many items
                     (= (type k) :string) (= input (k:sub 0 (length input))))
            (table.insert matches (.. prefix k))))))

    (fn add-matches [input tbl prefix] ; add matches, descending into tbl fields
                     (= (type k) :string) (= input (k:sub 0 (length input)))
                     (or (not method?) (= :function (type (. tbl k)))))
            (table.insert matches (if method?
                                      (.. prefix ":" k)
                                      (.. prefix k)))))))

    (fn descend [input tbl prefix add-matches method?]
      (let [splitter (if method? "^([^:]+):(.*)" "^([^.]+)%.(.*)")
            (head tail) (input:match splitter)
            raw-head (if (or (= tbl env) (= tbl env.___replLocals___))
                         (. scope.manglings head)
                         head)]
        (when (= (type (. tbl raw-head)) :table)
          (set stop-looking? true)
          (if method?
              (add-partials tail (. tbl raw-head) (.. prefix head) true)
              (add-matches tail (. tbl raw-head) (.. prefix head))))))

    (fn add-matches [input tbl prefix]
      (let [prefix (if prefix (.. prefix ".") "")]
        (if (not (input:find "%.")) ; no more dots, so add matches
        (if (and (not (input:find "%.")) (input:find ":")) ; found a method call
            (descend input tbl prefix add-matches true)
            (not (input:find "%.")) ; done descending; add matches
            (add-partials input tbl prefix)
            (let [(head tail) (input:match "^([^.]+)%.(.*)")
                  raw-head (if (or (= tbl env) (= tbl env.___replLocals___))
                               (. scope.manglings head)
                               head)]
              (when (= (type (. tbl raw-head)) :table)
                (set stop-looking? true)
                (add-matches tail (. tbl raw-head) (.. prefix head)))))))
            (descend input tbl prefix add-matches false))))

    (each [_ source (ipairs [scope.specials scope.macros
                             (or env.___replLocals___ []) env env._G])]

M test/repl.fnl => test/repl.fnl +18 -6
@@ 25,7 25,7 @@
(fn assert-equal-unordered [a b msg]
  (l.assertEquals (table.sort a) (table.sort b) msg))

(fn test-completion []
(fn test-local-completion []
  (let [(send comp) (wrap-repl)]
    (send "(local [foo foo-ba* moe-larry] [1 2 {:*curly* \"Why soitenly\"}])")
    (send "(local [!x-y !x_y] [1 2])")


@@ 40,7 40,11 @@
                            "completions on mangled locals do not collide")
    (send "(local dynamic-index (setmetatable {:a 1 :b 2} {:__index #($2:upper)}))")
    (assert-equal-unordered (comp "dynamic-index.") [:dynamic-index.a :dynamic-index.b]
                            "completion doesn't error on table with a fn on mt.__index"))
                            "completion doesn't error on table with a fn on mt.__index")
    (let [(ok msg) (pcall send ",complete ]")]
      (l.assertTrue ok "shouldn't kill the repl on a parse error"))))

(fn test-macro-completion []
  (let [(send comp) (wrap-repl)]
    (send "(local mac {:incremented 9 :unsanitary 2})")
    (send "(import-macros mac :test.macros)")


@@ 48,9 52,15 @@
      ;; local should be shadowed!
      (l.assertNotEquals c1 "mac.incremented")
      (l.assertNotEquals c2 "mac.incremented")
      (l.assertNil c3))
    (let [(ok msg) (pcall send ",complete ]")]
      (l.assertTrue ok "shouldn't kill the repl on a parse error"))))
      (l.assertNil c3))))

(fn test-method-completion []
  (let [(send comp) (wrap-repl)]
    (send "(local ttt {:abc 12 :fff (fn [] :val) :inner {:foo #:f :fa #:f}})")
    (l.assertEquals (comp "ttt:f") ["ttt:fff"] "method completion works on fns")
    (assert-equal-unordered (comp "ttt.inner.f") ["ttt:foo" "ttt:fa"]
                            "method completion nests")
    (l.assertEquals (comp "ttt:ab") [] "no method completion on numbers")))

(fn test-help []
  (let [send (wrap-repl)


@@ 120,7 130,9 @@
;; this case the feature will work fine; we just can't use this method of
;; testing it on PUC 5.1, so skip it.
(if (or (not= _VERSION "Lua 5.1") (= (type _G.jit) "table"))
    {: test-completion
    {: test-local-completion
     : test-macro-completion
     : test-method-completion
     : test-help
     : test-exit
     : test-reload