Skip to content

Commit ec6882c

Browse files
committed
Ask user when renaming overriding symbols
When renaming a symbol that is overriding another symbol, we now ask the user whether we should rename the base symbol or only this member.
1 parent 9963693 commit ec6882c

File tree

5 files changed

+103
-27
lines changed

5 files changed

+103
-27
lines changed

language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,7 @@ class DottyLanguageServer extends LanguageServer
353353
val uriTrees = driver.openedTrees(uri)
354354
val pos = sourcePosition(driver, uri, params.getPosition)
355355
val path = Interactive.pathTo(uriTrees, pos)
356-
val syms = Interactive.enclosingSourceSymbols(path, pos).flatMap { sym =>
357-
sym :: sym.allOverriddenSymbols.toList
358-
}
356+
val syms = Interactive.enclosingSourceSymbols(path, pos)
359357
val newName = params.getNewName
360358

361359
def findRenamedReferences(trees: List[SourceTree], syms: List[Symbol], withName: Name): List[SourceTree] = {
@@ -376,13 +374,25 @@ class DottyLanguageServer extends LanguageServer
376374
findRenamedReferences(uriTrees, syms, nameTree.name)
377375

378376
case _ =>
379-
val names = syms.map(_.name.sourceModuleName).toSet
377+
val (include, allSymbols) =
378+
if (syms.exists(_.allOverriddenSymbols.nonEmpty)) {
379+
showMessageRequest(MessageType.Info,
380+
RENAME_OVERRIDDEN_QUESTION,
381+
List(
382+
RENAME_OVERRIDDEN -> (() => (Include.all, syms.flatMap(s => s :: s.allOverriddenSymbols.toList))),
383+
RENAME_NO_OVERRIDDEN -> (() => (Include.all.except(Include.overridden), syms)))
384+
).get.getOrElse((Include.empty, List.empty[Symbol]))
385+
} else {
386+
(Include.all, syms)
387+
}
388+
389+
val names = allSymbols.map(_.name.sourceModuleName).toSet
380390
val trees = names.flatMap(name => driver.allTreesContaining(name.toString)).toList
381-
syms.flatMap { sym =>
391+
allSymbols.flatMap { sym =>
382392
Interactive.findTreesMatching(trees,
383-
Include.all,
384-
sym,
385-
t => names.exists(Interactive.sameName(t.name, _)))
393+
include,
394+
sym,
395+
t => names.exists(Interactive.sameName(t.name, _)))
386396
}
387397
}
388398

@@ -566,12 +576,40 @@ class DottyLanguageServer extends LanguageServer
566576
}
567577
}
568578

579+
/**
580+
* Send a `window/showMessageRequest` to the client, asking to choose between `choices`, and
581+
* perform the associated operation.
582+
*
583+
* @param tpe The type of the request
584+
* @param message The message accompanying the request
585+
* @param choices The choices and their associated operation
586+
* @return A future that will complete with the result of executing the action corresponding to
587+
* the user's response.
588+
*/
589+
private def showMessageRequest[T](tpe: MessageType,
590+
message: String,
591+
choices: List[(String, () => T)]): CompletableFuture[Option[T]] = {
592+
val options = choices.map((title, _) => new MessageActionItem(title))
593+
val request = new ShowMessageRequestParams(options.asJava)
594+
request.setMessage(message)
595+
request.setType(tpe)
596+
597+
client.showMessageRequest(request).thenApply { (answer: MessageActionItem) =>
598+
choices.find(_._1 == answer.getTitle).map {
599+
case (_, action) => action()
600+
}
601+
}
602+
}
569603
}
570604

571605
object DottyLanguageServer {
572606
/** Configuration file normally generated by sbt-dotty */
573607
final val IDE_CONFIG_FILE = ".dotty-ide.json"
574608

609+
final val RENAME_OVERRIDDEN_QUESTION = "Do you want to rename the base member, or only this member?"
610+
final val RENAME_OVERRIDDEN= "Rename the base member"
611+
final val RENAME_NO_OVERRIDDEN = "Rename only this member"
612+
575613
/** Convert an lsp4j.Position to a SourcePosition */
576614
def sourcePosition(driver: InteractiveDriver, uri: URI, pos: lsp4j.Position): SourcePosition = {
577615
val actualPosition =

language-server/test/dotty/tools/languageserver/RenameTest.scala

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -216,19 +216,23 @@ class RenameTest {
216216
}
217217

218218
@Test def renameOverridden: Unit = {
219-
def testRename(m: CodeMarker) =
219+
def testRename(m: CodeMarker, expectations: Set[CodeRange], withOverridden: Option[Boolean]) =
220220
withSources(
221221
code"""class A { def ${m1}foo${m2}: Int = 0 }
222222
class B extends A { override def ${m3}foo${m4}: Int = 1 }
223223
class C extends A { override def ${m5}foo${m6}: Int = 2 }"""
224-
).rename(m, "NewName", Set(m1 to m2, m3 to m4, m5 to m6))
225-
226-
testRename(m1)
227-
testRename(m2)
228-
testRename(m3)
229-
testRename(m4)
230-
testRename(m5)
231-
testRename(m6)
224+
).rename(m, "NewName", expectations, withOverridden)
225+
226+
testRename(m1, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = None)
227+
testRename(m2, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = None)
228+
testRename(m3, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = Some(true))
229+
testRename(m4, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = Some(true))
230+
testRename(m5, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = Some(true))
231+
testRename(m6, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = Some(true))
232+
testRename(m3, Set(m3 to m4), withOverridden = Some(false))
233+
testRename(m4, Set(m3 to m4), withOverridden = Some(false))
234+
testRename(m5, Set(m5 to m6), withOverridden = Some(false))
235+
testRename(m6, Set(m5 to m6), withOverridden = Some(false))
232236

233237
}
234238

language-server/test/dotty/tools/languageserver/util/CodeTester.scala

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,20 @@ class CodeTester(projects: List[Project]) {
9292
* Performs a workspace-wide renaming of the symbol under `marker`, verifies that the positions to
9393
* update match `expected`.
9494
*
95-
* @param marker The position from which to ask for renaming.
96-
* @param newName The new name to give to the symbol.
97-
* @param expected The expected positions to change.
95+
* @param marker The position from which to ask for renaming.
96+
* @param newName The new name to give to the symbol.
97+
* @param expected The expected positions to change.
98+
* @param withOverridden If `None`, do not expect the server to ask whether to include overridden
99+
* symbol. Otherwise, wait for this question from the server and include
100+
* overridden symbols if this is true.
98101
*
99102
* @see dotty.tools.languageserver.util.actions.CodeRename
100103
*/
101-
def rename(marker: CodeMarker, newName: String, expected: Set[CodeRange]): this.type =
102-
doAction(new CodeRename(marker, newName, expected)) // TODO apply changes to the sources and positions
104+
def rename(marker: CodeMarker,
105+
newName: String,
106+
expected: Set[CodeRange],
107+
withOverridden: Option[Boolean] = None): this.type =
108+
doAction(new CodeRename(marker, newName, expected, withOverridden)) // TODO apply changes to the sources and positions
103109

104110
/**
105111
* Queries for all the symbols referenced in the source file in `marker`, verifies that they match

language-server/test/dotty/tools/languageserver/util/actions/CodeRename.scala

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ package dotty.tools.languageserver.util.actions
22

33
import dotty.tools.languageserver.util.embedded.CodeMarker
44
import dotty.tools.languageserver.util.{CodeRange, PositionContext}
5+
import dotty.tools.languageserver.DottyLanguageServer.{RENAME_OVERRIDDEN, RENAME_NO_OVERRIDDEN}
56

6-
import org.junit.Assert.{assertEquals, assertNull}
7+
import org.junit.Assert.{assertEquals, assertNull, fail}
8+
9+
import org.eclipse.lsp4j.{MessageActionItem, ShowMessageRequestParams}
10+
11+
import java.util.concurrent.CompletableFuture
712

813
import scala.collection.JavaConverters._
914

@@ -17,10 +22,31 @@ import scala.collection.JavaConverters._
1722
*/
1823
class CodeRename(override val marker: CodeMarker,
1924
newName: String,
20-
expected: Set[CodeRange]) extends ActionOnMarker {
25+
expected: Set[CodeRange],
26+
withOverridden: Option[Boolean]) extends ActionOnMarker {
27+
28+
private final val TIMEOUT_MS = 10000
2129

2230
override def execute(): Exec[Unit] = {
23-
val results = server.rename(marker.toRenameParams(newName)).get()
31+
val query = server.rename(marker.toRenameParams(newName))
32+
33+
withOverridden.foreach { includeOverridden =>
34+
var question: (ShowMessageRequestParams, CompletableFuture[MessageActionItem]) = null
35+
val startTime = System.currentTimeMillis()
36+
do {
37+
Thread.sleep(50)
38+
question = client.requests.get.headOption.orNull
39+
} while (question == null && System.currentTimeMillis() - startTime < TIMEOUT_MS)
40+
41+
if (question == null) fail("The server didn't ask about overridden symbols.")
42+
43+
val answerStr = if (includeOverridden) RENAME_OVERRIDDEN else RENAME_NO_OVERRIDDEN
44+
val action = question._1.getActions.asScala.find(_.getTitle == answerStr).get
45+
question._2.complete(action)
46+
}
47+
48+
val results = query.get()
49+
2450
val changes = results.getChanges.asScala.mapValues(_.asScala.toSet.map(ch => (ch.getNewText, ch.getRange)))
2551
val expectedChanges = expected.groupBy(_.file.uri).mapValues(_.map(range => (newName, range.toRange)))
2652

language-server/test/dotty/tools/languageserver/util/server/TestClient.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class TestClient extends WorksheetClient {
2323
val diagnostics = new Log[PublishDiagnosticsParams]
2424
val telemetry = new Log[Any]
2525
val worksheetOutput = new Log[WorksheetRunOutput]
26+
val requests = new Log[(ShowMessageRequestParams, CompletableFuture[MessageActionItem])]
2627

2728
override def logMessage(message: MessageParams) = {
2829
log += message
@@ -37,8 +38,9 @@ class TestClient extends WorksheetClient {
3738
}
3839

3940
override def showMessageRequest(requestParams: ShowMessageRequestParams) = {
40-
log += requestParams
41-
new CompletableFuture[MessageActionItem]
41+
val reply = new CompletableFuture[MessageActionItem]
42+
requests += ((requestParams, reply))
43+
reply
4244
}
4345

4446
override def publishDiagnostics(diagnosticsParams: PublishDiagnosticsParams) = {

0 commit comments

Comments
 (0)