mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix(projects): introduce workspace API and remove 'Project' model
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -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)
|
||||
|
||||
+12
-10
@@ -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<String, String>) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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<Checkable<GradleTask>>()
|
||||
|
||||
return@executeAsync project.subProjects
|
||||
return@executeAsync workspace.getSubProjects()
|
||||
.flatMap<GradleProject, GradleTask> { it.tasks }
|
||||
.map<GradleTask, Checkable<GradleTask>> {
|
||||
Checkable<GradleTask>(false, it)
|
||||
|
||||
+4
-4
@@ -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<File> {
|
||||
override fun scanFiles(workspace: IWorkspace): Collection<File> {
|
||||
return mutableListOf<File>().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<File>
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -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<File>
|
||||
fun scanFiles(workspace: IWorkspace): Collection<File>
|
||||
|
||||
/**
|
||||
* 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<File>)
|
||||
suspend fun indexFiles(workspace: IWorkspace, files: Collection<File>)
|
||||
}
|
||||
+10
-7
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<String, BuildVariantInfo>
|
||||
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<AndroidModule>
|
||||
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<AndroidModule>
|
||||
|
||||
/**
|
||||
* Get the list of modules in this project which are Android library modules.
|
||||
*
|
||||
* @return The list of Android library modules.
|
||||
*/
|
||||
fun getAndroidLibraryModules(): List<AndroidModule>
|
||||
|
||||
/**
|
||||
* 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 <code>file</code> was created.
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<GradleProject>
|
||||
|
||||
/**
|
||||
* 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<String, BuildVariantInfo>
|
||||
|
||||
/**
|
||||
* 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<AndroidModule>
|
||||
|
||||
/**
|
||||
* 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")
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<GradleProject>,
|
||||
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<AndroidModule> {
|
||||
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
|
||||
}
|
||||
}
|
||||
+43
-48
@@ -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<BasicAndroidVariantMetadata> = 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<File> {
|
||||
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<File> {
|
||||
val project = IProjectManager.getInstance().rootProject ?: return emptySet()
|
||||
val project = IProjectManager.getInstance().getWorkspace() ?: return emptySet()
|
||||
val result = mutableSetOf<File>()
|
||||
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<String>, result: MutableSet<File>) {
|
||||
private fun collectLibraries(root: IWorkspace, libraries: Set<String>, result: MutableSet<File>) {
|
||||
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<ModuleProject> {
|
||||
val root = IProjectManager.getInstance().rootProject ?: return emptyList()
|
||||
val root = IProjectManager.getInstance().getWorkspace() ?: return emptyList()
|
||||
val result = mutableListOf<ModuleProject>()
|
||||
|
||||
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<ResourceTable> {
|
||||
return mutableSetOf<ResourceTable>().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<ResourceTable> {
|
||||
if (pck == SdkConstants.ANDROID_PKG) {
|
||||
return getFrameworkResourceTable()?.let { listOf(it) } ?: emptyList()
|
||||
}
|
||||
|
||||
val tables: List<ResourceTable> =
|
||||
mutableListOf<ResourceTable>().apply {
|
||||
getResourceTable()?.let { add(it) }
|
||||
addAll(getSourceResourceTables())
|
||||
addAll(getDependencyResourceTables())
|
||||
}
|
||||
val tables: List<ResourceTable> = mutableListOf<ResourceTable>().apply {
|
||||
getResourceTable()?.let { add(it) }
|
||||
addAll(getSourceResourceTables())
|
||||
addAll(getDependencyResourceTables())
|
||||
}
|
||||
|
||||
val result = mutableListOf<ResourceTable>()
|
||||
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
|
||||
}
|
||||
}
|
||||
+40
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<AndroidModule>.filterIsAndroidApp() = filter(AndroidModule::isApplication)
|
||||
|
||||
/**
|
||||
* Returns a sequence containing only Android library projects.
|
||||
*/
|
||||
fun Sequence<AndroidModule>.filterIsAndroidLib() = filter(AndroidModule::isApplication)
|
||||
+67
-124
@@ -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<String, BuildVariantInfo> = 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<ModuleProject>().forEach {
|
||||
rootProject.getSubProjects().filterIsInstance<ModuleProject>().forEach {
|
||||
emit(it)
|
||||
}
|
||||
}
|
||||
@@ -138,71 +136,15 @@ class ProjectManagerImpl : IProjectManager, EventReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAndroidModules(): List<AndroidModule> {
|
||||
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<AndroidModule> {
|
||||
return getAndroidModules().filter { it.projectType == com.android.builder.model.v2.ide.ProjectType.APPLICATION }
|
||||
}
|
||||
|
||||
override fun getAndroidLibraryModules(): List<AndroidModule> {
|
||||
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<String, BuildVariantInfo>) -> Unit = {}) {
|
||||
val rootProject = checkNotNull(this.rootProject) {
|
||||
val rootProject = checkNotNull(this.getWorkspace()) {
|
||||
"Cannot update build variants. Root project model is null."
|
||||
}
|
||||
|
||||
val buildVariants = mutableMapOf<String, BuildVariantInfo>()
|
||||
rootProject.subProjects.forEach { subproject ->
|
||||
rootProject.getSubProjects().forEach { subproject ->
|
||||
if (subproject is AndroidModule) {
|
||||
|
||||
// variant names are not expected to be modified
|
||||
val variantNames = ImmutableList.builder<String>()
|
||||
.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
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<GradleProject>,
|
||||
private val projectSyncIssues: ProjectSyncIssues
|
||||
) : IWorkspace {
|
||||
|
||||
private val variantSelections = mutableMapOf<String, BuildVariantInfo>()
|
||||
|
||||
internal fun setVariantSelections(selections: Map<String, BuildVariantInfo>) {
|
||||
this.variantSelections.apply {
|
||||
clear()
|
||||
putAll(selections)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getProjectDir(): File {
|
||||
return this.projectDir
|
||||
}
|
||||
|
||||
override fun getRootProject(): GradleProject {
|
||||
return this.rootProject
|
||||
}
|
||||
|
||||
override fun getSubProjects(): List<GradleProject> {
|
||||
return ImmutableList.copyOf(this.subProjects)
|
||||
}
|
||||
|
||||
override fun getProjectSyncIssues(): ProjectSyncIssues {
|
||||
return this.projectSyncIssues
|
||||
}
|
||||
|
||||
override fun getAndroidVariantSelections(): Map<String, BuildVariantInfo> {
|
||||
return ImmutableMap.copyOf(this.variantSelections)
|
||||
}
|
||||
|
||||
override fun findProject(path: String): GradleProject? {
|
||||
return this.subProjects.find { it.path == path }
|
||||
}
|
||||
|
||||
override fun androidProjects(): Sequence<AndroidModule> {
|
||||
return subProjects.asSequence().filterIsInstance<AndroidModule>()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
+12
-9
@@ -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()
|
||||
@@ -107,12 +107,12 @@ class JavaModule(
|
||||
}
|
||||
|
||||
override fun getCompileModuleProjects(): List<ModuleProject> {
|
||||
val root = IProjectManager.getInstance().rootProject ?: return emptyList()
|
||||
val workspace = IProjectManager.getInstance().getWorkspace() ?: return emptyList()
|
||||
return this.dependencies
|
||||
.filterIsInstance(JavaModuleProjectDependency::class.java)
|
||||
.filterIsInstance<JavaModuleProjectDependency>()
|
||||
.filter { it.scope == SCOPE_COMPILE }
|
||||
.mapNotNull { root.findByPath(it.projectPath) }
|
||||
.filterIsInstance(ModuleProject::class.java)
|
||||
.mapNotNull { workspace.findProject(it.projectPath) }
|
||||
.filterIsInstance<ModuleProject>()
|
||||
}
|
||||
|
||||
fun getDependencyClasspaths(): Set<File> {
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
+37
-33
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+3
-1
@@ -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)
|
||||
|
||||
+3
-1
@@ -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))
|
||||
|
||||
+3
-1
@@ -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()
|
||||
|
||||
|
||||
+3
-1
@@ -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 {
|
||||
|
||||
+3
-1
@@ -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 {
|
||||
|
||||
+3
-1
@@ -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)
|
||||
|
||||
+3
-1
@@ -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)
|
||||
|
||||
+3
-1
@@ -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 {
|
||||
|
||||
+3
-1
@@ -82,7 +82,9 @@ class GenerateConstructorAction : FieldBasedAction() {
|
||||
private fun generateConstructor(data: ActionData, selected: MutableSet<String>) {
|
||||
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()
|
||||
|
||||
|
||||
+3
-1
@@ -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 =
|
||||
|
||||
+3
-1
@@ -89,7 +89,9 @@ class GenerateSettersAndGettersAction : FieldBasedAction() {
|
||||
private fun generateForFields(data: ActionData, names: MutableSet<String>) {
|
||||
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()
|
||||
|
||||
|
||||
+3
-1
@@ -84,7 +84,9 @@ class GenerateToStringMethodAction : FieldBasedAction() {
|
||||
private fun generateToString(data: ActionData, selected: MutableSet<String>) {
|
||||
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()
|
||||
|
||||
|
||||
+9
-3
@@ -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<MethodPtr>) {
|
||||
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
|
||||
|
||||
+2
-1
@@ -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)
|
||||
|
||||
+3
-3
@@ -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<File> {
|
||||
override fun scanFiles(workspace: IWorkspace): Collection<File> {
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
override suspend fun indexFiles(
|
||||
project: Project,
|
||||
workspace: IWorkspace,
|
||||
files: Collection<File>
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -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<? extends JavaFileObject> __) {
|
||||
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
|
||||
@@ -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<JavaCompilerService>()
|
||||
for (module in listOf(appModule, androidLib, anotherAndroidLib, javaLib, anotherJavaLib)) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+2
-2
@@ -92,12 +92,12 @@ fun processXmlFile(file: File, expectedType: AaptResourceType): Pair<XmlProcesso
|
||||
throw InflateException("File is not a layout file.")
|
||||
}
|
||||
|
||||
if (IProjectManager.getInstance().rootProject == null) {
|
||||
if (IProjectManager.getInstance().getWorkspace() == null) {
|
||||
throw InflateException("GradleProject is not initialized!")
|
||||
}
|
||||
|
||||
val module =
|
||||
IProjectManager.getInstance().findModuleForFile(file, false) as? AndroidModule
|
||||
IProjectManager.getInstance().getWorkspace()?.findModuleForFile(file, false) as? AndroidModule
|
||||
?: throw InflateException("Cannot find module for given file. Is the project initialized?")
|
||||
val resFile =
|
||||
ResourceFile(
|
||||
|
||||
@@ -33,7 +33,7 @@ fun startParse(file: File) {
|
||||
if (isParsing) {
|
||||
return
|
||||
}
|
||||
(IProjectManager.getInstance().findModuleForFile(file, false) as? AndroidModule)?.let {
|
||||
(IProjectManager.getInstance().getWorkspace()?.findModuleForFile(file, false) as? AndroidModule)?.let {
|
||||
startParse(it)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-2
@@ -27,6 +27,7 @@ import com.itsaky.androidide.projects.android.AndroidModule
|
||||
import com.itsaky.androidide.projects.builder.BuildService
|
||||
import com.itsaky.androidide.projects.util.findAppModule
|
||||
import com.itsaky.androidide.testing.tooling.ToolingApiTestLauncher
|
||||
import com.itsaky.androidide.testing.tooling.models.ToolingApiTestLauncherParams
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Ignore
|
||||
import org.robolectric.Robolectric
|
||||
@@ -43,11 +44,17 @@ object XmlInflaterTest {
|
||||
return
|
||||
}
|
||||
|
||||
ToolingApiTestLauncher.launchServer {
|
||||
val params = ToolingApiTestLauncherParams()
|
||||
ToolingApiTestLauncher.launchServer(params) {
|
||||
assertThat(result?.isSuccessful).isTrue()
|
||||
|
||||
Lookup.getDefault().register(BuildService.KEY_PROJECT_PROXY, project)
|
||||
runBlocking { IProjectManager.getInstance().setupProject(project) }
|
||||
|
||||
val projectManager = IProjectManager.getInstance()
|
||||
projectManager.openProject(params.projectDir.toFile())
|
||||
|
||||
runBlocking { projectManager.setupProject(project) }
|
||||
|
||||
init.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ import com.itsaky.androidide.lsp.xml.providers.AdvancedEditProvider.onContentCha
|
||||
import com.itsaky.androidide.lsp.xml.providers.CodeFormatProvider
|
||||
import com.itsaky.androidide.lsp.xml.providers.XmlCompletionProvider
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.projects.Project
|
||||
import com.itsaky.androidide.projects.IWorkspace
|
||||
import com.itsaky.androidide.utils.DocumentUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
@@ -81,10 +81,9 @@ class XMLLanguageServer : ILanguageServer {
|
||||
this.settings = settings
|
||||
}
|
||||
|
||||
override fun setupWithProject(project: Project) {}
|
||||
override fun setupWorkspace(workspace: IWorkspace) {}
|
||||
override fun complete(params: CompletionParams?): CompletionResult {
|
||||
val completionProvider: ICompletionProvider
|
||||
completionProvider = if (!getSettings().completionsEnabled()) {
|
||||
val completionProvider: ICompletionProvider = if (!getSettings().completionsEnabled()) {
|
||||
NoCompletionsProvider()
|
||||
} else {
|
||||
XmlCompletionProvider(getSettings())
|
||||
|
||||
Reference in New Issue
Block a user