Skip to content

Commit 018d838

Browse files
authored
Add action for adding a docstring snippet. (#1084)
* Add an action for adding a docstring snippet with the method signature to a method definition. * Add an action to update the method signature in a docstring attached to the method.
1 parent eed8507 commit 018d838

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

src/requests/actions.jl

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,73 @@ function convert_from_raw(x, _, conn)
576576
return nothing
577577
end
578578

579+
# Checks if parent is a parent/grandparent/... of child
580+
function is_parent_of(parent::EXPR, child::EXPR)
581+
while child isa EXPR
582+
if child == parent
583+
return true
584+
end
585+
child = child.parent
586+
end
587+
return false
588+
end
589+
590+
function is_in_function_signature(x::EXPR, params; with_docstring=false)
591+
# TODO: Perhaps also allow this if the cursor is inside a docstring?
592+
func = _get_parent_fexpr(x, CSTParser.defines_function)
593+
func === nothing && return false
594+
sig = func.args[1]
595+
if x.head === :FUNCTION || is_parent_of(sig, x)
596+
hasdoc = func.parent isa EXPR && func.parent.head === :macrocall && func.parent.args[1] isa EXPR &&
597+
func.parent.args[1].head === :globalrefdoc
598+
return with_docstring == hasdoc
599+
end
600+
return false
601+
end
602+
603+
function add_docstring_template(x, _, conn)
604+
is_in_function_signature(x, nothing) || return
605+
func = _get_parent_fexpr(x, CSTParser.defines_function)
606+
func === nothing && return
607+
file, func_offset = get_file_loc(func)
608+
sig = func.args[1]
609+
_, sig_offset = get_file_loc(sig)
610+
docstr = "\"\"\"\n " * get_text(file)[sig_offset .+ (1:sig.span)] * "\n\nTBW\n\"\"\"\n"
611+
tde = TextDocumentEdit(VersionedTextDocumentIdentifier(get_uri(file), get_version(file)), TextEdit[
612+
TextEdit(Range(file, func_offset:func_offset), docstr)
613+
])
614+
JSONRPC.send(conn, workspace_applyEdit_request_type, ApplyWorkspaceEditParams(missing, WorkspaceEdit(missing, TextDocumentEdit[tde])))
615+
return
616+
end
617+
618+
function update_docstring_sig(x, _, conn)
619+
is_in_function_signature(x, nothing; with_docstring=true) || return
620+
func = _get_parent_fexpr(x, CSTParser.defines_function)
621+
# Current docstring
622+
docstr_expr = func.parent.args[3]
623+
docstr = valof(docstr_expr)
624+
file, docstr_offset = get_file_loc(docstr_expr)
625+
# New signature in the code
626+
sig = func.args[1]
627+
_, sig_offset = get_file_loc(sig)
628+
sig_str = get_text(file)[sig_offset .+ (1:sig.span)]
629+
# Heuristic for finding a signature in the current docstring
630+
reg = r"\A .*$"m
631+
if (m = match(reg, valof(docstr_expr)); m !== nothing)
632+
docstr = replace(docstr, reg => string(" ", sig_str))
633+
else
634+
docstr = string(" ", sig_str, "\n\n", docstr)
635+
end
636+
newline = endswith(docstr, "\n") ? "" : "\n"
637+
# Rewrap in """"
638+
docstr = string("\"\"\"\n", docstr, newline, "\"\"\"")
639+
tde = TextDocumentEdit(VersionedTextDocumentIdentifier(get_uri(file), get_version(file)), TextEdit[
640+
TextEdit(Range(file, docstr_offset .+ (0:docstr_expr.span)), docstr)
641+
])
642+
JSONRPC.send(conn, workspace_applyEdit_request_type, ApplyWorkspaceEditParams(missing, WorkspaceEdit(missing, TextDocumentEdit[tde])))
643+
return
644+
end
645+
579646
# Adding a CodeAction requires defining:
580647
# * a unique id
581648
# * a description
@@ -682,3 +749,21 @@ LSActions["RewriteAsRegularString"] = ServerAction(
682749
(x, _) -> is_string_literal(x; inraw=true),
683750
convert_from_raw,
684751
)
752+
753+
LSActions["AddDocstringTemplate"] = ServerAction(
754+
"AddDocstringTemplate",
755+
"Add docstring template for this method",
756+
missing,
757+
missing,
758+
is_in_function_signature,
759+
add_docstring_template,
760+
)
761+
762+
LSActions["UpdateDocstringSignature"] = ServerAction(
763+
"UpdateDocstringSignature",
764+
"Update method signature in docstring",
765+
missing,
766+
missing,
767+
(args...) -> is_in_function_signature(args...; with_docstring=true),
768+
update_docstring_sig,
769+
)

test/requests/actions.jl

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,76 @@ end
148148
raw_str = "he\$l\"lo"
149149
str = "\"he\\\$l\\\"lo\"" # Will be `"he\$l\"lo` when unescaped/printed
150150
@test repr(raw_str) == str
151+
end
152+
153+
@testset "Add docstring template" begin
154+
doc = settestdoc("""
155+
f(x) = x
156+
157+
@inline f(x) = x
158+
159+
f(x::T) where T <: Int = x
160+
161+
function g(x)
162+
end
163+
164+
function g(x::T) where T <: Int
165+
end
151166
167+
"docstring"
168+
h(x) = x
169+
170+
"docstring"
171+
function h(x)
172+
end
173+
""")
174+
175+
ok_locations = [
176+
[(0, i) for i in 0:3],
177+
[(2, i) for i in 8:11],
178+
[(4, i) for i in 0:21],
179+
[(6, i) for i in 0:12],
180+
[(9, i) for i in 0:30],
181+
]
182+
not_ok_locations = [
183+
[(13, i) for i in 0:4],
184+
[(16, i) for i in 0:12],
185+
]
186+
for loc in Iterators.flatten(ok_locations)
187+
@test any(c.command == "AddDocstringTemplate" for c in action_request_test(loc...))
188+
end
189+
for loc in Iterators.flatten(not_ok_locations)
190+
@test !any(c.command == "AddDocstringTemplate" for c in action_request_test(loc...))
191+
end
192+
193+
c = filter(c -> c.command == "AddDocstringTemplate", action_request_test(0, 1))[1]
194+
LanguageServer.workspace_executeCommand_request(LanguageServer.ExecuteCommandParams(missing, c.command, c.arguments), server, server.jr_endpoint)
195+
end
196+
197+
@testset "Update docstring signature" begin
198+
doc = settestdoc("""
199+
"hello"
200+
f(x) = x
201+
202+
\"\"\"hello\"\"\"
203+
g(x) = x
204+
205+
\"\"\"
206+
h()
207+
208+
hello
209+
\"\"\"
210+
function h(x)
211+
end
212+
213+
i(x) = x
214+
""")
215+
216+
@test any(c.command == "UpdateDocstringSignature" for c in action_request_test(1, 0))
217+
@test any(c.command == "UpdateDocstringSignature" for c in action_request_test(4, 0))
218+
@test any(c.command == "UpdateDocstringSignature" for c in action_request_test(11, 0))
219+
@test !any(c.command == "UpdateDocstringSignature" for c in action_request_test(14, 0))
220+
221+
c = filter(c -> c.command == "UpdateDocstringSignature", action_request_test(1, 0))[1]
222+
LanguageServer.workspace_executeCommand_request(LanguageServer.ExecuteCommandParams(missing, c.command, c.arguments), server, server.jr_endpoint)
152223
end

0 commit comments

Comments
 (0)