Skip to content

Commit 44a61ea

Browse files
KacperFKorbankasiaMareksusuro
authored
pc: Properly adjust indentation when inlining blocks (scala#22915)
closes scalameta/metals#7137 --------- Co-authored-by: Katarzyna Marek <[email protected]> Co-authored-by: Robert Marek <[email protected]>
1 parent 64442a0 commit 44a61ea

File tree

3 files changed

+167
-15
lines changed

3 files changed

+167
-15
lines changed

presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProviderImpl.scala renamed to presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProvider.scala

+118-13
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,76 @@ import dotty.tools.dotc.core.StdNames
1616
import dotty.tools.dotc.core.Symbols.Symbol
1717
import dotty.tools.dotc.interactive.Interactive
1818
import dotty.tools.dotc.interactive.InteractiveDriver
19+
import dotty.tools.dotc.util.SourceFile
1920
import dotty.tools.dotc.util.SourcePosition
2021
import dotty.tools.pc.utils.InteractiveEnrichments.*
2122
import dotty.tools.pc.IndexedContext.Result
2223

2324
import org.eclipse.lsp4j as l
2425

25-
final class PcInlineValueProviderImpl(
26+
final class PcInlineValueProvider(
2627
driver: InteractiveDriver,
2728
val params: OffsetParams
28-
) extends WithSymbolSearchCollector[Option[Occurence]](driver, params)
29-
with InlineValueProvider:
29+
) extends WithSymbolSearchCollector[Option[Occurence]](driver, params):
30+
31+
// We return a result or an error
32+
def getInlineTextEdits(): Either[String, List[l.TextEdit]] =
33+
defAndRefs() match {
34+
case Right((defn, refs)) =>
35+
val edits =
36+
if (defn.shouldBeRemoved) {
37+
val defEdit = definitionTextEdit(defn)
38+
val refsEdits = refs.map(referenceTextEdit(defn))
39+
defEdit :: refsEdits
40+
} else refs.map(referenceTextEdit(defn))
41+
Right(edits)
42+
case Left(error) => Left(error)
43+
}
44+
45+
private def referenceTextEdit(
46+
definition: Definition
47+
)(ref: Reference): l.TextEdit =
48+
if (definition.requiresBrackets && ref.requiresBrackets)
49+
new l.TextEdit(
50+
ref.range,
51+
s"""(${ref.rhs})"""
52+
)
53+
else new l.TextEdit(ref.range, ref.rhs)
54+
55+
private def definitionTextEdit(definition: Definition): l.TextEdit =
56+
new l.TextEdit(
57+
extend(
58+
definition.rangeOffsets.start,
59+
definition.rangeOffsets.end,
60+
definition.range
61+
),
62+
""
63+
)
64+
65+
private def extend(
66+
startOffset: Int,
67+
endOffset: Int,
68+
range: l.Range
69+
): l.Range = {
70+
val (startWithSpace, endWithSpace): (Int, Int) =
71+
extendRangeToIncludeWhiteCharsAndTheFollowingNewLine(
72+
text
73+
)(startOffset, endOffset)
74+
val startPos = new l.Position(
75+
range.getStart.getLine,
76+
range.getStart.getCharacter - (startOffset - startWithSpace)
77+
)
78+
val endPos =
79+
if (endWithSpace - 1 >= 0 && text(endWithSpace - 1) == '\n')
80+
new l.Position(range.getEnd.getLine + 1, 0)
81+
else
82+
new l.Position(
83+
range.getEnd.getLine,
84+
range.getEnd.getCharacter + endWithSpace - endOffset
85+
)
86+
87+
new l.Range(startPos, endPos)
88+
}
3089

3190
val position: l.Position = pos.toLsp.getStart().nn
3291

@@ -41,7 +100,7 @@ final class PcInlineValueProviderImpl(
41100
Some(Occurence(tree, parent, adjustedPos))
42101
case _ => None
43102

44-
override def defAndRefs(): Either[String, (Definition, List[Reference])] =
103+
def defAndRefs(): Either[String, (Definition, List[Reference])] =
45104
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
46105
val allOccurences = result().flatten
47106
for
@@ -60,7 +119,6 @@ final class PcInlineValueProviderImpl(
60119
val defPos = definition.tree.sourcePos
61120
val defEdit = Definition(
62121
defPos.toLsp,
63-
adjustRhs(definition.tree.rhs.sourcePos),
64122
RangeOffset(defPos.start, defPos.end),
65123
definitionRequiresBrackets(definition.tree.rhs)(using newctx),
66124
deleteDefinition
@@ -70,6 +128,18 @@ final class PcInlineValueProviderImpl(
70128
end for
71129
end defAndRefs
72130

131+
private def stripIndentPrefix(rhs: String, refIndent: String, defIndent: String): String =
132+
val rhsLines = rhs.split("\n").toList
133+
rhsLines match
134+
case h :: Nil => rhs
135+
case h :: t =>
136+
val noPrefixH = h.stripPrefix(refIndent)
137+
if noPrefixH.startsWith("{") then
138+
noPrefixH ++ t.map(refIndent ++ _.stripPrefix(defIndent)).mkString("\n","\n", "")
139+
else
140+
((" " ++ h) :: t).map(refIndent ++ _.stripPrefix(defIndent)).mkString("\n", "\n", "")
141+
case Nil => rhs
142+
73143
private def definitionRequiresBrackets(tree: Tree)(using Context): Boolean =
74144
NavigateAST
75145
.untypedPath(tree.span)
@@ -102,12 +172,12 @@ final class PcInlineValueProviderImpl(
102172

103173
end referenceRequiresBrackets
104174

105-
private def adjustRhs(pos: SourcePosition) =
175+
private def extendWithSurroundingParens(pos: SourcePosition) =
176+
/** Move `point` by `step` as long as the character at `point` is `acceptedChar` */
106177
def extend(point: Int, acceptedChar: Char, step: Int): Int =
107178
val newPoint = point + step
108-
if newPoint > 0 && newPoint < text.length && text(
109-
newPoint
110-
) == acceptedChar
179+
if newPoint > 0 && newPoint < text.length &&
180+
text(newPoint) == acceptedChar
111181
then extend(newPoint, acceptedChar, step)
112182
else point
113183
val adjustedStart = extend(pos.start, '(', -1)
@@ -139,7 +209,7 @@ final class PcInlineValueProviderImpl(
139209
.exists(e => e.isTerm)
140210
def allreferences = allOccurences.filterNot(_.isDefn)
141211
def inlineAll() =
142-
makeRefsEdits(allreferences, symbols).map((true, _))
212+
makeRefsEdits(allreferences, symbols, definition).map((true, _))
143213
if definition.tree.sourcePos.toLsp.encloses(position)
144214
then if defIsLocal then inlineAll() else Left(Errors.notLocal)
145215
else
@@ -150,14 +220,28 @@ final class PcInlineValueProviderImpl(
150220
ref <- list
151221
.find(_.pos.toLsp.encloses(position))
152222
.toRight(Errors.didNotFindReference)
153-
refEdits <- makeRefsEdits(List(ref), symbols)
223+
refEdits <- makeRefsEdits(List(ref), symbols, definition)
154224
yield (false, refEdits)
155225
end if
156226
end getReferencesToInline
157227

228+
extension (pos: SourcePosition)
229+
def startColumnIndentPadding: String = {
230+
val source = pos.source
231+
val offset = pos.start
232+
var idx = source.startOfLine(offset)
233+
val pad = new StringBuilder
234+
while (idx != offset && idx < source.content().length && source.content()(idx).isWhitespace) {
235+
pad.append(if (idx < source.content().length && source.content()(idx) == '\t') '\t' else ' ')
236+
idx += 1
237+
}
238+
pad.result()
239+
}
240+
158241
private def makeRefsEdits(
159242
refs: List[Occurence],
160-
symbols: Set[Symbol]
243+
symbols: Set[Symbol],
244+
definition: DefinitionTree
161245
): Either[String, List[Reference]] =
162246
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
163247
def buildRef(occurrence: Occurence): Either[String, Reference] =
@@ -178,6 +262,11 @@ final class PcInlineValueProviderImpl(
178262
Right(
179263
Reference(
180264
occurrence.pos.toLsp,
265+
stripIndentPrefix(
266+
extendWithSurroundingParens(definition.tree.rhs.sourcePos),
267+
occurrence.tree.startPos.startColumnIndentPadding,
268+
definition.tree.startPos.startColumnIndentPadding
269+
),
181270
occurrence.parent.map(p =>
182271
RangeOffset(p.sourcePos.start, p.sourcePos.end)
183272
),
@@ -196,7 +285,7 @@ final class PcInlineValueProviderImpl(
196285
)
197286
end makeRefsEdits
198287

199-
end PcInlineValueProviderImpl
288+
end PcInlineValueProvider
200289

201290
case class Occurence(tree: Tree, parent: Option[Tree], pos: SourcePosition):
202291
def isDefn =
@@ -205,3 +294,19 @@ case class Occurence(tree: Tree, parent: Option[Tree], pos: SourcePosition):
205294
case _ => false
206295

207296
case class DefinitionTree(tree: ValDef, pos: SourcePosition)
297+
298+
case class RangeOffset(start: Int, end: Int)
299+
300+
case class Definition(
301+
range: l.Range,
302+
rangeOffsets: RangeOffset,
303+
requiresBrackets: Boolean,
304+
shouldBeRemoved: Boolean
305+
)
306+
307+
case class Reference(
308+
range: l.Range,
309+
rhs: String,
310+
parentOffsets: Option[RangeOffset],
311+
requiresBrackets: Boolean
312+
)

presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ case class ScalaPresentationCompiler(
355355
val empty: Either[String, List[l.TextEdit]] = Right(List())
356356
(compilerAccess
357357
.withInterruptableCompiler(empty, params.token()) { pc =>
358-
new PcInlineValueProviderImpl(pc.compiler(), params)
358+
new PcInlineValueProvider(pc.compiler(), params)
359359
.getInlineTextEdits()
360360
}(params.toQueryContext))
361361
.thenApply {

presentation-compiler/test/dotty/tools/pc/tests/edit/InlineValueSuite.scala

+48-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,8 @@ class InlineValueSuite extends BaseCodeActionSuite with CommonMtagsEnrichments:
228228
| for {
229229
| i <- List(1,2,3)
230230
| } yield i + 1
231-
|val b = (for {
231+
|val b = (
232+
| for {
232233
| i <- List(1,2,3)
233234
| } yield i + 1).map(_ + 1)
234235
|}""".stripMargin
@@ -407,6 +408,52 @@ class InlineValueSuite extends BaseCodeActionSuite with CommonMtagsEnrichments:
407408
InlineErrors.variablesAreShadowed("T.a")
408409
)
409410

411+
@Test def `i7137` =
412+
checkEdit(
413+
"""|object O {
414+
| def foo = {
415+
| val newValue =
416+
| val x = true
417+
| x
418+
| val xx =new<<V>>alue
419+
| }
420+
|}
421+
|""".stripMargin,
422+
"""|object O {
423+
| def foo = {
424+
| val xx =
425+
| val x = true
426+
| x
427+
| }
428+
|}
429+
|""".stripMargin
430+
)
431+
432+
@Test def `i7137a` =
433+
checkEdit(
434+
"""|object O {
435+
| def foo = {
436+
| val newValue = {
437+
| val x = true
438+
| x
439+
| }
440+
| def bar =
441+
| val xx = new<<V>>alue
442+
| }
443+
|}
444+
|""".stripMargin,
445+
"""|object O {
446+
| def foo = {
447+
| def bar =
448+
| val xx = {
449+
| val x = true
450+
| x
451+
| }
452+
| }
453+
|}
454+
|""".stripMargin
455+
)
456+
410457
def checkEdit(
411458
original: String,
412459
expected: String,

0 commit comments

Comments
 (0)