replace completions with a completely new completions.fnl implementation
the tests are failing now, because it's generating completions differently, with no documentation or completion types, but it seems like its working well.
This commit is contained in:
parent
5017177b44
commit
1e3edf800d
@ -206,7 +206,7 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
|
||||
|
||||
(λ search-name-and-scope [server file name scope ?opts]
|
||||
"find a definition just from the name of the item, and the scope it is in"
|
||||
(assert (= (type name) :string))
|
||||
(assert (= (type name) :string) "search-name-and-scope needs a string")
|
||||
(let [split (utils.multi-sym-split name)
|
||||
stack (stack-add-split! [] split)
|
||||
opts (or ?opts {})]
|
||||
@ -257,15 +257,15 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
|
||||
_ child (ipairs ast)
|
||||
&until result]
|
||||
(if (contains? child byte)
|
||||
(recurse child byte)))
|
||||
(recurse child byte)))
|
||||
(and (not (sym? ast)) (not (varg? ast)))
|
||||
(accumulate [(result _parent) nil
|
||||
key value (pairs ast)
|
||||
&until result]
|
||||
(if (contains? key byte)
|
||||
(recurse key byte)
|
||||
(contains? value byte)
|
||||
(recurse value byte)))))))
|
||||
(recurse key byte)
|
||||
(contains? value byte)
|
||||
(recurse value byte)))))))
|
||||
(values
|
||||
(accumulate [result nil _ top-level-form (ipairs ast) &until result]
|
||||
(if (contains? top-level-form byte)
|
||||
|
||||
96
src/fennel-ls/completions.fnl
Normal file
96
src/fennel-ls/completions.fnl
Normal file
@ -0,0 +1,96 @@
|
||||
(local files (require :fennel-ls.files))
|
||||
(local utils (require :fennel-ls.utils))
|
||||
(local analyzer (require :fennel-ls.analyzer))
|
||||
(local docs (require :fennel-ls.docs))
|
||||
(local fennel (require :fennel))
|
||||
(local message (require :fennel-ls.message))
|
||||
(local format (require :fennel-ls.formatter))
|
||||
|
||||
;; CompletionItemKind
|
||||
(local _kinds
|
||||
{:Text 1 :Method 2 :Function 3 :Constructor 4 :Field 5 :Variable 6 :Class 7
|
||||
:Interface 8 :Module 9 :Property 10 :Unit 11 :Value 12 :Enum 13 :Keyword 14
|
||||
:Snippet 15 :Color 16 :File 17 :Reference 18 :Folder 19 :EnumMember 20
|
||||
:Constant 21 :Struct 22 :Event 23 :Operator 24 :TypeParameter 25})
|
||||
|
||||
(λ textDocument/completion [server _send {: position :textDocument {: uri}}]
|
||||
;; get the file
|
||||
(let [file (files.get-by-uri server uri)
|
||||
;; find where the cursor is
|
||||
byte (utils.position->byte file.text position server.position-encoding)
|
||||
;; find what ast objects are under the cursor
|
||||
(?symbol parents) (analyzer.find-symbol file.ast byte)
|
||||
;; check what context I'm in
|
||||
in-call-position? (and (fennel.list? (. parents 1))
|
||||
(= ?symbol (. parents 1 1)))
|
||||
;; find the first one that contains a scope
|
||||
scope (or (accumulate [?find nil _ parent (ipairs parents) &until ?find]
|
||||
(. file.scopes parent))
|
||||
file.scope)
|
||||
range (if ?symbol (message.ast->range server file ?symbol) {:start position :end position})
|
||||
results []
|
||||
seen []]
|
||||
|
||||
(fn add-completion [definition name]
|
||||
"add the completion. also recursively adds the fields' completions"
|
||||
(when (not (. seen definition))
|
||||
(set (. seen definition) true)
|
||||
(table.insert results
|
||||
(doto (format.completion-item-format name definition)
|
||||
(tset :filterText name)
|
||||
(tset :textEdit {:newText name : range})))
|
||||
|
||||
(when (= (type definition.definition) :string)
|
||||
(each [key value (pairs (-> (docs.get-global server :string) (. :fields)))]
|
||||
(add-completion value (.. name ":" key))))
|
||||
(when (fennel.table? definition.definition)
|
||||
(each [field value (pairs definition.definition)]
|
||||
(when (= (type field) :string)
|
||||
(case (analyzer.search-ast server definition.file value [] {})
|
||||
def (do
|
||||
(when (or (?. def :metadata :fnl/arglist)
|
||||
(and (fennel.list? def.definition)
|
||||
(or (fennel.sym? (. def.definition 1) "fn"))
|
||||
(or (fennel.sym? (. def.definition 1) "λ"))))
|
||||
(add-completion def (.. name ":" field)))
|
||||
(add-completion def (.. name "." field)))
|
||||
_ (do
|
||||
(io.stderr:write "BAD!!!! undocumented field: " (tostring field) "\n")
|
||||
{:label field})))))
|
||||
(when definition.fields
|
||||
(each [field value (pairs definition.fields)]
|
||||
(when (= (type field) :string)
|
||||
(add-completion value (.. name "." field)))))
|
||||
|
||||
(set (. seen definition) false)))
|
||||
;; end yield
|
||||
|
||||
(each [_ global* (ipairs file.allowed-globals)]
|
||||
(case (analyzer.search-name-and-scope server file global* scope)
|
||||
def (add-completion def global*)
|
||||
_ (do
|
||||
(io.stderr:write "BAD!!!! undocumented global: " (tostring global*) "\n")
|
||||
{:label global*})))
|
||||
(var scope scope)
|
||||
(while scope
|
||||
(each [mangling (pairs scope.manglings)]
|
||||
(case (analyzer.search-name-and-scope server file mangling scope)
|
||||
def (add-completion def mangling)
|
||||
_ (do
|
||||
(io.stderr:write "BAD!!!! undocumented mangling: " (tostring mangling) "\n")
|
||||
{:label mangling})))
|
||||
(when in-call-position?
|
||||
(each [macro* (pairs scope.macros)]
|
||||
;; TODO make it work like the other ones
|
||||
(table.insert results {:label macro* :filterText macro* :textEdit {:newText macro* : range}}))
|
||||
|
||||
(each [special (pairs scope.specials)]
|
||||
(case (analyzer.search-name-and-scope server file special scope)
|
||||
def (add-completion def special)
|
||||
_ (do
|
||||
(io.stderr:write "BAD!!!! undocumented special: " (tostring special) "\n")
|
||||
{:label special}))))
|
||||
(set scope scope.parent))
|
||||
results))
|
||||
|
||||
{: textDocument/completion}
|
||||
@ -96,12 +96,7 @@ However, when not an option, fennel-ls will fall back to positionEncoding=\"utf-
|
||||
(set server.macro-modules {})
|
||||
(set server.root-uri params.rootUri)
|
||||
(set server.position-encoding (choose-position-encoding params))
|
||||
(reload server)
|
||||
;; Eglot does completions differently than every other client I've seen so
|
||||
;; far, in that it considers foo.bar to be one "symbol". If the user types
|
||||
;; `foo.b`, every other client accepts `bar` as a completion, bun eglot wants
|
||||
;; the full `foo.bar` symbol.
|
||||
(set server.EGLOT_COMPLETION_QUIRK_MODE (= (?. params :clientInfo :name) :Eglot)))
|
||||
(reload server))
|
||||
|
||||
(λ validate [{: configuration} invalid]
|
||||
(when (not= :string (type configuration.fennel-path))
|
||||
|
||||
@ -37,7 +37,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
;; :notebookDocumentSync nil
|
||||
:completionProvider {:workDoneProgress false
|
||||
:resolveProvider false
|
||||
:triggerCharacters ["(" "[" "{" "." ":" "\""]
|
||||
:triggerCharacters ["(" "[" "{"]
|
||||
:completionItem {:labelDetailsSupport false}}
|
||||
:hoverProvider {:workDoneProgress false}
|
||||
:signatureHelpProvider {:workDoneProgress false
|
||||
@ -163,135 +163,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
symbol)}
|
||||
(catch _ nil))))
|
||||
|
||||
;; CompletionItemKind
|
||||
(local kinds
|
||||
{:Text 1 :Method 2 :Function 3 :Constructor 4 :Field 5 :Variable 6 :Class 7
|
||||
:Interface 8 :Module 9 :Property 10 :Unit 11 :Value 12 :Enum 13 :Keyword 14
|
||||
:Snippet 15 :Color 16 :File 17 :Reference 18 :Folder 19 :EnumMember 20
|
||||
:Constant 21 :Struct 22 :Event 23 :Operator 24 :TypeParameter 25})
|
||||
|
||||
;; All of the helper functions for textDocument/completion are here until I
|
||||
;; finish refactoring them, and then they can find a home in analyzer.fnl
|
||||
(λ field-completion-helper [server ?result last-found-binding]
|
||||
(case ?result
|
||||
{: definition : file}
|
||||
(case (values definition (type definition))
|
||||
;; fields of a string are hardcoded to "string"
|
||||
(_str :string) (icollect [label info (pairs (. (docs.get-global server :string) :fields))]
|
||||
(formatter.completion-item-format label info))
|
||||
;; fields of a table
|
||||
(where (tbl :table) (fennel.table? tbl)) (let [keys []
|
||||
result []]
|
||||
(icollect [label _ (pairs tbl) &into keys]
|
||||
label)
|
||||
(when (?. last-found-binding 1 :fields)
|
||||
(icollect [label _ (pairs (. last-found-binding 1 :fields)) &into keys]
|
||||
label))
|
||||
(icollect [_ label (pairs keys) &into result]
|
||||
(if (= (type label) :string)
|
||||
(let [last-found-binding []]
|
||||
(case (analyzer.search-ast server file tbl [label] {:save-last-binding last-found-binding})
|
||||
def (do
|
||||
(icollect [_ completion (ipairs (or (field-completion-helper server def last-found-binding) [])) &into result]
|
||||
(do
|
||||
(set completion.filterText completion.label)
|
||||
(set completion.insertText (.. label "." completion.label))
|
||||
(set completion.label completion.insertText)
|
||||
completion))
|
||||
(formatter.completion-item-format label def))
|
||||
_ {: label :kind kinds.Field}))))
|
||||
result))
|
||||
{: metadata : fields}
|
||||
(let [_metadata metadata]
|
||||
(icollect [label info (pairs fields)]
|
||||
(formatter.completion-item-format label info)))
|
||||
_ nil))
|
||||
|
||||
(λ collect-scope [scope typ server file ?target ?default-kind]
|
||||
(let [result (or ?target [])]
|
||||
(var scope scope)
|
||||
(while scope
|
||||
(icollect [name (pairs (. scope typ)) &into result]
|
||||
(let [last-found-binding []
|
||||
item (case (analyzer.search-name-and-scope server file name scope {:save-last-binding last-found-binding})
|
||||
def (do
|
||||
(icollect [_ completion (ipairs (or (field-completion-helper server def last-found-binding) [])) &into result]
|
||||
(do
|
||||
(set completion.filterText completion.label)
|
||||
(set completion.insertText (.. name "." completion.label))
|
||||
(set completion.label completion.insertText)
|
||||
completion))
|
||||
(formatter.completion-item-format name def))
|
||||
_ {:label name})]
|
||||
(when (= nil item.kind)
|
||||
(set item.kind ?default-kind))
|
||||
item))
|
||||
(set scope scope.parent))
|
||||
result))
|
||||
|
||||
(λ scope-completion [server file _byte ?symbol parents]
|
||||
(let [scope (or (accumulate [result nil
|
||||
_ parent (ipairs parents)
|
||||
&until result]
|
||||
(. file.scopes parent))
|
||||
file.scope)
|
||||
?parent (. parents 1)
|
||||
result []
|
||||
in-call-position? (and (fennel.list? ?parent)
|
||||
(= ?symbol (. ?parent 1)))]
|
||||
(collect-scope scope :manglings server file result kinds.Variable)
|
||||
|
||||
(when in-call-position?
|
||||
(collect-scope scope :macros server file result kinds.Keyword)
|
||||
(collect-scope scope :specials server file result kinds.Operator))
|
||||
(icollect [_ k (ipairs file.allowed-globals) &into result]
|
||||
(let [last-found-binding []]
|
||||
(case (analyzer.search-name-and-scope server file k scope {:save-last-binding last-found-binding})
|
||||
def (do (icollect [_ completion (ipairs (or (field-completion-helper server def last-found-binding) [])) &into result]
|
||||
(do
|
||||
(set completion.filterText completion.label)
|
||||
(set completion.insertText (.. k "." completion.label))
|
||||
(set completion.label completion.insertText)
|
||||
completion))
|
||||
(formatter.completion-item-format k def))
|
||||
_ {:label k})))))
|
||||
|
||||
(λ field-completion [server file symbol split]
|
||||
(let [stack (fcollect [i (- (length split) 1) 2 -1]
|
||||
(. split i))
|
||||
last-found-binding []
|
||||
result (analyzer.search-main server file symbol {:save-last-binding last-found-binding} {: stack})]
|
||||
(field-completion-helper server result last-found-binding)))
|
||||
|
||||
(λ requests.textDocument/completion [server _send {: position :textDocument {: uri}}]
|
||||
(let [file (files.get-by-uri server uri)
|
||||
byte (utils.position->byte file.text position server.position-encoding)
|
||||
(?symbol parents) (analyzer.find-symbol file.ast byte)]
|
||||
(case (-?> ?symbol utils.multi-sym-split)
|
||||
|
||||
;; completion from current scope
|
||||
(where (or nil [_ nil]))
|
||||
(let [input-range (if ?symbol (message.multisym->range server file ?symbol -1) {:start position :end position})
|
||||
?completions (scope-completion server file byte ?symbol parents)]
|
||||
(if ?completions
|
||||
(let [?completions (utils.uniq-by ?completions #$.label)]
|
||||
(each [_ completion (ipairs ?completions)]
|
||||
(set completion.textEdit {:newText completion.label :range input-range}))
|
||||
?completions)))
|
||||
|
||||
;; completion from field
|
||||
[_a _b &as split]
|
||||
(let [input-range (message.multisym->range server file ?symbol -1)
|
||||
?completions (field-completion server file ?symbol split)]
|
||||
(if ?completions
|
||||
(if server.EGLOT_COMPLETION_QUIRK_MODE
|
||||
(let [prefix (string.gsub (tostring ?symbol) "[^.:]*$" "")]
|
||||
(each [_ completion (ipairs ?completions)]
|
||||
(set completion.filterText (.. prefix completion.label))
|
||||
(set completion.insertText (.. prefix completion.label))))
|
||||
(each [_ completion (ipairs ?completions)]
|
||||
(set completion.textEdit {:newText completion.label :range input-range}))))
|
||||
?completions))))
|
||||
(set requests.textDocument/completion (. (require :fennel-ls.completions) :textDocument/completion))
|
||||
|
||||
(λ requests.textDocument/rename [server _send {: position :textDocument {: uri} :newName new-name}]
|
||||
(let [file (files.get-by-uri server uri)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user