Browse Source

Indent: recurse into multiline strings, closes #27

pull/30/head
Fredrik Ekre 1 year ago
parent
commit
aeabe11718
No known key found for this signature in database
GPG Key ID: DE82E6D5E364C0A2
  1. 1
      src/Runic.jl
  2. 123
      src/runestone.jl
  3. 30
      test/runtests.jl

1
src/Runic.jl

@ -318,6 +318,7 @@ function format_node!(ctx::Context, node::Node)::Union{Node, Nothing, NullNode}
@return_something parens_around_op_calls_in_colon(ctx, node) @return_something parens_around_op_calls_in_colon(ctx, node)
@return_something for_loop_use_in(ctx, node) @return_something for_loop_use_in(ctx, node)
@return_something braces_around_where_rhs(ctx, node) @return_something braces_around_where_rhs(ctx, node)
@return_something indent_multiline_strings(ctx, node)
@return_something four_space_indent(ctx, node) @return_something four_space_indent(ctx, node)
@return_something spaces_in_listlike(ctx, node) @return_something spaces_in_listlike(ctx, node)
ctx.call_depth -= 1 ctx.call_depth -= 1

123
src/runestone.jl

@ -2757,3 +2757,126 @@ function insert_delete_mark_newlines(ctx::Context, node::Node)
end end
return nothing return nothing
end end
function indent_multiline_strings(ctx::Context, node::Node)
if !(kind(node) in KSet"string cmdstring" && JuliaSyntax.has_flags(node, JuliaSyntax.TRIPLE_STRING_FLAG))
return nothing
end
triplekind = kind(node) === K"string" ? K"\"\"\"" : K"```"
itemkind = kind(node) === K"string" ? K"String" : K"CmdString"
indent_span = 4 * ctx.indent_level
indented = indent_span > 0
pos = position(ctx.fmt_io)
kids = verified_kids(node)
kids′ = kids
any_changes = false
# Fastpath for the common case of top level multiline strings like e.g. docstrings
if !indented && findfirst(x -> kind(x) === K"Whitespace", kids) === nothing
return nothing
end
# Opening triple quote
open_idx = findfirst(x -> kind(x) === triplekind, kids)
close_idx = findlast(x -> kind(x) === triplekind, kids)
@assert close_idx == length(kids) # ?
open_kid = kids[open_idx]
@assert kind(open_kid) === triplekind
accept_node!(ctx, open_kid)
# Loop over the lines/expressions
idx = open_idx + 1
state = :expect_something
while idx < close_idx
kid = kids[idx]
if state === :expect_something
if kind(kid) === itemkind
if indented && read_bytes(ctx, kid)[end] == UInt8('\n')
state = :expect_indent_ws
end
accept_node!(ctx, kid)
any_changes && push!(kids′, kid)
elseif kind(kid) === K"Whitespace"
# Delete this one
replace_bytes!(ctx, "", span(kid))
if kids′ === kids
kids′ = kids[1:(idx - 1)]
end
any_changes = true
else
accept_node!(ctx, kid)
any_changes && push!(kids′, kid)
end
else
@assert state === :expect_indent_ws
state = :expect_something
if kind(kid) === itemkind && span(kid) == 1 && peek(ctx.fmt_io) == UInt8('\n')
# If this line is empty there shouldn't be a whitespace node. Switch the
# state and loop around with the same idx.
state = :expect_something
continue # Skip the index increment
elseif begin
cond = false
if kind(kid) === K"Whitespace" && idx + 1 < close_idx &&
kind(kids[idx + 1]) === itemkind && span(kids[idx + 1]) == 1
peekpos = position(ctx.fmt_io)
accept_node!(ctx, kid)
accept_node!(ctx, kids[idx + 1])
seek(ctx.fmt_io, position(ctx.fmt_io) - 1)
cond = peek(ctx.fmt_io) == UInt8('\n')
seek(ctx.fmt_io, peekpos)
end
cond
end
# If this whitespace is followed by an empty string it should be deleted
state = :expect_something
continue # Skip the index increment
elseif kind(kid) === K"Whitespace" && span(kid) == indent_span
@assert all(x -> x === UInt8(' '), read_bytes(ctx, kid))
accept_node!(ctx, kid)
any_changes && push!(kids′, kid)
elseif kind(kid) === K"Whitespace"
replace_bytes!(ctx, " "^indent_span, span(kid))
if kids′ === kids
kids′ = kids[1:(idx - 1)]
end
kid′ = Node(head(kid), indent_span, tags(kid))
any_changes = true
push!(kids′, kid′)
accept_node!(ctx, kid′)
else
replace_bytes!(ctx, " "^indent_span, 0)
if kids′ === kids
kids′ = kids[1:(idx - 1)]
end
kid′ = Node(JuliaSyntax.SyntaxHead(K"Whitespace", JuliaSyntax.TRIVIA_FLAG), indent_span)
any_changes = true
push!(kids′, kid′)
accept_node!(ctx, kid′)
continue # Skip the index increment
end
end
idx += 1
end
# Make sure to add indent before the closing triple quote
if state === :expect_indent_ws
replace_bytes!(ctx, " "^indent_span, 0)
if kids′ === kids
kids′ = kids[1:(idx - 1)]
end
kid′ = Node(JuliaSyntax.SyntaxHead(K"Whitespace", JuliaSyntax.TRIVIA_FLAG), indent_span)
any_changes = true
push!(kids′, kid′)
accept_node!(ctx, kid′)
end
@assert idx == close_idx
# Closing triple quote
close_kid = kids[close_idx]
@assert kind(close_kid) === triplekind
accept_node!(ctx, close_kid)
any_changes && push!(kids′, close_kid)
# Reset stream
seek(ctx.fmt_io, pos)
return any_changes ? make_node(node, kids′) : nothing
end

30
test/runtests.jl

@ -893,3 +893,33 @@ end
@testset "parsing new syntax" begin @testset "parsing new syntax" begin
@test format_string("public a, b") == "public a, b" # Julia 1.11 @test format_string("public a, b") == "public a, b" # Julia 1.11
end end
@testset "indent of multiline strings" begin
for triple in ("\"\"\"", "```"), sp in ("", " ", " "),
(pre, post) in (("", ""), ("pre", ""), ("pre", "post"))
otriple = pre * triple
ctriple = triple * post
# Level 0
@test format_string("$(sp)$(otriple)\n$(sp)a\n$(sp)b\n$(sp)$(ctriple)") ===
"$(sp)$(otriple)\na\nb\n$(ctriple)"
@test format_string("$(sp)$(otriple)\n$(sp)a\n\n$(sp)b\n$(sp)$(ctriple)") ===
"$(sp)$(otriple)\na\n\nb\n$(ctriple)"
@test format_string("x = $(otriple)\n$(sp)a\n$(sp)b\n$(sp)$(ctriple)") ===
"x = $(otriple)\na\nb\n$(ctriple)"
@test format_string("$(sp)$(otriple)a\n$(sp)a\n$(sp)b\n$(sp)$(ctriple)") ===
"$(sp)$(otriple)a\na\nb\n$(ctriple)"
@test format_string("$(sp)$(otriple)\n$(sp)a\$(b)c\n$(sp)$(ctriple)") ===
"$(sp)$(otriple)\na\$(b)c\n$(ctriple)"
# Level 1
@test format_string("begin\n$(sp)$(otriple)\n$(sp)a\n$(sp)b\n$(sp)$(ctriple)\nend") ===
"begin\n $(otriple)\n a\n b\n $(ctriple)\nend"
@test format_string("begin\n$(sp)$(otriple)\n$(sp)a\n$(sp)\n$(sp)b\n$(sp)$(ctriple)\nend") ===
"begin\n $(otriple)\n a\n\n b\n $(ctriple)\nend"
@test format_string("begin\n$(sp)x = $(otriple)\n$(sp)a\n$(sp)b\n$(sp)$(ctriple)\nend") ===
"begin\n x = $(otriple)\n a\n b\n $(ctriple)\nend"
@test format_string("begin\n$(sp)$(otriple)a\n$(sp)a\n$(sp)b\n$(sp)$(ctriple)\nend") ===
"begin\n $(otriple)a\n a\n b\n $(ctriple)\nend"
@test format_string("begin\n$(sp)$(otriple)\n$(sp)a\$(b)c\n$(sp)$(ctriple)\nend") ===
"begin\n $(otriple)\n a\$(b)c\n $(ctriple)\nend"
end
end

Loading…
Cancel
Save