First attempt at code actions, no tests yet
This commit is contained in:
parent
ca2dbf9237
commit
7c81ffcbb3
@ -10,6 +10,7 @@
|
|||||||
:method "initialize"
|
:method "initialize"
|
||||||
:params {:capabilities {:general {:positionEncodings [:utf-8]}}
|
:params {:capabilities {:general {:positionEncodings [:utf-8]}}
|
||||||
:clientInfo {:name "fennel-ls"}
|
:clientInfo {:name "fennel-ls"}
|
||||||
|
; " don't think this is a valid URI, but I want to operate in the current directory
|
||||||
:rootUri "file://."}}))]
|
:rootUri "file://."}}))]
|
||||||
(var should-err? false)
|
(var should-err? false)
|
||||||
(each [_ filename (ipairs filenames)]
|
(each [_ filename (ipairs filenames)]
|
||||||
|
|||||||
@ -9,6 +9,10 @@ the `file.diagnostics` field, filling it with diagnostics."
|
|||||||
(local {:scopes {:global {: specials}}}
|
(local {:scopes {:global {: specials}}}
|
||||||
(require :fennel.compiler))
|
(require :fennel.compiler))
|
||||||
|
|
||||||
|
(local diagnostic-mt {:__json_exclude_keys {:quickfix true}})
|
||||||
|
(fn diagnostic [self]
|
||||||
|
(setmetatable self diagnostic-mt))
|
||||||
|
|
||||||
(local ops {"+" 1 "-" 1 "*" 1 "/" 1 "//" 1 "%" 1 "^" 1 ">" 1 "<" 1 ">=" 1 "<=" 1 "=" 1 "not=" 1 ".." 1 "." 1 "and" 1 "or" 1 "band" 1 "bor" 1 "bxor" 1 "bnot" 1 "lshift" 1 "rshift" 1})
|
(local ops {"+" 1 "-" 1 "*" 1 "/" 1 "//" 1 "%" 1 "^" 1 ">" 1 "<" 1 ">=" 1 "<=" 1 "=" 1 "not=" 1 ".." 1 "." 1 "and" 1 "or" 1 "band" 1 "bor" 1 "bxor" 1 "bnot" 1 "lshift" 1 "rshift" 1})
|
||||||
(fn special? [item]
|
(fn special? [item]
|
||||||
(and (sym? item)
|
(and (sym? item)
|
||||||
@ -28,11 +32,14 @@ the `file.diagnostics` field, filling it with diagnostics."
|
|||||||
&until reference]
|
&until reference]
|
||||||
(or (= ref.ref-type :read)
|
(or (= ref.ref-type :read)
|
||||||
(= ref.ref-type :mutate)))))
|
(= ref.ref-type :mutate)))))
|
||||||
{:range (message.ast->range self file symbol)
|
(diagnostic
|
||||||
:message (.. "unused definition: " (tostring symbol))
|
{:range (message.ast->range self file symbol)
|
||||||
:severity message.severity.WARN
|
:message (.. "unused definition: " (tostring symbol))
|
||||||
:code 301
|
:severity message.severity.WARN
|
||||||
:codeDescription "unused-definition"}))
|
:code 301
|
||||||
|
:codeDescription "unused-definition"
|
||||||
|
:quickfix #[{:range (message.ast->range symbol)
|
||||||
|
:newText (.. "_" (tostring symbol))}]})))
|
||||||
|
|
||||||
(λ unknown-module-field [self file]
|
(λ unknown-module-field [self file]
|
||||||
"any multisym whose definition can't be found through a (require) call"
|
"any multisym whose definition can't be found through a (require) call"
|
||||||
@ -73,15 +80,21 @@ the `file.diagnostics` field, filling it with diagnostics."
|
|||||||
(sym? (. last-item 1) :table.unpack))
|
(sym? (. last-item 1) :table.unpack))
|
||||||
(. file.lexical last-item)
|
(. file.lexical last-item)
|
||||||
(. file.lexical call))
|
(. file.lexical call))
|
||||||
{:range (message.ast->range self file last-item)
|
(diagnostic
|
||||||
:message (.. "faulty unpack call: " (tostring op) " isn't variadic at runtime."
|
{:range (message.ast->range self file last-item)
|
||||||
(if (sym? op "..")
|
:message (.. "faulty unpack call: " (tostring op) " isn't variadic at runtime."
|
||||||
(let [unpackme (view (. last-item 2))]
|
(if (sym? op "..")
|
||||||
(.. " Use (table.concat " unpackme ") instead of (.. (unpack " unpackme "))"))
|
(let [unpackme (view (. last-item 2))]
|
||||||
(.. " Use a loop when you have a dynamic number of arguments to (" (tostring op) ")")))
|
(.. " Use (table.concat " unpackme ") instead of (.. (unpack " unpackme "))"))
|
||||||
:severity message.severity.WARN
|
(.. " Use a loop when you have a dynamic number of arguments to (" (tostring op) ")")))
|
||||||
:code 304
|
:severity message.severity.WARN
|
||||||
:codeDescription "bad-unpack"})))
|
:code 304
|
||||||
|
:codeDescription "bad-unpack"
|
||||||
|
:quickfix (if (and (= (length call) 2)
|
||||||
|
(= (length (. call 2)) 2)
|
||||||
|
(sym? op ".."))
|
||||||
|
#[{:range (message.ast->range self file call)
|
||||||
|
:newText (.. "(table.concat " (view (. call 2 2)) ")")}])}))))
|
||||||
|
|
||||||
(λ var-never-set [self file symbol definition]
|
(λ var-never-set [self file symbol definition]
|
||||||
(if (and definition.var? (not definition.var-set) (. file.lexical symbol))
|
(if (and definition.var? (not definition.var-set) (. file.lexical symbol))
|
||||||
@ -94,15 +107,23 @@ the `file.diagnostics` field, filling it with diagnostics."
|
|||||||
(local op-identity-value {:+ 0 :* 1 :and true :or false :band -1 :bor 0 :.. ""})
|
(local op-identity-value {:+ 0 :* 1 :and true :or false :band -1 :bor 0 :.. ""})
|
||||||
(λ op-with-no-arguments [self file op call]
|
(λ op-with-no-arguments [self file op call]
|
||||||
"A call like (+) that could be replaced with a literal"
|
"A call like (+) that could be replaced with a literal"
|
||||||
(if (and (op? op)
|
(let [identity (. op-identity-value (tostring op))]
|
||||||
(not (. call 2))
|
(if (and (op? op)
|
||||||
(. file.lexical call)
|
(not (. call 2))
|
||||||
(not= nil (. op-identity-value (tostring op))))
|
(. file.lexical call)
|
||||||
{:range (message.ast->range self file call)
|
(not= nil identity))
|
||||||
:message (.. "write " (view (. op-identity-value (tostring op))) " instead of (" (tostring op) ")")
|
(diagnostic
|
||||||
:severity message.severity.WARN
|
{:range (message.ast->range self file call)
|
||||||
:code 306
|
:message (.. "write " (view identity) " instead of (" (tostring op) ")")
|
||||||
:codeDescription "op-with-no-arguments"}))
|
:severity message.severity.WARN
|
||||||
|
:code 306
|
||||||
|
:codeDescription "op-with-no-arguments"
|
||||||
|
:quickfix #[{:range (message.ast->range self file call)
|
||||||
|
:newText (view identity)}]}))))
|
||||||
|
|
||||||
|
; (fn quickfix.op-with-no-arguments [item]
|
||||||
|
; {:range item.range
|
||||||
|
; :newText (tostring item.data)})
|
||||||
|
|
||||||
(λ multival-in-middle-of-call [self file fun call arg index]
|
(λ multival-in-middle-of-call [self file fun call arg index]
|
||||||
"generally, values and unpack are signs that the user is trying to do
|
"generally, values and unpack are signs that the user is trying to do
|
||||||
|
|||||||
@ -11,7 +11,6 @@ Every time the client sends a message, it gets handled by a function in the corr
|
|||||||
(local language (require :fennel-ls.language))
|
(local language (require :fennel-ls.language))
|
||||||
(local formatter (require :fennel-ls.formatter))
|
(local formatter (require :fennel-ls.formatter))
|
||||||
(local utils (require :fennel-ls.utils))
|
(local utils (require :fennel-ls.utils))
|
||||||
|
|
||||||
(local fennel (require :fennel))
|
(local fennel (require :fennel))
|
||||||
|
|
||||||
(local requests [])
|
(local requests [])
|
||||||
@ -33,7 +32,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
|||||||
:referencesProvider {:workDoneProgress false}
|
:referencesProvider {:workDoneProgress false}
|
||||||
;; :documentHighlightProvider nil
|
;; :documentHighlightProvider nil
|
||||||
;; :documentSymbolProvider nil
|
;; :documentSymbolProvider nil
|
||||||
;; :codeActionProvider nil
|
:codeActionProvider {:workDoneProgress false}
|
||||||
;; :codeLensProvider nil
|
;; :codeLensProvider nil
|
||||||
;; :documentLinkProvider nil
|
;; :documentLinkProvider nil
|
||||||
;; :colorProvider nil
|
;; :colorProvider nil
|
||||||
@ -229,6 +228,23 @@ Every time the client sends a message, it gets handled by a function in the corr
|
|||||||
{:changes {definition.file.uri usages}})
|
{:changes {definition.file.uri usages}})
|
||||||
(catch _ nil))))
|
(catch _ nil))))
|
||||||
|
|
||||||
|
(fn pos<= [pos-1 pos-2]
|
||||||
|
(or (< pos-1.line pos-2.line)
|
||||||
|
(and (= pos-1.line pos-2.line)
|
||||||
|
(<= pos-1.character pos-2.character))))
|
||||||
|
|
||||||
|
(fn overlap? [range-1 range-2]
|
||||||
|
(and (pos<= range-1.start range-2.end)
|
||||||
|
(pos<= range-2.start range-1.end)))
|
||||||
|
|
||||||
|
(λ requests.textDocument/codeAction [self send {: range :textDocument {: uri} &as params}]
|
||||||
|
(let [file (state.get-by-uri self uri)]
|
||||||
|
(icollect [_ diagnostic (ipairs file.diagnostics)]
|
||||||
|
(if (and (overlap? diagnostic.range range)
|
||||||
|
diagnostic.quickfix)
|
||||||
|
{:title diagnostic.codeDescription
|
||||||
|
:edit {:changes {uri (diagnostic.quickfix)}}}))))
|
||||||
|
|
||||||
(λ 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))
|
||||||
(state.set-uri-contents self uri (utils.apply-changes file.text contentChanges self.position-encoding))
|
(state.set-uri-contents self uri (utils.apply-changes file.text contentChanges self.position-encoding))
|
||||||
@ -243,12 +259,12 @@ Every time the client sends a message, it gets handled by a function in the corr
|
|||||||
|
|
||||||
(λ notifications.textDocument/didSave [self send {:textDocument {: uri}}]
|
(λ notifications.textDocument/didSave [self send {:textDocument {: uri}}]
|
||||||
;; TODO be careful about which modules need to be recomputed, and also eagerly flush existing files
|
;; TODO be careful about which modules need to be recomputed, and also eagerly flush existing files
|
||||||
(tset (require :fennel) :macro-loaded []))
|
(set fennel.macro-loaded []))
|
||||||
|
|
||||||
(λ notifications.textDocument/didClose [self send {:textDocument {: uri}}]
|
(λ notifications.textDocument/didClose [self send {:textDocument {: uri}}]
|
||||||
(local file (state.get-by-uri self uri))
|
(local file (state.get-by-uri self uri))
|
||||||
(set file.open? false)
|
(set file.open? false)
|
||||||
(tset (require :fennel) :macro-loaded [])
|
(set fennel.macro-loaded [])
|
||||||
;; TODO only reload from disk if we didn't get a didSave, instead of always
|
;; TODO only reload from disk if we didn't get a didSave, instead of always
|
||||||
(state.flush-uri self uri))
|
(state.flush-uri self uri))
|
||||||
|
|
||||||
|
|||||||
15
test/code-action.fnl
Normal file
15
test/code-action.fnl
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
(local _faith (require :faith))
|
||||||
|
(local {: view} (require :fennel))
|
||||||
|
(local {: create-client-with-files} (require :test.utils))
|
||||||
|
|
||||||
|
(fn check [file-contents]
|
||||||
|
(let [{: self : uri :locations [range]} (create-client-with-files file-contents)
|
||||||
|
response (self:code-action uri range.range)]
|
||||||
|
;; (print "hello" (view response))))
|
||||||
|
nil))
|
||||||
|
|
||||||
|
(fn test-thing []
|
||||||
|
(check "(+====)"
|
||||||
|
"op-with-no-arguments"))
|
||||||
|
|
||||||
|
{: test-thing}
|
||||||
@ -21,5 +21,6 @@
|
|||||||
:test.completion
|
:test.completion
|
||||||
:test.references
|
:test.references
|
||||||
:test.diagnostic
|
:test.diagnostic
|
||||||
|
:test.code-action
|
||||||
:test.rename
|
:test.rename
|
||||||
:test.misc])
|
:test.misc])
|
||||||
|
|||||||
@ -86,6 +86,13 @@
|
|||||||
:textDocument {:uri file}
|
:textDocument {:uri file}
|
||||||
: newName})))
|
: newName})))
|
||||||
|
|
||||||
|
(fn code-action [self file range]
|
||||||
|
(dispatch.handle* self.server
|
||||||
|
(message.create-request (next-id! self) :textDocument/codeAction
|
||||||
|
{: range
|
||||||
|
:textDocument {:uri file}
|
||||||
|
:context {:diagnostics []}})))
|
||||||
|
|
||||||
(set mt.__index
|
(set mt.__index
|
||||||
{: open-file!
|
{: open-file!
|
||||||
: pretend-this-file-exists!
|
: pretend-this-file-exists!
|
||||||
@ -93,7 +100,8 @@
|
|||||||
: definition
|
: definition
|
||||||
: hover
|
: hover
|
||||||
: references
|
: references
|
||||||
: rename})
|
: rename
|
||||||
|
: code-action})
|
||||||
|
|
||||||
{: create-client
|
{: create-client
|
||||||
: default-encoding
|
: default-encoding
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user