From 56bf3588b9719f6021f53461f966f9934d6aa591 Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Wed, 1 Sep 2021 12:34:51 +0200 Subject: [PATCH 01/12] Implement find in files LSP extension --- .../metals/FindNonSourceTextRequest.scala | 3 + .../internal/metals/FindTextInFiles.scala | 127 ++++++++++++++++++ .../metals/MetalsLanguageServer.scala | 39 ++++++ .../src/main/scala/tests/TestingServer.scala | 4 + .../scala/tests/FindTextInFilesSuite.scala | 38 ++++++ 5 files changed, 211 insertions(+) create mode 100644 metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala create mode 100644 metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala create mode 100644 tests/unit/src/test/scala/tests/FindTextInFilesSuite.scala diff --git a/metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala b/metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala new file mode 100644 index 00000000000..1b374590075 --- /dev/null +++ b/metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala @@ -0,0 +1,3 @@ +package scala.meta.internal.metals + +case class FindNonSourceTextRequest(mask: java.lang.String, content: java.lang.String) \ No newline at end of file diff --git a/metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala b/metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala new file mode 100644 index 00000000000..7d9fcd48f8e --- /dev/null +++ b/metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala @@ -0,0 +1,127 @@ +package scala.meta.internal.metals + +import org.eclipse.lsp4j.Location +import org.eclipse.lsp4j.Range +import org.eclipse.lsp4j.Position +import scala.collection.mutable.ArrayBuffer +import scala.meta.internal.metals.MetalsEnrichments._ +import scala.util.control.NonFatal +import scala.meta.io.AbsolutePath +import scala.meta.internal.io.FileIO +import java.nio.file.Files +import java.nio.file.SimpleFileVisitor +import java.nio.file.Path +import java.nio.file.attribute.BasicFileAttributes +import java.nio.file.FileVisitResult +import java.io.BufferedReader +import java.io.InputStreamReader + +class FindTextInFiles( + buildTargets: BuildTargets, + workspace: () => AbsolutePath +) { + def find(mask: String, content: String): List[Location] = { + if (mask == null || content == null) { + List.empty[Location] + } else { + val allLocations: ArrayBuffer[Location] = new ArrayBuffer[Location]() + + buildTargets.allWorkspaceJars.foreach { classpathEntry => + try { + val jarLocations: List[Location] = + if (classpathEntry.isFile && classpathEntry.isJar) { + visitJar(classpathEntry, mask, content) + } else Nil + + allLocations ++= jarLocations + } catch { + case NonFatal(e) => + scribe.error( + s"Failed to find in non-source files for $classpathEntry", + e + ) + } + } + + allLocations.toList + } + } + + def isSuitableFile(path: AbsolutePath, mask: String): Boolean = { + path.isFile && path.filename.contains(mask) + } + + def visitJar( + path: AbsolutePath, + mask: String, + content: String + ): List[Location] = { + val jarLocations: ArrayBuffer[Location] = new ArrayBuffer[Location]() + + FileIO.withJarFileSystem(path, create = false, close = true) { root => + Files.walkFileTree( + root.toNIO, + new SimpleFileVisitor[Path] { + override def visitFile( + file: Path, + attrs: BasicFileAttributes + ): FileVisitResult = { + val absPath = AbsolutePath(file) + if (isSuitableFile(absPath, mask)) { + val fileRanges: List[Range] = visitFileInsideJar(absPath, content) + + val fileLocations: List[Location] = + if (fileRanges.nonEmpty) { + val result = absPath.toFileOnDisk(workspace()) + fileRanges.map(range => new Location(result.toString, range)) + } else Nil + + jarLocations ++= fileLocations + } + + FileVisitResult.CONTINUE + } + } + ) + } + + jarLocations.toList + } + + def visitFileInsideJar(path: AbsolutePath, content: String): List[Range] = { + var reader: BufferedReader = null + val positions: ArrayBuffer[Int] = new ArrayBuffer[Int]() + val results: ArrayBuffer[Range] = new ArrayBuffer[Range]() + val contentLength: Int = content.length() + + try { + reader = new BufferedReader( + new InputStreamReader(Files.newInputStream(path.toNIO)) + ) + var lineNumber: Int = 0 + var line: String = reader.readLine() + while (line != null) { + var occurence = line.indexOf(content) + while (occurence != -1) { + positions += occurence + occurence = line.indexOf(content, occurence + 1) + } + + positions.foreach { position => + results += new Range( + new Position(lineNumber, position), + new Position(lineNumber, position + contentLength) + ) + } + + positions.clear() + lineNumber = lineNumber + 1 + line = reader.readLine() + } + } finally { + if (reader != null) reader.close() + } + + results.toList + } +} diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala index 275b5f64d4b..6bee8ac5af6 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala @@ -266,6 +266,7 @@ class MetalsLanguageServer( var worksheetProvider: WorksheetProvider = _ var popupChoiceReset: PopupChoiceReset = _ var stacktraceAnalyzer: StacktraceAnalyzer = _ + var findTextInFiles: FindTextInFiles = _ private val clientConfig: ClientConfiguration = new ClientConfiguration( @@ -754,6 +755,7 @@ class MetalsLanguageServer( () => bspSession.map(_.mainConnectionIsBloop).getOrElse(false) ) } + findTextInFiles = new FindTextInFiles(buildTargets, () => workspace) } } @@ -1887,6 +1889,43 @@ class MetalsLanguageServer( .orNull }.asJava + @JsonRequest("metals/findNonSourceFiles") + def findTextInFilesCommand( + params: FindNonSourceTextRequest + ): CompletableFuture[util.List[Location]] = { + def readMask: Future[String] = Option(params.mask) match { + case Some(mask) => + Future.successful(mask) + case None => + languageClient + .metalsInputBox( + MetalsInputBoxParams(value = ".conf", prompt = "Enter file mask") + ) + .asScala + .map(result => result.value) + } + + def readContent: Future[String] = Option(params.content) match { + case Some(content) => + Future.successful(content) + case None => + languageClient + .metalsInputBox( + MetalsInputBoxParams( + prompt = "Enter content to search for" + ) + ) + .asScala + .map(result => result.value) + } + + (for { + mask <- readMask + content <- readContent + locations <- Future(findTextInFiles.find(mask, content).asJava) + } yield locations).asJava + } + private def generateBspConfig(): Future[Unit] = { val servers: List[BuildTool with BuildServerProvider] = buildTools.loadSupported().collect { diff --git a/tests/unit/src/main/scala/tests/TestingServer.scala b/tests/unit/src/main/scala/tests/TestingServer.scala index 5ae71d0348e..9749441739c 100644 --- a/tests/unit/src/main/scala/tests/TestingServer.scala +++ b/tests/unit/src/main/scala/tests/TestingServer.scala @@ -1472,6 +1472,10 @@ final class TestingServer( Assertions.assertNoDiff(obtained, expected) } + def findTextInFiles(mask: String, content: String): Future[List[Location]] = { + server.findTextInFilesCommand(FindNonSourceTextRequest(mask, content)).asScala.map(_.asScala.toList) + } + def textContents(filename: String): String = toPath(filename).toInputFromBuffers(buffers).text def textContentsOnDisk(filename: String): String = diff --git a/tests/unit/src/test/scala/tests/FindTextInFilesSuite.scala b/tests/unit/src/test/scala/tests/FindTextInFilesSuite.scala new file mode 100644 index 00000000000..edf316c59ce --- /dev/null +++ b/tests/unit/src/test/scala/tests/FindTextInFilesSuite.scala @@ -0,0 +1,38 @@ +package tests + +import scala.meta.internal.metals.Directories + +import org.eclipse.lsp4j.Location +import org.eclipse.lsp4j.Position +import org.eclipse.lsp4j.Range + +class FindTextInFilesSuite extends BaseLspSuite("find-text-in-jar-files") { + test("find exact string match in .conf file inside jar") { + val expectedUri = + s"${workspace.resolve(Directories.dependencies)}/akka-actor_2.12-2.6.16.jar/reference.conf" + val expectedLocations: List[Location] = List( + new Location( + expectedUri, + new Range(new Position(95, 2), new Position(95, 20)) + ), + new Location( + expectedUri, + new Range(new Position(1177, 40), new Position(1177, 58)) + ) + ) + for { + _ <- initialize( + s"""/metals.json + |{ + | "a": { + | "scalaVersion": "2.12.4", + | "libraryDependencies": ["com.typesafe.akka::akka-actor-typed:2.6.16"] + | } + |} + """.stripMargin + ) + locations <- server.findTextInFiles(".conf", "jvm-shutdown-hooks") + _ = assertEquals(locations, expectedLocations) + } yield () + } +} From 103add78baf264521141cd8ed873bc1df11edbfc Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Wed, 1 Sep 2021 12:37:15 +0200 Subject: [PATCH 02/12] Fix formatting --- .../meta/internal/metals/FindNonSourceTextRequest.scala | 5 ++++- tests/unit/src/main/scala/tests/TestingServer.scala | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala b/metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala index 1b374590075..4cd48eb0a6b 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala @@ -1,3 +1,6 @@ package scala.meta.internal.metals -case class FindNonSourceTextRequest(mask: java.lang.String, content: java.lang.String) \ No newline at end of file +case class FindNonSourceTextRequest( + mask: java.lang.String, + content: java.lang.String +) diff --git a/tests/unit/src/main/scala/tests/TestingServer.scala b/tests/unit/src/main/scala/tests/TestingServer.scala index 9749441739c..c9bd0e7d99e 100644 --- a/tests/unit/src/main/scala/tests/TestingServer.scala +++ b/tests/unit/src/main/scala/tests/TestingServer.scala @@ -1473,7 +1473,10 @@ final class TestingServer( } def findTextInFiles(mask: String, content: String): Future[List[Location]] = { - server.findTextInFilesCommand(FindNonSourceTextRequest(mask, content)).asScala.map(_.asScala.toList) + server + .findTextInFilesCommand(FindNonSourceTextRequest(mask, content)) + .asScala + .map(_.asScala.toList) } def textContents(filename: String): String = From a021707cf655990b3e789905401869ebf463ae6f Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Wed, 1 Sep 2021 13:09:58 +0200 Subject: [PATCH 03/12] Organize imports --- .../internal/metals/FindTextInFiles.scala | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala b/metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala index 7d9fcd48f8e..ae401af2f9a 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala @@ -1,20 +1,23 @@ package scala.meta.internal.metals -import org.eclipse.lsp4j.Location -import org.eclipse.lsp4j.Range -import org.eclipse.lsp4j.Position -import scala.collection.mutable.ArrayBuffer -import scala.meta.internal.metals.MetalsEnrichments._ -import scala.util.control.NonFatal -import scala.meta.io.AbsolutePath -import scala.meta.internal.io.FileIO +import java.io.BufferedReader +import java.io.InputStreamReader +import java.nio.file.FileVisitResult import java.nio.file.Files -import java.nio.file.SimpleFileVisitor import java.nio.file.Path +import java.nio.file.SimpleFileVisitor import java.nio.file.attribute.BasicFileAttributes -import java.nio.file.FileVisitResult -import java.io.BufferedReader -import java.io.InputStreamReader + +import scala.collection.mutable.ArrayBuffer +import scala.util.control.NonFatal + +import scala.meta.internal.io.FileIO +import scala.meta.internal.metals.MetalsEnrichments._ +import scala.meta.io.AbsolutePath + +import org.eclipse.lsp4j.Location +import org.eclipse.lsp4j.Position +import org.eclipse.lsp4j.Range class FindTextInFiles( buildTargets: BuildTargets, From 24d87c6426555d77f156f05e624669629ade0546 Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Wed, 1 Sep 2021 18:49:48 +0200 Subject: [PATCH 04/12] Addressed some of comments --- .../metals/FindNonSourceTextRequest.scala | 6 - .../metals/FindTextInDependencyJars.scala | 107 ++++++++++++++ .../FindTextInDependencyJarsRequest.scala | 6 + .../internal/metals/FindTextInFiles.scala | 130 ------------------ .../metals/MetalsLanguageServer.scala | 18 ++- .../src/main/scala/tests/TestingServer.scala | 8 +- ...la => FindTextInDependencyJarsSuite.scala} | 16 ++- 7 files changed, 143 insertions(+), 148 deletions(-) delete mode 100644 metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala create mode 100644 metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala create mode 100644 metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJarsRequest.scala delete mode 100644 metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala rename tests/unit/src/test/scala/tests/{FindTextInFilesSuite.scala => FindTextInDependencyJarsSuite.scala} (70%) diff --git a/metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala b/metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala deleted file mode 100644 index 4cd48eb0a6b..00000000000 --- a/metals/src/main/scala/scala/meta/internal/metals/FindNonSourceTextRequest.scala +++ /dev/null @@ -1,6 +0,0 @@ -package scala.meta.internal.metals - -case class FindNonSourceTextRequest( - mask: java.lang.String, - content: java.lang.String -) diff --git a/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala b/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala new file mode 100644 index 00000000000..5c81c8f6862 --- /dev/null +++ b/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala @@ -0,0 +1,107 @@ +package scala.meta.internal.metals + +import java.io.BufferedReader +import java.io.InputStreamReader +import java.nio.file.Files + +import scala.collection.mutable.ArrayBuffer +import scala.util.control.NonFatal + +import scala.meta.internal.io.FileIO +import scala.meta.internal.metals.MetalsEnrichments._ +import scala.meta.io.AbsolutePath + +import org.eclipse.lsp4j.Location +import org.eclipse.lsp4j.Position +import org.eclipse.lsp4j.Range + +class FindTextInDependencyJars( + buildTargets: BuildTargets, + workspace: () => AbsolutePath +) { + def find(mask: String, content: String): List[Location] = { + val allLocations: ArrayBuffer[Location] = new ArrayBuffer[Location]() + + buildTargets.allWorkspaceJars.foreach { classpathEntry => + try { + val jarLocations: List[Location] = + if (classpathEntry.isFile && classpathEntry.isJar) { + visitJar(classpathEntry, mask, content) + } else Nil + + allLocations ++= jarLocations + } catch { + case NonFatal(e) => + scribe.error( + s"Failed to find text in dependency files for $classpathEntry", + e + ) + } + } + + allLocations.toList + } + + private def isSuitableFile(path: AbsolutePath, mask: String): Boolean = { + path.isFile && path.filename.contains(mask) + } + + private def visitJar( + path: AbsolutePath, + mask: String, + content: String + ): List[Location] = { + FileIO + .withJarFileSystem(path, create = false, close = true) { root => + FileIO + .listAllFilesRecursively(root) + .filter(isSuitableFile(_, mask)) + .flatMap { absPath => + val fileRanges: List[Range] = visitFileInsideJar(absPath, content) + if (fileRanges.nonEmpty) { + val result = absPath.toFileOnDisk(workspace()) + fileRanges + .map(range => new Location(result.toURI.toString, range)) + } else Nil + } + } + .toList + } + + private def visitFileInsideJar(path: AbsolutePath, content: String): List[Range] = { + var reader: BufferedReader = null + val positions: ArrayBuffer[Int] = new ArrayBuffer[Int]() + val results: ArrayBuffer[Range] = new ArrayBuffer[Range]() + val contentLength: Int = content.length() + + try { + reader = new BufferedReader( + new InputStreamReader(Files.newInputStream(path.toNIO)) + ) + var lineNumber: Int = 0 + var line: String = reader.readLine() + while (line != null) { + var occurence = line.indexOf(content) + while (occurence != -1) { + positions += occurence + occurence = line.indexOf(content, occurence + 1) + } + + positions.foreach { position => + results += new Range( + new Position(lineNumber, position), + new Position(lineNumber, position + contentLength) + ) + } + + positions.clear() + lineNumber = lineNumber + 1 + line = reader.readLine() + } + } finally { + if (reader != null) reader.close() + } + + results.toList + } +} diff --git a/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJarsRequest.scala b/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJarsRequest.scala new file mode 100644 index 00000000000..8444729ac3b --- /dev/null +++ b/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJarsRequest.scala @@ -0,0 +1,6 @@ +package scala.meta.internal.metals + +case class FindTextInDependencyJarsRequest( + mask: String, + content: String +) diff --git a/metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala b/metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala deleted file mode 100644 index ae401af2f9a..00000000000 --- a/metals/src/main/scala/scala/meta/internal/metals/FindTextInFiles.scala +++ /dev/null @@ -1,130 +0,0 @@ -package scala.meta.internal.metals - -import java.io.BufferedReader -import java.io.InputStreamReader -import java.nio.file.FileVisitResult -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.SimpleFileVisitor -import java.nio.file.attribute.BasicFileAttributes - -import scala.collection.mutable.ArrayBuffer -import scala.util.control.NonFatal - -import scala.meta.internal.io.FileIO -import scala.meta.internal.metals.MetalsEnrichments._ -import scala.meta.io.AbsolutePath - -import org.eclipse.lsp4j.Location -import org.eclipse.lsp4j.Position -import org.eclipse.lsp4j.Range - -class FindTextInFiles( - buildTargets: BuildTargets, - workspace: () => AbsolutePath -) { - def find(mask: String, content: String): List[Location] = { - if (mask == null || content == null) { - List.empty[Location] - } else { - val allLocations: ArrayBuffer[Location] = new ArrayBuffer[Location]() - - buildTargets.allWorkspaceJars.foreach { classpathEntry => - try { - val jarLocations: List[Location] = - if (classpathEntry.isFile && classpathEntry.isJar) { - visitJar(classpathEntry, mask, content) - } else Nil - - allLocations ++= jarLocations - } catch { - case NonFatal(e) => - scribe.error( - s"Failed to find in non-source files for $classpathEntry", - e - ) - } - } - - allLocations.toList - } - } - - def isSuitableFile(path: AbsolutePath, mask: String): Boolean = { - path.isFile && path.filename.contains(mask) - } - - def visitJar( - path: AbsolutePath, - mask: String, - content: String - ): List[Location] = { - val jarLocations: ArrayBuffer[Location] = new ArrayBuffer[Location]() - - FileIO.withJarFileSystem(path, create = false, close = true) { root => - Files.walkFileTree( - root.toNIO, - new SimpleFileVisitor[Path] { - override def visitFile( - file: Path, - attrs: BasicFileAttributes - ): FileVisitResult = { - val absPath = AbsolutePath(file) - if (isSuitableFile(absPath, mask)) { - val fileRanges: List[Range] = visitFileInsideJar(absPath, content) - - val fileLocations: List[Location] = - if (fileRanges.nonEmpty) { - val result = absPath.toFileOnDisk(workspace()) - fileRanges.map(range => new Location(result.toString, range)) - } else Nil - - jarLocations ++= fileLocations - } - - FileVisitResult.CONTINUE - } - } - ) - } - - jarLocations.toList - } - - def visitFileInsideJar(path: AbsolutePath, content: String): List[Range] = { - var reader: BufferedReader = null - val positions: ArrayBuffer[Int] = new ArrayBuffer[Int]() - val results: ArrayBuffer[Range] = new ArrayBuffer[Range]() - val contentLength: Int = content.length() - - try { - reader = new BufferedReader( - new InputStreamReader(Files.newInputStream(path.toNIO)) - ) - var lineNumber: Int = 0 - var line: String = reader.readLine() - while (line != null) { - var occurence = line.indexOf(content) - while (occurence != -1) { - positions += occurence - occurence = line.indexOf(content, occurence + 1) - } - - positions.foreach { position => - results += new Range( - new Position(lineNumber, position), - new Position(lineNumber, position + contentLength) - ) - } - - positions.clear() - lineNumber = lineNumber + 1 - line = reader.readLine() - } - } finally { - if (reader != null) reader.close() - } - - results.toList - } -} diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala index 6bee8ac5af6..c7dff843107 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala @@ -266,7 +266,7 @@ class MetalsLanguageServer( var worksheetProvider: WorksheetProvider = _ var popupChoiceReset: PopupChoiceReset = _ var stacktraceAnalyzer: StacktraceAnalyzer = _ - var findTextInFiles: FindTextInFiles = _ + var findTextInJars: FindTextInDependencyJars = _ private val clientConfig: ClientConfiguration = new ClientConfiguration( @@ -755,7 +755,8 @@ class MetalsLanguageServer( () => bspSession.map(_.mainConnectionIsBloop).getOrElse(false) ) } - findTextInFiles = new FindTextInFiles(buildTargets, () => workspace) + findTextInJars = + new FindTextInDependencyJars(buildTargets, () => workspace) } } @@ -1889,9 +1890,9 @@ class MetalsLanguageServer( .orNull }.asJava - @JsonRequest("metals/findNonSourceFiles") - def findTextInFilesCommand( - params: FindNonSourceTextRequest + @JsonRequest("metals/findTextInDependencyJars") + def findTextInDependencyJars( + params: FindTextInDependencyJarsRequest ): CompletableFuture[util.List[Location]] = { def readMask: Future[String] = Option(params.mask) match { case Some(mask) => @@ -1922,8 +1923,11 @@ class MetalsLanguageServer( (for { mask <- readMask content <- readContent - locations <- Future(findTextInFiles.find(mask, content).asJava) - } yield locations).asJava + locations <- + if (mask == null || content == null) { + Future.successful(List.empty[Location]) + } else Future(findTextInJars.find(mask, content)) + } yield locations.asJava).asJava } private def generateBspConfig(): Future[Unit] = { diff --git a/tests/unit/src/main/scala/tests/TestingServer.scala b/tests/unit/src/main/scala/tests/TestingServer.scala index c9bd0e7d99e..3d6a8b123c0 100644 --- a/tests/unit/src/main/scala/tests/TestingServer.scala +++ b/tests/unit/src/main/scala/tests/TestingServer.scala @@ -35,6 +35,7 @@ import scala.meta.internal.metals.DebugUnresolvedMainClassParams import scala.meta.internal.metals.DidFocusResult import scala.meta.internal.metals.Directories import scala.meta.internal.metals.HoverExtParams +import scala.meta.internal.metals.FindTextInDependencyJarsRequest import scala.meta.internal.metals.InitializationOptions import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals.MetalsLanguageServer @@ -1472,9 +1473,12 @@ final class TestingServer( Assertions.assertNoDiff(obtained, expected) } - def findTextInFiles(mask: String, content: String): Future[List[Location]] = { + def findTextInDependencyJars( + mask: String, + content: String + ): Future[List[Location]] = { server - .findTextInFilesCommand(FindNonSourceTextRequest(mask, content)) + .findTextInDependencyJars(FindTextInDependencyJarsRequest(mask, content)) .asScala .map(_.asScala.toList) } diff --git a/tests/unit/src/test/scala/tests/FindTextInFilesSuite.scala b/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala similarity index 70% rename from tests/unit/src/test/scala/tests/FindTextInFilesSuite.scala rename to tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala index edf316c59ce..4f60dbe8616 100644 --- a/tests/unit/src/test/scala/tests/FindTextInFilesSuite.scala +++ b/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala @@ -6,10 +6,17 @@ import org.eclipse.lsp4j.Location import org.eclipse.lsp4j.Position import org.eclipse.lsp4j.Range -class FindTextInFilesSuite extends BaseLspSuite("find-text-in-jar-files") { +class FindTextInDependencyJarsSuite + extends BaseLspSuite("find-text-in-dependency-jars") { test("find exact string match in .conf file inside jar") { val expectedUri = - s"${workspace.resolve(Directories.dependencies)}/akka-actor_2.12-2.6.16.jar/reference.conf" + workspace + .resolve(Directories.dependencies) + .resolve("akka-actor_2.12-2.6.16.jar") + .resolve("reference.conf") + .toURI + .toString() + val expectedLocations: List[Location] = List( new Location( expectedUri, @@ -31,7 +38,10 @@ class FindTextInFilesSuite extends BaseLspSuite("find-text-in-jar-files") { |} """.stripMargin ) - locations <- server.findTextInFiles(".conf", "jvm-shutdown-hooks") + locations <- server.findTextInDependencyJars( + ".conf", + "jvm-shutdown-hooks" + ) _ = assertEquals(locations, expectedLocations) } yield () } From 6c4a7f8a20c6cd154abb00480e493fc6a1e4bf5f Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Wed, 1 Sep 2021 18:51:59 +0200 Subject: [PATCH 05/12] Fix fmt --- .../meta/internal/metals/FindTextInDependencyJars.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala b/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala index 5c81c8f6862..93414097109 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala @@ -68,7 +68,10 @@ class FindTextInDependencyJars( .toList } - private def visitFileInsideJar(path: AbsolutePath, content: String): List[Range] = { + private def visitFileInsideJar( + path: AbsolutePath, + content: String + ): List[Range] = { var reader: BufferedReader = null val positions: ArrayBuffer[Int] = new ArrayBuffer[Int]() val results: ArrayBuffer[Range] = new ArrayBuffer[Range]() From 849d8f227ecab6807442bb1fb739e81aaaa2aec6 Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Wed, 6 Oct 2021 20:29:47 +0200 Subject: [PATCH 06/12] Address review comments --- .../metals/FindTextInDependencyJars.scala | 110 ---------- .../FindTextInDependencyJarsRequest.scala | 6 - .../metals/MetalsLanguageServer.scala | 43 +--- .../findfiles/FindTextInDependencyJars.scala | 202 ++++++++++++++++++ .../FindTextInDependencyJarsRequest.scala | 26 +++ .../src/main/scala/tests/TestingServer.scala | 18 +- .../tests/FindTextInDependencyJarsSuite.scala | 4 +- 7 files changed, 251 insertions(+), 158 deletions(-) delete mode 100644 metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala delete mode 100644 metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJarsRequest.scala create mode 100644 metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala create mode 100644 metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJarsRequest.scala diff --git a/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala b/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala deleted file mode 100644 index 93414097109..00000000000 --- a/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJars.scala +++ /dev/null @@ -1,110 +0,0 @@ -package scala.meta.internal.metals - -import java.io.BufferedReader -import java.io.InputStreamReader -import java.nio.file.Files - -import scala.collection.mutable.ArrayBuffer -import scala.util.control.NonFatal - -import scala.meta.internal.io.FileIO -import scala.meta.internal.metals.MetalsEnrichments._ -import scala.meta.io.AbsolutePath - -import org.eclipse.lsp4j.Location -import org.eclipse.lsp4j.Position -import org.eclipse.lsp4j.Range - -class FindTextInDependencyJars( - buildTargets: BuildTargets, - workspace: () => AbsolutePath -) { - def find(mask: String, content: String): List[Location] = { - val allLocations: ArrayBuffer[Location] = new ArrayBuffer[Location]() - - buildTargets.allWorkspaceJars.foreach { classpathEntry => - try { - val jarLocations: List[Location] = - if (classpathEntry.isFile && classpathEntry.isJar) { - visitJar(classpathEntry, mask, content) - } else Nil - - allLocations ++= jarLocations - } catch { - case NonFatal(e) => - scribe.error( - s"Failed to find text in dependency files for $classpathEntry", - e - ) - } - } - - allLocations.toList - } - - private def isSuitableFile(path: AbsolutePath, mask: String): Boolean = { - path.isFile && path.filename.contains(mask) - } - - private def visitJar( - path: AbsolutePath, - mask: String, - content: String - ): List[Location] = { - FileIO - .withJarFileSystem(path, create = false, close = true) { root => - FileIO - .listAllFilesRecursively(root) - .filter(isSuitableFile(_, mask)) - .flatMap { absPath => - val fileRanges: List[Range] = visitFileInsideJar(absPath, content) - if (fileRanges.nonEmpty) { - val result = absPath.toFileOnDisk(workspace()) - fileRanges - .map(range => new Location(result.toURI.toString, range)) - } else Nil - } - } - .toList - } - - private def visitFileInsideJar( - path: AbsolutePath, - content: String - ): List[Range] = { - var reader: BufferedReader = null - val positions: ArrayBuffer[Int] = new ArrayBuffer[Int]() - val results: ArrayBuffer[Range] = new ArrayBuffer[Range]() - val contentLength: Int = content.length() - - try { - reader = new BufferedReader( - new InputStreamReader(Files.newInputStream(path.toNIO)) - ) - var lineNumber: Int = 0 - var line: String = reader.readLine() - while (line != null) { - var occurence = line.indexOf(content) - while (occurence != -1) { - positions += occurence - occurence = line.indexOf(content, occurence + 1) - } - - positions.foreach { position => - results += new Range( - new Position(lineNumber, position), - new Position(lineNumber, position + contentLength) - ) - } - - positions.clear() - lineNumber = lineNumber + 1 - line = reader.readLine() - } - } finally { - if (reader != null) reader.close() - } - - results.toList - } -} diff --git a/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJarsRequest.scala b/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJarsRequest.scala deleted file mode 100644 index 8444729ac3b..00000000000 --- a/metals/src/main/scala/scala/meta/internal/metals/FindTextInDependencyJarsRequest.scala +++ /dev/null @@ -1,6 +0,0 @@ -package scala.meta.internal.metals - -case class FindTextInDependencyJarsRequest( - mask: String, - content: String -) diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala index c7dff843107..abada90e16c 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala @@ -57,6 +57,7 @@ import scala.meta.internal.metals.codelenses.WorksheetCodeLens import scala.meta.internal.metals.debug.BuildTargetClasses import scala.meta.internal.metals.debug.DebugParametersJsonParsers import scala.meta.internal.metals.debug.DebugProvider +import scala.meta.internal.metals.findfiles._ import scala.meta.internal.metals.formatting.OnTypeFormattingProvider import scala.meta.internal.metals.formatting.RangeFormattingProvider import scala.meta.internal.metals.newScalaFile.NewFileProvider @@ -755,8 +756,11 @@ class MetalsLanguageServer( () => bspSession.map(_.mainConnectionIsBloop).getOrElse(false) ) } - findTextInJars = - new FindTextInDependencyJars(buildTargets, () => workspace) + findTextInJars = new FindTextInDependencyJars( + buildTargets, + () => workspace, + languageClient + ) } } @@ -1894,40 +1898,7 @@ class MetalsLanguageServer( def findTextInDependencyJars( params: FindTextInDependencyJarsRequest ): CompletableFuture[util.List[Location]] = { - def readMask: Future[String] = Option(params.mask) match { - case Some(mask) => - Future.successful(mask) - case None => - languageClient - .metalsInputBox( - MetalsInputBoxParams(value = ".conf", prompt = "Enter file mask") - ) - .asScala - .map(result => result.value) - } - - def readContent: Future[String] = Option(params.content) match { - case Some(content) => - Future.successful(content) - case None => - languageClient - .metalsInputBox( - MetalsInputBoxParams( - prompt = "Enter content to search for" - ) - ) - .asScala - .map(result => result.value) - } - - (for { - mask <- readMask - content <- readContent - locations <- - if (mask == null || content == null) { - Future.successful(List.empty[Location]) - } else Future(findTextInJars.find(mask, content)) - } yield locations.asJava).asJava + findTextInJars.find(params).map(_.asJava).asJava } private def generateBspConfig(): Future[Unit] = { diff --git a/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala b/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala new file mode 100644 index 00000000000..48504191870 --- /dev/null +++ b/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala @@ -0,0 +1,202 @@ +package scala.meta.internal.metals.findfiles + +import java.io.BufferedReader +import java.io.InputStreamReader +import java.nio.file.Files + +import scala.collection.mutable.ArrayBuffer +import scala.concurrent.ExecutionContext +import scala.concurrent.Future +import scala.util.control.NonFatal + +import scala.meta.internal.io.FileIO +import scala.meta.internal.metals.MetalsEnrichments._ +import scala.meta.internal.metals.ScalafmtConfig.PathMatcher.Nio +import scala.meta.internal.metals._ +import scala.meta.io.AbsolutePath + +import org.eclipse.lsp4j.Location +import org.eclipse.lsp4j.Position +import org.eclipse.lsp4j.Range + +class FindTextInDependencyJars( + buildTargets: BuildTargets, + workspace: () => AbsolutePath, + languageClient: MetalsLanguageClient +)(implicit ec: ExecutionContext) { + import FindTextInDependencyJars._ + + def find(request: FindTextInDependencyJarsRequest): Future[List[Location]] = { + val req = Request.fromRequest(request) + + def readInclude: Future[Option[String]] = + paramOrInput(req.options.flatMap(_.include))( + MetalsInputBoxParams(value = ".conf", prompt = "Enter file mask") + ) + + def readPattern: Future[Option[String]] = + paramOrInput(Option(req.query.pattern))( + MetalsInputBoxParams(prompt = "Enter content to search for") + ) + + readInclude.zipWith(readPattern) { (maybeInclude, maybePattern) => + maybeInclude + .zip(maybePattern) + .map { case (include, pattern) => + val allLocations: ArrayBuffer[Location] = new ArrayBuffer[Location]() + + buildTargets.allWorkspaceJars.foreach { classpathEntry => + try { + val jarLocations: List[Location] = + if (classpathEntry.isFile && classpathEntry.isJar) { + visitJar( + path = classpathEntry, + include = include, + exclude = req.options.flatMap(_.exclude), + pattern = pattern + ) + } else Nil + + allLocations ++= jarLocations + } catch { + case NonFatal(e) => + scribe.error( + s"Failed to find text in dependency files for $classpathEntry", + e + ) + } + } + + allLocations.toList + } + .flatten + .toList + } + } + + private def isSuitableFile( + path: AbsolutePath, + include: String, + exclude: Option[String] + ): Boolean = { + path.isFile && + Nio(include).matches(path) && + exclude.forall(e => !Nio(e).matches(path)) + } + + private def visitJar( + path: AbsolutePath, + include: String, + exclude: Option[String], + pattern: String + ): List[Location] = { + FileIO + .withJarFileSystem(path, create = false, close = true) { root => + FileIO + .listAllFilesRecursively(root) + .filter(isSuitableFile(_, include, exclude)) + .flatMap { absPath => + val fileRanges: List[Range] = visitFileInsideJar(absPath, pattern) + if (fileRanges.nonEmpty) { + val result = absPath.toFileOnDisk(workspace()) + fileRanges + .map(range => new Location(result.toURI.toString, range)) + } else Nil + } + } + .toList + } + + private def visitFileInsideJar( + path: AbsolutePath, + pattern: String + ): List[Range] = { + var reader: BufferedReader = null + val positions: ArrayBuffer[Int] = new ArrayBuffer[Int]() + val results: ArrayBuffer[Range] = new ArrayBuffer[Range]() + val contentLength: Int = pattern.length() + + try { + reader = new BufferedReader( + new InputStreamReader(Files.newInputStream(path.toNIO)) + ) + var lineNumber: Int = 0 + var line: String = reader.readLine() + while (line != null) { + var occurence = line.indexOf(pattern) + while (occurence != -1) { + positions += occurence + occurence = line.indexOf(pattern, occurence + 1) + } + + positions.foreach { position => + results += new Range( + new Position(lineNumber, position), + new Position(lineNumber, position + contentLength) + ) + } + + positions.clear() + lineNumber = lineNumber + 1 + line = reader.readLine() + } + } finally { + if (reader != null) reader.close() + } + + results.toList + } + + private def paramOrInput( + param: Option[String] + )(input: => MetalsInputBoxParams): Future[Option[String]] = { + param match { + case Some(value) => + Future.successful(Some(value)) + case None => + languageClient + .metalsInputBox(input) + .asScala + .map(checkResult) + } + } + + private def checkResult(result: MetalsInputBoxResult) = result match { + case name if !name.cancelled && name.value.nonEmpty => + Some(name.value) + case _ => + None + } +} + +object FindTextInDependencyJars { + // These are just more typesafe wrappers, duplicating the structure of original model + private case class Request(options: Option[Options], query: TextSearchQuery) + private object Request { + def fromRequest(request: FindTextInDependencyJarsRequest): Request = { + val options = Option(request.options).map { options => + Options( + include = Option(options.include), + exclude = Option(options.exclude) + ) + } + + val query = TextSearchQuery( + pattern = request.query.pattern, + isRegExp = Option(request.query.isRegExp), + isCaseSensitive = Option(request.query.isCaseSensitive), + isWordMatch = Option(request.query.isWordMatch) + ) + + Request(options = options, query = query) + } + } + + private case class Options(include: Option[String], exclude: Option[String]) + private case class TextSearchQuery( + pattern: String, + isRegExp: Option[Boolean], + isCaseSensitive: Option[Boolean], + isWordMatch: Option[Boolean] + ) +} diff --git a/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJarsRequest.scala b/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJarsRequest.scala new file mode 100644 index 00000000000..04516e9a4d4 --- /dev/null +++ b/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJarsRequest.scala @@ -0,0 +1,26 @@ +package scala.meta.internal.metals.findfiles + +import javax.annotation.Nullable + +case class FindTextInDependencyJarsRequest( + @Nullable + options: FindTextInFilesOptions, + query: TextSearchQuery +) + +case class TextSearchQuery( + pattern: String, + @Nullable + isRegExp: java.lang.Boolean, + @Nullable + isCaseSensitive: java.lang.Boolean, + @Nullable + isWordMatch: java.lang.Boolean +) + +case class FindTextInFilesOptions( + @Nullable + include: String, + @Nullable + exclude: String +) diff --git a/tests/unit/src/main/scala/tests/TestingServer.scala b/tests/unit/src/main/scala/tests/TestingServer.scala index 3d6a8b123c0..988c0c13b6d 100644 --- a/tests/unit/src/main/scala/tests/TestingServer.scala +++ b/tests/unit/src/main/scala/tests/TestingServer.scala @@ -35,7 +35,6 @@ import scala.meta.internal.metals.DebugUnresolvedMainClassParams import scala.meta.internal.metals.DidFocusResult import scala.meta.internal.metals.Directories import scala.meta.internal.metals.HoverExtParams -import scala.meta.internal.metals.FindTextInDependencyJarsRequest import scala.meta.internal.metals.InitializationOptions import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals.MetalsLanguageServer @@ -50,6 +49,7 @@ import scala.meta.internal.metals.UserConfiguration import scala.meta.internal.metals.WindowStateDidChangeParams import scala.meta.internal.metals.debug.Stoppage import scala.meta.internal.metals.debug.TestDebugger +import scala.meta.internal.metals.findfiles._ import scala.meta.internal.mtags.Semanticdbs import scala.meta.internal.parsing.Trees import scala.meta.internal.semanticdb.Scala.Symbols @@ -1474,11 +1474,21 @@ final class TestingServer( } def findTextInDependencyJars( - mask: String, - content: String + include: String, + pattern: String ): Future[List[Location]] = { server - .findTextInDependencyJars(FindTextInDependencyJarsRequest(mask, content)) + .findTextInDependencyJars( + FindTextInDependencyJarsRequest( + FindTextInFilesOptions(include = include, exclude = null), + TextSearchQuery( + pattern = pattern, + isRegExp = null, + isCaseSensitive = null, + isWordMatch = null + ) + ) + ) .asScala .map(_.asScala.toList) } diff --git a/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala b/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala index 4f60dbe8616..ec927ac6126 100644 --- a/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala +++ b/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala @@ -39,8 +39,8 @@ class FindTextInDependencyJarsSuite """.stripMargin ) locations <- server.findTextInDependencyJars( - ".conf", - "jvm-shutdown-hooks" + include = ".conf", + pattern = "jvm-shutdown-hooks" ) _ = assertEquals(locations, expectedLocations) } yield () From 74af023a2638a66b147fc2fb2b9bbb67f7b2331c Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Wed, 6 Oct 2021 22:09:36 +0200 Subject: [PATCH 07/12] Use glob syntax --- .../internal/metals/findfiles/FindTextInDependencyJars.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala b/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala index 48504191870..701a283c0cd 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala @@ -80,8 +80,8 @@ class FindTextInDependencyJars( exclude: Option[String] ): Boolean = { path.isFile && - Nio(include).matches(path) && - exclude.forall(e => !Nio(e).matches(path)) + Nio(s"glob:**$include").matches(path) && + exclude.forall(e => !Nio(s"glob:**$e").matches(path)) } private def visitJar( From 43a44fb2b2668af871cc1bc6010c5f095cfe3351 Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Fri, 8 Oct 2021 15:58:44 +0200 Subject: [PATCH 08/12] Don't create unnecessary PathMatcher --- .../findfiles/FindTextInDependencyJars.scala | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala b/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala index 701a283c0cd..ea6c576ea42 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala @@ -44,6 +44,9 @@ class FindTextInDependencyJars( .zip(maybePattern) .map { case (include, pattern) => val allLocations: ArrayBuffer[Location] = new ArrayBuffer[Location]() + val includeMatcher = Nio(s"glob:**$include") + val excludeMatcher = + req.options.flatMap(_.exclude).map(e => Nio(s"glob:**$e")) buildTargets.allWorkspaceJars.foreach { classpathEntry => try { @@ -51,8 +54,8 @@ class FindTextInDependencyJars( if (classpathEntry.isFile && classpathEntry.isJar) { visitJar( path = classpathEntry, - include = include, - exclude = req.options.flatMap(_.exclude), + include = includeMatcher, + exclude = excludeMatcher, pattern = pattern ) } else Nil @@ -76,18 +79,18 @@ class FindTextInDependencyJars( private def isSuitableFile( path: AbsolutePath, - include: String, - exclude: Option[String] + include: Nio, + exclude: Option[Nio] ): Boolean = { path.isFile && - Nio(s"glob:**$include").matches(path) && - exclude.forall(e => !Nio(s"glob:**$e").matches(path)) + include.matches(path) && + exclude.forall(matcher => !matcher.matches(path)) } private def visitJar( path: AbsolutePath, - include: String, - exclude: Option[String], + include: Nio, + exclude: Option[Nio], pattern: String ): List[Location] = { FileIO From 427937fbb104056df430336a2191b5f55aa5d318 Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Fri, 8 Oct 2021 16:33:56 +0200 Subject: [PATCH 09/12] Include JDK zip sources --- .../internal/metals/MetalsEnrichments.scala | 5 +++ .../findfiles/FindTextInDependencyJars.scala | 41 ++++++++++--------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsEnrichments.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsEnrichments.scala index 22a3edde247..4d83bdfe66a 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsEnrichments.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsEnrichments.scala @@ -434,6 +434,11 @@ object MetalsEnrichments filename.endsWith(".jar") || filename.endsWith(".srcjar") } + def isZip: Boolean = { + val filename = path.toNIO.getFileName.toString + filename.endsWith(".zip") + } + /** * Reads file contents from editor buffer with fallback to disk. */ diff --git a/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala b/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala index ea6c576ea42..dbb3ab6614f 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/findfiles/FindTextInDependencyJars.scala @@ -48,26 +48,29 @@ class FindTextInDependencyJars( val excludeMatcher = req.options.flatMap(_.exclude).map(e => Nio(s"glob:**$e")) - buildTargets.allWorkspaceJars.foreach { classpathEntry => - try { - val jarLocations: List[Location] = - if (classpathEntry.isFile && classpathEntry.isJar) { - visitJar( - path = classpathEntry, - include = includeMatcher, - exclude = excludeMatcher, - pattern = pattern + (buildTargets.allWorkspaceJars ++ JdkSources()).foreach { + classpathEntry => + try { + val locations: List[Location] = + if ( + classpathEntry.isFile && (classpathEntry.isJar || classpathEntry.isZip) + ) { + visitJar( + path = classpathEntry, + include = includeMatcher, + exclude = excludeMatcher, + pattern = pattern + ) + } else Nil + + allLocations ++= locations + } catch { + case NonFatal(e) => + scribe.error( + s"Failed to find text in dependency files for $classpathEntry", + e ) - } else Nil - - allLocations ++= jarLocations - } catch { - case NonFatal(e) => - scribe.error( - s"Failed to find text in dependency files for $classpathEntry", - e - ) - } + } } allLocations.toList From 1c3187d82aab62d6770119275d352d669a84f0b6 Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Fri, 8 Oct 2021 17:23:41 +0200 Subject: [PATCH 10/12] Add searching in JDK lib --- .../tests/FindTextInDependencyJarsSuite.scala | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala b/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala index ec927ac6126..5789218b566 100644 --- a/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala +++ b/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala @@ -17,6 +17,17 @@ class FindTextInDependencyJarsSuite .toURI .toString() + val expectedJdkUri = + workspace + .resolve(Directories.dependencies) + .resolve("src.zip") + .resolve("java.base") + .resolve("java") + .resolve("lang") + .resolve("String.java") + .toURI + .toString() + val expectedLocations: List[Location] = List( new Location( expectedUri, @@ -27,6 +38,14 @@ class FindTextInDependencyJarsSuite new Range(new Position(1177, 40), new Position(1177, 58)) ) ) + + val expectedJdkLocation: List[Location] = List( + new Location( + expectedJdkUri, + new Range(new Position(625, 4), new Position(625, 40)) + ) + ) + for { _ <- initialize( s"""/metals.json @@ -42,7 +61,12 @@ class FindTextInDependencyJarsSuite include = ".conf", pattern = "jvm-shutdown-hooks" ) + jdkLocations <- server.findTextInDependencyJars( + include = ".java", + pattern = "public String(StringBuffer buffer) {" + ) _ = assertEquals(locations, expectedLocations) + _ = assertEquals(jdkLocations, expectedJdkLocation) } yield () } } From 3239879e1baea3af45acf146d825e09e91a9ef7d Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Fri, 8 Oct 2021 17:46:05 +0200 Subject: [PATCH 11/12] Fix JDK check in test --- .../tests/FindTextInDependencyJarsSuite.scala | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala b/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala index 5789218b566..8f95a6e95d2 100644 --- a/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala +++ b/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala @@ -9,6 +9,8 @@ import org.eclipse.lsp4j.Range class FindTextInDependencyJarsSuite extends BaseLspSuite("find-text-in-dependency-jars") { test("find exact string match in .conf file inside jar") { + val isJavaAtLeast9 = scala.util.Properties.isJavaAtLeast(9.toString) + val expectedUri = workspace .resolve(Directories.dependencies) @@ -17,16 +19,18 @@ class FindTextInDependencyJarsSuite .toURI .toString() - val expectedJdkUri = - workspace - .resolve(Directories.dependencies) - .resolve("src.zip") - .resolve("java.base") + val expectedJdkUri = { + val base = workspace.resolve(Directories.dependencies).resolve("src.zip") + val jdkDependent = + if (isJavaAtLeast9) base.resolve("java.base") + else base + jdkDependent .resolve("java") .resolve("lang") .resolve("String.java") .toURI .toString() + } val expectedLocations: List[Location] = List( new Location( @@ -39,12 +43,15 @@ class FindTextInDependencyJarsSuite ) ) - val expectedJdkLocation: List[Location] = List( - new Location( - expectedJdkUri, - new Range(new Position(625, 4), new Position(625, 40)) + val expectedJdkLocation: List[Location] = { + val line = if (isJavaAtLeast9) 625 else 577 + List( + new Location( + expectedJdkUri, + new Range(new Position(line, 4), new Position(line, 40)) + ) ) - ) + } for { _ <- initialize( From fe9bb0f40575cdae370733fb7bb7185d2231bd09 Mon Sep 17 00:00:00 2001 From: Z1kkurat Date: Fri, 8 Oct 2021 21:12:47 +0200 Subject: [PATCH 12/12] Add test case for JDK 17 --- .../src/test/scala/tests/FindTextInDependencyJarsSuite.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala b/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala index 8f95a6e95d2..ac7a8e2df41 100644 --- a/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala +++ b/tests/unit/src/test/scala/tests/FindTextInDependencyJarsSuite.scala @@ -10,6 +10,7 @@ class FindTextInDependencyJarsSuite extends BaseLspSuite("find-text-in-dependency-jars") { test("find exact string match in .conf file inside jar") { val isJavaAtLeast9 = scala.util.Properties.isJavaAtLeast(9.toString) + val isJavaAtLeast17 = scala.util.Properties.isJavaAtLeast(17.toString) val expectedUri = workspace @@ -44,7 +45,7 @@ class FindTextInDependencyJarsSuite ) val expectedJdkLocation: List[Location] = { - val line = if (isJavaAtLeast9) 625 else 577 + val line = if (isJavaAtLeast17) 1443 else if (isJavaAtLeast9) 625 else 577 List( new Location( expectedJdkUri,