find definition is more advanced but more brittle

This commit is contained in:
XeroOl 2022-08-07 18:47:18 -05:00
parent 61947d3219
commit 80d4455ed3
No known key found for this signature in database
GPG Key ID: 9DD4B4B4DAED0322
18 changed files with 6566 additions and 699 deletions

View File

@ -1,13 +1,21 @@
LUA_LIB=/usr/lib/liblua.so.5.4 LUA_LIB=/usr/lib/liblua.so.5.4
LUA_INCLUDE_DIR=/usr/include/lua5.4 LUA_INCLUDE_DIR=/usr/include/lua5.4
SOURCES=$(wildcard src/*.fnl) FENNEL=./fennel
SOURCES+=$(wildcard src/fennel-ls/*.fnl) EXE=fennel-ls
.PHONY: test SRC=$(wildcard src/*.fnl)
SRC+=$(wildcard src/fennel-ls/*.fnl)
.PHONY: clean test
all: $(EXE)
$(EXE): $(SRC)
LUA_PATH="./src/?.lua;./src/?/init.lua" FENNEL_PATH="./src/?.fnl;./src/?/init.fnl" $(FENNEL) --compile-binary src/fennel-ls.fnl fennel-ls $(LUA_LIB) $(LUA_INCLUDE_DIR)
fennel-ls: $(SOURCES)
LUA_PATH="./src/?.lua;./src/?/init.lua" FENNEL_PATH="./src/?.fnl;./src/?/init.fnl" ./fennel --compile-binary src/fennel-ls.fnl fennel-ls $(LUA_LIB) $(LUA_INCLUDE_DIR)
clean: clean:
rm -f fennel-ls rm -f fennel-ls
test: test:
FENNEL_PATH="./src/?.fnl;./src/?/init.fnl" ./fennel --correlate test/init.fnl --verbose # requires busted to be installed
FENNEL_PATH="./src/?.fnl;./src/?/init.fnl" $(FENNEL) --correlate test/init.fnl --verbose

View File

@ -4,28 +4,30 @@ THIS PROJECT IS NOT IN A USABLE STATE YET. CHECK BACK LATER.
A language server for fennel-ls. A language server for fennel-ls.
(Planned) Features: Features / To Do List / Things I would enjoy patches for:
[ ] for planned features
[X] for implemented features
* [X] Able to connect to a client - [X] Able to connect to a client
* [ ] Support for UTF-8 characters that aren't just plain ASCII. (especially `λ`) - [ ] Support for UTF-8 characters that aren't just plain ASCII. (especially `λ`)
* [ ] Settings to configure lua / fennel path, allowed globals, etc - [ ] Settings to configure lua / fennel path, allowed globals, etc
* [ ] Builds for anything other than arch linux - [ ] Builds for anything other than arch linux
* [X] Go-to-definition for (require) statements - [ ] Go-to-definition
* [X] basic go-to-definition for in-file definitions - [ ] directly on require statements
* [ ] Go-to-definition for in-file definitions in all cases - [X] for definitions in the same file
* [ ] Go-to-definition for definitions in other files - [ ] for definitions in other files
* [ ] Go-to-definition into lua code - [ ] follows multisyms through table constructor
* [ ] Reports compiler errors - [ ] follows multisyms through mutations (difficult)
* [ ] Reports linting issues - [ ] for methods/metamethods (difficult, in the general case may require type annotations or some insane global type inference logic)
* [ ] basic completion suggestions - [ ] into lua files (maybe cheeseable with antifennel if it has --correlate?)
* [ ] Hover over a symbol for documentation
* [ ] Signature help - [ ] Reports compiler errors
* [ ] Go-to-references on definitions of things - [ ] including in macro files
* [ ] integration with fnlfmt - [ ] Reports linting issues
* [ ] Maybe some sort of type checking??? - [ ] Completion Suggestions
* [ ] completion suggestions for fields / methods - [ ] Hover over a symbol for documentation
- [ ] Signature help
- [ ] Go-to-references on definition sites
- [ ] integration with fnlfmt
- [ ] Maybe some sort of type checking??
## Setup: ## Setup:

1185
fennel

File diff suppressed because it is too large Load Diff

5686
fennel.lua Normal file

File diff suppressed because it is too large Load Diff

2
file.fnl Normal file
View File

@ -0,0 +1,2 @@
(match [0 10]
[1 a] a)

View File

@ -1,31 +1,42 @@
(local fennel (require :fennel)) (local fennel (require :fennel))
(local util (require :fennel-ls.util)) (local util (require :fennel-ls.util))
(fn get-ast-info [ast info] (λ get-ast-info [?ast info]
"find a given key of info from an AST object" "find a given key of info from an AST object"
(or (. (getmetatable ast) info) (or (?. (getmetatable ?ast) info)
(. ast info))) (. ?ast info)))
(fn contains? [ast byte] (λ contains? [?ast byte]
"check if a byte is in range of the AST object" "check if a byte is in range of the AST object"
(and (= (type ast) :table) (and (= (type ?ast) :table)
(get-ast-info ast :bytestart) (get-ast-info ?ast :bytestart)
(get-ast-info ast :byteend) (get-ast-info ?ast :byteend)
(<= (get-ast-info ast :bytestart) (<= (get-ast-info ?ast :bytestart)
byte byte
(get-ast-info ast :byteend)))) (+ 1 (get-ast-info ?ast :byteend)))))
(fn past? [ast byte] (λ does-not-contain? [?ast byte]
"check if a byte is in range of the AST object"
(and (= (type ?ast) :table)
(get-ast-info ?ast :bytestart)
(get-ast-info ?ast :byteend)
(not
(<= (get-ast-info ?ast :bytestart)
byte
(+ 1 (get-ast-info ?ast :byteend))))))
(λ past? [?ast byte]
"check if a byte is past the range of the AST object" "check if a byte is past the range of the AST object"
(and (= (type ast) :table) (and (= (type ?ast) :table)
(get-ast-info ast :bytestart) (get-ast-info ?ast :bytestart)
(< byte (get-ast-info ast :bytestart)) (< byte (get-ast-info ?ast :bytestart))
false)) false))
(fn range [text ast] (λ range [text ?ast]
"create a LSP range representing the span of an AST object" "create a LSP range representing the span of an AST object"
(if (= (type ast) :table) (if (= (type ?ast) :table)
(match (values (get-ast-info ast :bytestart) (get-ast-info ast :byteend)) (match (values (get-ast-info ?ast :bytestart) (get-ast-info ?ast :byteend))
(i j) (i j)
(let [(start-line start-col) (util.byte->pos text i) (let [(start-line start-col) (util.byte->pos text i)
(end-line end-col) (util.byte->pos text (+ j 1))] (end-line end-col) (util.byte->pos text (+ j 1))]
@ -33,5 +44,6 @@
:end {:line end-line :character end-col}})))) :end {:line end-line :character end-col}}))))
{: contains? {: contains?
: does-not-contain?
: past? : past?
: range} : range}

View File

@ -15,19 +15,28 @@
(λ table? [t] (λ table? [t]
(= :table (type t))) (= :table (type t)))
(λ string? [t]
(= :string (type t)))
(λ multisym? [t] (λ multisym? [t]
(and (fennel.sym? t) (and (fennel.sym? t)
(let [t (tostring t)] (let [t (tostring t)]
(or (t:find "%.") (or (t:find "%.")
(t:find ":"))))) (t:find ":")))))
(λ iter [t]
(if (or (fennel.list? t)
(fennel.sequence? t))
(ipairs t)
(pairs t)))
(λ analyze [file] (λ analyze [file]
(assert file.text (fennel.view file)) (assert file.text (fennel.view file))
(assert file.uri) (assert file.uri)
(set file.references []) (set file.references [])
(local scope-notes (local definitions
(doto {} (doto {}
(setmetatable (setmetatable
{:__index {:__index
@ -36,27 +45,29 @@
(tset self key val) (tset self key val)
val))}))) val))})))
(λ find-reference [name ?scope] (λ find-variable [name ?scope]
(when ?scope (when ?scope
(or (. scope-notes ?scope (tostring name)) (or (. definitions ?scope name)
(find-reference name ?scope.parent)))) (find-variable name ?scope.parent))))
(λ reference [ast scope] (λ reference [ast scope]
"called whenever a variable is referenced" "called whenever a variable is referenced"
(assert (fennel.sym? ast)) (assert (fennel.sym? ast))
;; find reference
(let [name (string.match (tostring ast) "[^%.:]+") (let [name (string.match (tostring ast) "[^%.:]+")
target (find-reference name scope)] target (find-variable (tostring name) scope)]
(table.insert file.references {:from ast :to target}))) (tset file.references ast target)))
(λ define [?definition binding scope] (λ define [?definition binding scope]
"called whenever a local variable or destructure statement is introduced" "called whenever a local variable or destructure statement is introduced"
;; right now I'm not keeping track of *how* the symbol was destructured: just finding all the symbols for now. ;; right now I'm not keeping track of *how* the symbol was destructured: just finding all the symbols for now.
(λ recurse [binding] (λ recurse [binding]
(if (fennel.sym? binding) (if (fennel.sym? binding)
;; this seems to defeat match's symbols in specifically the one case I've tested (tset (. definitions scope)
(if (not (?. scope :parent :gensyms (tostring binding))) (tostring binding)
(tset (. scope-notes scope) (tostring binding) binding)) {: binding :definition ?definition})
(each [k v ((if (fennel.list? binding) ipairs pairs) binding)] (table? binding)
(each [k v (iter binding)]
(recurse v)))) (recurse v))))
(recurse binding)) (recurse binding))
@ -66,7 +77,10 @@
(and (fennel.sym? name) (and (fennel.sym? name)
(not (multisym? name)) ;; not dealing with multisym for now (not (multisym? name)) ;; not dealing with multisym for now
(fennel.sequence? args))) (fennel.sequence? args)))
(tset (. scope-notes scope.parent) (tostring name) ast))) (tset (. definitions scope.parent)
(tostring name)
{:binding name
:definition ast})))
(λ define-function-args [ast scope] (λ define-function-args [ast scope]
(local args (local args
@ -79,15 +93,13 @@
(λ define-function [ast scope] (λ define-function [ast scope]
"Introduces the various symbols exported by a function. "Introduces the various symbols exported by a function.
This cannot be done through the :fn feature of the compiler plugin system, because it needs to be This cannot be done through the :fn feature of the compiler plugin system, because it needs to be
called before the body of the function happens" called *before* the body of the function is processed."
(define-function-name ast scope) (define-function-name ast scope)
(define-function-args ast scope)) (define-function-args ast scope))
(λ call [ast scope] (λ call [ast scope]
"called for every function call. Most calls aren't interesting, but (require) and (local) are" "called for every function call. Most calls aren't interesting, but fn is"
(match ast (match ast
(where [-require- mod] (= :string (type mod)))
(insert file.references {:from ast :to-other-module [mod]})
[-fn-] [-fn-]
(define-function ast scope) (define-function ast scope)
[-λ-] [-λ-]
@ -102,8 +114,13 @@ called before the body of the function happens"
: call : call
:destructure define}) :destructure define})
(pcall fennel.compileString file.text (set file.ast (icollect [ok ast (fennel.parser file.text)]
{:filename file.uri ast))
:plugins [plugin]})) (local scope (fennel.scope))
(each [_i form (ipairs file.ast)]
(fennel.compile form
{:filename file.uri
: scope
:plugins [plugin]})))
{: analyze} {: analyze}

View File

@ -5,6 +5,9 @@ Every time the client sends a message, it gets handled by a function in the corr
(ie, a textDocument/didChange notification will call notifications.textDocument/didChange (ie, a textDocument/didChange notification will call notifications.textDocument/didChange
and a textDocument/defintion request will call requests.textDocument/didChange)" and a textDocument/defintion request will call requests.textDocument/didChange)"
(local fennel (require :fennel)) (local fennel (require :fennel))
(local sym? fennel.sym?)
(local list? fennel.list?)
(local fennelutils (require :fennel.utils))
(local parser (require :fennel-ls.parser)) (local parser (require :fennel-ls.parser))
(local util (require :fennel-ls.util)) (local util (require :fennel-ls.util))
@ -61,26 +64,94 @@ Every time the client sends a message, it gets handled by a function in the corr
{:capabilities capabilities {:capabilities capabilities
:serverInfo {:name "fennel-ls" :version "0.0.0"}}) :serverInfo {:name "fennel-ls" :version "0.0.0"}})
(fn string? [j] (λ string? [j]
(= (type j) :string)) (= (type j) :string))
(local require* (fennel.sym :require)) (λ get-assignment-of-symbol [file symbol]
(local local* (fennel.sym :local)) ;; TODO inline
(. file.references symbol))
;; These three functions are mutually recursive
(var (search-item
search-assignment
search-symbol)
nil)
(set search-item
(λ search-item [self file item stack]
(if ;; table
(fennelutils.table? item)
(if (. item (. stack (length stack)))
(search-item self file (. item (table.remove stack)) stack)
nil)
;; symbol
(sym? item)
(search-symbol self file item stack)
;; TODO
;; functioncall (into body)
;; require functioncall (into module)
;; else
true (error (.. "I don't know what to do with " (fennel.view item))))))
(set search-assignment
(λ search-assignment [self file binding ?definition stack]
(if (= 0 (length stack))
binding
;; TODO sift down the binding
(search-item self file ?definition stack))))
(set search-symbol
(λ search-symbol [self file symbol stack]
(let [split (util.multi-sym-split symbol)]
(for [i (length split) 2 -1]
(table.insert stack (. split i))))
(match (get-assignment-of-symbol file symbol)
to (search-assignment self file to.binding to.definition stack)
nil nil)))
(λ iter [t]
(if (or (fennel.list? t)
(fennel.sequence? t))
(ipairs t)
(pairs t)))
(λ find-symbol* [ast byte]
(if (not= :table (type ast))
nil
(parser.does-not-contain? ast byte)
nil
(sym? ast)
ast
(or (fennel.list? ast)
(fennel.sequence? ast))
;; TODO binary search
(accumulate [result nil
_ v (ipairs ast) &until (or result (parser.past? v byte))]
(find-symbol* v byte))
:else (accumulate [result nil
k v (pairs ast) &until result]
(or
(find-symbol* k byte)
(find-symbol* v byte)))))
(λ find-symbol [ast byte]
;; TODO binary search
(accumulate [result nil
_ v (ipairs ast) &until (or result (parser.past? v byte))]
(find-symbol* v byte)))
(λ requests.textDocument/definition [self send {: position :textDocument {: uri}}] (λ requests.textDocument/definition [self send {: position :textDocument {: uri}}]
(local file (state.get-by-uri self uri)) (local file (state.get-by-uri self uri))
(local byte (util.pos->byte file.text position.line position.character)) (local byte (util.pos->byte file.text position.line position.character))
(accumulate [result nil (local stack [])
_ reference (ipairs file.references) &until (or result (parser.past? reference.from byte))] (match (find-symbol file.ast byte)
(if (parser.contains? reference.from byte) symbol
(match reference (match (search-symbol self file symbol stack)
{: from : to} definition
{:range (parser.range file.text to) {:range (parser.range file.text definition)
:uri file.uri} :uri uri})
nil nil))
{: from : to-other-module}
{:range {:start {:line 0 :character 0}
:end {:line 0 :character 0}}
:uri (mod.lookup self (. to-other-module 1))}))))
(λ notifications.textDocument/didChange [self send {: contentChanges :textDocument {: uri}}] (λ notifications.textDocument/didChange [self send {: contentChanges :textDocument {: uri}}]
(local file (state.get-by-uri self uri)) (local file (state.get-by-uri self uri))

View File

@ -69,8 +69,21 @@ These functions are all pure functions, which makes me happy."
{: text} {: text}
text))) text)))
(fn multi-sym-split [sym ?offset]
(local sym (tostring sym))
(local offset (or ?offset (length sym)))
(local next-separator (or (sym:find ".[%.:]" offset)
(length sym)))
(local sym (sym:sub 1 next-separator))
(icollect [word (: (.. sym ".") :gmatch "(.-)[%.:]")]
word))
(fn reversed [tab])
{: uri->path {: uri->path
: path->uri : path->uri
: pos->byte : pos->byte
: byte->pos : byte->pos
: apply-changes} : apply-changes
: multi-sym-split}

10
test.fnl Normal file
View File

@ -0,0 +1,10 @@
(var (a b c) nil)
(set a (fn a []
(b)))
(set b (fn b []
(print "yay")))
(a)

View File

@ -1,5 +1,5 @@
(import-macros {: assert-matches : describe : it : before-each} :test.macros) (import-macros {: is-matching : describe : it : before-each} :test.macros)
(local assert (require :luassert)) (local is (require :luassert))
(local fennel (require :fennel)) (local fennel (require :fennel))
(local {: ROOT-URI (local {: ROOT-URI
@ -17,7 +17,7 @@
{:position {:character char :line line} {:position {:character char :line line}
:textDocument {:uri (.. ROOT-URI "/" request-file)}})) :textDocument {:uri (.. ROOT-URI "/" request-file)}}))
uri (.. ROOT-URI "/" response-file)] uri (.. ROOT-URI "/" response-file)]
(assert-matches (is-matching
message message
[{:jsonrpc "2.0" :id 2 [{:jsonrpc "2.0" :id 2
:result {: uri :result {: uri
@ -25,15 +25,8 @@
:end {:line end-line :character end-col}}}}] :end {:line end-line :character end-col}}}}]
(.. "expected position: " start-line " " start-col " " end-line " " end-col)))) (.. "expected position: " start-line " " start-col " " end-line " " end-col))))
(it "handles (local _ (require XXX)"
(check "example.fnl" 0 11 "foo.fnl" 0 0 0 0))
(it "handles (require XXX))"
(check "example.fnl" 1 5 "bar.fnl" 0 0 0 0))
(it "can go to a fn" (it "can go to a fn"
;; TODO maybe it's better to just go to the name of the function, not the whole list (check "example.fnl" 9 3 "example.fnl" 4 4 4 7))
(check "example.fnl" 9 3 "example.fnl" 4 0 7 20))
(it "can go to a local" (it "can go to a local"
(check "example.fnl" 7 17 "example.fnl" 6 9 6 10)) (check "example.fnl" 7 17 "example.fnl" 6 9 6 10))
@ -48,11 +41,20 @@
(check "example.fnl" 19 12 "example.fnl" 17 8 17 9)) (check "example.fnl" 19 12 "example.fnl" 17 8 17 9))
(it "can sort out the unification rule with match (variable introduced)" (it "can sort out the unification rule with match (variable introduced)"
(check "example.fnl" 20 12 "example.fnl" 20 9 20 10)) (check "example.fnl" 20 13 "example.fnl" 20 9 20 10))
(it "can go to a destructured local" (it "can go to a destructured local"
(check "example.fnl" 21 9 "example.fnl" 16 13 16 16))) (check "example.fnl" 21 9 "example.fnl" 16 13 16 16))
;; (it "can go to a function inside a table")
(it "can go to a function inside a table"
(check "example.fnl" 28 6 "example.fnl" 4 4 4 7)))
;; (it "handles (local _ (require XXX)"
;; (check "example.fnl" 0 11 "foo.fnl" 0 0 0 0))
;; (it "handles (require XXX))"
;; (check "example.fnl" 1 5 "bar.fnl" 0 0 0 0))
;; (it "can go to a field inside of a table") ;; (it "can go to a field inside of a table")
;; (it "can go to a destructured function argument") ;; (it "can go to a destructured function argument")
;; (it "can go to a reference that occurs in a macro") ;; (it "can go to a reference that occurs in a macro")

View File

@ -4,3 +4,4 @@
(require :test.string-processing-test) (require :test.string-processing-test)
(require :test.lsp-test) (require :test.lsp-test)
(require :test.goto-definition-test) (require :test.goto-definition-test)
(require :test.misc-test)

View File

@ -1,5 +1,5 @@
(import-macros {: assert-matches : describe : it} :test.macros) (import-macros {: is-matching : describe : it} :test.macros)
(local assert (require :luassert)) (local is (require :luassert))
(local fennel (require :fennel)) (local fennel (require :fennel))
(local stringio (require :test.pl.stringio)) (local stringio (require :test.pl.stringio))
@ -10,30 +10,30 @@
(it "parses incoming messages" (it "parses incoming messages"
(let [out (stringio.open (let [out (stringio.open
"Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}")] "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}")]
(assert.same (is.same
{"my json content" "is cool"} {"my json content" "is cool"}
(json-rpc.read out)))) (json-rpc.read out))))
(it "can read multiple incoming messages" (it "can read multiple incoming messages"
(let [out (stringio.open (let [out (stringio.open
"Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}Content-Length: 29\r\n\r\n{\"my json content\":\"is neat\"}")] "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}Content-Length: 29\r\n\r\n{\"my json content\":\"is neat\"}")]
(assert.same (is.same
{"my json content" "is cool"} {"my json content" "is cool"}
(json-rpc.read out)) (json-rpc.read out))
(assert.same (is.same
{"my json content" "is neat"} {"my json content" "is neat"}
(json-rpc.read out)) (json-rpc.read out))
(assert.same (is.same
nil nil
(json-rpc.read out)))) (json-rpc.read out))))
(it "can report compiler errors" (it "can report compiler errors"
(let [out (stringio.open "Content-Length: 9\r\n\r\n{{{{{}}}}")] (let [out (stringio.open "Content-Length: 9\r\n\r\n{{{{{}}}}")]
(assert (= (type (json-rpc.read out)) :string))))) (is (= (type (json-rpc.read out)) :string)))))
(describe "write" (describe "write"
(it "serializes outgoing messages" (it "serializes outgoing messages"
(let [in (stringio.create)] (let [in (stringio.create)]
(json-rpc.write in {"my json content" "is cool"}) (json-rpc.write in {"my json content" "is cool"})
(assert.same "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}" (is.same "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}"
(in:value)))))) (in:value))))))

View File

@ -1,5 +1,5 @@
(import-macros {: assert-matches : describe : it} :test.macros) (import-macros {: is-matching : describe : it} :test.macros)
(local assert (require :luassert)) (local is (require :luassert))
(local {: ROOT-PATH : ROOT-URI} (require :test.util)) (local {: ROOT-PATH : ROOT-URI} (require :test.util))
(local dispatch (require :fennel-ls.dispatch)) (local dispatch (require :fennel-ls.dispatch))
@ -21,7 +21,7 @@
(describe "language server" (describe "language server"
(it "responds to initialize" (it "responds to initialize"
(assert-matches (is-matching
(dispatch.handle* [] server-initialize-message) (dispatch.handle* [] server-initialize-message)
[{:jsonrpc "2.0" :id 1 [{:jsonrpc "2.0" :id 1
:result {:capabilities {} :result {:capabilities {}

View File

@ -16,7 +16,7 @@
(fn [] ,...))) (fn [] ,...)))
(fn assert-matches [item pattern ?msg] (fn is-matching [item pattern ?msg]
"check if item matches a pattern according to fennel's `match` builtin" "check if item matches a pattern according to fennel's `match` builtin"
`(match ,item `(match ,item
,pattern nil ,pattern nil
@ -31,5 +31,5 @@
{: it {: it
: describe : describe
: assert-matches : is-matching
: before-each} : before-each}

22
test/misc-test.fnl Normal file
View File

@ -0,0 +1,22 @@
(import-macros {: is-matching : describe : it : before-each} :test.macros)
(local is (require :luassert))
(local fennel (require :fennel))
(local {: multi-sym-split} (require :fennel-ls.util))
(describe "multi-sym-split"
(it "should be 1 on regular syms"
(is.same ["foo"] (multi-sym-split "foo" 2)))
(it "should be 1 before the :"
(is.same ["foo"] (multi-sym-split "foo:bar" 3)))
(it "should be 2 at the :"
(is.same ["foo" "bar"] (multi-sym-split "foo:bar" 4)))
(it "should be 2 after the :"
(is.same ["is" "equal"] (multi-sym-split "is.equal" 5)))
(it "should be big"
(is.same ["a" "b" "c" "d" "e" "f"] (multi-sym-split "a.b.c.d.e.f"))
(is.same ["obj" "bar"] (multi-sym-split (fennel.sym "obj.bar")))))

View File

@ -1,10 +1,10 @@
(import-macros {: assert-matches : describe : it} :test.macros) (import-macros {: is-matching : describe : it} :test.macros)
(local assert (require :luassert)) (local is (require :luassert))
(local fennel (require :fennel)) (local fennel (require :fennel))
(local util (require :fennel-ls.util)) (local util (require :fennel-ls.util))
(describe "document" (describe "util"
;; fixme: ;; fixme:
;; test for errors on out of bounds ;; test for errors on out of bounds
@ -17,7 +17,7 @@
;; (document.replace my-document 0 0 0 0 "どれみふぁそらてぃど") ;; (document.replace my-document 0 0 0 0 "どれみふぁそらてぃど")
;; (document.replace my-document 0 1 0 3 "😀") ;; (document.replace my-document 0 1 0 3 "😀")
;; (document.replace my-document 0 11 0 11 "end") ;; (document.replace my-document 0 11 0 11 "end")
;; (assert-matches my-document {:text "ど😀ふぁそらてぃどend"}))) ;; (is-matching my-document {:text "ど😀ふぁそらてぃどend"})))
(describe "apply-changes" (describe "apply-changes"
@ -26,7 +26,7 @@
:end {:line end-line :character end-col}}) :end {:line end-line :character end-col}})
(it "updates the start of a line" (it "updates the start of a line"
(assert.equal (is.equal
(util.apply-changes (util.apply-changes
"replace beginning" "replace beginning"
[{:range (range 0 0 0 7) [{:range (range 0 0 0 7)
@ -34,7 +34,7 @@
"the beginning")) "the beginning"))
(it "updates the end of a line" (it "updates the end of a line"
(assert.equal (is.equal
(util.apply-changes (util.apply-changes
"first line\nsecond line\nreplace end" "first line\nsecond line\nreplace end"
[{:range (range 2 7 2 11) [{:range (range 2 7 2 11)
@ -42,7 +42,7 @@
"first line\nsecond line\nreplacement")) "first line\nsecond line\nreplacement"))
(it "replaces a line" (it "replaces a line"
(assert.equal (is.equal
(util.apply-changes (util.apply-changes
"replace all" "replace all"
[{:range (range 0 0 0 11) [{:range (range 0 0 0 11)
@ -50,7 +50,7 @@
"new string")) "new string"))
(it "can handle substituting things" (it "can handle substituting things"
(assert.equal (is.equal
(util.apply-changes (util.apply-changes
"replace beginning" "replace beginning"
[{:range {:start {:line 0 :character 0} [{:range {:start {:line 0 :character 0}
@ -59,7 +59,7 @@
"the beginning")) "the beginning"))
(it "can handle replacing everything" (it "can handle replacing everything"
(assert.equal (is.equal
(util.apply-changes (util.apply-changes
"this is the\nold file" "this is the\nold file"
[{:text "And this is the\nnew file"}]) [{:text "And this is the\nnew file"}])

View File

@ -21,4 +21,10 @@
[0 b] b)) [0 b] b))
(print foo)) (print foo))
{: bar} (fn b []
(print "function"))
(local obj {: bar :a b})
(obj.bar 2 3)
(obj:a)