diff --git a/Jenkinsfile b/Jenkinsfile index 506578cd05..31b9866408 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -148,6 +148,7 @@ pipeline { branch 'master' branch 'develop' branch 'rc/*' + branch 'poc/*' } } steps { diff --git a/als-server/shared/src/main/scala/org/mulesoft/als/server/workspace/ProjectConfigurationProvider.scala b/als-server/shared/src/main/scala/org/mulesoft/als/server/workspace/ProjectConfigurationProvider.scala index 04ad591f85..1be255999e 100644 --- a/als-server/shared/src/main/scala/org/mulesoft/als/server/workspace/ProjectConfigurationProvider.scala +++ b/als-server/shared/src/main/scala/org/mulesoft/als/server/workspace/ProjectConfigurationProvider.scala @@ -17,6 +17,13 @@ trait ProjectConfigurationProvider { def getMainFile(folder: String): Option[Future[String]] def getProjectRoot(folder: String): Option[Future[String]] = getMainFile(folder).map(_.map(m => m.substring(0, m.lastIndexOf(Fs.separatorChar)))) + + /** + * Iterates through the folder to extract each project inside. + * @param folder + * @return by default each root folder is the root of the project + */ + def getProjectsFromFolder(folder: String): Future[Seq[String]] = Future.successful(Seq(folder)) } object IgnoreProjectConfigurationAdapter extends ProjectConfigurationProvider { diff --git a/als-server/shared/src/main/scala/org/mulesoft/als/server/workspace/WorkspaceManager.scala b/als-server/shared/src/main/scala/org/mulesoft/als/server/workspace/WorkspaceManager.scala index 4ef2b5d637..da6cdd2ee4 100644 --- a/als-server/shared/src/main/scala/org/mulesoft/als/server/workspace/WorkspaceManager.scala +++ b/als-server/shared/src/main/scala/org/mulesoft/als/server/workspace/WorkspaceManager.scala @@ -108,11 +108,14 @@ class WorkspaceManager protected ( override def initialize(workspaceFolders: List[WorkspaceFolder]): Future[Unit] = { val newWorkspaces = extractCleanURIs(workspaceFolders) - workspaces - .initialize(newWorkspaces) - .map { _ => // Drop all old workspaces - dependencies.foreach(d => d.withUnitAccessor(this)) - } + val newProjects = Future.sequence(newWorkspaces.map(projectConfigurationProvider.getProjectsFromFolder)).map(_.flatten) + newProjects.flatMap(projects => + workspaces + .initialize(projects) + .map { _ => // Drop all old workspaces + dependencies.foreach(d => d.withUnitAccessor(this)) + } + ) } private def extractCleanURIs(workspaceFolders: List[WorkspaceFolder]) = diff --git a/als-server/shared/src/test/resources/workspace/multi-project/api.raml b/als-server/shared/src/test/resources/workspace/multi-project/api.raml new file mode 100644 index 0000000000..70693ede94 --- /dev/null +++ b/als-server/shared/src/test/resources/workspace/multi-project/api.raml @@ -0,0 +1,2 @@ +#%RAML 1.0 +title: a \ No newline at end of file diff --git a/als-server/shared/src/test/resources/workspace/multi-project/other/api.raml b/als-server/shared/src/test/resources/workspace/multi-project/other/api.raml new file mode 100644 index 0000000000..85cc737fa5 --- /dev/null +++ b/als-server/shared/src/test/resources/workspace/multi-project/other/api.raml @@ -0,0 +1,2 @@ +#%RAML 1.0 +title: b \ No newline at end of file diff --git a/als-server/shared/src/test/resources/workspace/multi-project/project1/api.raml b/als-server/shared/src/test/resources/workspace/multi-project/project1/api.raml new file mode 100644 index 0000000000..85cc737fa5 --- /dev/null +++ b/als-server/shared/src/test/resources/workspace/multi-project/project1/api.raml @@ -0,0 +1,2 @@ +#%RAML 1.0 +title: b \ No newline at end of file diff --git a/als-server/shared/src/test/resources/workspace/multi-project/project2/api.raml b/als-server/shared/src/test/resources/workspace/multi-project/project2/api.raml new file mode 100644 index 0000000000..924d548ea1 --- /dev/null +++ b/als-server/shared/src/test/resources/workspace/multi-project/project2/api.raml @@ -0,0 +1,2 @@ +#%RAML 1.0 +title: c \ No newline at end of file diff --git a/als-server/shared/src/test/resources/workspace/multi-project/project3/api.raml b/als-server/shared/src/test/resources/workspace/multi-project/project3/api.raml new file mode 100644 index 0000000000..7a76fe6122 --- /dev/null +++ b/als-server/shared/src/test/resources/workspace/multi-project/project3/api.raml @@ -0,0 +1,2 @@ +#%RAML 1.0 +title: d \ No newline at end of file diff --git a/als-server/shared/src/test/resources/workspace/multi-project/project3/sub/api.raml b/als-server/shared/src/test/resources/workspace/multi-project/project3/sub/api.raml new file mode 100644 index 0000000000..70693ede94 --- /dev/null +++ b/als-server/shared/src/test/resources/workspace/multi-project/project3/sub/api.raml @@ -0,0 +1,2 @@ +#%RAML 1.0 +title: a \ No newline at end of file diff --git a/als-server/shared/src/test/resources/workspace/multi-project/project3/sub/project5/api.raml b/als-server/shared/src/test/resources/workspace/multi-project/project3/sub/project5/api.raml new file mode 100644 index 0000000000..7a76fe6122 --- /dev/null +++ b/als-server/shared/src/test/resources/workspace/multi-project/project3/sub/project5/api.raml @@ -0,0 +1,2 @@ +#%RAML 1.0 +title: d \ No newline at end of file diff --git a/als-server/shared/src/test/resources/workspace/multi-project/sub/api.raml b/als-server/shared/src/test/resources/workspace/multi-project/sub/api.raml new file mode 100644 index 0000000000..70693ede94 --- /dev/null +++ b/als-server/shared/src/test/resources/workspace/multi-project/sub/api.raml @@ -0,0 +1,2 @@ +#%RAML 1.0 +title: a \ No newline at end of file diff --git a/als-server/shared/src/test/resources/workspace/multi-project/sub/project4/api.raml b/als-server/shared/src/test/resources/workspace/multi-project/sub/project4/api.raml new file mode 100644 index 0000000000..7a76fe6122 --- /dev/null +++ b/als-server/shared/src/test/resources/workspace/multi-project/sub/project4/api.raml @@ -0,0 +1,2 @@ +#%RAML 1.0 +title: d \ No newline at end of file diff --git a/als-server/shared/src/test/scala/org/mulesoft/als/server/workspace/WorkspaceConfigurationTest.scala b/als-server/shared/src/test/scala/org/mulesoft/als/server/workspace/WorkspaceConfigurationTest.scala deleted file mode 100644 index 918d1e49af..0000000000 --- a/als-server/shared/src/test/scala/org/mulesoft/als/server/workspace/WorkspaceConfigurationTest.scala +++ /dev/null @@ -1,335 +0,0 @@ -package org.mulesoft.als.server.workspace - -// TODO: when implemented Validation Profile and Semantic Extension, assert in tests the mutability of AmfConfiguration -// for example, start test, register/unregister dialect, check that the resulting unit still has the starting dialects -//class WorkspaceConfigurationTest extends LanguageServerBaseTest with ChangesWorkspaceConfiguration { -// override def rootPath: String = "" -// -// implicit override def executionContext: ExecutionContext = -// scala.concurrent.ExecutionContext.Implicits.global -// implicit val p: Platform = platform -// private val mainApiUri = "file://folder/api.raml" -// private val isolatedUri = "file://folder/isolated.raml" -// private val exchangeUri = "file://folder/exchange.json" -// private val validationProfileUri = "file://folder/profile.yaml" -// val api = "#%RAML 1.0\ntitle: test\n" -// val isolated = "#%RAML 1.0\ntitle: test2\n" -// val validationProfile = "#%Validation Profile 1.0\nprofile: MyProfile" -// val exchange = "{\n \"main\": \"api.raml\"\n}" -// private val extensionUri = "file://folder/extension.yaml" -// private val extensionContent = """#%Dialect 1.0 -// |dialect: Annotation mappings -// |version: 1.0 -// | -// |external: -// | aml: http://a.ml/vocab# -// | apicontract: http://a.ml/vocabularies/apiContract# -// | -// |documents: -// | root: -// | encodes: string # just necessary to avoid errors, won't really do anything -// | -// |annotationMappings: -// | RateLimitingAnnotationMapping: -// | domain: apicontract.WebAPI -// | propertyTerm: aml.rate-limit -// | range: integer -// | minimum: 0 -// | maximum: 10000 -// | mandatory: true -// | -// | MaintainerAnnotationMapping: -// | domain: apicontract.API -// | propertyTerm: aml.maintainer -// | range: PersonNodeMapping -// | -// |nodeMappings: -// | PersonNodeMapping: -// | mapping: -// | name: -// | range: string -// | surname: -// | range: string -// | email: -// | range: string -// | -// |extensions: -// | maintainer: MaintainerAnnotationMapping -// | rateLimiting: RateLimitingAnnotationMapping""".stripMargin -// -// def rl(withExchangeFile: Boolean): ResourceLoader = new ResourceLoader { -// -// /** Fetch specified resource and return associated content. Resource should have been previously accepted. */ -// override def fetch(resource: String): Future[Content] = { -// val content = -// if (resource == mainApiUri) api -// else if (resource == isolatedUri) isolated -// else if (resource == validationProfileUri) validationProfile -// else if (withExchangeFile && resource == exchangeUri) exchange -// else if (resource == extensionUri) extensionContent -// else throw new ResourceNotFound("Not found: " + resource) -// -// Future.successful(new Content(content, resource)) -// } -// -// /** Accepts specified resource. */ -// override def accepts(resource: String): Boolean = -// resource == mainApiUri || -// (withExchangeFile && resource == exchangeUri) || -// resource == isolatedUri || -// resource == extensionUri || -// resource == validationProfileUri -// } -// -// test("Unit from main tree should contain configuration") { -// AmfConfigurationWrapper(Seq(rl(true))).flatMap(amfConfiguration => { -// val (factory: WorkspaceManagerFactory, listener) = createPatchedWorkspaceManagerFactory(amfConfiguration) -// val workspaceManager: WorkspaceManager = factory.workspaceManager -// val server = buildServer(factory) -// for { -// _ <- server.initialize(AlsInitializeParams(None, Some(TraceKind.Off), rootUri = Some(s"file://folder"))) -// _ <- listener.nextCall // parse main file -// unit <- workspaceManager.getUnit(mainApiUri, UUID.randomUUID().toString) -// } yield { -// assert(unit.mainFile.contains(mainApiUri)) -// assert(unit.context.projectInfo.config.mainFile.contains("api.raml")) -// } -// }) -// } -// -// test("Isolated unit should contain configuration") { -// AmfConfigurationWrapper(Seq(rl(true))).flatMap(amfConfiguration => { -// val (factory: WorkspaceManagerFactory, listener) = createPatchedWorkspaceManagerFactory(amfConfiguration) -// val workspaceManager: WorkspaceManager = factory.workspaceManager -// val server = buildServer(factory) -// for { -// _ <- server.initialize(AlsInitializeParams(None, Some(TraceKind.Off), rootUri = Some(s"file://folder"))) -// _ <- listener.nextCall // parse main file -// _ <- openFileNotification(server)(isolatedUri, isolated) -// _ <- listener.nextCall // parse isolated -// unit <- workspaceManager.getUnit(isolatedUri, UUID.randomUUID().toString) -// } yield { -// assert(unit.mainFile.isEmpty) -// assert(unit.context.projectInfo.config.mainFile.contains("api.raml")) -// } -// }) -// -// } -// test("Should update the configuration by command for new Units") { -// AmfConfigurationWrapper(Seq(rl(true))).flatMap(amfConfiguration => { -// val (factory: WorkspaceManagerFactory, parserListener) = createPatchedWorkspaceManagerFactory(amfConfiguration) -// val workspaceManager: WorkspaceManager = factory.workspaceManager -// val initialArgs = changeConfigArgs(Some(mainApiUri)) -// val args = changeConfigArgs(Some(isolatedUri)) -// val server = buildServer(factory) -// for { -// _ <- server.initialize(AlsInitializeParams(None, Some(TraceKind.Off), rootUri = Some(s"file://folder"))) -// _ <- changeWorkspaceConfiguration(workspaceManager, initialArgs) -// _ <- parserListener.nextCall // parse main file -// _ <- openFileNotification(server)(isolatedUri, isolated) -// _ <- parserListener.nextCall // parse isolated -// firstUnit <- workspaceManager.getUnit(mainApiUri, UUID.randomUUID().toString) -// _ <- changeWorkspaceConfiguration(workspaceManager, args) -// _ <- parserListener.nextCall -// _ <- openFileNotification(server)(mainApiUri, isolated) -// _ <- parserListener.nextCall -// secondUnit <- workspaceManager.getUnit(mainApiUri, UUID.randomUUID().toString) -// thirdUnit <- workspaceManager.getUnit(isolatedUri, UUID.randomUUID().toString) -// } yield { -// assert(firstUnit.mainFile.contains(mainApiUri)) -// assert(firstUnit.context.projectInfo.config.mainFile.contains("api.raml")) -// -// assert(secondUnit.mainFile.isEmpty) -// assert(secondUnit.context.projectInfo.config.mainFile.contains("isolated.raml")) -// -// assert(thirdUnit.mainFile.contains(isolatedUri)) -// assert(thirdUnit.context.projectInfo.config.mainFile.contains("isolated.raml")) -// } -// }) -// -// } -// -// test("Should notify project dependencies the configuration used") { -// AmfConfigurationWrapper(Seq(rl(true))).flatMap(amfConfiguration => { -// val (factory: WorkspaceManagerFactory, listener) = createPatchedWorkspaceManagerFactory(amfConfiguration) -// val server = buildServer(factory) -// for { -// _ <- server.initialize(AlsInitializeParams(None, Some(TraceKind.Off), rootUri = Some(s"file://folder"))) -// mainFileResult <- listener.nextCall -// _ <- openFileNotification(server)(isolatedUri, isolated) -// isolatedResult <- listener.nextCall -// } yield { -// assert(!isolatedResult.tree) -// assert(isolatedResult.parseResult.context.projectInfo.config.mainFile.contains("api.raml")) -// assert(isolatedResult.parseResult.context.projectInfo.config.mainFile.contains("api.raml")) -// -// assert(mainFileResult.tree) -// assert(mainFileResult.parseResult.context.projectInfo.config.mainFile.contains("api.raml")) -// } -// }) -// } -// -// test("Should notify resolution dependencies the configuration used") { -// val listener = new MockResolutionListener(logger) -// AmfConfigurationWrapper(Seq(rl(true))).flatMap(amfConfiguration => { -// val (factory: WorkspaceManagerFactory, _) = -// createPatchedWorkspaceManagerFactory(amfConfiguration, List.empty, List(listener)) -// val server = buildServer(factory) -// for { -// _ <- server.initialize(AlsInitializeParams(None, Some(TraceKind.Off), rootUri = Some(s"file://folder"))) -// mainFileResult <- listener.nextCall -// _ <- openFileNotification(server)(isolatedUri, isolated) -// isolatedResult <- listener.nextCall -// } yield { -// assert(isolatedResult.projectInfo.config.mainFile.contains("api.raml")) -// assert(mainFileResult.projectInfo.config.mainFile.contains("api.raml")) -// } -// }) -// } -// -// def getWorkspaceConfiguration(server: LanguageServer, uri: String): Future[GetWorkspaceConfigurationResult] = -// server -// .resolveHandler(GetWorkspaceConfigurationRequestType) -// .get(GetWorkspaceConfigurationParams(TextDocumentIdentifier(uri))) -// -// test("Get workspace notification request should return current configuration") { -// val listener = new MockResolutionListener(logger) -// AmfConfigurationWrapper(Seq(rl(true))).flatMap(amfConfiguration => { -// val (factory: WorkspaceManagerFactory, _) = -// createPatchedWorkspaceManagerFactory(amfConfiguration, List.empty, List(listener)) -// val args = changeConfigArgs(Some(isolatedUri)) -// val args2 = changeConfigArgs(Some(isolatedUri), None, Set.empty, Set(validationProfileUri)) -// val args3 = changeConfigArgs(Some(isolatedUri), None, Set("dependency.yaml")) -// val args4 = changeConfigArgs(Some(isolatedUri), None, Set.empty, Set.empty, Set(extensionUri)) -// val workspaceManager = factory.workspaceManager -// withServer[Assertion](buildServer(factory)) { -// server => -// for { -// _ <- server.initialize(AlsInitializeParams(None, Some(TraceKind.Off), rootUri = Some(s"file://folder"))) -// config1 <- getWorkspaceConfiguration(server, mainApiUri) -// _ <- changeWorkspaceConfiguration(workspaceManager, args) -// _ <- listener.nextCall -// config2 <- getWorkspaceConfiguration(server, mainApiUri) -// _ <- changeWorkspaceConfiguration(workspaceManager, args2) -// _ <- listener.nextCall -// config3 <- getWorkspaceConfiguration(server, mainApiUri) -// _ <- changeWorkspaceConfiguration(workspaceManager, args3) -// _ <- listener.nextCall -// config4 <- getWorkspaceConfiguration(server, mainApiUri) -// _ <- changeWorkspaceConfiguration(workspaceManager, args4) -// config5 <- getWorkspaceConfiguration(server, mainApiUri) -// registered <- workspaceManager.getWorkspace(mainApiUri).map(_.registeredDialects) -// } yield { -// def customValidationProfiles(config: GetWorkspaceConfigurationResult) = -// config.configuration.dependencies -// .filter(f => f.isRight && f.right.exists(_.scope == CUSTOM_VALIDATION)) -// .map(_.right.get.file) -// def semanticExtensions(config: GetWorkspaceConfigurationResult) = -// config.configuration.dependencies -// .filter(f => f.isRight && f.right.exists(_.scope == SEMANTIC_EXTENSION)) -// .map(_.right.get.file) -// def dependencies(config: GetWorkspaceConfigurationResult) = -// config.configuration.dependencies.flatMap { -// case Left(value) => Some(value) -// case Right(value) if !Set(CUSTOM_VALIDATION, SEMANTIC_EXTENSION).contains(value.scope) => -// Some(value.file) -// case _ => None -// } -// assert(config1.workspace == """file://folder""") -// assert(config2.workspace == """file://folder""") -// assert(config3.workspace == """file://folder""") -// assert(config4.workspace == """file://folder""") -// assert(config1.configuration.mainPath.isEmpty) -// assert(config2.configuration.mainPath.contains("isolated.raml")) -// assert(config3.configuration.mainPath.contains("isolated.raml")) -// assert(config4.configuration.mainPath.contains("isolated.raml")) -// assert(customValidationProfiles(config1).isEmpty) -// assert(customValidationProfiles(config2).isEmpty) -// assert(customValidationProfiles(config3).contains(validationProfileUri)) -// assert(config1.configuration.dependencies.isEmpty) -// assert(config2.configuration.dependencies.isEmpty) -// assert(dependencies(config3).isEmpty) -// assert(dependencies(config4).contains("dependency.yaml")) -// assert(semanticExtensions(config1).isEmpty) -// assert(semanticExtensions(config2).isEmpty) -// assert(semanticExtensions(config3).isEmpty) -// assert(semanticExtensions(config5).contains(extensionUri)) -// assert(registered.flatMap(_.location()).contains(extensionUri)) -// } -// } -// }) -// } -// -// def createPatchedWorkspaceManagerFactory(amfConfiguration: AmfConfigurationWrapper, -// projectDependencies: List[BaseUnitListener] = List.empty, -// resolutionDependencies: List[ResolvedUnitListener] = List.empty) -// : (WorkspaceManagerFactory, MockParseListener) = { -// -// val clientNotifier = new MockDiagnosticClientNotifier -// val telemetryManager: TelemetryManager = new TelemetryManager(clientNotifier, logger) -// val directoryResolver: DirectoryResolver = new PlatformDirectoryResolver(platform) -// val parserListener = new MockParseListener() -// (WorkspaceManagerFactory( -// projectDependencies :+ parserListener, -// resolutionDependencies, -// telemetryManager, -// directoryResolver, -// logger, -// new ConfigurationManager(), -// None, -// None -// ), -// parserListener) -// } -// -// def buildServer(factory: WorkspaceManagerFactory): LanguageServer = -// new LanguageServerBuilder(factory.documentManager, -// factory.workspaceManager, -// factory.configurationManager, -// factory.resolutionTaskManager) -// .addRequestModule(factory.workspaceConfigurationManager) -// .build() -// -// class MockResolutionListener(override val logger: Logger) -// extends ResolvedUnitListener -// with AbstractTestClientNotifier[AmfResolvedUnit] -// with TimeoutFuture { -// override type RunType = CallbackRunnable -// -// override protected def runnable(ast: AmfResolvedUnit, uuid: String): CallbackRunnable = -// new CallbackRunnable(ast.baseUnit.id, ast, this) -// -// override protected def onSuccess(uuid: String, uri: String): Unit = -// logger.log(s"success: $uri, uuid: $uuid", MessageSeverity.DEBUG, "MockResolutionListener", "onSuccess") -// -// override protected def onFailure(uuid: String, uri: String, t: Throwable): Unit = -// logger.log(s"Failed: $uri, uuid: $uuid", MessageSeverity.ERROR, "MockResolutionListener", "onFailure") -// -// override protected def onNewAstPreprocess(resolved: AmfResolvedUnit, uuid: String): Unit = -// logger.debug("notified", "MockResolutionListener", "onNewAstPreprocess") -// -// override def onRemoveFile(uri: String): Unit = {} -// -// override def nextCall: Future[AmfResolvedUnit] = timeoutFuture(super.nextCall, 1000) -// -// class CallbackRunnable(val uri: String, ast: AmfResolvedUnit, callback: MockResolutionListener) -// extends Runnable[Unit] { -// val kind = "CallbackRunnable" -// var cancelled = false -// override def run(): Promise[Unit] = { -// callback.notify(ast) -// Promise.successful() -// } -// -// override def conflicts(other: Runnable[Any]): Boolean = -// other.asInstanceOf[CallbackRunnable].kind == kind && uri == other -// .asInstanceOf[CallbackRunnable] -// .uri -// -// override def cancel(): Unit = cancelled = true -// -// override def isCanceled(): Boolean = cancelled -// } -// } -// -//} diff --git a/als-server/shared/src/test/scala/org/mulesoft/als/server/workspace/multiproject/MultiProjectConfigurationProviderTest.scala b/als-server/shared/src/test/scala/org/mulesoft/als/server/workspace/multiproject/MultiProjectConfigurationProviderTest.scala new file mode 100644 index 0000000000..048f710528 --- /dev/null +++ b/als-server/shared/src/test/scala/org/mulesoft/als/server/workspace/multiproject/MultiProjectConfigurationProviderTest.scala @@ -0,0 +1,77 @@ +package org.mulesoft.als.server.workspace.multiproject + +import org.mulesoft.als.common.PlatformDirectoryResolver +import org.mulesoft.als.server.client.scala.LanguageServerBuilder +import org.mulesoft.als.server.modules.WorkspaceManagerFactoryBuilder +import org.mulesoft.als.server.protocol.LanguageServer +import org.mulesoft.als.server.protocol.configuration.AlsInitializeParams +import org.mulesoft.als.server.textsync.TextDocumentContainer +import org.mulesoft.als.server.{LanguageServerBaseTest, MockDiagnosticClientNotifier, MockFilesInClientNotifier} +import org.mulesoft.amfintegration.amfconfiguration.EditorConfiguration +import org.mulesoft.lsp.configuration.TraceKind +import org.scalatest.compatible.Assertion + +import scala.concurrent.ExecutionContext + +class MultiProjectConfigurationProviderTest extends LanguageServerBaseTest { + + override implicit val executionContext: ExecutionContext = ExecutionContext.Implicits.global + + override def rootPath: String = "workspace/multi-project" + + def buildServer(alsClient: Option[MockFilesInClientNotifier] = None): LanguageServer = { + val container = TextDocumentContainer() + val editorConfiguration = EditorConfiguration() + val builder = + new WorkspaceManagerFactoryBuilder( + new MockDiagnosticClientNotifier, + editorConfiguration, + Option(TestMultiProjectConfigurationProvider(container, editorConfiguration, new PlatformDirectoryResolver(container.platform))) + ) + + + val maybeInProjectManager = alsClient.map(builder.filesInProjectManager) + val factory = builder.buildWorkspaceManagerFactory() + + val b = new LanguageServerBuilder( + factory.documentManager, + factory.workspaceManager, + factory.configurationManager, + factory.resolutionTaskManager + ) + maybeInProjectManager.foreach(b.addInitializableModule) + b.build() + } + + val defaultInitializationParams: AlsInitializeParams = AlsInitializeParams(None, Some(TraceKind.Off), rootPath = Some(filePath(""))) + + test("Configuration provider reads multiple projects in folder") { + val totalProjects = 4 + val provider = TestMultiProjectConfigurationProvider(TextDocumentContainer(), EditorConfiguration(), new PlatformDirectoryResolver(platform)) + for{ + projectPaths <- provider.getProjectsFromFolder(filePath("")) + } yield { + (0 until totalProjects).map(1+).map(r => s"project$r").foreach(name => assert(projectPaths.map(_.split("/").last).contains(name))) + projectPaths.length shouldBe totalProjects + } + } + + test("initialize correct projects for rootPath") { + withServer[Assertion](buildServer(), defaultInitializationParams) { server => + server.workspaceFolders().length shouldBe 4 + } + } + + test("get correct main file for project") { + val notifier = new MockFilesInClientNotifier + withServer[Assertion](buildServer(Some(notifier)), defaultInitializationParams) { server => + for { + _ <- setMainFile(server)(filePath("project1"), "api.raml") + filesInProject <- notifier.nextCall + } yield { + filesInProject.uris.size shouldBe 1 + filesInProject.uris.head.endsWith("project1/api.raml") shouldBe true + } + } + } +} diff --git a/als-server/shared/src/test/scala/org/mulesoft/als/server/workspace/multiproject/TestMultiProjectConfigurationProvider.scala b/als-server/shared/src/test/scala/org/mulesoft/als/server/workspace/multiproject/TestMultiProjectConfigurationProvider.scala new file mode 100644 index 0000000000..74a1cbdb64 --- /dev/null +++ b/als-server/shared/src/test/scala/org/mulesoft/als/server/workspace/multiproject/TestMultiProjectConfigurationProvider.scala @@ -0,0 +1,32 @@ +package org.mulesoft.als.server.workspace.multiproject + +import org.mulesoft.als.common.DirectoryResolver +import org.mulesoft.als.server.modules.workspace.DefaultProjectConfigurationProvider +import org.mulesoft.als.server.textsync.TextDocumentContainer +import org.mulesoft.amfintegration.amfconfiguration.EditorConfiguration +import org.mulesoft.amfintegration.amfconfiguration.executioncontext.Implicits.global + +import scala.concurrent.Future + + +case class TestMultiProjectConfigurationProvider( + container: TextDocumentContainer, + editorConfiguration: EditorConfiguration, + directoryResolver: DirectoryResolver + ) extends DefaultProjectConfigurationProvider(container, editorConfiguration) { + private def isProject(folder: String): Boolean = folder.split("/").last.startsWith("project") + + override def getProjectsFromFolder(folder: String): Future[Seq[String]] = { + if(isProject(folder)) Future.successful(Seq(folder)) + else { + directoryResolver.isDirectory(folder).flatMap{ + case true => + directoryResolver.readDir(folder) + case false => + Future.successful(Seq.empty) + }.flatMap(folders => Future.sequence(folders.map(trailSlash(folder).concat).map(getProjectsFromFolder)).map(_.flatten)) + } + } + private def trailSlash(f: String): String = + if (f.endsWith("/")) f else s"$f/" +} diff --git a/dependencies.properties b/dependencies.properties index 5fe8d85bc7..bb68f60912 100644 --- a/dependencies.properties +++ b/dependencies.properties @@ -1,4 +1,4 @@ -version=7.1.0-SNAPSHOT +version=7.1.0-MULTI-PROJECT-SNAPSHOT amf=5.7.0 amf.custom-validator.js=1.7.2 amf.custom-validator-scalajs=0.7.0