Skip to content

Commit c9c060e

Browse files
authored
Merge pull request #1077 from julia-vscode/sp/inlay-hints
Initial InlayHint provider implementation
2 parents d327f5a + 7fb2791 commit c9c060e

File tree

10 files changed

+218
-20
lines changed

10 files changed

+218
-20
lines changed

src/languageserverinstance.jl

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ mutable struct LanguageServerInstance
4343
lint_missingrefs::Symbol
4444
lint_disableddirs::Vector{String}
4545
completion_mode::Symbol
46+
inlay_hints::Bool
47+
inlay_hints_variable_types::Bool
48+
inlay_hints_parameter_names::Symbol
4649

4750
combined_msg_queue::Channel{Any}
4851

@@ -73,7 +76,7 @@ mutable struct LanguageServerInstance
7376
Dict{URI,Document}(),
7477
env_path,
7578
depot_path,
76-
SymbolServer.SymbolServerInstance(depot_path, symserver_store_path; symbolcache_upstream = symbolcache_upstream),
79+
SymbolServer.SymbolServerInstance(depot_path, symserver_store_path; symbolcache_upstream=symbolcache_upstream),
7780
Channel(Inf),
7881
StaticLint.ExternalEnv(deepcopy(SymbolServer.stdlibs), SymbolServer.collect_extended_methods(SymbolServer.stdlibs), collect(keys(SymbolServer.stdlibs))),
7982
Dict(),
@@ -83,6 +86,9 @@ mutable struct LanguageServerInstance
8386
:all,
8487
LINT_DIABLED_DIRS,
8588
:qualify, # options: :import or :qualify, anything else turns this off
89+
true,
90+
true,
91+
:literals,
8692
Channel{Any}(Inf),
8793
err_handler,
8894
:created,
@@ -184,7 +190,7 @@ function trigger_symbolstore_reload(server::LanguageServerInstance)
184190
ssi_ret, payload = SymbolServer.getstore(
185191
server.symbol_server,
186192
server.env_path,
187-
function (msg, percentage = missing)
193+
function (msg, percentage=missing)
188194
if server.clientcapability_window_workdoneprogress && server.current_symserver_progress_token !== nothing
189195
msg = ismissing(percentage) ? msg : string(msg, " ($percentage%)")
190196
JSONRPC.send(
@@ -196,7 +202,7 @@ function trigger_symbolstore_reload(server::LanguageServerInstance)
196202
@info msg
197203
end,
198204
server.err_handler,
199-
download = server.symserver_use_download
205+
download=server.symserver_use_download
200206
)
201207

202208
server.number_of_outstanding_symserver_requests -= 1
@@ -289,7 +295,7 @@ function Base.run(server::LanguageServerInstance; timings = [])
289295
add_timer_message!(did_show_timer, timings, "(async) listening to client events")
290296
while true
291297
msg = JSONRPC.get_next_message(server.jr_endpoint)
292-
put!(server.combined_msg_queue, (type = :clientmsg, msg = msg))
298+
put!(server.combined_msg_queue, (type=:clientmsg, msg=msg))
293299
end
294300
catch err
295301
bt = catch_backtrace()
@@ -300,7 +306,7 @@ function Base.run(server::LanguageServerInstance; timings = [])
300306
end
301307
finally
302308
if isopen(server.combined_msg_queue)
303-
put!(server.combined_msg_queue, (type = :close,))
309+
put!(server.combined_msg_queue, (type=:close,))
304310
close(server.combined_msg_queue)
305311
end
306312
@debug "LS: Client listener task done."
@@ -312,7 +318,7 @@ function Base.run(server::LanguageServerInstance; timings = [])
312318
add_timer_message!(did_show_timer, timings, "(async) listening to symbol server events")
313319
while true
314320
msg = take!(server.symbol_results_channel)
315-
put!(server.combined_msg_queue, (type = :symservmsg, msg = msg))
321+
put!(server.combined_msg_queue, (type=:symservmsg, msg=msg))
316322
end
317323
catch err
318324
bt = catch_backtrace()
@@ -323,7 +329,7 @@ function Base.run(server::LanguageServerInstance; timings = [])
323329
end
324330
finally
325331
if isopen(server.combined_msg_queue)
326-
put!(server.combined_msg_queue, (type = :close,))
332+
put!(server.combined_msg_queue, (type=:close,))
327333
close(server.combined_msg_queue)
328334
end
329335
@debug "LS: Symbol server listener task done."
@@ -371,6 +377,7 @@ function Base.run(server::LanguageServerInstance; timings = [])
371377
msg_dispatcher[julia_getDocFromWord_request_type] = request_wrapper(julia_getDocFromWord_request, server)
372378
msg_dispatcher[textDocument_selectionRange_request_type] = request_wrapper(textDocument_selectionRange_request, server)
373379
msg_dispatcher[textDocument_documentLink_request_type] = request_wrapper(textDocument_documentLink_request, server)
380+
msg_dispatcher[textDocument_inlayHint_request_type] = request_wrapper(textDocument_inlayHint_request, server)
374381

375382
# The exit notification message should not be wrapped in request_wrapper (which checks
376383
# if the server have been requested to be shut down). Instead, this message needs to be

src/protocol/features.jl

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,49 @@ end
296296
arguments::Union{Vector{Any},Missing}
297297
end
298298

299+
##############################################################################
300+
# inlay hints
301+
@dict_readable struct InlayHintOptions <: Outbound
302+
workDoneToken::Union{Int,String,Missing} # ProgressToken
303+
resolveProvider::Bool
304+
end
305+
306+
@dict_readable struct InlayHintRegistrationOptions <: Outbound
307+
workDoneToken::Union{Int,String,Missing} # ProgressToken
308+
resolveProvider::Bool # InlayHintOptions
309+
id::Union{Missing, String} # StaticRegistrationOptions
310+
documentSelector::Union{Nothing, DocumentSelector} # TextDocumentRegistrationOptions
311+
end
312+
313+
@dict_readable struct InlayHintParams <: Outbound
314+
textDocument::TextDocumentIdentifier
315+
range::Range
316+
workDoneToken::Union{Int,String,Missing} # ProgressToken
317+
end
318+
319+
@dict_readable struct InlayHintLabelPart <: Outbound
320+
value::String
321+
tooltip::Union{Missing, String, MarkupContent}
322+
location::Union{Missing, Location}
323+
command::Union{Missing, Command}
324+
end
325+
326+
const InlayHintKind = Int
327+
const InlayHintKinds = (
328+
Type = 1,
329+
Parameter = 2
330+
)
331+
332+
@dict_readable struct InlayHint <: Outbound
333+
position::Position
334+
label::Union{String, Vector{InlayHintLabelPart}}
335+
kind::Union{Missing, InlayHintKind}
336+
textEdits::Union{Missing, Vector{TextEdit}}
337+
tooltip::Union{Missing, String, MarkupContent}
338+
paddingLeft::Union{Missing, Bool}
339+
paddingRight::Union{Missing, Bool}
340+
data::Union{Missing, Any}
341+
end
299342

300343
##############################################################################
301344

src/protocol/initialize.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ struct ServerCapabilities <: Outbound
183183
foldingRangeProvider::Union{Bool,FoldingRangeOptions,FoldingRangeRegistrationOptions,Missing}
184184
executeCommandProvider::Union{ExecuteCommandOptions,Missing}
185185
selectionRangeProvider::Union{Bool,SelectionRangeOptions,SelectionRangeRegistrationOptions,Missing}
186+
inlayHintProvider::Union{Bool,InlayHintOptions,InlayHintRegistrationOptions}
186187
workspaceSymbolProvider::Union{Bool,Missing}
187188
workspace::Union{WorkspaceOptions,Missing}
188189
experimental::Union{Any,Missing}

src/protocol/messagedefs.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const textDocument_willSaveWaitUntil_request_type = JSONRPC.RequestType("textDoc
1919
const textDocument_publishDiagnostics_notification_type = JSONRPC.NotificationType("textDocument/publishDiagnostics", PublishDiagnosticsParams)
2020
const textDocument_selectionRange_request_type = JSONRPC.RequestType("textDocument/selectionRange", SelectionRangeParams, Union{Vector{SelectionRange}, Nothing})
2121
const textDocument_documentLink_request_type = JSONRPC.RequestType("textDocument/documentLink", DocumentLinkParams, Union{Vector{DocumentLink}, Nothing})
22+
const textDocument_inlayHint_request_type = JSONRPC.RequestType("textDocument/inlayHint", InlayHintParams, Union{Vector{InlayHint}, Nothing})
2223

2324
const workspace_executeCommand_request_type = JSONRPC.RequestType("workspace/executeCommand", ExecuteCommandParams, Any)
2425
const workspace_symbol_request_type = JSONRPC.RequestType("workspace/symbol", WorkspaceSymbolParams, Union{Vector{SymbolInformation}, Nothing})

src/requests/features.jl

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,3 +576,100 @@ function get_selection_range_of_expr(x::EXPR)
576576
l2, c2 = get_position_from_offset(doc, offset + x.span)
577577
SelectionRange(Range(l1, c1, l2, c2), get_selection_range_of_expr(x.parent))
578578
end
579+
580+
function textDocument_inlayHint_request(params::InlayHintParams, server::LanguageServerInstance, conn)::Union{Vector{InlayHint},Nothing}
581+
if !server.inlay_hints
582+
return nothing
583+
end
584+
585+
doc = getdocument(server, params.textDocument.uri)
586+
587+
start, stop = get_offset(doc, params.range.start), get_offset(doc, params.range.stop)
588+
589+
return collect_inlay_hints(getcst(doc), server, doc, start, stop)
590+
end
591+
592+
function collect_inlay_hints(x::EXPR, server::LanguageServerInstance, doc, start, stop, pos=0, hints=InlayHint[])
593+
if x isa EXPR && parentof(x) isa EXPR &&
594+
CSTParser.iscall(parentof(x)) &&
595+
!(
596+
parentof(parentof(x)) isa EXPR &&
597+
CSTParser.defines_function(parentof(parentof(x)))
598+
) &&
599+
parentof(x).args[1] != x # function calls
600+
601+
if server.inlay_hints_parameter_names === :all || (
602+
server.inlay_hints_parameter_names === :literals &&
603+
CSTParser.isliteral(x)
604+
)
605+
sigs = collect_signatures(x, doc, server)
606+
if !isempty(sigs)
607+
args = length(parentof(x).args) - 1
608+
if args > 0
609+
filter!(s -> length(s.parameters) == args, sigs)
610+
if !isempty(sigs)
611+
pars = first(sigs).parameters
612+
thisarg = 0
613+
for a in parentof(x).args
614+
if x == a
615+
break
616+
end
617+
thisarg += 1
618+
end
619+
if thisarg <= args && thisarg <= length(pars)
620+
label = pars[thisarg].label
621+
if label == "#unused#"
622+
label = "_"
623+
end
624+
push!(
625+
hints,
626+
InlayHint(
627+
Position(get_position_from_offset(doc, pos)...),
628+
string(label, ':'),
629+
InlayHintKinds.Parameter,
630+
missing,
631+
pars[thisarg].documentation,
632+
false,
633+
true,
634+
missing
635+
)
636+
)
637+
end
638+
end
639+
end
640+
end
641+
end
642+
elseif x isa EXPR && parentof(x) isa EXPR &&
643+
CSTParser.isassignment(parentof(x)) &&
644+
parentof(x).args[1] == x &&
645+
StaticLint.hasbinding(x) # assignment
646+
if server.inlay_hints_variable_types
647+
typ = _completion_type(StaticLint.bindingof(x))
648+
if typ !== missing
649+
push!(
650+
hints,
651+
InlayHint(
652+
Position(get_position_from_offset(doc, pos + x.span)...),
653+
string("::", typ),
654+
InlayHintKinds.Type,
655+
missing,
656+
missing,
657+
missing,
658+
missing,
659+
missing
660+
)
661+
)
662+
end
663+
end
664+
end
665+
if length(x) > 0
666+
for a in x
667+
if pos < stop && pos + a.fullspan > start
668+
collect_inlay_hints(a, server, doc, start, stop, pos, hints)
669+
end
670+
pos += a.fullspan
671+
pos > stop && break
672+
end
673+
end
674+
return hints
675+
end

src/requests/init.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function ServerCapabilities(client::ClientCapabilities)
3131
ExecuteCommandOptions(missing, collect(keys(LSActions))),
3232
true,
3333
true,
34+
true,
3435
WorkspaceOptions(WorkspaceFoldersOptions(true, true)),
3536
missing
3637
)

src/requests/signatures.jl

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,29 @@ function textDocument_signatureHelp_request(params::TextDocumentPositionParams,
1010
index_at(get_text_document(doc), params.position)
1111
offset = get_offset(doc, params.position)
1212
x = get_expr(getcst(doc), offset)
13-
arg = 0
13+
14+
sigs = collect_signatures(x, doc, server)
15+
16+
if (isempty(sigs) || (headof(x) === :RPAREN))
17+
return SignatureHelp(SignatureInformation[], 0, 0)
18+
end
19+
20+
arg = fcall_arg_number(x)
21+
22+
return SignatureHelp(filter(s -> length(s.parameters) > arg, sigs), 0, arg)
23+
end
24+
25+
function fcall_arg_number(x)
26+
if headof(x) === :LPAREN
27+
0
28+
else
29+
sum(headof(a) === :COMMA for a in parentof(x).trivia)
30+
end
31+
end
32+
33+
function collect_signatures(x, doc, server)
34+
sigs = SignatureInformation[]
35+
1436
if x isa EXPR && parentof(x) isa EXPR && CSTParser.iscall(parentof(x))
1537
if CSTParser.isidentifier(parentof(x).args[1])
1638
call_name = parentof(x).args[1]
@@ -25,22 +47,13 @@ function textDocument_signatureHelp_request(params::TextDocumentPositionParams,
2547
get_signatures(f_binding, tls, sigs, getenv(doc, server))
2648
end
2749
end
28-
if (isempty(sigs) || (headof(x) === :RPAREN))
29-
return SignatureHelp(SignatureInformation[], 0, 0)
30-
end
3150

32-
if headof(x) === :LPAREN
33-
arg = 0
34-
else
35-
arg = sum(headof(a) === :COMMA for a in parentof(x).trivia)
36-
end
37-
return SignatureHelp(filter(s -> length(s.parameters) > arg, sigs), 0, arg)
51+
return sigs
3852
end
3953

4054
function get_signatures(b, tls::StaticLint.Scope, sigs::Vector{SignatureInformation}, env) end
4155

4256
function get_signatures(b::StaticLint.Binding, tls::StaticLint.Scope, sigs::Vector{SignatureInformation}, env)
43-
4457
if b.val isa StaticLint.Binding
4558
get_signatures(b.val, tls, sigs, env)
4659
end

src/requests/workspace.jl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,10 @@ function request_julia_config(server::LanguageServerInstance, conn)
107107
ConfigurationItem(missing, "julia.lint.run"),
108108
ConfigurationItem(missing, "julia.lint.missingrefs"),
109109
ConfigurationItem(missing, "julia.lint.disabledDirs"),
110-
ConfigurationItem(missing, "julia.completionmode")
110+
ConfigurationItem(missing, "julia.completionmode"),
111+
ConfigurationItem(missing, "julia.inlayHints.static.enabled"),
112+
ConfigurationItem(missing, "julia.inlayHints.static.variableTypes.enabled"),
113+
ConfigurationItem(missing, "julia.inlayHints.static.parameterNames.enabled"),
111114
]))
112115

113116
new_runlinter = something(response[11], true)
@@ -116,6 +119,9 @@ function request_julia_config(server::LanguageServerInstance, conn)
116119
new_lint_missingrefs = Symbol(something(response[12], :all))
117120
new_lint_disableddirs = something(response[13], LINT_DIABLED_DIRS)
118121
new_completion_mode = Symbol(something(response[14], :import))
122+
inlayHints = something(response[15], true)
123+
inlayHintsVariableTypes = something(response[16], true)
124+
inlayHintsParameterNames = Symbol(something(response[17], :literals))
119125

120126
rerun_lint = begin
121127
any(getproperty(server.lint_options, opt) != getproperty(new_SL_opts, opt) for opt in fieldnames(StaticLint.LintOptions)) ||
@@ -129,6 +135,9 @@ function request_julia_config(server::LanguageServerInstance, conn)
129135
server.lint_missingrefs = new_lint_missingrefs
130136
server.lint_disableddirs = new_lint_disableddirs
131137
server.completion_mode = new_completion_mode
138+
server.inlay_hints = inlayHints
139+
server.inlay_hints_variable_types = inlayHintsVariableTypes
140+
server.inlay_hints_parameter_names = inlayHintsParameterNames
132141

133142
if rerun_lint
134143
relintserver(server)

src/utilities.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,32 @@ function get_expr(x, offset::UnitRange{Int}, pos=0, ignorewhitespace=false)
200200
end
201201
end
202202

203+
get_inner_expr(doc::Document, rng::Range) = get_inner_expr(getcst(doc), get_offset(doc, rng))
204+
# full (not only trivia) expr containing rng, modulo whitespace
205+
function get_inner_expr(x, rng::UnitRange{Int}, pos=0, pos_span = 0)
206+
if all(pos .> rng)
207+
return nothing
208+
end
209+
if length(x) > 0 && headof(x) !== :NONSTDIDENTIFIER
210+
pos_span′ = pos_span
211+
for a in x
212+
if a in x.args && all(pos_span′ .< rng .<= (pos + a.fullspan))
213+
return get_inner_expr(a, rng, pos, pos_span′)
214+
end
215+
pos += a.fullspan
216+
pos_span′ = pos - (a.fullspan - a.span)
217+
end
218+
elseif pos == 0
219+
return x
220+
elseif all(pos_span .< rng .<= (pos + x.fullspan))
221+
return x
222+
end
223+
pos -= x.fullspan
224+
if all(pos_span .< rng .<= (pos + x.fullspan))
225+
return x
226+
end
227+
end
228+
203229
function get_expr1(x, offset, pos=0)
204230
if length(x) == 0 || headof(x) === :NONSTDIDENTIFIER
205231
if pos <= offset <= pos + x.span

test/requests/test_features.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ end
9393

9494
@testitem "doc symbols" begin
9595
include("../test_shared_server.jl")
96-
96+
9797
doc = settestdoc("""
9898
a = 1
9999
b = 2

0 commit comments

Comments
 (0)