diff --git a/Project.toml b/Project.toml index ed972ded..c38227d6 100644 --- a/Project.toml +++ b/Project.toml @@ -28,7 +28,7 @@ JSON = "0.20, 0.21" julia = "1" CSTParser = "3.1" DocumentFormat = "3.2.2" -StaticLint = "6.0.1" +StaticLint = "7" Tokenize = "0.5.10" JSONRPC = "1.1" SymbolServer = "6" diff --git a/src/LanguageServer.jl b/src/LanguageServer.jl index a4838c4f..ca700fde 100644 --- a/src/LanguageServer.jl +++ b/src/LanguageServer.jl @@ -27,6 +27,7 @@ include("requests/completions.jl") include("requests/workspace.jl") include("requests/actions.jl") include("requests/init.jl") +include("requests/signatures.jl") include("utilities.jl") end diff --git a/src/requests/features.jl b/src/requests/features.jl index 5c046fc0..70966fb4 100644 --- a/src/requests/features.jl +++ b/src/requests/features.jl @@ -1,93 +1,4 @@ -function get_signatures(b, tls, sigs, server, visited=nothing) end # Fallback - -function get_signatures(b::StaticLint.Binding, tls::StaticLint.Scope, sigs::Vector{SignatureInformation}, server, visited=StaticLint.Binding[]) - if b in visited # TODO: remove - throw(LSInfiniteLoop("Possible infinite loop.")) # TODO: remove - else # TODO: remove - push!(visited, b) # TODO: remove - end # TODO: remove - if b.type == StaticLint.CoreTypes.Function && b.val isa EXPR && CSTParser.defines_function(b.val) - get_siginfo_from_call(b.val, sigs) - elseif b.val isa EXPR && CSTParser.defines_struct(b.val) - args = b.val.args[3] - if length(args) > 0 - inner_constructor_i = findfirst(a -> CSTParser.defines_function(a), args.args) - if inner_constructor_i !== nothing - get_siginfo_from_call(args.args[inner_constructor_i], sigs) - else - params = ParameterInformation[] - for field in args.args - field_name = CSTParser.rem_decl(field) - push!(params, ParameterInformation(field_name isa EXPR && CSTParser.isidentifier(field_name) ? valof(field_name) : "", missing)) - end - push!(sigs, SignatureInformation(string(Expr(b.val)), "", params)) - end - end - return - elseif b.val isa SymbolServer.SymStore - return get_signatures(b.val, tls, sigs, server) - else - return - end - - get_signatures(b.prev, tls, sigs, server, visited) -end - -function get_signatures(b::T, tls::StaticLint.Scope, sigs::Vector{SignatureInformation}, server, visited=nothing) where T <: Union{SymbolServer.FunctionStore,SymbolServer.DataTypeStore} - StaticLint.iterate_over_ss_methods(b, tls, server, function (m) - push!(sigs, SignatureInformation(string(m), "", (a -> ParameterInformation(string(a[1]), string(a[2]))).(m.sig))) - return false - end) -end - - -function get_siginfo_from_call(call, sigs) end # Fallback - -function get_siginfo_from_call(call::EXPR, sigs) - sig = CSTParser.rem_where_decl(CSTParser.get_sig(call)) - params = ParameterInformation[] - if sig isa EXPR && sig.args !== nothing - for i = 2:length(sig.args) - if (argbinding = bindingof(sig.args[i])) !== nothing - push!(params, ParameterInformation(valof(argbinding.name) isa String ? valof(argbinding.name) : "", missing)) - end - end - push!(sigs, SignatureInformation(string(Expr(sig)), "", params)) - end -end - -function textDocument_signatureHelp_request(params::TextDocumentPositionParams, server::LanguageServerInstance, conn) - doc = getdocument(server, URI2(params.textDocument.uri)) - sigs = SignatureInformation[] - offset = get_offset(doc, params.position) - rng = Range(doc, offset:offset) - x = get_expr(getcst(doc), offset) - arg = 0 - if x isa EXPR && parentof(x) isa EXPR && CSTParser.iscall(parentof(x)) - if CSTParser.isidentifier(parentof(x).args[1]) - call_name = parentof(x).args[1] - elseif CSTParser.iscurly(parentof(x).args[1]) && CSTParser.isidentifier(parentof(x).args[1].args[1]) - call_name = parentof(x).args[1].args[1] - elseif CSTParser.is_getfield_w_quotenode(parentof(x).args[1]) - call_name = parentof(x).args[1].args[2].args[1] - else - call_name = nothing - end - if call_name !== nothing && (f_binding = refof(call_name)) !== nothing && (tls = StaticLint.retrieve_toplevel_scope(call_name)) !== nothing - get_signatures(f_binding, tls, sigs, server) - end - end - if (isempty(sigs) || (headof(x) === :RPAREN)) - return SignatureHelp(SignatureInformation[], 0, 0) - end - if headof(x) === :LPAREN - arg = 0 - else - arg = sum(headof(a) === :COMMA for a in parentof(x).trivia) - end - return SignatureHelp(filter(s -> length(s.parameters) > arg, sigs), 0, arg) -end # TODO: should be in StaticLint. visited check is costly. resolve_shadow_binding(b) = b @@ -104,15 +15,15 @@ function resolve_shadow_binding(b::StaticLint.Binding, visited=StaticLint.Bindin end end -function get_definitions(x, tls, server, locations, visited=nothing) end # Fallback +function get_definitions(x, tls, server, locations) end # Fallback -function get_definitions(x::SymbolServer.ModuleStore, tls, server, locations, visited=nothing) +function get_definitions(x::SymbolServer.ModuleStore, tls, server, locations) if haskey(x.vals, :eval) && x[:eval] isa SymbolServer.FunctionStore - get_definitions(x[:eval], tls, server, locations, visited) + get_definitions(x[:eval], tls, server, locations) end end -function get_definitions(x::T, tls, server, locations, visited=nothing) where T <: Union{SymbolServer.FunctionStore,SymbolServer.DataTypeStore} +function get_definitions(x::Union{SymbolServer.FunctionStore,SymbolServer.DataTypeStore}, tls, server, locations) StaticLint.iterate_over_ss_methods(x, tls, server, function (m) try if isfile(m.file) @@ -125,23 +36,26 @@ function get_definitions(x::T, tls, server, locations, visited=nothing) where T end) end -function get_definitions(b::StaticLint.Binding, tls, server, locations, visited=StaticLint.Binding[]) - if b in visited # TODO: remove - throw(LSInfiniteLoop("Possible infinite loop.")) # TODO: remove - else # TODO: remove - push!(visited, b) # TODO: remove - end # TODO: remove - +function get_definitions(b::StaticLint.Binding, tls, server, locations) if !(b.val isa EXPR) - return get_definitions(b.val, tls, server, locations, visited) + get_definitions(b.val, tls, server, locations) end - doc1, o = get_file_loc(b.val) - if doc1 isa Document - push!(locations, Location(doc1._uri, Range(doc1, o .+ (0:b.val.span)))) + if b.type === StaticLint.CoreTypes.Function || b.type === StaticLint.CoreTypes.DataType + for ref in b.refs + method = StaticLint.get_method(ref) + if method !== nothing + get_definitions(method, tls, server, locations) + end + end + elseif b.val isa EXPR + get_definitions(b.val, tls, server, locations) end +end - if b.type === StaticLint.CoreTypes.Function && b.prev isa StaticLint.Binding && (b.prev.type === StaticLint.CoreTypes.Function || b.prev.type === StaticLint.CoreTypes.DataType) - return get_definitions(b.prev, tls, server, locations, visited) +function get_definitions(x::EXPR, tls::StaticLint.Scope, server, locations) + doc1, o = get_file_loc(x) + if doc1 isa Document + push!(locations, Location(doc1._uri, Range(doc1, o .+ (0:x.span)))) end end @@ -208,31 +122,18 @@ function find_references(textDocument::TextDocumentIdentifier, position::Positio offset = get_offset(doc, position) x = get_expr1(getcst(doc), offset) if x isa EXPR && StaticLint.hasref(x) && refof(x) isa StaticLint.Binding - refs = find_references(refof(x)) - for r in refs - doc1, o = get_file_loc(r) - if doc1 isa Document - push!(locations, Location(doc1._uri, Range(doc1, o .+ (0:r.span)))) + for r in refof(x).refs + if r isa EXPR + doc1, o = get_file_loc(r) + if doc1 isa Document + push!(locations, Location(doc1._uri, Range(doc1, o .+ (0:r.span)))) + end end end end return locations end -function find_references(b::StaticLint.Binding, refs=EXPR[], from_end=false) - if !from_end && (b.type === StaticLint.CoreTypes.Function || b.type === StaticLint.CoreTypes.DataType) - b = StaticLint.last_method(b) - end - for r in b.refs - r isa EXPR && push!(refs, r) - end - if b.prev isa StaticLint.Binding && (b.prev.type === StaticLint.CoreTypes.Function || b.prev.type === StaticLint.CoreTypes.DataType) - return find_references(b.prev, refs, true) - else - return refs - end -end - function textDocument_references_request(params::ReferenceParams, server::LanguageServerInstance, conn) return find_references(params.textDocument, params.position, server) end diff --git a/src/requests/hover.jl b/src/requests/hover.jl index 8b7cd32e..bfeb1a8f 100644 --- a/src/requests/hover.jl +++ b/src/requests/hover.jl @@ -27,9 +27,24 @@ function get_hover(x::EXPR, documentation::String, server) end function get_hover(b::StaticLint.Binding, documentation::String, server) - if b.val isa EXPR + if b.val isa StaticLint.Binding + documentation = get_hover(b.val, documentation, server) + elseif b.val isa EXPR if CSTParser.defines_function(b.val) || CSTParser.defines_datatype(b.val) documentation = get_func_hover(b, documentation, server) + for r in b.refs + method = StaticLint.get_method(r) + if method isa EXPR + documentation = get_preceding_docs(method, documentation) + if CSTParser.defines_function(method) + documentation = string(ensure_ends_with(documentation), "```julia\n", Expr(CSTParser.get_sig(method)), "\n```\n") + elseif CSTParser.defines_datatype(method) + documentation = string(ensure_ends_with(documentation), "```julia\n", Expr(method), "\n```\n") + end + elseif method isa SymbolServer.SymStore + documentation = get_hover(method, documentation, server) + end + end else try documentation = if binding_has_preceding_docs(b) @@ -42,13 +57,11 @@ function get_hover(b::StaticLint.Binding, documentation::String, server) documentation = string(documentation, "```julia\n", Expr(b.val), "\n```\n") catch err doc1, offset1 = get_file_loc(b.val) - throw(LSHoverError(string("get_hover failed to convert the following to coode: ", String(codeunits(get_text(doc1))[offset1 .+ (1:b.val.span)])))) + throw(LSHoverError(string("get_hover failed to convert the following to code: ", String(codeunits(get_text(doc1))[offset1 .+ (1:b.val.span)])))) end end elseif b.val isa SymbolServer.SymStore documentation = get_hover(b.val, documentation, server) - elseif b.val isa StaticLint.Binding - documentation = get_hover(b.val, documentation, server) end return documentation end @@ -93,38 +106,17 @@ function get_hover(f::SymbolServer.FunctionStore, documentation::String, server) end -get_func_hover(x, documentation, server, visited=nothing) = documentation -get_func_hover(x::SymbolServer.SymStore, documentation, server, visited=nothing) = get_hover(x, documentation, server) +get_func_hover(x, documentation, server) = documentation +get_func_hover(x::SymbolServer.SymStore, documentation, server) = get_hover(x, documentation, server) -function get_func_hover(b::StaticLint.Binding, documentation, server, visited=StaticLint.Binding[]) - if b in visited # TODO: remove - # throw(LSInfiniteLoop("Possible infinite loop.")) # TODO: remove - # There is a cycle in the links between Bindings. Root cause is in StaticLint but there is no reason to allow it to crash the language server. If we have done a complete circuit here then we have all the information we need and can return. - return documentation - else # TODO: remove - push!(visited, b) # TODO: remove - end # TODO: remove - if b.val isa EXPR - documentation = if binding_has_preceding_docs(b) - # Binding has preceding docs so use them.. - string(documentation, Expr(parentof(b.val).args[3])) - elseif const_binding_has_preceding_docs(b) - string(documentation, Expr(parentof(parentof(b.val)).args[3])) - else - documentation - end - if CSTParser.defines_function(b.val) - documentation = string(ensure_ends_with(documentation), "```julia\n", Expr(CSTParser.get_sig(b.val)), "\n```\n") - elseif CSTParser.defines_datatype(b.val) - documentation = string(ensure_ends_with(documentation), "```julia\n", Expr(b.val), "\n```\n") - end - elseif b.val isa SymbolServer.SymStore - return get_hover(b.val, documentation, server) - end - if b.prev isa StaticLint.Binding && (b.prev.type == StaticLint.CoreTypes.Function || b.prev.type == StaticLint.CoreTypes.DataType || b.prev.val isa Union{SymbolServer.FunctionStore,SymbolServer.DataTypeStore}) || (b.prev isa SymbolServer.FunctionStore || b.prev isa SymbolServer.DataTypeStore) - return get_func_hover(b.prev, documentation, server, visited) +function get_preceding_docs(expr::EXPR, documentation) + if expr_has_preceding_docs(expr) + string(documentation, Expr(parentof(expr).args[3])) + elseif is_const_expr(parentof(expr)) && expr_has_preceding_docs(parentof(expr)) + string(documentation, Expr(parentof(parentof(expr)).args[3])) + else + documentation end - return documentation end ensure_ends_with(s, c = "\n") = endswith(s, c) ? s : string(s, c) diff --git a/src/requests/signatures.jl b/src/requests/signatures.jl new file mode 100644 index 00000000..17e19ac1 --- /dev/null +++ b/src/requests/signatures.jl @@ -0,0 +1,80 @@ +function textDocument_signatureHelp_request(params::TextDocumentPositionParams, server::LanguageServerInstance, conn) + doc = getdocument(server, URI2(params.textDocument.uri)) + sigs = SignatureInformation[] + offset = get_offset(doc, params.position) + x = get_expr(getcst(doc), offset) + arg = 0 + if x isa EXPR && parentof(x) isa EXPR && CSTParser.iscall(parentof(x)) + if CSTParser.isidentifier(parentof(x).args[1]) + call_name = parentof(x).args[1] + elseif CSTParser.iscurly(parentof(x).args[1]) && CSTParser.isidentifier(parentof(x).args[1].args[1]) + call_name = parentof(x).args[1].args[1] + elseif CSTParser.is_getfield_w_quotenode(parentof(x).args[1]) + call_name = parentof(x).args[1].args[2].args[1] + else + call_name = nothing + end + if call_name !== nothing && (f_binding = refof(call_name)) !== nothing && (tls = StaticLint.retrieve_toplevel_scope(call_name)) !== nothing + get_signatures(f_binding, tls, sigs, server) + end + end + if (isempty(sigs) || (headof(x) === :RPAREN)) + return SignatureHelp(SignatureInformation[], 0, 0) + end + + if headof(x) === :LPAREN + arg = 0 + else + arg = sum(headof(a) === :COMMA for a in parentof(x).trivia) + end + return SignatureHelp(filter(s -> length(s.parameters) > arg, sigs), 0, arg) +end + +function get_signatures(b::StaticLint.Binding, tls::StaticLint.Scope, sigs::Vector{SignatureInformation}, server) + if b.val isa StaticLint.Binding + get_signatures(b.val, tls, sigs, server) + end + if b.type == StaticLint.CoreTypes.Function || b.type == StaticLint.CoreTypes.DataType + b.val isa SymbolServer.SymStore && get_signatures(b.val, tls, sigs, server) + for ref in b.refs + method = StaticLint.get_method(ref) + if method !== nothing + get_signatures(method, tls, sigs, server) + end + end + end +end + +function get_signatures(b::T, tls::StaticLint.Scope, sigs::Vector{SignatureInformation}, server) where T <: Union{SymbolServer.FunctionStore,SymbolServer.DataTypeStore} + StaticLint.iterate_over_ss_methods(b, tls, server, function (m) + push!(sigs, SignatureInformation(string(m), "", (a -> ParameterInformation(string(a[1]), string(a[2]))).(m.sig))) + return false + end) +end + +function get_signatures(x::EXPR, tls::StaticLint.Scope, sigs::Vector{SignatureInformation}, server) + if CSTParser.defines_function(x) + sig = CSTParser.rem_where_decl(CSTParser.get_sig(x)) + params = ParameterInformation[] + if sig isa EXPR && sig.args !== nothing + for i = 2:length(sig.args) + if (argbinding = bindingof(sig.args[i])) !== nothing + push!(params, ParameterInformation(valof(argbinding.name) isa String ? valof(argbinding.name) : "", missing)) + end + end + push!(sigs, SignatureInformation(string(Expr(sig)), "", params)) + end + elseif CSTParser.defines_struct(x) + args = x.args[3] + if length(args) > 0 + if !any(CSTParser.defines_function, args.args) + params = ParameterInformation[] + for field in args.args + field_name = CSTParser.rem_decl(field) + push!(params, ParameterInformation(field_name isa EXPR && CSTParser.isidentifier(field_name) ? valof(field_name) : "", missing)) + end + push!(sigs, SignatureInformation(string(Expr(x)), "", params)) + end + end + end +end