From 1165f12ccbf7bc766552bb7ba77bfdf31330b755 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Mon, 1 Jul 2024 15:58:06 +0530 Subject: [PATCH] fix(projects): introduce workspace API and remove 'Project' model --- .../actions/etc/FindInProjectAction.kt | 4 +- .../androidide/actions/etc/LaunchAppAction.kt | 8 +- .../androidide/actions/internalActionUtils.kt | 11 +- .../actions/sidebar/TerminalSidebarAction.kt | 2 +- .../androidide/activities/MainActivity.kt | 4 +- .../activities/editor/BaseEditorActivity.kt | 6 +- .../editor/ProjectHandlerActivity.kt | 22 +- .../fragments/RunTasksDialogFragment.kt | 4 +- .../indexing/platform/PlatformIndexService.kt | 8 +- .../androidide/indexing/IIndexService.kt | 10 +- .../api/DefaultLanguageServerRegistry.java | 17 +- .../androidide/lsp/api/ILanguageServer.kt | 14 +- .../lsp/util/commonCompletionUtils.kt | 3 +- .../androidide/projects/IProjectManager.kt | 97 ++------- .../itsaky/androidide/projects/IWorkspace.kt | 131 ++++++++++++ .../com/itsaky/androidide/projects/Project.kt | 122 ----------- .../projects/android/AndroidModule.kt | 91 ++++----- .../projects/android/androidModuleExts.kt | 40 ++++ .../projects/internal/ProjectManagerImpl.kt | 191 ++++++------------ .../projects/internal/WorkspaceImpl.kt | 143 +++++++++++++ ...ransformer.kt => WorkspaceModelBuilder.kt} | 21 +- .../androidide/projects/java/JavaModule.kt | 8 +- .../androidide/projects/util/projects.kt | 6 +- ...eProjectTest.kt => ModuleWorkspaceTest.kt} | 70 ++++--- .../androidide/lsp/java/JavaLanguageServer.kt | 21 +- .../lsp/java/actions/BaseJavaCodeAction.kt | 3 +- .../lsp/java/actions/FieldBasedAction.kt | 5 +- .../actions/diagnostics/AddImportAction.kt | 6 +- .../actions/diagnostics/AddThrowsAction.kt | 4 +- .../diagnostics/CreateMissingMethodAction.kt | 4 +- .../actions/diagnostics/FieldToBlockAction.kt | 4 +- .../actions/diagnostics/RemoveClassAction.kt | 4 +- .../actions/diagnostics/RemoveMethodAction.kt | 4 +- .../diagnostics/RemoveUnusedThrowsAction.kt | 4 +- .../SuppressUncheckedWarningAction.kt | 4 +- .../diagnostics/VariableToStatementAction.kt | 4 +- .../generators/GenerateConstructorAction.kt | 4 +- .../GenerateMissingConstructorAction.kt | 4 +- .../GenerateSettersAndGettersAction.kt | 4 +- .../GenerateToStringMethodAction.kt | 4 +- .../OverrideSuperclassMethodsAction.kt | 12 +- .../lsp/java/edits/AdvancedJavaEditHandler.kt | 3 +- .../indexing/internal/JavaIndexServiceImpl.kt | 6 +- .../androidide/lsp/java/parser/Parser.java | 30 +-- .../java/providers/JavaDiagnosticProvider.kt | 2 +- .../lsp/java/JavaCompilerProviderTest.kt | 12 +- .../itsaky/androidide/lsp/testing/LSPTest.kt | 7 +- .../inflater/utils/inflaterUtils.kt | 4 +- .../androidide/inflater/utils/module.kt | 2 +- .../androidide/inflater/XmlInflaterTest.kt | 11 +- .../androidide/lsp/xml/XMLLanguageServer.kt | 7 +- 51 files changed, 674 insertions(+), 538 deletions(-) create mode 100644 core/projects/src/main/java/com/itsaky/androidide/projects/IWorkspace.kt delete mode 100644 core/projects/src/main/java/com/itsaky/androidide/projects/Project.kt create mode 100644 core/projects/src/main/java/com/itsaky/androidide/projects/android/androidModuleExts.kt create mode 100644 core/projects/src/main/java/com/itsaky/androidide/projects/internal/WorkspaceImpl.kt rename core/projects/src/main/java/com/itsaky/androidide/projects/internal/{ProjectTransformer.kt => WorkspaceModelBuilder.kt} (95%) rename core/projects/src/test/java/com/itsaky/androidide/projects/api/{ModuleProjectTest.kt => ModuleWorkspaceTest.kt} (79%) diff --git a/core/app/src/main/java/com/itsaky/androidide/actions/etc/FindInProjectAction.kt b/core/app/src/main/java/com/itsaky/androidide/actions/etc/FindInProjectAction.kt index 03bb49c80..cb3641d11 100644 --- a/core/app/src/main/java/com/itsaky/androidide/actions/etc/FindInProjectAction.kt +++ b/core/app/src/main/java/com/itsaky/androidide/actions/etc/FindInProjectAction.kt @@ -47,8 +47,8 @@ class FindInProjectAction() : EditorActivityAction() { return } - val project = IProjectManager.getInstance().rootProject - if (project == null || project.subProjects.isEmpty()) { + val project = IProjectManager.getInstance().getWorkspace() + if (project == null || project.getSubProjects().isEmpty()) { markInvisible() return } diff --git a/core/app/src/main/java/com/itsaky/androidide/actions/etc/LaunchAppAction.kt b/core/app/src/main/java/com/itsaky/androidide/actions/etc/LaunchAppAction.kt index 6757f3c39..7f8d51585 100644 --- a/core/app/src/main/java/com/itsaky/androidide/actions/etc/LaunchAppAction.kt +++ b/core/app/src/main/java/com/itsaky/androidide/actions/etc/LaunchAppAction.kt @@ -26,6 +26,7 @@ import com.itsaky.androidide.actions.EditorActivityAction import com.itsaky.androidide.actions.markInvisible import com.itsaky.androidide.actions.openApplicationModuleChooser import com.itsaky.androidide.projects.IProjectManager +import com.itsaky.androidide.projects.android.androidAppProjects import com.itsaky.androidide.utils.IntentUtils import com.itsaky.androidide.utils.flashError import org.slf4j.LoggerFactory @@ -58,8 +59,11 @@ class LaunchAppAction(context: Context, override val order: Int) : EditorActivit visible = true - val projectManager = IProjectManager.getInstance() - enabled = projectManager.getAndroidAppModules().isNotEmpty() + enabled = IProjectManager.getInstance() + .getWorkspace() + ?.androidAppProjects() + ?.iterator() + ?.hasNext() == true } override suspend fun execAction(data: ActionData) { diff --git a/core/app/src/main/java/com/itsaky/androidide/actions/internalActionUtils.kt b/core/app/src/main/java/com/itsaky/androidide/actions/internalActionUtils.kt index 635ef56b8..8bfdb8dbf 100644 --- a/core/app/src/main/java/com/itsaky/androidide/actions/internalActionUtils.kt +++ b/core/app/src/main/java/com/itsaky/androidide/actions/internalActionUtils.kt @@ -41,8 +41,12 @@ inline fun openApplicationModuleChooser(data: ActionData, */ inline fun openApplicationModuleChooser(context: Context, crossinline callback: (AndroidModule) -> Unit) { - val projectManager = IProjectManager.getInstance() - val applications = projectManager.getAndroidAppModules() + val applications = IProjectManager.getInstance() + .getWorkspace() + ?.androidProjects() + ?.filter(AndroidModule::isApplication) + ?.toList() ?: emptyList() + if (applications.isEmpty()) { flashError(R.string.msg_launch_failure_no_app_module) ILogger.ROOT.error("Cannot run application. No application modules found in project.") @@ -61,7 +65,8 @@ inline fun openApplicationModuleChooser(context: Context, context, context.getString(R.string.title_choose_application), applications.map { it.path }.toTypedArray(), - 0) { selection -> + 0 + ) { selection -> val app = applications[selection] ILogger.ROOT.info("Selected application: '{}'", app.path) callback(app) diff --git a/core/app/src/main/java/com/itsaky/androidide/actions/sidebar/TerminalSidebarAction.kt b/core/app/src/main/java/com/itsaky/androidide/actions/sidebar/TerminalSidebarAction.kt index 8ed41e464..d2de6d27e 100644 --- a/core/app/src/main/java/com/itsaky/androidide/actions/sidebar/TerminalSidebarAction.kt +++ b/core/app/src/main/java/com/itsaky/androidide/actions/sidebar/TerminalSidebarAction.kt @@ -58,7 +58,7 @@ class TerminalSidebarAction(context: Context, override val order: Int) : Abstrac ) putExtra( TERMUX_ACTIVITY.EXTRA_SESSION_NAME, - IProjectManager.getInstance().rootProject?.rootProject?.name + IProjectManager.getInstance().getWorkspace()?.getRootProject()?.name ) putExtra(TERMUX_ACTIVITY.EXTRA_FAILSAFE_SESSION, isFailsafe) } diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt b/core/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt index 0c3422ec5..9be17ee9f 100755 --- a/core/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt +++ b/core/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt @@ -32,7 +32,7 @@ import com.itsaky.androidide.activities.editor.EditorActivityKt import com.itsaky.androidide.app.EdgeToEdgeIDEActivity import com.itsaky.androidide.databinding.ActivityMainBinding import com.itsaky.androidide.preferences.internal.GeneralPreferences -import com.itsaky.androidide.projects.internal.ProjectManagerImpl +import com.itsaky.androidide.projects.IProjectManager import com.itsaky.androidide.resources.R.string import com.itsaky.androidide.templates.ITemplateProvider import com.itsaky.androidide.utils.DialogUtils @@ -196,7 +196,7 @@ class MainActivity : EdgeToEdgeIDEActivity() { } internal fun openProject(root: File) { - ProjectManagerImpl.getInstance().projectPath = root.absolutePath + IProjectManager.getInstance().openProject(root) startActivity(Intent(this, EditorActivityKt::class.java)) } diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt b/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt index 9223edc0c..37698604c 100644 --- a/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt +++ b/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt @@ -84,7 +84,6 @@ import com.itsaky.androidide.models.Range import com.itsaky.androidide.models.SearchResult import com.itsaky.androidide.preferences.internal.BuildPreferences import com.itsaky.androidide.projects.IProjectManager -import com.itsaky.androidide.projects.internal.ProjectManagerImpl import com.itsaky.androidide.tasks.cancelIfActive import com.itsaky.androidide.ui.CodeEditorView import com.itsaky.androidide.ui.ContentTranslatingDrawerLayout @@ -325,9 +324,8 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele registerLanguageServers() if (savedInstanceState != null && savedInstanceState.containsKey(KEY_PROJECT_PATH)) { - ProjectManagerImpl.getInstance().projectPath = savedInstanceState.getString( - KEY_PROJECT_PATH - )!! + IProjectManager.getInstance() + .openProject(savedInstanceState.getString(KEY_PROJECT_PATH)!!) } onBackPressedDispatcher.addCallback(this, onBackPressedCallback) diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt b/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt index f60c1eba5..5569d59ce 100644 --- a/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt +++ b/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt @@ -39,9 +39,9 @@ import com.itsaky.androidide.lookup.Lookup import com.itsaky.androidide.lsp.IDELanguageClientImpl import com.itsaky.androidide.lsp.java.utils.CancelChecker import com.itsaky.androidide.preferences.internal.GeneralPreferences -import com.itsaky.androidide.projects.internal.ProjectManagerImpl import com.itsaky.androidide.projects.GradleProject import com.itsaky.androidide.projects.builder.BuildService +import com.itsaky.androidide.projects.internal.ProjectManagerImpl import com.itsaky.androidide.services.builder.GradleBuildService import com.itsaky.androidide.services.builder.GradleBuildServiceConnnection import com.itsaky.androidide.services.builder.gradleDistributionParams @@ -320,7 +320,8 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { // use the default variant selections if (currentVariants == null) { log.debug( - "No variant selection information available. Default build variants will be selected.") + "No variant selection information available. Default build variants will be selected." + ) initializeProject(emptyMap()) return } @@ -355,7 +356,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { */ fun initializeProject(buildVariants: Map) { val manager = ProjectManagerImpl.getInstance() - val projectDir = File(manager.projectPath) + val projectDir = manager.projectDir if (!projectDir.exists()) { log.error("GradleProject directory does not exist. Cannot initialize project") return @@ -471,7 +472,8 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { if (pid != metadata.pid) { log.warn( "Tooling server pid mismatch. Expected: {}, Actual: {}. Replacing memory watcher...", - pid, metadata.pid) + pid, metadata.pid + ) memoryUsageWatcher.watchProcess(metadata.pid, PROC_GRADLE_TOOLING) resetMemUsageChart() } @@ -495,7 +497,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { editorActivityScope.launch(Dispatchers.IO) { manager.setupProject() manager.notifyProjectUpdate() - updateBuildVariants(manager.androidBuildVariants) + updateBuildVariants(manager.requireWorkspace().getAndroidVariantSelections()) com.itsaky.androidide.tasks.runOnUiThread { postProjectInit(true, null) @@ -552,7 +554,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { protected open fun createFindInProjectDialog(): AlertDialog? { val manager = ProjectManagerImpl.getInstance() - if (manager.rootProject == null) { + if (manager.getWorkspace() == null) { log.warn("No root project model found. Is the project initialized?") flashError(getString(string.msg_project_not_initialized)) return null @@ -560,7 +562,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { val moduleDirs = try { - manager.rootProject!!.subProjects.stream().map(GradleProject::projectDir) + manager.getWorkspace()!!.getSubProjects().stream().map(GradleProject::projectDir) .collect(Collectors.toList()) } catch (e: Throwable) { flashError(getString(string.msg_no_modules)) @@ -655,13 +657,13 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { val manager = ProjectManagerImpl.getInstance() GeneralPreferences.lastOpenedProject = manager.projectDirPath try { - val project = manager.rootProject - if (project == null) { + val workspace = manager.getWorkspace() + if (workspace == null) { log.warn("GradleProject not initialized. Skipping initial setup...") return } - var projectName = project.rootProject.name + var projectName = workspace.getRootProject().name if (projectName.isEmpty()) { projectName = manager.projectDir.name } diff --git a/core/app/src/main/java/com/itsaky/androidide/fragments/RunTasksDialogFragment.kt b/core/app/src/main/java/com/itsaky/androidide/fragments/RunTasksDialogFragment.kt index 4b8e0f588..91393defb 100644 --- a/core/app/src/main/java/com/itsaky/androidide/fragments/RunTasksDialogFragment.kt +++ b/core/app/src/main/java/com/itsaky/androidide/fragments/RunTasksDialogFragment.kt @@ -177,10 +177,10 @@ class RunTasksDialogFragment : BottomSheetDialogFragment() { viewModel.displayedChild = CHILD_LOADING executeAsync({ - val project = IProjectManager.getInstance().rootProject + val workspace = IProjectManager.getInstance().getWorkspace() ?: return@executeAsync emptyList>() - return@executeAsync project.subProjects + return@executeAsync workspace.getSubProjects() .flatMap { it.tasks } .map> { Checkable(false, it) diff --git a/core/app/src/main/java/com/itsaky/androidide/indexing/platform/PlatformIndexService.kt b/core/app/src/main/java/com/itsaky/androidide/indexing/platform/PlatformIndexService.kt index 470facd11..4cc543daa 100644 --- a/core/app/src/main/java/com/itsaky/androidide/indexing/platform/PlatformIndexService.kt +++ b/core/app/src/main/java/com/itsaky/androidide/indexing/platform/PlatformIndexService.kt @@ -19,7 +19,7 @@ package com.itsaky.androidide.indexing.platform import com.google.auto.service.AutoService import com.itsaky.androidide.indexing.IIndexService -import com.itsaky.androidide.projects.Project +import com.itsaky.androidide.projects.IWorkspace import org.slf4j.LoggerFactory import java.io.File @@ -36,9 +36,9 @@ internal class PlatformIndexService : IIndexService { override val displayName: String get() = "Android Platform Indexing Service" - override fun scanFiles(project: Project): Collection { + override fun scanFiles(workspace: IWorkspace): Collection { return mutableListOf().apply { - project.findAndroidModules().forEach { androidModule -> + workspace.androidProjects().forEach { androidModule -> add(androidModule.getPlatformDir()?.also { log.debug("Adding {} to the list of indexable paths", it) } ?: return@forEach) @@ -47,7 +47,7 @@ internal class PlatformIndexService : IIndexService { } override suspend fun indexFiles( - project: Project, + workspace: IWorkspace, files: Collection ) { } diff --git a/core/indexing-api/src/main/java/com/itsaky/androidide/indexing/IIndexService.kt b/core/indexing-api/src/main/java/com/itsaky/androidide/indexing/IIndexService.kt index dfe51abf0..76247a705 100644 --- a/core/indexing-api/src/main/java/com/itsaky/androidide/indexing/IIndexService.kt +++ b/core/indexing-api/src/main/java/com/itsaky/androidide/indexing/IIndexService.kt @@ -17,7 +17,7 @@ package com.itsaky.androidide.indexing -import com.itsaky.androidide.projects.Project +import com.itsaky.androidide.projects.IWorkspace import kotlinx.coroutines.Deferred import java.io.File @@ -36,16 +36,16 @@ interface IIndexService { /** * Called to scan files that need to be indexed by this index service. * - * @param project The root project model which can be used to query the project properties. + * @param workspace The root workspace model which can be used to query the workspace properties. * @return A [Deferred] collection of files that need to be indexed. */ - fun scanFiles(project: Project): Collection + fun scanFiles(workspace: IWorkspace): Collection /** * Called to index the given files. * - * @param project The root project model which can be used to query the project properties. + * @param workspace The root workspace model which can be used to query the workspace properties. * @param files The files to index. */ - suspend fun indexFiles(project: Project, files: Collection) + suspend fun indexFiles(workspace: IWorkspace, files: Collection) } \ No newline at end of file diff --git a/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.java b/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.java index b2d692862..227210714 100644 --- a/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.java +++ b/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.java @@ -19,17 +19,20 @@ package com.itsaky.androidide.lsp.api; import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.itsaky.androidide.eventbus.events.project.ProjectInitializedEvent; -import com.itsaky.androidide.projects.Project; +import com.itsaky.androidide.projects.IWorkspace; import com.itsaky.androidide.utils.ILogger; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; /** * Thread-safe implementation of {@link ILanguageServerRegistry}. @@ -117,14 +120,14 @@ public class DefaultLanguageServerRegistry extends ILanguageServerRegistry { @Subscribe(threadMode = ThreadMode.BACKGROUND) @SuppressWarnings("unused") public void onProjectInitialized(ProjectInitializedEvent event) { - final var project = event.get(Project.class); - if (project == null) { + final var workspace = event.get(IWorkspace.class); + if (workspace == null) { return; } ILogger.ROOT.debug("Dispatching ProjectInitializedEvent to language servers..."); for (final var server : mRegister.values()) { - server.setupWithProject(project); + server.setupWorkspace(workspace); } } } diff --git a/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.kt b/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.kt index 17da5262c..42c4050ac 100644 --- a/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.kt +++ b/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.kt @@ -46,7 +46,7 @@ import com.itsaky.androidide.lsp.models.ReferenceResult import com.itsaky.androidide.lsp.models.SignatureHelp import com.itsaky.androidide.lsp.models.SignatureHelpParams import com.itsaky.androidide.models.Range -import com.itsaky.androidide.projects.Project +import com.itsaky.androidide.projects.IWorkspace import java.nio.file.Path /** @@ -90,14 +90,14 @@ interface ILanguageServer { fun applySettings(settings: IServerSettings?) /** - * Setup this language server with the given project. Servers are not expected to keep a reference - * to the provided project. Instead, use - * [getRootProject()][com.itsaky.androidide.projects.IProjectManager.rootProject] to - * obtain the project instance. + * Setup this language server with the given workspace. Servers are not expected to keep a reference + * to the provided workspace. Instead, use + * [getRootWorkspace()][com.itsaky.androidide.projects.IProjectManager.workspace] to + * obtain the workspace instance. * - * @param project The initialized project. + * @param workspace The initialized workspace. */ - fun setupWithProject(project: Project) + fun setupWorkspace(workspace: IWorkspace) /** * Compute code completions for the given completion params. diff --git a/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/util/commonCompletionUtils.kt b/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/util/commonCompletionUtils.kt index 10e67af69..dd0bb67dc 100644 --- a/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/util/commonCompletionUtils.kt +++ b/core/lsp-api/src/main/java/com/itsaky/androidide/lsp/util/commonCompletionUtils.kt @@ -32,7 +32,8 @@ fun setupLookupForCompletion(file: File) { } fun setupLookupForCompletion(file: Path) { - val module = IProjectManager.getInstance().findModuleForFile(file, false) ?: return + val module = + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(file, false) ?: return val lookup = Lookup.getDefault() lookup.update(ModuleProject.COMPLETION_MODULE_KEY, module) diff --git a/core/projects/src/main/java/com/itsaky/androidide/projects/IProjectManager.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/IProjectManager.kt index 5bff16b4b..462fa736d 100644 --- a/core/projects/src/main/java/com/itsaky/androidide/projects/IProjectManager.kt +++ b/core/projects/src/main/java/com/itsaky/androidide/projects/IProjectManager.kt @@ -20,13 +20,10 @@ package com.itsaky.androidide.projects import androidx.annotation.RestrictTo import com.android.builder.model.v2.models.ProjectSyncIssues import com.itsaky.androidide.lookup.Lookup -import com.itsaky.androidide.projects.android.AndroidModule import com.itsaky.androidide.projects.builder.BuildService import com.itsaky.androidide.tooling.api.IProject -import com.itsaky.androidide.tooling.api.models.BuildVariantInfo import com.itsaky.androidide.utils.ServiceLoader import java.io.File -import java.nio.file.Path /** * Project manager. @@ -51,21 +48,16 @@ interface IProjectManager { } } - /** - * The root project model. - */ - val rootProject: Project? - /** * The path to the project's root directory. */ val projectDirPath: String + get() = projectDir.path /** * The project's root directory. */ val projectDir: File - get() = File(projectDirPath) /** * Issues that were encountered during project synchronization. @@ -73,11 +65,14 @@ interface IProjectManager { val projectSyncIssues: ProjectSyncIssues? /** - * Build variant information about Android modules in the project. - * - * The entries in this map are the Gradle project paths mapped to the corresponding [BuildVariantInfo]s. + * Open the given project directory. */ - val androidBuildVariants: Map + fun openProject(directory: File) + + /** + * Same as [openProject]. + */ + fun openProject(path: String) = openProject(File(path)) /** * Setup the project with the given [project proxy][project] from the Tooling API. @@ -90,81 +85,19 @@ interface IProjectManager { ) /** - * Get the list of the modules in this project which are Android projects. + * Get the workspace instance. * - * @return The list of Android modules. + * @return The configured workspace, or `null`. */ - fun getAndroidModules(): List + fun getWorkspace(): IWorkspace? /** - * Get the list of modules in this project which are Android application modules. + * Get the workspace instance. * - * @return The list of Android application modules. + * @return The configured workspace. + * @throws IWorkspace.NotConfiguredException If the workspace has not been configured yet. */ - fun getAndroidAppModules(): List - - /** - * Get the list of modules in this project which are Android library modules. - * - * @return The list of Android library modules. - */ - fun getAndroidLibraryModules(): List - - /** - * Find the module for the given file. - * - * @param file The file to find the module for. - * @return The module project, or `null` if not found. - */ - fun findModuleForFile(file: File): ModuleProject? { - return findModuleForFile(file, true) - } - - /** - * Find the module for the given file. - * - * @param file The file to find the module for. - * @param checkExistance Whether to check if the file exists or not. - * @return The [ModuleProject] for the given file, or `null` if cannot be found. - */ - fun findModuleForFile(file: File, checkExistance: Boolean): ModuleProject? - - /** - * Find the module for the given file. - * - * @param file The file to find the module for. - * @return The module project, or `null` if not found. - */ - fun findModuleForFile(file: Path): ModuleProject? { - return findModuleForFile(file, true) - } - - /** - * Find the module for the given file. - * - * @param file The file to find the module for. - * @param checkExistance Whether to check if the file exists or not. - * @return The [ModuleProject] for the given file, or `null` if cannot be found. - */ - fun findModuleForFile(file: Path, checkExistance: Boolean): ModuleProject? { - return findModuleForFile(file.toFile(), checkExistance) - } - - /** - * Check if any of the module projects contain the given [file] in their source folder. - * - * @param file The file to check. - * @return `true` if the given [file] is a source file in any of the mdoules, `false` otherwise. - */ - fun containsSourceFile(file: Path): Boolean - - /** - * Checks if the given file is a resource file in any of the included Android modules. - * - * @param file The file to check. - * @return `true` if the given file is a resource file in any of the Android modules, `false` otherwise. - */ - fun isAndroidResource(file: File): Boolean + fun requireWorkspace(): IWorkspace = getWorkspace() ?: throw IWorkspace.NotConfiguredException() /** * Notify the project manager that the given file was created. diff --git a/core/projects/src/main/java/com/itsaky/androidide/projects/IWorkspace.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/IWorkspace.kt new file mode 100644 index 000000000..e15745ac0 --- /dev/null +++ b/core/projects/src/main/java/com/itsaky/androidide/projects/IWorkspace.kt @@ -0,0 +1,131 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.projects + +import com.android.builder.model.v2.models.ProjectSyncIssues +import com.itsaky.androidide.projects.android.AndroidModule +import com.itsaky.androidide.tooling.api.models.BuildVariantInfo +import java.io.File +import java.nio.file.Path + +/** + * Workspace represents everything related to the project opened in the IDE. + * + * @author Akash Yadav + */ +interface IWorkspace { + + // TODO: Add support for composite (included) Gradle builds + + /** + * Get the project directory for the workspace. This is usually the root project directory. + */ + fun getProjectDir(): File + + /** + * Get the root project model. + */ + fun getRootProject(): GradleProject + + /** + * Get the subprojects included in the root project. + */ + fun getSubProjects(): List + + /** + * Get the issues that were encountered while synchronizing the project with Gradle files. + */ + fun getProjectSyncIssues(): ProjectSyncIssues + + /** + * Get the build variants that are selected (configured/synchronized) for each of the Android modules + * in this workspace. The keys in the returned map are the paths of the module projects and the values + * are the information about the project's variants. + */ + fun getAndroidVariantSelections(): Map + + /** + * Finds the project by the given path. + * + * @return The project with the given path or `null` if no project is available with that path. + */ + fun findProject(path: String): GradleProject? + + /** + * Get the project with the given project path. + * + * @param path The project path. + * @return The project with the given path. + * @throws ProjectNotFoundException If the project could not be found. + */ + fun getProject(path: String): GradleProject = + findProject(path) ?: throw ProjectNotFoundException(path) + + /** + * List all the [AndroidModule]s in this project. If this project is itself an Android module, + * then it also added to the list at index `0`. + * + * @return A sequence of android modules. + */ + fun androidProjects(): Sequence + + /** + * Find the module project for the given file path. + * + * @param file The file to find the module for. + * @param checkExistance Whether to check if the file exists or not. + * @return The [ModuleProject] if found, `null` otherwise. + */ + fun findModuleForFile(file: Path, checkExistance: Boolean = false): ModuleProject? + + /** + * Find the module project for the given file path. + * + * @param file The file to find the module for. + * @param checkExistance Whether to check if the file exists or not. + * @return The [ModuleProject] if found, `null` otherwise. + */ + fun findModuleForFile(file: File, checkExistance: Boolean = false): ModuleProject? + + /** + * Check if any of the module projects contain the given [file] in their source folder. + * + * @param file The file to check. + * @return `true` if the given [file] is a source file in any of the mdoules, `false` otherwise. + */ + fun containsSourceFile(file: Path): Boolean + + /** + * Checks if the given file is a resource file in any of the included Android modules. + * + * @param file The file to check. + * @return `true` if the given file is a resource file in any of the Android modules, `false` otherwise. + */ + fun isAndroidResource(file: File): Boolean + + /** + * Thrown by [IWorkspace] if the project with a given path could not be found in the workspace. + */ + class ProjectNotFoundException(path: String) : + RuntimeException("Could not find project with path: $path") + + /** + * Thrown by [IProjectManager] when trying to access the workspace and it is not configured yet. + */ + class NotConfiguredException() : RuntimeException("Workspace not configured") +} \ No newline at end of file diff --git a/core/projects/src/main/java/com/itsaky/androidide/projects/Project.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/Project.kt deleted file mode 100644 index f66b5376b..000000000 --- a/core/projects/src/main/java/com/itsaky/androidide/projects/Project.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - */ - -package com.itsaky.androidide.projects - -import com.android.builder.model.v2.ide.ProjectType -import com.android.builder.model.v2.models.ProjectSyncIssues -import com.itsaky.androidide.projects.android.AndroidModule -import java.io.File -import java.nio.file.Path - -/** - * Model for representing the whole project that is opened in the IDE (including the root project). - * - * @property rootProject The root Gradle project. - * @property subProjects List of all project that are included the project. - * @property projectSyncIssues The issues that occurred while syncing the project. - * @author Akash Yadav - */ -data class Project( - val rootProject: GradleProject, - val subProjects: List, - val projectSyncIssues: ProjectSyncIssues -) { - - /** - * Finds the project by the given path. - * - * @return The project with the given path or `null` if no project is available with that path. - */ - fun findByPath(path: String): GradleProject? { - return this.subProjects.find { it.path == path } - } - - /** - * Find the first [AndroidModule] in this project. If this project is itself an Android module, - * return `this` instance. - * - * @return The first Android module. - */ - fun findFirstAndroidModule(): AndroidModule? { - val android = this.subProjects.find { it is AndroidModule } - if (android != null) { - return android as AndroidModule - } - return null - } - - /** - * Find the first [AndroidModule] in this project whose `projectType` is - * [com.android.builder.model.v2.ide.ProjectType.APPLICATION]. If this project is itself an - * Android app module, return `this` instance. - * - * @return The first Android module. - */ - fun findFirstAndroidAppModule(): AndroidModule? { - val app = this.subProjects.find { it is AndroidModule && it.projectType == ProjectType.APPLICATION } - - if (app != null) { - return app as AndroidModule - } - return null - } - - /** - * Finds all the [AndroidModule]s in this project. If this project is itself an Android module, - * then it also added to the list at index `0`. - * - * @return The list of android modules. - */ - fun findAndroidModules(): List { - return subProjects.filterIsInstance(AndroidModule::class.java).toMutableList() - } - - fun findModuleForFile(file: Path): ModuleProject? { - return findModuleForFile(file.toFile()) - } - - @JvmOverloads - fun findModuleForFile(file: File, checkExistance: Boolean = false): ModuleProject? { - - if (!file.exists() && checkExistance) { - return null - } - - val path = file.path - var longestPath = "" - var moduleWithLongestPath: ModuleProject? = null - - for (module in subProjects) { - if (module !is ModuleProject) { - continue - } - - val moduleDir = module.projectDir.path - if (path.startsWith(moduleDir) && longestPath.length < moduleDir.length) { - longestPath = moduleDir - moduleWithLongestPath = module - } - } - - if (longestPath.isEmpty() || moduleWithLongestPath == null) { - return null - } - - return moduleWithLongestPath - } -} \ No newline at end of file diff --git a/core/projects/src/main/java/com/itsaky/androidide/projects/android/AndroidModule.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/android/AndroidModule.kt index f4c31f296..fcee7fdb1 100644 --- a/core/projects/src/main/java/com/itsaky/androidide/projects/android/AndroidModule.kt +++ b/core/projects/src/main/java/com/itsaky/androidide/projects/android/AndroidModule.kt @@ -31,8 +31,8 @@ import com.itsaky.androidide.builder.model.DefaultSourceSetContainer import com.itsaky.androidide.builder.model.DefaultViewBindingOptions import com.itsaky.androidide.builder.model.UNKNOWN_PACKAGE import com.itsaky.androidide.projects.IProjectManager +import com.itsaky.androidide.projects.IWorkspace import com.itsaky.androidide.projects.ModuleProject -import com.itsaky.androidide.projects.Project import com.itsaky.androidide.tooling.api.ProjectType.Android import com.itsaky.androidide.tooling.api.models.BasicAndroidVariantMetadata import com.itsaky.androidide.tooling.api.models.GradleTask @@ -111,16 +111,21 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. val variants: List = listOf(), val configuredVariant: BasicAndroidVariantMetadata?, val classesJar: File? -) : - ModuleProject( - name, - description, - path, - projectDir, - buildDir, - buildScript, - tasks - ) { +) : ModuleProject( + name, description, path, projectDir, buildDir, buildScript, tasks +) { + + /** + * Whether this project is an Android library project. + */ + val isLibrary: Boolean + get() = this.projectType == ProjectType.LIBRARY + + /** + * Whether this project is an Android application project. + */ + val isApplication: Boolean + get() = this.projectType == ProjectType.APPLICATION companion object { @@ -166,8 +171,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. override fun getSourceDirectories(): Set { if (mainSourceSet == null) { log.warn( - "No main source set is available for project {}. Cannot get source directories.", - name + "No main source set is available for project {}. Cannot get source directories.", name ) return mutableSetOf() } @@ -202,7 +206,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. } override fun getCompileClasspaths(): Set { - val project = IProjectManager.getInstance().rootProject ?: return emptySet() + val project = IProjectManager.getInstance().getWorkspace() ?: return emptySet() val result = mutableSetOf() result.addAll(getModuleClasspaths()) @@ -210,11 +214,11 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. return result } - private fun collectLibraries(root: Project, libraries: Set, result: MutableSet) { + private fun collectLibraries(root: IWorkspace, libraries: Set, result: MutableSet) { for (library in libraries) { val lib = this.libraryMap[library] ?: continue if (lib.type == PROJECT) { - val module = root.findByPath(lib.projectInfo!!.projectPath) ?: continue + val module = root.findProject(lib.projectInfo!!.projectPath) ?: continue if (module !is ModuleProject) { continue } @@ -231,7 +235,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. } override fun getCompileModuleProjects(): List { - val root = IProjectManager.getInstance().rootProject ?: return emptyList() + val root = IProjectManager.getInstance().getWorkspace() ?: return emptyList() val result = mutableListOf() for (library in this.libraries) { @@ -240,7 +244,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. continue } - val module = root.findByPath(lib.projectInfo!!.projectPath) ?: continue + val module = root.findProject(lib.projectInfo!!.projectPath) ?: continue if (module !is ModuleProject) { continue } @@ -362,27 +366,19 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. fun getDependencyResourceTables(): Set { return mutableSetOf().also { var deps: Int - it.addAll( - libraryMap.values - .filter { library -> - library.type == ANDROID_LIBRARY && - library.androidLibraryData!!.resFolder.exists() && - library.findPackageName() != UNKNOWN_PACKAGE - } - .also { libs -> deps = libs.size } - .mapNotNull { library -> - ResourceTableRegistry.getInstance() - .let { registry -> - registry.isLoggingEnabled = false - registry.forPackage( - library.packageName, - library.androidLibraryData!!.resFolder, - ).also { - registry.isLoggingEnabled = true - } + it.addAll(libraryMap.values.filter { library -> + library.type == ANDROID_LIBRARY && library.androidLibraryData!!.resFolder.exists() && library.findPackageName() != UNKNOWN_PACKAGE + }.also { libs -> deps = libs.size }.mapNotNull { library -> + ResourceTableRegistry.getInstance().let { registry -> + registry.isLoggingEnabled = false + registry.forPackage( + library.packageName, + library.androidLibraryData!!.resFolder, + ).also { + registry.isLoggingEnabled = true } - } - ) + } + }) log.info("Created {} resource tables for {} dependencies of module '{}'", it.size, deps, path) } @@ -409,19 +405,17 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. * @param pck The package to look for. */ fun findAllResourceTableForPackage( - pck: String, - hasGroup: AaptResourceType? = null + pck: String, hasGroup: AaptResourceType? = null ): List { if (pck == SdkConstants.ANDROID_PKG) { return getFrameworkResourceTable()?.let { listOf(it) } ?: emptyList() } - val tables: List = - mutableListOf().apply { - getResourceTable()?.let { add(it) } - addAll(getSourceResourceTables()) - addAll(getDependencyResourceTables()) - } + val tables: List = mutableListOf().apply { + getResourceTable()?.let { add(it) } + addAll(getSourceResourceTables()) + addAll(getDependencyResourceTables()) + } val result = mutableListOf() for (table in tables) { @@ -495,7 +489,8 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. fun getSelectedVariant(): BasicAndroidVariantMetadata? { val projectManager = IProjectManager.getInstance() - val info = projectManager.androidBuildVariants[this.path] + val info = + (projectManager.getWorkspace()?.getAndroidVariantSelections() ?: emptyMap())[this.path] if (info == null) { log.error( "Failed to find selected build variant for module: '{}'", this.path @@ -520,4 +515,4 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. * @return The Android SDK platform directory for this Android module, or `null` if none is found. */ fun getPlatformDir() = bootClassPaths.firstOrNull { it.name == "android.jar" }?.parentFile -} +} \ No newline at end of file diff --git a/core/projects/src/main/java/com/itsaky/androidide/projects/android/androidModuleExts.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/android/androidModuleExts.kt new file mode 100644 index 000000000..d65bcb1b9 --- /dev/null +++ b/core/projects/src/main/java/com/itsaky/androidide/projects/android/androidModuleExts.kt @@ -0,0 +1,40 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.projects.android + +import com.itsaky.androidide.projects.IWorkspace + +/** + * Get a sequence of all Android application projects in this workspace. + */ +fun IWorkspace.androidAppProjects() = androidProjects().filterIsAndroidApp() + +/** + * Get a sequence of all Android library projects in this workspace. + */ +fun IWorkspace.androidLibraryProjects() = androidProjects().filterIsAndroidLib() + +/** + * Returns a sequence containing only Android application projects. + */ +fun Sequence.filterIsAndroidApp() = filter(AndroidModule::isApplication) + +/** + * Returns a sequence containing only Android library projects. + */ +fun Sequence.filterIsAndroidLib() = filter(AndroidModule::isApplication) \ No newline at end of file diff --git a/core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectManagerImpl.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectManagerImpl.kt index 67cbc71ba..28306f206 100644 --- a/core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectManagerImpl.kt +++ b/core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectManagerImpl.kt @@ -31,15 +31,14 @@ import com.itsaky.androidide.eventbus.events.project.ProjectInitializedEvent import com.itsaky.androidide.lookup.Lookup import com.itsaky.androidide.projects.CachingProject import com.itsaky.androidide.projects.IProjectManager +import com.itsaky.androidide.projects.IWorkspace +import com.itsaky.androidide.projects.ModuleProject import com.itsaky.androidide.projects.R import com.itsaky.androidide.projects.android.AndroidModule -import com.itsaky.androidide.projects.ModuleProject -import com.itsaky.androidide.projects.Project import com.itsaky.androidide.projects.builder.BuildService import com.itsaky.androidide.tasks.executeAsync import com.itsaky.androidide.tooling.api.IAndroidProject import com.itsaky.androidide.tooling.api.IProject -import com.itsaky.androidide.tooling.api.ProjectType import com.itsaky.androidide.tooling.api.messages.result.InitializeResult import com.itsaky.androidide.tooling.api.models.BuildVariantInfo import com.itsaky.androidide.utils.DocumentUtils @@ -58,8 +57,6 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import org.slf4j.LoggerFactory import java.io.File -import java.nio.file.Files -import java.nio.file.Path import java.util.Locale import kotlin.io.path.extension import kotlin.io.path.isDirectory @@ -74,52 +71,53 @@ import kotlin.io.path.pathString @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) class ProjectManagerImpl : IProjectManager, EventReceiver { - lateinit var projectPath: String + private var _workspace: WorkspaceImpl? = null + private var _projectDir: File? = null + var projectInitialized: Boolean = false var cachedInitResult: InitializeResult? = null - override var rootProject: Project? = null - private set - - override var androidBuildVariants: Map = emptyMap() - private set - - override val projectDirPath: String - get() = projectPath + override val projectDir: File + get() = checkNotNull(_projectDir) { + "Cannot get project directory. Path has not been set." + } override val projectSyncIssues: ProjectSyncIssues? - get() = rootProject?.projectSyncIssues + get() = getWorkspace()?.getProjectSyncIssues() - companion object { - private val log = LoggerFactory.getLogger(ProjectManagerImpl::class.java) + override fun getWorkspace(): IWorkspace? { + return _workspace + } - @JvmStatic - fun getInstance(): ProjectManagerImpl = IProjectManager.getInstance() as ProjectManagerImpl + override fun openProject(directory: File) { + // IMP: Always use canonical path + this._projectDir = directory.canonicalFile } override suspend fun setupProject(project: IProject) { - this.rootProject = withStopWatch("Transform project proxy") { + this._workspace = withStopWatch("Transform project proxy") { withContext(Dispatchers.IO) { - ProjectTransformer().transform(CachingProject(project)) + WorkspaceModelBuilder.build(projectDir, CachingProject(project)) } } - val rootProject = this.rootProject ?: return + val rootProject = this.getWorkspace() ?: return // build variants must be updated before the sources and classpaths are indexed updateBuildVariants { buildVariants -> - androidBuildVariants = buildVariants + _workspace!!.setVariantSelections(buildVariants) } log.info( - "Found {} project sync issues: {}", rootProject.projectSyncIssues.syncIssues.size, - rootProject.projectSyncIssues.syncIssues + "Found {} project sync issues: {}", + rootProject.getProjectSyncIssues().syncIssues.size, + rootProject.getProjectSyncIssues().syncIssues ) withStopWatch("Setup project") { val indexerScope = CoroutineScope(Dispatchers.Default) val modulesFlow = flow { - rootProject.subProjects.filterIsInstance().forEach { + rootProject.getSubProjects().filterIsInstance().forEach { emit(it) } } @@ -138,71 +136,15 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { } } - override fun getAndroidModules(): List { - val rootProject = this.rootProject ?: return emptyList() - return rootProject.subProjects.mapNotNull { module -> - if (module.type != ProjectType.Android) { - return@mapNotNull null - } - - return@mapNotNull module as AndroidModule - } - } - - override fun getAndroidAppModules(): List { - return getAndroidModules().filter { it.projectType == com.android.builder.model.v2.ide.ProjectType.APPLICATION } - } - - override fun getAndroidLibraryModules(): List { - return getAndroidModules().filter { it.projectType == com.android.builder.model.v2.ide.ProjectType.LIBRARY } - } - - override fun findModuleForFile(file: File, checkExistance: Boolean): ModuleProject? { - if (!isInitialized()) { - return null - } - - return this.rootProject!!.findModuleForFile(file, checkExistance) - } - - override fun containsSourceFile(file: Path): Boolean { - if (!isInitialized()) { - return false - } - - if (!Files.exists(file)) { - return false - } - - for (module in this.rootProject!!.subProjects) { - if (module !is ModuleProject) { - continue - } - - val source = module.compileJavaSourceClasses.findSource(file) - if (source != null) { - return true - } - } - - return false - } - - override fun isAndroidResource(file: File): Boolean { - val module = findModuleForFile(file) ?: return false - if (module is AndroidModule) { - return module.getResourceDirectories().find { file.path.startsWith(it.path) } != null - } - return true - } - override fun destroy() { log.info("Destroying project manager") - this.rootProject = null + + this._workspace?.setVariantSelections(emptyMap()) + this._workspace = null + + this._projectDir = null this.cachedInitResult = null this.projectInitialized = false - - (this.androidBuildVariants as? MutableMap?)?.clear() } @JvmOverloads @@ -223,7 +165,7 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { return } - val tasks = getAndroidModules().flatMap { module -> + val tasks = getWorkspace()?.androidProjects()?.flatMap { module -> val variant = module.getSelectedVariant() if (variant == null) { log.error( @@ -243,28 +185,25 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { if (module.viewBindingOptions.isEnabled) "dataBindingGenBaseClasses$variantNameCapitalized" else null, "process${variantNameCapitalized}Resources" ).mapNotNull { it?.let { "${module.path}:${it}" } } - } + }?.toList() ?: emptyList() - builder - .executeTasks(*tasks.toTypedArray()) - .whenComplete { result, taskErr -> - if (result == null || !result.isSuccessful || taskErr != null) { - log.warn( - "Execution for tasks failed: {} {}", tasks, - taskErr ?: "" - ) - } else { - notifyProjectUpdate() - } + builder.executeTasks(*tasks.toTypedArray()).whenComplete { result, taskErr -> + if (result == null || !result.isSuccessful || taskErr != null) { + log.warn( + "Execution for tasks failed: {} {}", tasks, taskErr ?: "" + ) + } else { + notifyProjectUpdate() } + } } fun notifyProjectUpdate() { executeAsync { - rootProject?.apply { - subProjects.forEach { subproject -> + getWorkspace()?.apply { + getSubProjects().forEach { subproject -> if (subproject is ModuleProject) { subproject.indexSources() } @@ -272,26 +211,25 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { } val event = ProjectInitializedEvent() - event.put(Project::class.java, rootProject) + event.put(IWorkspace::class.java, getWorkspace()) EventBus.getDefault().post(event) } } private fun updateBuildVariants(onUpdated: (Map) -> Unit = {}) { - val rootProject = checkNotNull(this.rootProject) { + val rootProject = checkNotNull(this.getWorkspace()) { "Cannot update build variants. Root project model is null." } val buildVariants = mutableMapOf() - rootProject.subProjects.forEach { subproject -> + rootProject.getSubProjects().forEach { subproject -> if (subproject is AndroidModule) { // variant names are not expected to be modified val variantNames = ImmutableList.builder() .addAll(subproject.variants.map { variant -> variant.name }).build() - val variantName = subproject.configuredVariant?.name - ?: IAndroidProject.DEFAULT_VARIANT + val variantName = subproject.configuredVariant?.name ?: IAndroidProject.DEFAULT_VARIANT buildVariants[subproject.path] = BuildVariantInfo(subproject.path, variantNames, variantName) @@ -301,12 +239,10 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { onUpdated(buildVariants) } - private fun isInitialized() = rootProject != null - private fun generateSourcesIfNecessary(event: FileEvent) { val builder = Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE) ?: return val file = event.file - if (!isAndroidResource(file)) { + if (getWorkspace()?.isAndroidResource(file) != true) { return } @@ -325,16 +261,14 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { return@apply } - val module = findModuleForFile(this, false) ?: return@apply + val module = getWorkspace()?.findModuleForFile(this, false) ?: return@apply if (module !is AndroidModule) { return@apply } - val isResource = - module.mainSourceSet?.sourceProvider?.resDirectories?.any { - this.pathString.contains(it.path) - } - ?: false + val isResource = module.mainSourceSet?.sourceProvider?.resDirectories?.any { + this.pathString.contains(it.path) + } ?: false if (isResource) { module.updateResourceTable() @@ -360,7 +294,7 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { generateSourcesIfNecessary(event) if (DocumentUtils.isJavaFile(event.file.toPath())) { - findModuleForFile(event.file, false)?.let { + getWorkspace()?.findModuleForFile(event.file, false)?.let { val sourceRoot = it.findSourceRoot(event.file) ?: return@let // add the source node entry @@ -378,9 +312,10 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { // Do not check for Java file DocumentUtils.isJavaFile(...) as it checks for file existence as // well. As the file is already deleted, it will always return false if (event.file.extension == "java") { - findModuleForFile(event.file, false) - ?.compileJavaSourceClasses - ?.findSource(event.file.toPath()) + getWorkspace()?.findModuleForFile( + event.file, + false + )?.compileJavaSourceClasses?.findSource(event.file.toPath()) ?.let { it.parent?.removeChild(it) } } } @@ -394,18 +329,26 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { // well. As the file is already renamed to another filename, it will always return false if (event.file.extension == "java") { // remove the source node entry - findModuleForFile(event.file, false) - ?.compileJavaSourceClasses - ?.findSource(event.file.toPath()) + getWorkspace()?.findModuleForFile( + event.file, + false + )?.compileJavaSourceClasses?.findSource(event.file.toPath()) ?.let { it.parent?.removeChild(it) } } if (DocumentUtils.isJavaFile(event.newFile.toPath())) { - findModuleForFile(event.newFile, false)?.let { + getWorkspace()?.findModuleForFile(event.newFile, false)?.let { val sourceRoot = it.findSourceRoot(event.newFile) ?: return@let // add the new source node entry it.compileJavaSourceClasses.append(event.newFile.toPath(), sourceRoot) } } } + + companion object { + private val log = LoggerFactory.getLogger(ProjectManagerImpl::class.java) + + @JvmStatic + fun getInstance(): ProjectManagerImpl = IProjectManager.getInstance() as ProjectManagerImpl + } } \ No newline at end of file diff --git a/core/projects/src/main/java/com/itsaky/androidide/projects/internal/WorkspaceImpl.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/internal/WorkspaceImpl.kt new file mode 100644 index 000000000..519f511c2 --- /dev/null +++ b/core/projects/src/main/java/com/itsaky/androidide/projects/internal/WorkspaceImpl.kt @@ -0,0 +1,143 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.projects.internal + +import com.android.builder.model.v2.models.ProjectSyncIssues +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import com.itsaky.androidide.projects.GradleProject +import com.itsaky.androidide.projects.IWorkspace +import com.itsaky.androidide.projects.ModuleProject +import com.itsaky.androidide.projects.android.AndroidModule +import com.itsaky.androidide.tooling.api.models.BuildVariantInfo +import java.io.File +import java.nio.file.Files +import java.nio.file.Path + +/** + * Model for representing the whole project that is opened in the IDE (including the root project). + * + * @property rootProject The root Gradle project. + * @property subProjects List of all project that are included the project. + * @property projectSyncIssues The issues that occurred while syncing the project. + * @author Akash Yadav + */ +internal class WorkspaceImpl( + private val projectDir: File, + private val rootProject: GradleProject, + private val subProjects: List, + private val projectSyncIssues: ProjectSyncIssues +) : IWorkspace { + + private val variantSelections = mutableMapOf() + + internal fun setVariantSelections(selections: Map) { + this.variantSelections.apply { + clear() + putAll(selections) + } + } + + override fun getProjectDir(): File { + return this.projectDir + } + + override fun getRootProject(): GradleProject { + return this.rootProject + } + + override fun getSubProjects(): List { + return ImmutableList.copyOf(this.subProjects) + } + + override fun getProjectSyncIssues(): ProjectSyncIssues { + return this.projectSyncIssues + } + + override fun getAndroidVariantSelections(): Map { + return ImmutableMap.copyOf(this.variantSelections) + } + + override fun findProject(path: String): GradleProject? { + return this.subProjects.find { it.path == path } + } + + override fun androidProjects(): Sequence { + return subProjects.asSequence().filterIsInstance() + } + + override fun findModuleForFile(file: Path, checkExistance: Boolean): ModuleProject? { + return findModuleForFile(file.toFile(), checkExistance) + } + + override fun findModuleForFile(file: File, checkExistance: Boolean): ModuleProject? { + + if (!file.exists() && checkExistance) { + return null + } + + val path = file.canonicalPath + var longestPath = "" + var moduleWithLongestPath: ModuleProject? = null + + for (module in subProjects) { + if (module !is ModuleProject) { + continue + } + + val moduleDir = module.projectDir.canonicalPath + if (path.startsWith(moduleDir) && longestPath.length < moduleDir.length) { + longestPath = moduleDir + moduleWithLongestPath = module + } + } + + if (longestPath.isEmpty() || moduleWithLongestPath == null) { + return null + } + + return moduleWithLongestPath + } + + override fun containsSourceFile(file: Path): Boolean { + if (!Files.exists(file)) { + return false + } + + for (module in subProjects) { + if (module !is ModuleProject) { + continue + } + + val source = module.compileJavaSourceClasses.findSource(file) + if (source != null) { + return true + } + } + + return false + } + + override fun isAndroidResource(file: File): Boolean { + val module = findModuleForFile(file, true) ?: return false + if (module is AndroidModule) { + return module.getResourceDirectories().find { file.path.startsWith(it.path) } != null + } + return true + } +} \ No newline at end of file diff --git a/core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectTransformer.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/internal/WorkspaceModelBuilder.kt similarity index 95% rename from core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectTransformer.kt rename to core/projects/src/main/java/com/itsaky/androidide/projects/internal/WorkspaceModelBuilder.kt index 50c0ec62b..be505d598 100644 --- a/core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectTransformer.kt +++ b/core/projects/src/main/java/com/itsaky/androidide/projects/internal/WorkspaceModelBuilder.kt @@ -17,10 +17,9 @@ package com.itsaky.androidide.projects.internal -import com.itsaky.androidide.projects.android.AndroidModule import com.itsaky.androidide.projects.GradleProject +import com.itsaky.androidide.projects.android.AndroidModule import com.itsaky.androidide.projects.java.JavaModule -import com.itsaky.androidide.projects.Project import com.itsaky.androidide.tooling.api.IAndroidProject import com.itsaky.androidide.tooling.api.IGradleProject import com.itsaky.androidide.tooling.api.IJavaProject @@ -31,6 +30,7 @@ import com.itsaky.androidide.tooling.api.models.BasicProjectMetadata import com.itsaky.androidide.tooling.api.models.JavaProjectMetadata import com.itsaky.androidide.tooling.api.models.params.StringParameter import org.slf4j.LoggerFactory +import java.io.File import java.util.concurrent.CopyOnWriteArrayList /** @@ -38,13 +38,14 @@ import java.util.concurrent.CopyOnWriteArrayList * * @author Akash Yadav */ -class ProjectTransformer { +internal object WorkspaceModelBuilder { - companion object { - private val log = LoggerFactory.getLogger(ProjectTransformer::class.java) - } + private val log = LoggerFactory.getLogger(WorkspaceModelBuilder::class.java) - fun transform(project: IProject): Project? { + fun build( + projectDir: File, + project: IProject + ): WorkspaceImpl? { try { val allProjects = project.getProjects().get() val selectionResult = project.selectProject(StringParameter("")).get() @@ -56,10 +57,12 @@ class ProjectTransformer { ProjectType.Gradle -> transform(project.asGradleProject()) ProjectType.Android -> transform(project.asAndroidProject()) else -> throw IllegalStateException( - "Root project must be either an Android project or a Gradle project") + "Root project must be either an Android project or a Gradle project" + ) } - return Project( + return WorkspaceImpl( + projectDir, rootProject, CopyOnWriteArrayList(transform(allProjects, project)), project.getProjectSyncIssues().get() diff --git a/core/projects/src/main/java/com/itsaky/androidide/projects/java/JavaModule.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/java/JavaModule.kt index 9c6576cda..438a60c26 100644 --- a/core/projects/src/main/java/com/itsaky/androidide/projects/java/JavaModule.kt +++ b/core/projects/src/main/java/com/itsaky/androidide/projects/java/JavaModule.kt @@ -107,12 +107,12 @@ class JavaModule( } override fun getCompileModuleProjects(): List { - val root = IProjectManager.getInstance().rootProject ?: return emptyList() + val workspace = IProjectManager.getInstance().getWorkspace() ?: return emptyList() return this.dependencies - .filterIsInstance(JavaModuleProjectDependency::class.java) + .filterIsInstance() .filter { it.scope == SCOPE_COMPILE } - .mapNotNull { root.findByPath(it.projectPath) } - .filterIsInstance(ModuleProject::class.java) + .mapNotNull { workspace.findProject(it.projectPath) } + .filterIsInstance() } fun getDependencyClasspaths(): Set { diff --git a/core/projects/src/main/java/com/itsaky/androidide/projects/util/projects.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/util/projects.kt index 716f7a102..f9a552abc 100644 --- a/core/projects/src/main/java/com/itsaky/androidide/projects/util/projects.kt +++ b/core/projects/src/main/java/com/itsaky/androidide/projects/util/projects.kt @@ -18,13 +18,17 @@ package com.itsaky.androidide.projects.util import androidx.annotation.RestrictTo +import androidx.annotation.VisibleForTesting import com.itsaky.androidide.projects.IProjectManager import com.itsaky.androidide.projects.android.AndroidModule +import com.itsaky.androidide.projects.android.androidAppProjects /** * **For testing purposes only!** */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +@VisibleForTesting fun findAppModule(): AndroidModule? { - return IProjectManager.getInstance().getAndroidAppModules().firstOrNull { it.path == ":app" } + return IProjectManager.getInstance().getWorkspace()?.androidAppProjects() + ?.firstOrNull { it.path == ":app" } } \ No newline at end of file diff --git a/core/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt b/core/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleWorkspaceTest.kt similarity index 79% rename from core/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt rename to core/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleWorkspaceTest.kt index 46ce858f5..a53cba50e 100644 --- a/core/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt +++ b/core/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleWorkspaceTest.kt @@ -19,11 +19,12 @@ package com.itsaky.androidide.projects.api import com.google.common.truth.Truth.assertThat import com.itsaky.androidide.lookup.Lookup -import com.itsaky.androidide.projects.android.AndroidModule import com.itsaky.androidide.projects.IProjectManager -import com.itsaky.androidide.projects.java.JavaModule +import com.itsaky.androidide.projects.android.AndroidModule import com.itsaky.androidide.projects.builder.BuildService +import com.itsaky.androidide.projects.java.JavaModule import com.itsaky.androidide.testing.tooling.ToolingApiTestLauncher +import com.itsaky.androidide.testing.tooling.models.ToolingApiTestLauncherParams import com.itsaky.androidide.tooling.api.IAndroidProject import com.itsaky.androidide.utils.FileProvider import kotlinx.coroutines.runBlocking @@ -38,24 +39,28 @@ import kotlin.io.path.writeText /** @author Akash Yadav */ @RunWith(RobolectricTestRunner::class) @Config(manifest = Config.DEFAULT_VALUE_STRING) -class ModuleProjectTest { +class ModuleWorkspaceTest { @Test fun test() { - ToolingApiTestLauncher.launchServer() { + val params = ToolingApiTestLauncherParams() + ToolingApiTestLauncher.launchServer(params) { assertThat(result?.isSuccessful).isTrue() Lookup.getDefault().register(BuildService.KEY_PROJECT_PROXY, project) + val projectManager = IProjectManager.getInstance() + projectManager.openProject(params.projectDir.toFile()) + verifyProjectManagerAPIs() assertThat(project).isNotNull() - val rootProject = IProjectManager.getInstance().rootProject + val rootProject = projectManager.getWorkspace() assertThat(rootProject).isNotNull() - val root = rootProject!!.rootProject + val root = rootProject!!.getRootProject() assertThat(root).isNotNull() assertThat(root.name).isEqualTo("TestApp") @@ -63,7 +68,7 @@ class ModuleProjectTest { assertThat(rootDir).isNotNull() assertThat(Files.isSameFile(rootDir.toPath(), FileProvider.testProjectRoot())).isTrue() - val app = rootProject.findByPath(":app") + val app = rootProject.findProject(":app") assertThat(app).isNotNull() assertThat(app).isInstanceOf(AndroidModule::class.java) @@ -79,7 +84,7 @@ class ModuleProjectTest { assertThat(app.compilerSettings.javaBytecodeVersion) .isEqualTo(app.compilerSettings.targetCompatibility) - val anotherAndroidLib = rootProject.findByPath(":another-android-library") + val anotherAndroidLib = rootProject.findProject(":another-android-library") assertThat(anotherAndroidLib).isNotNull() assertThat(anotherAndroidLib).isInstanceOf(AndroidModule::class.java) @@ -154,7 +159,7 @@ class ModuleProjectTest { .hasSize(3) } - val javaLibrary = rootProject.findByPath(":java-library") + val javaLibrary = rootProject.findProject(":java-library") assertThat(javaLibrary).isNotNull() assertThat(javaLibrary).isInstanceOf(JavaModule::class.java) @@ -181,7 +186,7 @@ class ModuleProjectTest { } } - rootProject.findByPath(":another-java-library").run { + rootProject.findProject(":another-java-library").run { assertThat(this).isInstanceOf(JavaModule::class.java) assertThat((this as JavaModule).compilerSettings).isNotNull() assertThat(compilerSettings.javaSourceVersion).isEqualTo("1.8") @@ -207,19 +212,18 @@ class ModuleProjectTest { Files.delete(testCls_renamed) } - val projectManager = IProjectManager.getInstance() - runBlocking { projectManager.setupProject() } + runBlocking { IProjectManager.getInstance().setupProject() } - val rootProject = IProjectManager.getInstance().rootProject - assertThat(rootProject).isNotNull() + val workspace = IProjectManager.getInstance().getWorkspace() + assertThat(workspace).isNotNull() - val root = rootProject?.rootProject + val root = workspace?.getRootProject() assertThat(root).isNotNull() assertThat(root!!.path).isEqualTo(":") val source = root.projectDir.toPath().resolve("app/src/main/java/com/itsaky/test/app/MainActivity.java") - val module = rootProject.findModuleForFile(source) + val module = workspace.findModuleForFile(source) assertThat(module).isNotNull() assertThat(module!!.path).isEqualTo(":app") assertThat(module.projectDir.path).isEqualTo(File(root.projectDir, "app").path) @@ -234,30 +238,30 @@ class ModuleProjectTest { assertThat(Files.isSameFile(source, sourceNode.file)).isTrue() // make sure the files are not indexed - assertThat(IProjectManager.getInstance().containsSourceFile(testCls)).isFalse() - assertThat(IProjectManager.getInstance().containsSourceFile(testCls_renamed)).isFalse() + assertThat(workspace.containsSourceFile(testCls)).isFalse() + assertThat(workspace.containsSourceFile(testCls_renamed)).isFalse() testCls.writeText("public class Test { }") - projectManager.notifyFileCreated(testCls.toFile()) - assertThat(IProjectManager.getInstance().containsSourceFile(testCls)).isTrue() - assertThat(IProjectManager.getInstance().containsSourceFile(testCls_renamed)).isFalse() - assertThat(IProjectManager.getInstance().findModuleForFile(testCls, true)).isEqualTo(module) - assertThat(IProjectManager.getInstance().findModuleForFile(testCls_renamed, true)).isNull() + IProjectManager.getInstance().notifyFileCreated(testCls.toFile()) + assertThat(workspace.containsSourceFile(testCls)).isTrue() + assertThat(workspace.containsSourceFile(testCls_renamed)).isFalse() + assertThat(workspace.findModuleForFile(testCls, true)).isEqualTo(module) + assertThat(workspace.findModuleForFile(testCls_renamed, true)).isNull() Files.move(testCls, testCls_renamed) - projectManager.notifyFileRenamed(testCls.toFile(), testCls_renamed.toFile()) - assertThat(IProjectManager.getInstance().containsSourceFile(testCls)).isFalse() - assertThat(IProjectManager.getInstance().containsSourceFile(testCls_renamed)).isTrue() - assertThat(IProjectManager.getInstance().findModuleForFile(testCls, true)).isNull() - assertThat(IProjectManager.getInstance().findModuleForFile(testCls_renamed, true)).isEqualTo( + IProjectManager.getInstance().notifyFileRenamed(testCls.toFile(), testCls_renamed.toFile()) + assertThat(workspace.containsSourceFile(testCls)).isFalse() + assertThat(workspace.containsSourceFile(testCls_renamed)).isTrue() + assertThat(workspace.findModuleForFile(testCls, true)).isNull() + assertThat(workspace.findModuleForFile(testCls_renamed, true)).isEqualTo( module ) Files.delete(testCls_renamed) - projectManager.notifyFileDeleted(testCls_renamed.toFile()) - assertThat(IProjectManager.getInstance().containsSourceFile(testCls)).isFalse() - assertThat(IProjectManager.getInstance().containsSourceFile(testCls_renamed)).isFalse() - assertThat(IProjectManager.getInstance().findModuleForFile(testCls, true)).isNull() - assertThat(IProjectManager.getInstance().findModuleForFile(testCls_renamed, true)).isNull() + IProjectManager.getInstance().notifyFileDeleted(testCls_renamed.toFile()) + assertThat(workspace.containsSourceFile(testCls)).isFalse() + assertThat(workspace.containsSourceFile(testCls_renamed)).isFalse() + assertThat(workspace.findModuleForFile(testCls, true)).isNull() + assertThat(workspace.findModuleForFile(testCls_renamed, true)).isNull() } } diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt index dca16c3d7..056d0d0f2 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt @@ -60,8 +60,8 @@ import com.itsaky.androidide.lsp.util.LSPEditorActions import com.itsaky.androidide.models.Range import com.itsaky.androidide.projects.FileManager.getActiveDocumentCount import com.itsaky.androidide.projects.IProjectManager.Companion.getInstance +import com.itsaky.androidide.projects.IWorkspace import com.itsaky.androidide.projects.ModuleProject -import com.itsaky.androidide.projects.Project import com.itsaky.androidide.utils.DocumentUtils import com.itsaky.androidide.utils.VMUtils import kotlinx.coroutines.CoroutineScope @@ -131,10 +131,10 @@ class JavaLanguageServer : ILanguageServer { this._settings = settings } - override fun setupWithProject(project: Project) { + override fun setupWorkspace(workspace: IWorkspace) { LSPEditorActions.ensureActionsMenuRegistered(JavaCodeActionsMenu) - // Once we have project initialized + // Once we have workspace initialized // Destory the NO_MODULE_COMPILER instance JavaCompilerService.NO_MODULE_COMPILER.destroy() @@ -144,15 +144,15 @@ class JavaLanguageServer : ILanguageServer { // Clear cached JAR file system for R.jar // Using the cached instance will result in completions not being updated for updated resources // TODO Clearing caches for JAR files ending with '/R.jar' is probably not a good idea - // Maybe this could be improved by using data from the AndroidModule project model + // Maybe this could be improved by using data from the AndroidModule workspace model clearCachesForPaths { path: String -> path.endsWith("/R.jar") } // Clear cached module-specific compilers JavaCompilerProvider.getInstance().destroy() // Cache classpath locations - for (subModule in project.subProjects) { - if (subModule !is ModuleProject || subModule.path == project.rootProject.path) { + for (subModule in workspace.getSubProjects()) { + if (subModule !is ModuleProject || subModule.path == workspace.getRootProject().path) { continue } SourceFileManager.forModule(subModule) @@ -173,7 +173,8 @@ class JavaLanguageServer : ILanguageServer { } completionProvider.reset( - compiler, settings, cachedCompletion) { cachedCompletion: CachedCompletion -> + compiler, settings, cachedCompletion + ) { cachedCompletion: CachedCompletion -> updateCachedCompletion(cachedCompletion) } @@ -239,9 +240,9 @@ class JavaLanguageServer : ILanguageServer { if (!DocumentUtils.isJavaFile(file)) { return JavaCompilerService.NO_MODULE_COMPILER } - val root = getInstance().rootProject + val workspace = getInstance().getWorkspace() ?: return JavaCompilerService.NO_MODULE_COMPILER - val module = root.findModuleForFile(file!!) ?: return JavaCompilerService.NO_MODULE_COMPILER + val module = workspace.findModuleForFile(file!!) ?: return JavaCompilerService.NO_MODULE_COMPILER return JavaCompilerProvider.get(module) } @@ -271,7 +272,7 @@ class JavaLanguageServer : ILanguageServer { // TODO Find an alternative to efficiently update changeDelta in JavaCompilerService instance JavaCompilerService.NO_MODULE_COMPILER.onDocumentChange(event) val module = getInstance() - .findModuleForFile(event.changedFile) + .getWorkspace()?.findModuleForFile(event.changedFile, true) if (module != null) { val compiler = JavaCompilerProvider.get(module) compiler.onDocumentChange(event) diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/BaseJavaCodeAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/BaseJavaCodeAction.kt index e5354f483..29b5d5862 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/BaseJavaCodeAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/BaseJavaCodeAction.kt @@ -105,7 +105,8 @@ abstract class BaseJavaCodeAction : EditorActionItem { } protected fun ActionData.requireCompiler(): JavaCompilerService { - val module = IProjectManager.getInstance().findModuleForFile(requireFile(), false) + val module = + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(requireFile(), false) requireNotNull(module) { "Cannot get compiler instance. Unable to find module for file: ${requireFile().name}" } diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/FieldBasedAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/FieldBasedAction.kt index bdfc75315..aa6abd5d9 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/FieldBasedAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/FieldBasedAction.kt @@ -60,7 +60,7 @@ abstract class FieldBasedAction : BaseJavaCodeAction() { com.itsaky.androidide.models.Range::class.java, CodeEditor::class.java ) || - IProjectManager.getInstance().rootProject == null + IProjectManager.getInstance().getWorkspace() == null ) { markInvisible() return @@ -73,7 +73,8 @@ abstract class FieldBasedAction : BaseJavaCodeAction() { override suspend fun execAction(data: ActionData): Any { val range = data[com.itsaky.androidide.models.Range::class.java]!! val file = data.requirePath() - val module = IProjectManager.getInstance().findModuleForFile(file, false) ?: return Any() + val module = + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(file, false) ?: return Any() return JavaCompilerProvider.get(module).compile(file).get { task -> val triple = findFields(task, file, range) diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/AddImportAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/AddImportAction.kt index 2dcd73c0e..a5d78325a 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/AddImportAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/AddImportAction.kt @@ -67,7 +67,7 @@ class AddImportAction : BaseJavaCodeAction() { val file = data.requireFile() val module = - IProjectManager.getInstance().findModuleForFile(file, false) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(file, false) ?: run { markInvisible() return @@ -99,7 +99,7 @@ class AddImportAction : BaseJavaCodeAction() { )!! val file = data.requireFile() val module = - IProjectManager.getInstance().findModuleForFile(file, false) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(file, false) ?: run { markInvisible() return Any() @@ -140,7 +140,7 @@ class AddImportAction : BaseJavaCodeAction() { val file = data.requireFile() val module = - IProjectManager.getInstance().findModuleForFile(file, false) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(file, false) ?: run { markInvisible() return diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/AddThrowsAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/AddThrowsAction.kt index f52e48134..3607367f9 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/AddThrowsAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/AddThrowsAction.kt @@ -64,7 +64,9 @@ class AddThrowsAction : BaseJavaCodeAction() { val diagnostic = data[DiagnosticItem::class.java]!! val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any()) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return Any() + ) val file = data.requirePath() return compiler.compile(file).get { task -> val needsThrow = CodeActionUtils.findMethod(task, diagnostic.range) diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/CreateMissingMethodAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/CreateMissingMethodAction.kt index 75181aeac..6b05125ed 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/CreateMissingMethodAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/CreateMissingMethodAction.kt @@ -66,7 +66,9 @@ class CreateMissingMethodAction : BaseJavaCodeAction() { val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!! val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any()) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return Any() + ) val file = data.requirePath() return compiler.compile(file).get { CreateMissingMethod(file, findPosition(it, diagnostic.range.start)) diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/FieldToBlockAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/FieldToBlockAction.kt index 1ce0af1ea..f2ce454f2 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/FieldToBlockAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/FieldToBlockAction.kt @@ -67,7 +67,9 @@ class FieldToBlockAction : BaseJavaCodeAction() { override suspend fun execAction(data: ActionData): Any { val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any()) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return Any() + ) val diagnostic = data[DiagnosticItem::class.java]!! val file = data.requirePath() diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveClassAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveClassAction.kt index 221e5354e..dec3679a0 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveClassAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveClassAction.kt @@ -65,7 +65,9 @@ class RemoveClassAction : BaseJavaCodeAction() { val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!! val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any()) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return Any() + ) val file = data.requirePath() return compiler.compile(file).get { diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveMethodAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveMethodAction.kt index b0aa7ef5a..78497826a 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveMethodAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveMethodAction.kt @@ -65,7 +65,9 @@ class RemoveMethodAction : BaseJavaCodeAction() { val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!! val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any()) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return Any() + ) val file = data.requirePath() return compiler.compile(file).get { diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveUnusedThrowsAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveUnusedThrowsAction.kt index b1c3d35c3..466333f65 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveUnusedThrowsAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/RemoveUnusedThrowsAction.kt @@ -66,7 +66,9 @@ class RemoveUnusedThrowsAction : BaseJavaCodeAction() { val d = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!! val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any()) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return Any() + ) val file = data.requirePath() return compiler.compile(file).get { task -> val notThrown = CodeActionUtils.extractNotThrownExceptionName(d.message) diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/SuppressUncheckedWarningAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/SuppressUncheckedWarningAction.kt index b831e43de..bf9c73ebd 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/SuppressUncheckedWarningAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/SuppressUncheckedWarningAction.kt @@ -65,7 +65,9 @@ class SuppressUncheckedWarningAction : BaseJavaCodeAction() { val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!! val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any()) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return Any() + ) val file = data.requirePath() return compiler.compile(file).get { task -> val warnedMethod = CodeActionUtils.findMethod(task, diagnostic.range) diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/VariableToStatementAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/VariableToStatementAction.kt index 32b1e0aa9..5a999d59d 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/VariableToStatementAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/diagnostics/VariableToStatementAction.kt @@ -70,7 +70,9 @@ class VariableToStatementAction : BaseJavaCodeAction() { val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!! val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any()) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return Any() + ) val path = data.requirePath() return compiler.compile(path).get { diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateConstructorAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateConstructorAction.kt index 2c07578d8..bb6683e93 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateConstructorAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateConstructorAction.kt @@ -82,7 +82,9 @@ class GenerateConstructorAction : FieldBasedAction() { private fun generateConstructor(data: ActionData, selected: MutableSet) { val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return + ) val range = data[com.itsaky.androidide.models.Range::class.java]!! val file = data.requirePath() diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateMissingConstructorAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateMissingConstructorAction.kt index 3e9484ae3..0ba17f17c 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateMissingConstructorAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateMissingConstructorAction.kt @@ -65,7 +65,9 @@ class GenerateMissingConstructorAction : BaseJavaCodeAction() { val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!! val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any()) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return Any() + ) val file = data.requirePath() return compiler.compile(file).get { task -> val needsConstructor = diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateSettersAndGettersAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateSettersAndGettersAction.kt index 1685e8d69..d434f2e01 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateSettersAndGettersAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateSettersAndGettersAction.kt @@ -89,7 +89,9 @@ class GenerateSettersAndGettersAction : FieldBasedAction() { private fun generateForFields(data: ActionData, names: MutableSet) { val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return + ) val range = data[com.itsaky.androidide.models.Range::class.java]!! val file = data.requirePath() diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateToStringMethodAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateToStringMethodAction.kt index 2712ebe1b..c0ea53598 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateToStringMethodAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/GenerateToStringMethodAction.kt @@ -84,7 +84,9 @@ class GenerateToStringMethodAction : FieldBasedAction() { private fun generateToString(data: ActionData, selected: MutableSet) { val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return + ) val range = data[com.itsaky.androidide.models.Range::class.java]!! val file = data.requirePath() diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/OverrideSuperclassMethodsAction.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/OverrideSuperclassMethodsAction.kt index f8c859277..f4b8657c5 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/OverrideSuperclassMethodsAction.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/actions/generators/OverrideSuperclassMethodsAction.kt @@ -95,7 +95,9 @@ class OverrideSuperclassMethodsAction : BaseJavaCodeAction() { val range = data[com.itsaky.androidide.models.Range::class.java]!! val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any()) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return Any() + ) val file = data.requirePath() return compiler.compile(file).get { task -> @@ -203,7 +205,9 @@ class OverrideSuperclassMethodsAction : BaseJavaCodeAction() { private fun overrideMethods(data: ActionData, checkedMethods: MutableList) { val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return + ) val file = data.requirePath() compiler.compile(file).run { task -> @@ -270,7 +274,9 @@ class OverrideSuperclassMethodsAction : BaseJavaCodeAction() { ) { val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(data.requireFile(), false) + ?: return + ) val editor = data[CodeEditor::class.java]!! val file = data.requirePath() val text = editor.text diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/edits/AdvancedJavaEditHandler.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/edits/AdvancedJavaEditHandler.kt index 7b8fed0d0..41610cf2c 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/edits/AdvancedJavaEditHandler.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/edits/AdvancedJavaEditHandler.kt @@ -41,7 +41,8 @@ abstract class AdvancedJavaEditHandler(protected val file: Path) : BaseJavaEditH index: Int ) { val compiler = JavaCompilerProvider.get( - IProjectManager.getInstance().findModuleForFile(file, false) ?: return) + IProjectManager.getInstance().getWorkspace()?.findModuleForFile(file, false) ?: return + ) performEdits(compiler, editor, item) executeCommand(editor, item.command) diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/indexing/internal/JavaIndexServiceImpl.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/indexing/internal/JavaIndexServiceImpl.kt index 6050a5c37..34b69d377 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/indexing/internal/JavaIndexServiceImpl.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/indexing/internal/JavaIndexServiceImpl.kt @@ -19,7 +19,7 @@ package com.itsaky.androidide.lsp.java.indexing.internal import com.google.auto.service.AutoService import com.itsaky.androidide.indexing.IIndexService -import com.itsaky.androidide.projects.Project +import com.itsaky.androidide.projects.IWorkspace import java.io.File /** @@ -31,12 +31,12 @@ internal class JavaIndexServiceImpl : IIndexService { override val displayName: String get() = "Java LSP Indexing Service" - override fun scanFiles(project: Project): Collection { + override fun scanFiles(workspace: IWorkspace): Collection { return emptySet() } override suspend fun indexFiles( - project: Project, + workspace: IWorkspace, files: Collection ) { } diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/parser/Parser.java b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/parser/Parser.java index 47d13c06e..d4108b45d 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/parser/Parser.java +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/parser/Parser.java @@ -23,12 +23,17 @@ import com.itsaky.androidide.models.Position; import com.itsaky.androidide.models.Range; import com.itsaky.androidide.projects.IProjectManager; import com.itsaky.androidide.projects.ModuleProject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.regex.Matcher; import java.util.regex.Pattern; + import jdkx.tools.JavaCompiler; import jdkx.tools.JavaFileObject; import openjdk.source.tree.ClassTree; @@ -42,8 +47,6 @@ import openjdk.source.util.SourcePositions; import openjdk.source.util.TreePath; import openjdk.source.util.Trees; import openjdk.tools.javac.api.JavacTool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class Parser { @@ -78,20 +81,23 @@ public class Parser { * Create a task that compiles a single file */ private static JavacTask singleFileTask(JavaFileObject file) { - final ModuleProject module = IProjectManager.getInstance() - .findModuleForFile(Paths.get(file.toUri())); + final var workspace = IProjectManager.getInstance().getWorkspace(); + final var module = workspace != null + ? workspace.findModuleForFile(Paths.get(file.toUri()), false) + : null; + if (module != null) { FILE_MANAGER = SourceFileManager.forModule(module); } return (JavacTask) - COMPILER.getTask( - null, - FILE_MANAGER, - Parser::ignoreError, - Collections.emptyList(), - Collections.emptyList(), - Collections.singletonList(file)); + COMPILER.getTask( + null, + FILE_MANAGER, + Parser::ignoreError, + Collections.emptyList(), + Collections.emptyList(), + Collections.singletonList(file)); } private static void ignoreError(jdkx.tools.Diagnostic __) { @@ -215,7 +221,7 @@ public class Parser { int endCol = (int) lines.getColumnNumber(end); return new Range( - new Position(startLine - 1, startCol - 1), new Position(endLine - 1, endCol - 1)); + new Position(startLine - 1, startCol - 1), new Position(endLine - 1, endCol - 1)); } private static int indexOf(CharSequence contents, String name, int start) { diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/JavaDiagnosticProvider.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/JavaDiagnosticProvider.kt index 8448d4401..ddac0ea91 100644 --- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/JavaDiagnosticProvider.kt +++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/JavaDiagnosticProvider.kt @@ -49,7 +49,7 @@ class JavaDiagnosticProvider { fun analyze(file: Path): DiagnosticResult { - val module = IProjectManager.getInstance().findModuleForFile(file, false) + val module = IProjectManager.getInstance().getWorkspace()?.findModuleForFile(file, false) ?: return DiagnosticResult.NO_UPDATE val compiler = JavaCompilerService(module) diff --git a/java/lsp/src/test/java/com/itsaky/androidide/lsp/java/JavaCompilerProviderTest.kt b/java/lsp/src/test/java/com/itsaky/androidide/lsp/java/JavaCompilerProviderTest.kt index 45695ea64..9a6fac10c 100644 --- a/java/lsp/src/test/java/com/itsaky/androidide/lsp/java/JavaCompilerProviderTest.kt +++ b/java/lsp/src/test/java/com/itsaky/androidide/lsp/java/JavaCompilerProviderTest.kt @@ -39,12 +39,12 @@ class JavaCompilerProviderTest { @Test fun `test module specific compilers`() { - val rootProject = IProjectManager.getInstance().rootProject!! - val appModule = rootProject.findByPath(":app")!! as ModuleProject - val androidLib = rootProject.findByPath(":android-library")!! as ModuleProject - val anotherAndroidLib = rootProject.findByPath(":another-android-library")!! as ModuleProject - val javaLib = rootProject.findByPath(":java-library")!! as ModuleProject - val anotherJavaLib = rootProject.findByPath(":another-java-library")!! as ModuleProject + val workspace = IProjectManager.getInstance().getWorkspace()!! + val appModule = workspace.getProject(":app") as ModuleProject + val androidLib = workspace.getProject(":android-library") as ModuleProject + val anotherAndroidLib = workspace.getProject(":another-android-library") as ModuleProject + val javaLib = workspace.getProject(":java-library") as ModuleProject + val anotherJavaLib = workspace.getProject(":another-java-library") as ModuleProject val compilers = mutableSetOf() for (module in listOf(appModule, androidLib, anotherAndroidLib, javaLib, anotherJavaLib)) { diff --git a/testing/lspTest/src/main/java/com/itsaky/androidide/lsp/testing/LSPTest.kt b/testing/lspTest/src/main/java/com/itsaky/androidide/lsp/testing/LSPTest.kt index 9720f2c41..b1ef39f41 100644 --- a/testing/lspTest/src/main/java/com/itsaky/androidide/lsp/testing/LSPTest.kt +++ b/testing/lspTest/src/main/java/com/itsaky/androidide/lsp/testing/LSPTest.kt @@ -38,6 +38,7 @@ import com.itsaky.androidide.projects.FileManager import com.itsaky.androidide.projects.IProjectManager import com.itsaky.androidide.projects.builder.BuildService import com.itsaky.androidide.testing.tooling.ToolingApiTestLauncher +import com.itsaky.androidide.testing.tooling.models.ToolingApiTestLauncherParams import com.itsaky.androidide.tooling.api.IProject import com.itsaky.androidide.tooling.api.IToolingApiServer import com.itsaky.androidide.utils.Environment @@ -96,7 +97,8 @@ abstract class LSPTest( mockkStatic(EditorPreferences::tabSize) every { EditorPreferences.tabSize } returns 4 - ToolingApiTestLauncher.launchServer { + val params = ToolingApiTestLauncherParams() + ToolingApiTestLauncher.launchServer(params) { assertThat(result?.isSuccessful).isTrue() @@ -116,13 +118,14 @@ abstract class LSPTest( throw IllegalStateException("Expected IProjectManager instance to be an EventReceiver") } + projectManager.openProject(params.projectDir.toFile()) runBlocking { projectManager.setupProject(project) } // We need to manually setup the language server with the project here // ProjectManager.notifyProjectUpdate() ILanguageServerRegistry.getDefault() .getServer(getServerId())!! - .setupWithProject(projectManager.rootProject!!) + .setupWorkspace(projectManager.getWorkspace()!!) isInitialized = true } diff --git a/utilities/xml-inflater/src/main/java/com/itsaky/androidide/inflater/utils/inflaterUtils.kt b/utilities/xml-inflater/src/main/java/com/itsaky/androidide/inflater/utils/inflaterUtils.kt index 2aff3167a..db9d753f0 100644 --- a/utilities/xml-inflater/src/main/java/com/itsaky/androidide/inflater/utils/inflaterUtils.kt +++ b/utilities/xml-inflater/src/main/java/com/itsaky/androidide/inflater/utils/inflaterUtils.kt @@ -92,12 +92,12 @@ fun processXmlFile(file: File, expectedType: AaptResourceType): Pair