Skip to content

Commit

Permalink
improvement: don't create service for folders w/ non-Scala projects (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
kasiaMarek authored Oct 2, 2023
1 parent 60a1d16 commit c85e193
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -303,14 +303,28 @@ object MetalsEnrichments

implicit class XtensionAbsolutePathBuffers(path: AbsolutePath) {

def hasScalaFiles: Boolean = {
def isScalaDir(file: File): Boolean = {
def isScalaProject(): Boolean =
containsProjectFilesSatisfying(_.isScala)
def isMetalsProject(): Boolean =
containsProjectFilesSatisfying(_.isScalaOrJavaFilename)

private def containsProjectFilesSatisfying(
fileNamePredicate: String => Boolean
): Boolean = {
val directoriesToCheck = Set("test", "src", "it")
def dirFilter(f: File) = directoriesToCheck(f.getName()) || f
.listFiles()
.exists(dir => dir.isDirectory && directoriesToCheck(dir.getName()))
def isScalaDir(
file: File,
dirFilter: File => Boolean = _ => true,
): Boolean = {
file.listFiles().exists { file =>
if (file.isDirectory()) isScalaDir(file)
else file.getName().endsWith(".scala")
if (file.isDirectory()) dirFilter(file) && isScalaDir(file)
else fileNamePredicate(file.getName())
}
}
path.isDirectory && isScalaDir(path.toFile)
path.isDirectory && isScalaDir(path.toFile, dirFilter)
}

def scalaSourcerootOption: String = s""""-P:semanticdb:sourceroot:$path""""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package scala.meta.internal.metals

import java.net.URI
import java.nio.file._
import java.sql.Connection
import java.util
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ScheduledExecutorService
Expand Down Expand Up @@ -128,10 +127,11 @@ class MetalsLspService(
shellRunner: ShellRunner,
timerProvider: TimerProvider,
initTreeView: () => Unit,
val folder: AbsolutePath,
folder: AbsolutePath,
folderVisibleName: Option[String],
headDoctor: HeadDoctor,
) extends Cancelable
) extends Folder(folder, folderVisibleName, isKnownMetalsProject = true)
with Cancelable
with TextDocumentService {
import serverInputs._

Expand Down Expand Up @@ -781,7 +781,7 @@ class MetalsLspService(

val treeView =
new FolderTreeViewProvider(
Folder(folder, folderVisibleName),
new Folder(folder, folderVisibleName, true),
buildTargets,
() => buildClient.ongoingCompilations(),
definitionIndex,
Expand Down Expand Up @@ -847,7 +847,7 @@ class MetalsLspService(
cancelable
}

def loadFingerPrints(): Unit = {
private def loadFingerPrints(): Unit = {
// load fingerprints from last execution
fingerprints.addAll(tables.fingerprints.load())
}
Expand All @@ -859,7 +859,7 @@ class MetalsLspService(
token: CancelToken,
): Future[Unit] = codeActionProvider.executeCommands(params, token)

def registerNiceToHaveFilePatterns(): Unit = {
private def registerNiceToHaveFilePatterns(): Unit = {
for {
params <- Option(initializeParams)
capabilities <- Option(params.getCapabilities)
Expand All @@ -885,9 +885,11 @@ class MetalsLspService(

val isInitialized = new AtomicBoolean(false)

def connectTables(): Connection = tables.connect()
def initialized(): Future[Unit] = {
loadFingerPrints()
registerNiceToHaveFilePatterns()
tables.connect()

def initialized(): Future[Unit] =
for {
_ <- maybeSetupScalaCli()
_ <-
Expand All @@ -902,6 +904,7 @@ class MetalsLspService(
)
)
} yield ()
}

def onShutdown(): Unit = {
tables.fingerprints.save(fingerprints.getAllFingerprints())
Expand Down Expand Up @@ -2035,7 +2038,7 @@ class MetalsLspService(
if (
!buildTools.isAutoConnectable()
&& buildTools.loadSupported.isEmpty
&& folder.hasScalaFiles
&& folder.isScalaProject()
) scalaCli.setupIDE(folder)
else Future.successful(())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,56 +10,98 @@ import scala.meta.internal.metals.logging.MetalsLogger
class WorkspaceFolders(
initialFolders: List[Folder],
createService: Folder => MetalsLspService,
onInitialize: MetalsLspService => Future[Unit],
shoutdownMetals: () => Future[Unit],
shutdownMetals: () => Future[Unit],
redirectSystemOut: Boolean,
initialServerConfig: MetalsServerConfig,
)(implicit ec: ExecutionContext) {

private val folderServices: AtomicReference[List[MetalsLspService]] =
new AtomicReference(initialFolders.map(createService))
private val folderServices: AtomicReference[WorkspaceFoldersServices] = {
val (scalaProjects, nonScalaProjects) =
initialFolders.partition(_.isMetalsProject) match {
case (Nil, nonScala) => (List(nonScala.head), nonScala.tail)
case t => t
}
val services = scalaProjects.map(createService(_))
new AtomicReference(WorkspaceFoldersServices(services, nonScalaProjects))
}

def getFolderServices: List[MetalsLspService] = folderServices.get()
def getFolderServices: List[MetalsLspService] = folderServices.get().services
def nonScalaProjects: List[Folder] = folderServices.get().nonScalaFolders

def changeFolderServices(
toRemove: List[Folder],
toAdd: List[Folder],
): Future[Unit] = {
val actualToRemove =
toRemove.filterNot(folder => toAdd.exists(_.path == folder.path))
def shouldBeRemoved(service: MetalsLspService) =
actualToRemove.exists(_.path == service.folder)
def isIn(services: List[MetalsLspService], service: MetalsLspService) =
services.exists(_.folder == service.folder)

val newServices = toAdd.map { folder =>
val newService = createService(folder)
newService.loadFingerPrints()
newService.registerNiceToHaveFilePatterns()
newService.connectTables()
newService
}
def shouldBeRemoved(folder: Folder) =
actualToRemove.exists(_.path == folder.path)

val (newScala, newNonScala) = toAdd.partition(_.isMetalsProject)
val newServices = newScala.map(createService(_))
if (newServices.isEmpty && getFolderServices.forall(shouldBeRemoved)) {
shoutdownMetals()
shutdownMetals()
} else {
val prev =
folderServices.getAndUpdate { current =>
val afterRemove = current.filterNot(shouldBeRemoved)
val newToAdd = newServices.filterNot(isIn(current, _))
afterRemove ++ newToAdd
val WorkspaceFoldersServices(prev, _) =
folderServices.getAndUpdate {
case WorkspaceFoldersServices(services, nonScalaProjects) =>
val updatedServices =
services.filterNot(shouldBeRemoved) ++
newServices.filterNot(isIn(services, _))
val updatedNonScala =
nonScalaProjects.filterNot(shouldBeRemoved) ++
newNonScala.filterNot(isIn(nonScalaProjects ++ services, _))
WorkspaceFoldersServices(
updatedServices,
updatedNonScala,
)
}
MetalsLogger.setupLspLogger(
folderServices.get().map(_.folder),
redirectSystemOut,
initialServerConfig,
)

setupLogger()

for {
_ <- Future.sequence(
newServices.filterNot(isIn(prev, _)).map(onInitialize)
newServices.filterNot(isIn(prev, _)).map(_.initialized())
)
_ <- Future(prev.filter(shouldBeRemoved).foreach(_.onShutdown()))
} yield ()
}
}

def convertToScalaProject(folder: Folder): MetalsLspService = {
val newService = createService(folder)
val WorkspaceFoldersServices(prev, _) = folderServices.getAndUpdate {
case wfs @ WorkspaceFoldersServices(services, nonScalaProjects) =>
if (!isIn(services, folder)) {
WorkspaceFoldersServices(
services :+ newService,
nonScalaProjects.filterNot(_ == folder),
)
} else wfs
}

prev.find(_.path == folder.path) match {
case Some(service) => service
case None =>
setupLogger()
newService.initialized()
newService
}
}

private def setupLogger() =
MetalsLogger.setupLspLogger(
getFolderServices.map(_.path),
redirectSystemOut,
initialServerConfig,
)

private def isIn(services: List[Folder], service: Folder) =
services.exists(_.path == service.path)

}

case class WorkspaceFoldersServices(
services: List[MetalsLspService],
nonScalaFolders: List[Folder],
)
Loading

0 comments on commit c85e193

Please sign in to comment.