Skip to content

Commit

Permalink
improvement: do not create MetalsLspService for non-Scala projects
Browse files Browse the repository at this point in the history
  • Loading branch information
kasiaMarek committed Aug 29, 2023
1 parent c0764e7 commit 6e56778
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -303,14 +303,23 @@ object MetalsEnrichments

implicit class XtensionAbsolutePathBuffers(path: AbsolutePath) {

def hasScalaFiles: Boolean = {
def isScalaDir(file: File): Boolean = {
def isScalaProject(extendSearchToScriptsAndSbt: Boolean = true): Boolean = {
val isScala: String => Boolean =
if (extendSearchToScriptsAndSbt) _.isScalaFilename else _.isScala
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 isScala(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 @@ -126,10 +125,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, isKnownScalaProject = true)
with Cancelable
with TextDocumentService {
import serverInputs._

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

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

def loadFingerPrints(): Unit = {
private def loadFingerPrints(): Unit = {
// load fingerprints from last execution
fingerprints.addAll(tables.fingerprints.load())
}
Expand All @@ -857,7 +857,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 @@ -883,9 +883,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 @@ -900,6 +902,7 @@ class MetalsLspService(
)
)
} yield ()
}

def onShutdown(): Unit = {
tables.fingerprints.save(fingerprints.getAllFingerprints())
Expand Down Expand Up @@ -1977,7 +1980,7 @@ class MetalsLspService(
if (
!buildTools.isAutoConnectable
&& buildTools.loadSupported.isEmpty
&& folder.hasScalaFiles
&& folder.isScalaProject(extendSearchToScriptsAndSbt = false)
) {
scalaCli.setupIDE(folder)
} else Future.successful(())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,56 +10,106 @@ 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(_.isScalaProject) 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(_.isScalaProject)
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.filter(_ == 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)

}

trait MetalsLspServiceCreator {
def apply(
folder: Folder,
updateLoggerFiles: () => Future[Unit],
forceIsScalaProject: Boolean = false,
): MetalsLspService
}

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

0 comments on commit 6e56778

Please sign in to comment.