Browse Source

Line continuation indent for triple-strings (#45)

This patch introduces line continuation based indent for triple strings,
which typically span multiple lines without any explicit newlines in the
syntax tree (since they are hidden inside the string).

This result in the following changes, some of which are clearly
bugfixes:

Operator chains:
```diff
 """
 abc
 """ * """
-def
-"""
+    def
+    """
```

Operator chain as assignment right hand side:
```diff
 x = """
-abc
-""" * """
-def
-"""
+    abc
+    """ * """
+    def
+    """
```

Implicit tuples:
```diff
 """
 abc
 """,
   """
-def
-"""
+    def
+    """
```

Note that single triple strings as a right hand side is excluded from
the indent rule, similar to having `if/try/let/...` blocks as a right
hand side.
pull/46/head
Fredrik Ekre 1 year ago committed by GitHub
parent
commit
af1b7377f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      src/chisels.jl
  2. 65
      src/runestone.jl
  3. 69
      test/runtests.jl

5
src/chisels.jl

@ -570,6 +570,11 @@ function is_triple_string_macro(node)
return false return false
end end
function is_triple_thing(node)
return is_triple_string(node) || is_triple_string_macro(node) ||
(kind(node) === K"juxtapose" && is_triple_string_macro(verified_kids(node)[1]))
end
########################## ##########################
# Utilities for IOBuffer # # Utilities for IOBuffer #
########################## ##########################

65
src/runestone.jl

@ -2053,10 +2053,18 @@ function indent_newlines_between_indices(
if !indent_closing_token && i == close_idx - 1 && kind(kid) === K"NewlineWs" if !indent_closing_token && i == close_idx - 1 && kind(kid) === K"NewlineWs"
continue continue
end end
# Tag all direct NewlineWs kids
if kind(kid) === K"NewlineWs" && !has_tag(kid, TAG_LINE_CONT) if kind(kid) === K"NewlineWs" && !has_tag(kid, TAG_LINE_CONT)
# Tag all direct NewlineWs kids
kid = add_tag(kid, TAG_LINE_CONT) kid = add_tag(kid, TAG_LINE_CONT)
this_kid_changed = true this_kid_changed = true
elseif is_triple_thing(kid) && (i != open_idx || has_tag(node, TAG_LINE_CONT))
# TODO: Might be too course to use the tag on the node here...
# Tag triple strings and triple string macros
kid′ = indent_triple_thing(ctx, kid)
if kid′ !== nothing
kid = kid′
this_kid_changed = true
end
end end
# NewlineWs nodes can also hide as the first or last leaf of a node, tag'em. # NewlineWs nodes can also hide as the first or last leaf of a node, tag'em.
# Skip leading newline if this kid is the first one # Skip leading newline if this kid is the first one
@ -2449,6 +2457,29 @@ function indent_short_circuit(ctx::Context, node::Node)
return indent_op_call(ctx, node) return indent_op_call(ctx, node)
end end
function indent_triple_thing(ctx::Context, node::Node)
@assert is_triple_thing(node)
if is_triple_string(node)
return has_tag(node, TAG_LINE_CONT) ? nothing : add_tag(node, TAG_LINE_CONT)
elseif is_triple_string_macro(node)
kids = verified_kids(node)
@assert is_triple_string(kids[2])
kid′ = indent_triple_thing(ctx, kids[2])
kid′ === nothing && return nothing
kids′ = copy(kids)
kids′[2] = kid′
return make_node(node, kids′)
else
@assert kind(node) === K"juxtapose" && is_triple_string_macro(verified_kids(node)[1])
kids = verified_kids(node)
kid′ = indent_triple_thing(ctx, kids[1])
kid′ === nothing && return nothing
kids′ = copy(kids)
kids′[1] = kid′
return make_node(node, kids′)
end
end
# TODO: This function can be used for more things than just indent_using I think. Perhaps # TODO: This function can be used for more things than just indent_using I think. Perhaps
# with a max_depth parameter. # with a max_depth parameter.
function continue_all_newlines( function continue_all_newlines(
@ -2465,6 +2496,13 @@ function continue_all_newlines(
else else
return nothing return nothing
end end
elseif is_triple_thing(node)
# Check skip_first inside to break the recursion and considier triple strings leafs
if !(skip_first && is_first)
return indent_triple_thing(ctx, node)
else
return nothing
end
else else
any_kid_changed = false any_kid_changed = false
kids = verified_kids(node) kids = verified_kids(node)
@ -2508,12 +2546,6 @@ function indent_assignment(ctx::Context, node::Node)
rhsidx = findnext(!JuliaSyntax.is_whitespace, kids, eqidx + 1)::Int rhsidx = findnext(!JuliaSyntax.is_whitespace, kids, eqidx + 1)::Int
r = (eqidx + 1):(rhsidx - 1) r = (eqidx + 1):(rhsidx - 1)
length(r) == 0 && return nothing length(r) == 0 && return nothing
if length(r) == 1 && kind(kids[r[1]]) === K"Whitespace"
return nothing
end
if !any(i -> kind(kids[i]) === K"NewlineWs", r)
return nothing
end
rhs = kids[rhsidx] rhs = kids[rhsidx]
# Some right hand sides have more "inertia" towards indentation. This is so that we # Some right hand sides have more "inertia" towards indentation. This is so that we
# will end up with e.g. # will end up with e.g.
@ -2533,8 +2565,7 @@ function indent_assignment(ctx::Context, node::Node)
# x = if cond # x = if cond
# end # end
# ``` # ```
blocklike = kind(rhs) in KSet"if try function let" || blocklike = kind(rhs) in KSet"if try function let" || is_triple_thing(rhs)
is_triple_string(rhs) || is_triple_string_macro(rhs)
blocklike && return nothing # TODO: Perhaps delete superfluous newlines? blocklike && return nothing # TODO: Perhaps delete superfluous newlines?
# Continue all newlines between the `=` and the rhs # Continue all newlines between the `=` and the rhs
kids′ = kids kids′ = kids
@ -2554,9 +2585,20 @@ function indent_assignment(ctx::Context, node::Node)
push!(kids′, kid′) push!(kids′, kid′)
end end
end end
# Mark the rhs for line continuation
if !has_tag(rhs, TAG_LINE_CONT)
rhs = add_tag(rhs, TAG_LINE_CONT)
changed = true
if kids′ === kids
kids′ = kids[1:(rhsidx - 1)]
end
push!(kids′, rhs)
else
changed && push!(kids′, rhs)
end
if changed if changed
@assert kids !== kids′ @assert kids !== kids′
for i in rhsidx:length(kids) for i in (rhsidx + 1):length(kids)
push!(kids′, kids[i]) push!(kids′, kids[i])
end end
return make_node(node, kids′) return make_node(node, kids′)
@ -2786,6 +2828,9 @@ function indent_multiline_strings(ctx::Context, node::Node)
triplekind = kind(node) === K"string" ? K"\"\"\"" : K"```" triplekind = kind(node) === K"string" ? K"\"\"\"" : K"```"
itemkind = kind(node) === K"string" ? K"String" : K"CmdString" itemkind = kind(node) === K"string" ? K"String" : K"CmdString"
indent_span = 4 * ctx.indent_level indent_span = 4 * ctx.indent_level
if has_tag(node, TAG_LINE_CONT)
indent_span += 4
end
indented = indent_span > 0 indented = indent_span > 0
pos = position(ctx.fmt_io) pos = position(ctx.fmt_io)

69
test/runtests.jl

@ -735,12 +735,16 @@ end
# Blocklike RHS # Blocklike RHS
for thing in ( for thing in (
"if c\n x\nend", "try\n x\ncatch\n y\nend", "if c\n x\nend", "try\n x\ncatch\n y\nend",
"let c = 1\n c\nend", "function()\n x\nend",
"\"\"\"\nfoo\n\"\"\"", "r\"\"\"\nfoo\n\"\"\"", "\"\"\"\nfoo\n\"\"\"", "r\"\"\"\nfoo\n\"\"\"",
"```\nfoo\n```", "r```\nfoo\n```", "```\nfoo\n```", "r```\nfoo\n```", "```\nfoo\n```x",
) )
@test format_string("a =$(nl)$(thing)") == "a =$(nl)$(thing)" @test format_string("a =$(nl)$(thing)") == "a =$(nl)$(thing)"
@test format_string("a =$(nl)# comment$(nl)$(thing)") == @test format_string("a =$(nl)# comment$(nl)$(thing)") ==
"a =$(nl)# comment$(nl)$(thing)" "a =$(nl)# comment$(nl)$(thing)"
@test format_string("a = $(thing)") == "a = $(thing)"
@test format_string("a = #=comment=#$(sp)$(thing)") ==
"a = #=comment=# $(thing)"
end end
end end
# using/import # using/import
@ -937,6 +941,29 @@ end
"$(otriple)\na\\\nb\n$(ctriple)" "$(otriple)\na\\\nb\n$(ctriple)"
@test format_string("begin\n$(otriple)\n$(sp)a\\\n$(sp)b\n$(sp)$(ctriple)\nend") === @test format_string("begin\n$(otriple)\n$(sp)a\\\n$(sp)b\n$(sp)$(ctriple)\nend") ===
"begin\n $(otriple)\n a\\\n b\n $(ctriple)\nend" "begin\n $(otriple)\n a\\\n b\n $(ctriple)\nend"
# Triple strings with continuation indent
@test format_string("x = $(otriple)\n$(sp)a\n$(sp)b\n$(sp)$(ctriple)") ===
"x = $(otriple)\na\nb\n$(ctriple)"
@test format_string("$(otriple)\n$(sp)a\n$(sp)b\n$(sp)$(ctriple) * $(otriple)\n$(sp)a\n$(sp)b\n$(sp)$(ctriple)") ===
"$(otriple)\na\nb\n$(ctriple) * $(otriple)\n a\n b\n $(ctriple)"
@test format_string("$(otriple)\n$(sp)a\n$(sp)b\n$(sp)$(ctriple) *\n$(otriple)\n$(sp)a\n$(sp)b\n$(sp)$(ctriple)") ===
"$(otriple)\na\nb\n$(ctriple) *\n $(otriple)\n a\n b\n $(ctriple)"
# Implicit tuple
@test format_string("$(otriple)\nabc\n$(ctriple), $(otriple)\ndef\n$(ctriple)") ===
"$(otriple)\nabc\n$(ctriple), $(otriple)\n def\n $(ctriple)"
@test format_string("$(otriple)\nabc\n$(ctriple),\n$(otriple)\ndef\n$(ctriple)") ===
"$(otriple)\nabc\n$(ctriple),\n $(otriple)\n def\n $(ctriple)"
# Operator chains
@test format_string("$(otriple)\nabc\n$(ctriple) * $(otriple)\ndef\n$(ctriple)") ===
"$(otriple)\nabc\n$(ctriple) * $(otriple)\n def\n $(ctriple)"
@test format_string("$(otriple)\nabc\n$(ctriple) *\n$(otriple)\ndef\n$(ctriple)") ===
"$(otriple)\nabc\n$(ctriple) *\n $(otriple)\n def\n $(ctriple)"
@test format_string("x = $(otriple)\nabc\n$(ctriple) *\n$(otriple)\ndef\n$(ctriple)") ===
"x = $(otriple)\n abc\n $(ctriple) *\n $(otriple)\n def\n $(ctriple)"
@test format_string("x = $(otriple)\nabc\n$(ctriple) *\n\"def\"") ===
"x = $(otriple)\n abc\n $(ctriple) *\n \"def\""
@test format_string("x = \"abc\" *\n$(otriple)\ndef\n$(ctriple)") ===
"x = \"abc\" *\n $(otriple)\n def\n $(ctriple)"
end end
end end
@ -961,13 +988,13 @@ end
end end
""", """,
) == """ ) == """
function f() function f()
$off $off
1+1 1+1
$on $on
1 + 1 1 + 1
end end
""" """
@test format_string( @test format_string(
""" """
function f() function f()
@ -978,13 +1005,13 @@ end
end end
""", """,
) == """ ) == """
function f() function f()
$off $off
1 + 1 1 + 1
$bon $bon
1 + 1 1 + 1
end end
""" """
@test format_string( @test format_string(
""" """
function f() function f()
@ -994,12 +1021,12 @@ end
end end
""", """,
) == """ ) == """
function f() function f()
$off $off
1 + 1 1 + 1
1 + 1 1 + 1
end end
""" """
end end
end end

Loading…
Cancel
Save