refactor: migrate ProjectManager to interface based API

This commit is contained in:
Akash Yadav
2023-08-27 18:51:42 +05:30
parent 7d64702e3a
commit f5143625ef
55 changed files with 734 additions and 565 deletions
@@ -22,7 +22,7 @@ import androidx.core.content.ContextCompat
import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction
import com.itsaky.androidide.models.ApkMetadata
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.utils.ApkInstaller
import com.itsaky.androidide.utils.InstallationResultHandler
@@ -53,8 +53,8 @@ class QuickRunAction(context: Context) : BaseBuildAction() {
log.debug("Installing APK(s) for variant: debug")
// TODO Handle multiple application modules
val projectManager = ProjectManager
val app = projectManager.getApplicationModule()
val projectManager = ProjectManagerImpl.getInstance()
val app = projectManager.app
if (app == null) {
log.warn("No application module found. Cannot install APKs")
return@execTasks
@@ -23,7 +23,7 @@ import com.itsaky.androidide.resources.R
import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorActivityAction
import com.itsaky.androidide.actions.markInvisible
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
/** @author Akash Yadav */
class FindInProjectAction() : EditorActivityAction() {
@@ -44,7 +44,7 @@ class FindInProjectAction() : EditorActivityAction() {
return
}
val project = ProjectManager.rootProject
val project = IProjectManager.getInstance().rootProject
if (project == null || project.subProjects.isEmpty()) {
markInvisible()
return
@@ -22,7 +22,7 @@ import androidx.core.content.ContextCompat
import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorRelatedAction
import com.itsaky.androidide.models.SaveResult
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.flashError
@@ -77,7 +77,7 @@ class SaveFileAction(context: Context) : EditorRelatedAction() {
val saveResult = result.result
if (saveResult.xmlSaved) {
ProjectManager.generateSources()
ProjectManagerImpl.getInstance().generateSources()
}
if (saveResult.gradleSaved) {
@@ -27,7 +27,7 @@ import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder
import com.itsaky.androidide.databinding.LayoutCreateFileJavaBinding
import com.itsaky.androidide.eventbus.events.file.FileCreationEvent
import com.itsaky.androidide.preferences.databinding.LayoutDialogTextInputBinding
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.utils.DialogUtils
import com.itsaky.androidide.utils.Environment
@@ -82,7 +82,7 @@ class NewFileAction(context: Context) :
return
}
val projectDir = ProjectManager.getProjectDirPath()
val projectDir = IProjectManager.getInstance().projectDirPath
Objects.requireNonNull(projectDir)
val isJava =
Pattern.compile(Pattern.quote(projectDir) + JAVA_PATH_REGEX).matcher(file.absolutePath).find()
@@ -25,7 +25,7 @@ import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.requireContext
import com.itsaky.androidide.activities.TerminalActivity
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import java.util.Objects
import kotlin.reflect.KClass
@@ -49,7 +49,7 @@ class TerminalSidebarAction(context: Context, override val order: Int) : Abstrac
val intent = Intent(context, TerminalActivity::class.java)
intent.putExtra(
TerminalActivity.KEY_WORKING_DIRECTORY,
Objects.requireNonNull(ProjectManager.getProjectDirPath())
Objects.requireNonNull(IProjectManager.getInstance().projectDirPath)
)
context.startActivity(intent)
return true
@@ -42,7 +42,7 @@ import com.itsaky.androidide.preferences.internal.confirmProjectOpen
import com.itsaky.androidide.preferences.internal.lastOpenedProject
import com.itsaky.androidide.preferences.internal.statConsentDialogShown
import com.itsaky.androidide.preferences.internal.statOptIn
import com.itsaky.androidide.projects.ProjectManager.projectPath
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.templates.ITemplateProvider
import com.itsaky.androidide.utils.DialogUtils
@@ -284,7 +284,7 @@ class MainActivity : LimitlessIDEActivity() {
}
internal fun openProject(root: File) {
projectPath = root.absolutePath
ProjectManagerImpl.getInstance().projectPath = root.absolutePath
startActivity(Intent(this, EditorActivityKt::class.java))
}
@@ -65,8 +65,8 @@ import com.itsaky.androidide.models.DiagnosticGroup
import com.itsaky.androidide.models.OpenedFile
import com.itsaky.androidide.models.Range
import com.itsaky.androidide.models.SearchResult
import com.itsaky.androidide.projects.ProjectManager.getProjectDirPath
import com.itsaky.androidide.projects.ProjectManager.projectPath
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.ui.ContentTranslatingDrawerLayout
import com.itsaky.androidide.ui.editor.CodeEditorView
import com.itsaky.androidide.uidesigner.UIDesignerActivity
@@ -186,7 +186,8 @@ abstract class BaseEditorActivity :
registerLanguageServers()
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_PROJECT_PATH)) {
projectPath = savedInstanceState.getString(KEY_PROJECT_PATH)!!
ProjectManagerImpl.getInstance().projectPath = savedInstanceState.getString(
KEY_PROJECT_PATH)!!
}
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
@@ -239,7 +240,7 @@ abstract class BaseEditorActivity :
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putString(KEY_PROJECT_PATH, getProjectDirPath())
outState.putString(KEY_PROJECT_PATH, IProjectManager.getInstance().projectDirPath)
super.onSaveInstanceState(outState)
}
@@ -47,7 +47,7 @@ import com.itsaky.androidide.models.OpenedFile
import com.itsaky.androidide.models.OpenedFilesCache
import com.itsaky.androidide.models.Range
import com.itsaky.androidide.models.SaveResult
import com.itsaky.androidide.projects.ProjectManager.generateSources
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.tasks.executeAsync
import com.itsaky.androidide.tasks.executeAsyncProvideError
import com.itsaky.androidide.ui.editor.CodeEditorView
@@ -330,7 +330,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {
}
if (processResources) {
generateSources()
ProjectManagerImpl.getInstance().generateSources()
}
return result.gradleSaved
@@ -41,14 +41,7 @@ import com.itsaky.androidide.lsp.IDELanguageClientImpl
import com.itsaky.androidide.preferences.internal.NO_OPENED_PROJECT
import com.itsaky.androidide.preferences.internal.gradleInstallationDir
import com.itsaky.androidide.preferences.internal.lastOpenedProject
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.ProjectManager.cachedInitResult
import com.itsaky.androidide.projects.ProjectManager.getProjectDirPath
import com.itsaky.androidide.projects.ProjectManager.notifyProjectUpdate
import com.itsaky.androidide.projects.ProjectManager.projectInitialized
import com.itsaky.androidide.projects.ProjectManager.projectPath
import com.itsaky.androidide.projects.ProjectManager.rootProject
import com.itsaky.androidide.projects.ProjectManager.setupProject
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.api.GradleProject
import com.itsaky.androidide.projects.builder.BuildService
@@ -168,7 +161,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {
// reset these values here
// sometimes, when the IDE closed and reopened instantly, these values prevent initialization
// of the project
ProjectManager.destroy()
ProjectManagerImpl.getInstance().destroy()
editorViewModel.isInitializing = false
editorViewModel.isBuildInProgress = false
@@ -353,13 +346,14 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {
* @param buildVariants A map of project paths to the selected build variants.
*/
fun initializeProject(buildVariants: Map<String, String>) {
val projectDir = File(projectPath)
val manager = ProjectManagerImpl.getInstance()
val projectDir = File(manager.projectPath)
if (!projectDir.exists()) {
log.error("GradleProject directory does not exist. Cannot initialize project")
return
}
val initialized = projectInitialized && cachedInitResult != null
val initialized = manager.projectInitialized && manager.cachedInitResult != null
log.debug("Is project initialized: $initialized")
// When returning after a configuration change between the initialization process,
// we do not want to start another project initialization
@@ -392,7 +386,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {
log.debug("Using cached initialize result as the project is already initialized")
CompletableFuture.supplyAsync {
log.warn("GradleProject has already been initialized. Skipping initialization process.")
cachedInitResult
manager.cachedInitResult
}
}
@@ -458,14 +452,15 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {
}
protected open fun onProjectInitialized(result: InitializeResult) {
if (isFromSavedInstance && projectInitialized && result == cachedInitResult) {
val manager = ProjectManagerImpl.getInstance()
if (isFromSavedInstance && manager.projectInitialized && result == manager.cachedInitResult) {
log.debug("Not setting up project as this a configuration change")
return
}
cachedInitResult = result
setupProject()
notifyProjectUpdate()
manager.cachedInitResult = result
manager.setupProject()
manager.notifyProjectUpdate()
updateBuildVariants()
ThreadUtils.runOnUiThread { postProjectInit(true) }
@@ -477,18 +472,19 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {
}
protected open fun postProjectInit(isSuccessful: Boolean) {
val manager = ProjectManagerImpl.getInstance()
if (!isSuccessful) {
setStatus(getString(string.msg_project_initialization_failed))
flashError(string.msg_project_initialization_failed)
editorViewModel.isInitializing = false
projectInitialized = false
manager.projectInitialized = false
return
}
initialSetup()
setStatus(getString(string.msg_project_initialized))
editorViewModel.isInitializing = false
projectInitialized = true
manager.projectInitialized = true
if (mFindInProjectDialog?.isShowing == true) {
mFindInProjectDialog!!.dismiss()
@@ -498,8 +494,9 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {
}
private fun updateBuildVariants() {
val manager = ProjectManagerImpl.getInstance()
executeAsyncProvideError({
rootProject?.run {
manager.rootProject?.run {
val buildVariants = mutableMapOf<String, BuildVariantInfo>()
subProjects.forEach { subproject ->
if (subproject is AndroidModule) {
@@ -533,7 +530,8 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {
}
protected open fun createFindInProjectDialog(): AlertDialog? {
if (rootProject == null) {
val manager = ProjectManagerImpl.getInstance()
if (manager.rootProject == null) {
log.warn("No root project model found. Is the project initialized?")
flashError(getString(string.msg_project_not_initialized))
return null
@@ -541,7 +539,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {
val moduleDirs =
try {
rootProject!!.subProjects.stream().map(GradleProject::projectDir)
manager.rootProject!!.subProjects.stream().map(GradleProject::projectDir)
.collect(Collectors.toList())
} catch (e: Throwable) {
flashError(getString(string.msg_no_modules))
@@ -633,9 +631,10 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {
}
private fun initialSetup() {
lastOpenedProject = getProjectDirPath()
val manager = ProjectManagerImpl.getInstance()
lastOpenedProject = manager.projectDirPath
try {
val project = rootProject
val project = manager.rootProject
if (project == null) {
log.warn("GradleProject not initialized. Skipping initial setup...")
return
@@ -643,7 +642,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {
var projectName = project.rootProject.name
if (projectName.isEmpty()) {
projectName = File(getProjectDirPath()).name
projectName = manager.projectDir.name
}
supportActionBar!!.subtitle = projectName
@@ -45,7 +45,7 @@ import com.itsaky.androidide.databinding.LayoutRunTaskBinding
import com.itsaky.androidide.databinding.LayoutRunTaskDialogBinding
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.models.Checkable
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.tasks.executeAsync
@@ -183,7 +183,7 @@ class RunTasksDialogFragment : BottomSheetDialogFragment() {
viewModel.displayedChild = CHILD_LOADING
executeAsync({
val project = ProjectManager.rootProject
val project = IProjectManager.getInstance().rootProject
val rootProject =
project?.rootProject ?: return@executeAsync emptyList<Checkable<GradleTask>>()
val tasks = rootProject.tasks.map { Checkable(false, it) }.toMutableList()
@@ -35,7 +35,7 @@ import com.itsaky.androidide.eventbus.events.filetree.FileClickEvent
import com.itsaky.androidide.eventbus.events.filetree.FileLongClickEvent
import com.itsaky.androidide.events.ExpandTreeNodeRequestEvent
import com.itsaky.androidide.events.ListProjectFilesRequestEvent
import com.itsaky.androidide.projects.ProjectManager.getProjectDirPath
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.tasks.TaskExecutor.executeAsync
import com.itsaky.androidide.tasks.callables.FileTreeCallable
@@ -211,7 +211,7 @@ class FileTreeFragment : BottomSheetDialogFragment(), TreeNodeClickListener,
// Fragment has been destroyed
return
}
val projectDirPath = getProjectDirPath()
val projectDirPath = IProjectManager.getInstance().projectDirPath
val projectDir = File(projectDirPath)
val rootNode = TreeNode(File(""))
rootNode.viewHolder = FileTreeViewHolder(requireContext())
@@ -20,8 +20,6 @@ package com.itsaky.androidide.handlers
import android.content.Context
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.navigation.fragment.NavHostFragment
import com.itsaky.androidide.activities.editor.BaseEditorActivity
import com.itsaky.androidide.eventbus.events.Event
import com.itsaky.androidide.eventbus.events.EventReceiver
import com.itsaky.androidide.eventbus.events.editor.OnCreateEvent
@@ -30,13 +28,13 @@ import com.itsaky.androidide.eventbus.events.editor.OnPauseEvent
import com.itsaky.androidide.eventbus.events.editor.OnResumeEvent
import com.itsaky.androidide.eventbus.events.editor.OnStartEvent
import com.itsaky.androidide.eventbus.events.editor.OnStopEvent
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.projects.util.BootClasspathProvider
import com.itsaky.androidide.utils.EditorActivityActions
import com.itsaky.androidide.utils.EditorSidebarActions
import com.itsaky.androidide.utils.Environment
import java.util.concurrent.CompletableFuture
import org.greenrobot.eventbus.EventBus
import java.util.concurrent.CompletableFuture
/**
* Observes lifecycle events if [com.itsaky.androidide.EditorActivityKt].
@@ -55,7 +53,7 @@ class EditorActivityLifecyclerObserver : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
CompletableFuture.runAsync(this::initBootclasspathProvider)
register(fileActionsHandler, ProjectManager)
register(fileActionsHandler, ProjectManagerImpl.getInstance())
dispatchEvent(OnStartEvent())
}
@@ -71,7 +69,7 @@ class EditorActivityLifecyclerObserver : DefaultLifecycleObserver {
}
override fun onStop(owner: LifecycleOwner) {
unregister(fileActionsHandler, ProjectManager)
unregister(fileActionsHandler, ProjectManagerImpl.getInstance())
dispatchEvent(OnStopEvent())
}
@@ -47,7 +47,8 @@ import com.itsaky.androidide.BuildConfig;
import com.itsaky.androidide.app.BaseApplication;
import com.itsaky.androidide.lookup.Lookup;
import com.itsaky.androidide.models.LogLine;
import com.itsaky.androidide.projects.ProjectManager;
import com.itsaky.androidide.projects.IProjectManager;
import com.itsaky.androidide.projects.ProjectManagerImpl;
import com.itsaky.androidide.projects.builder.BuildService;
import com.itsaky.androidide.resources.R;
import com.itsaky.androidide.services.ToolingServerNotStartedException;
@@ -220,7 +221,6 @@ public class GradleBuildService extends Service implements BuildService, IToolin
this.server = server;
Lookup.getDefault().update(BuildService.KEY_PROJECT_PROXY, projectProxy);
this.isToolingServerStarted = true;
ProjectManager.INSTANCE.setupProject(projectProxy, false);
}
@Override
@@ -328,12 +328,12 @@ public class GradleBuildService extends Service implements BuildService, IToolin
}
public boolean isGradleWrapperAvailable() {
final var projectDir = ProjectManager.INSTANCE.getProjectDirPath();
final var projectDir = IProjectManager.getInstance().getProjectDirPath();
if (TextUtils.isEmpty(projectDir)) {
return false;
}
final var projectRoot = Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDir());
final var projectRoot = Objects.requireNonNull(ProjectManagerImpl.getInstance().getProjectDir());
if (!projectRoot.exists()) {
return false;
}
@@ -372,7 +372,7 @@ public class GradleBuildService extends Service implements BuildService, IToolin
}
try {
final var projectDir = ProjectManager.INSTANCE.getProjectDir();
final var projectDir = ProjectManagerImpl.getInstance().getProjectDir();
final var files = ZipUtils.unzipFile(extracted, projectDir);
if (files != null && !files.isEmpty()) {
return new GradleWrapperCheckResult(true);
@@ -25,9 +25,8 @@ import androidx.lifecycle.ViewModel
import com.blankj.utilcode.util.FileUtils
import com.google.gson.GsonBuilder
import com.itsaky.androidide.models.OpenedFilesCache
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.tasks.executeAsync
import com.itsaky.androidide.tooling.api.models.BuildVariantInfo
import com.itsaky.androidide.utils.Environment
import java.io.File
@@ -234,7 +233,7 @@ class EditorViewModel : ViewModel() {
}
private fun getOpenedFilesCache(forWrite: Boolean = false): File {
var file = Environment.getProjectCacheDir(ProjectManager.projectPath)
var file = Environment.getProjectCacheDir(IProjectManager.getInstance().projectDir)
file = File(file, "editor/openedFiles.json")
if (file.exists() && forWrite) {
FileUtils.rename(file, "${file.name}.bak")
@@ -18,7 +18,7 @@
package com.itsaky.androidide.editor.snippets
import com.itsaky.androidide.editor.ui.IDEEditor
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import io.github.rosemoe.sora.widget.snippet.variable.FileBasedSnippetVariableResolver
/**
@@ -46,7 +46,7 @@ class FileVariableResolver(editor: IDEEditor) : FileBasedSnippetVariableResolver
TM_FILENAME_BASE -> file.nameWithoutExtension
TM_DIRECTORY -> file.parentFile?.absolutePath ?: ""
TM_FILEPATH -> file.absolutePath
RELATIVE_FILEPATH -> file.relativeTo(ProjectManager.getProjectDir()).absolutePath
RELATIVE_FILEPATH -> file.relativeTo(IProjectManager.getInstance().projectDir).absolutePath
else -> ""
}
}
@@ -17,7 +17,7 @@
package com.itsaky.androidide.editor.snippets
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import io.github.rosemoe.sora.widget.snippet.variable.WorkspaceBasedSnippetVariableResolver
/**
@@ -29,12 +29,13 @@ class WorkspaceVariableResolver :
WorkspaceBasedSnippetVariableResolver(), AbstractSnippetVariableResolver {
companion object {
private const val WORKSPACE_NAME = "WORKSPACE_NAME"
private const val WORKSPACE_FOLDER = "WORKSPACE_FOLDER"
}
override fun resolve(name: String): String {
val directory = ProjectManager.getProjectDir()
val directory = IProjectManager.getInstance().projectDir
return when (name) {
WORKSPACE_NAME -> directory.name
WORKSPACE_FOLDER -> directory.absolutePath
@@ -35,7 +35,6 @@ package com.itsaky.androidide.lsp.api;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.itsaky.androidide.lsp.models.CodeFormatResult;
import com.itsaky.androidide.lsp.models.CompletionParams;
import com.itsaky.androidide.lsp.models.CompletionResult;
@@ -51,9 +50,6 @@ import com.itsaky.androidide.lsp.models.SignatureHelp;
import com.itsaky.androidide.lsp.models.SignatureHelpParams;
import com.itsaky.androidide.models.Range;
import com.itsaky.androidide.progress.ICancelChecker;
import com.itsaky.androidide.projects.ProjectManager;
import com.itsaky.androidide.projects.api.GradleProject;
import com.itsaky.androidide.projects.api.Project;
import java.nio.file.Path;
import java.util.Collections;
@@ -105,8 +101,9 @@ public interface ILanguageServer {
/**
* Setup this language server with the given project. Servers are not expected to keep a reference
* to the provided project. Instead, use {@link ProjectManager#getRootProject()} to obtain the
* project instance.
* to the provided project. Instead, use
* {@link com.itsaky.androidide.projects.IProjectManager#getRootProject getRootProject()} to
* obtain the project instance.
*
* @param project The initialized project.
*/
@@ -165,7 +162,7 @@ public interface ILanguageServer {
*
* @param file The file to analyze.
* @return The diagnostic result. Points to {@link DiagnosticResult#NO_UPDATE} if no diagnotic
* items are available.
* items are available.
*/
@NonNull
DiagnosticResult analyze(@NonNull Path file);
@@ -18,7 +18,7 @@
package com.itsaky.androidide.lsp.util
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.api.ModuleProject
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
@@ -32,37 +32,37 @@ fun setupLookupForCompletion(file: File) {
}
fun setupLookupForCompletion(file: Path) {
val module = ProjectManager.findModuleForFile(file) ?: return
val module = IProjectManager.getInstance().findModuleForFile(file, false) ?: return
val lookup = Lookup.getDefault()
lookup.update(ModuleProject.COMPLETION_MODULE_KEY, module)
if (module is AndroidModule) {
val versions = module.getApiVersions()
if (versions != null) {
lookup.update(ApiVersions.COMPLETION_LOOKUP_KEY, versions)
}
val widgets = module.getWidgetTable()
if (widgets != null) {
lookup.update(WidgetTable.COMPLETION_LOOKUP_KEY, widgets)
}
val frameworkResources = module.getFrameworkResourceTable()
if (frameworkResources != null) {
lookup.update(ResourceTableRegistry.COMPLETION_FRAMEWORK_RES, frameworkResources)
}
val moduleResources = module.getSourceResourceTables()
if (moduleResources.isNotEmpty()) {
lookup.update(ResourceTableRegistry.COMPLETION_MODULE_RES, moduleResources)
}
val depResTables = module.getDependencyResourceTables()
if (depResTables.isNotEmpty()) {
lookup.update(ResourceTableRegistry.COMPLETION_DEP_RES, depResTables)
}
val manifestAttrTable = module.getManifestAttrTable()
if (manifestAttrTable != null) {
lookup.update(ResourceTableRegistry.COMPLETION_MANIFEST_ATTR_RES, manifestAttrTable)
@@ -61,9 +61,9 @@ import com.itsaky.androidide.lsp.util.LSPEditorActions;
import com.itsaky.androidide.models.Range;
import com.itsaky.androidide.progress.ICancelChecker;
import com.itsaky.androidide.projects.FileManager;
import com.itsaky.androidide.projects.ProjectManager;
import com.itsaky.androidide.projects.api.ModuleProject;
import com.itsaky.androidide.projects.IProjectManager;
import com.itsaky.androidide.projects.api.GradleProject;
import com.itsaky.androidide.projects.api.ModuleProject;
import com.itsaky.androidide.projects.api.Project;
import com.itsaky.androidide.utils.DocumentUtils;
import com.itsaky.androidide.utils.ILogger;
@@ -285,7 +285,7 @@ public class JavaLanguageServer implements ILanguageServer {
return JavaCompilerService.NO_MODULE_COMPILER;
}
final var root = ProjectManager.INSTANCE.getRootProject();
final var root = IProjectManager.getInstance().getRootProject();
if (root == null) {
return JavaCompilerService.NO_MODULE_COMPILER;
}
@@ -326,7 +326,8 @@ public class JavaLanguageServer implements ILanguageServer {
// TODO Find an alternative to efficiently update changeDelta in JavaCompilerService instance
JavaCompilerService.NO_MODULE_COMPILER.onDocumentChange(event);
final ModuleProject module = ProjectManager.INSTANCE.findModuleForFile(event.getChangedFile());
final ModuleProject module = IProjectManager.getInstance()
.findModuleForFile(event.getChangedFile());
if (module != null) {
final JavaCompilerService compiler = JavaCompilerProvider.get(module);
compiler.onDocumentChange(event);
@@ -32,7 +32,7 @@ import com.itsaky.androidide.lsp.java.JavaLanguageServer
import com.itsaky.androidide.lsp.java.R
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.lsp.java.rewrite.Rewrite
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.utils.DocumentUtils
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.flashError
@@ -104,7 +104,7 @@ abstract class BaseJavaCodeAction : EditorActionItem {
}
protected fun ActionData.requireCompiler(): JavaCompilerService {
val module = ProjectManager.findModuleForFile(requireFile())
val module = IProjectManager.getInstance().findModuleForFile(requireFile(), false)
requireNotNull(module) {
"Cannot get compiler instance. Unable to find module for file: ${requireFile().name}"
}
@@ -26,7 +26,7 @@ import com.itsaky.androidide.actions.requirePath
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
import com.itsaky.androidide.lsp.java.compiler.CompileTask
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarationAt
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.flashInfo
@@ -57,7 +57,7 @@ abstract class FieldBasedAction : BaseJavaCodeAction() {
com.itsaky.androidide.models.Range::class.java,
CodeEditor::class.java
) ||
ProjectManager.rootProject == null
IProjectManager.getInstance().rootProject == null
) {
markInvisible()
return
@@ -70,7 +70,7 @@ abstract class FieldBasedAction : BaseJavaCodeAction() {
override fun execAction(data: ActionData): Any {
val range = data[com.itsaky.androidide.models.Range::class.java]!!
val file = data.requirePath()
val module = ProjectManager.findModuleForFile(file) ?: return Any()
val module = IProjectManager.getInstance().findModuleForFile(file, false) ?: return Any()
return JavaCompilerProvider.get(module).compile(file).get { task ->
val triple = findFields(task, file, range)
@@ -31,7 +31,7 @@ import com.itsaky.androidide.lsp.java.rewrite.AddImport
import com.itsaky.androidide.lsp.java.rewrite.Rewrite
import com.itsaky.androidide.lsp.models.CodeActionItem
import com.itsaky.androidide.lsp.models.DiagnosticItem
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.utils.ILogger
import jdkx.tools.Diagnostic
@@ -63,7 +63,7 @@ class AddImportAction : BaseJavaCodeAction() {
val file = data.requireFile()
val module =
ProjectManager.findModuleForFile(file)
IProjectManager.getInstance().findModuleForFile(file, false)
?: run {
markInvisible()
return
@@ -95,7 +95,7 @@ class AddImportAction : BaseJavaCodeAction() {
)!!
val file = data.requireFile()
val module =
ProjectManager.findModuleForFile(file)
IProjectManager.getInstance().findModuleForFile(file, false)
?: run {
markInvisible()
return Any()
@@ -136,7 +136,7 @@ class AddImportAction : BaseJavaCodeAction() {
val file = data.requireFile()
val module =
ProjectManager.findModuleForFile(file)
IProjectManager.getInstance().findModuleForFile(file, false)
?: run {
markInvisible()
return
@@ -158,9 +158,11 @@ class AddImportAction : BaseJavaCodeAction() {
0 -> {
log.warn("No rewrites found. Cannot perform action")
}
1 -> {
client.performCodeAction(actions[0])
}
else -> {
val builder = newDialogBuilder(data)
builder.setTitle(label)
@@ -27,7 +27,7 @@ import com.itsaky.androidide.lsp.java.models.DiagnosticCode
import com.itsaky.androidide.lsp.java.rewrite.AddException
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
import com.itsaky.androidide.lsp.models.DiagnosticItem
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.utils.ILogger
@@ -58,7 +58,8 @@ class AddThrowsAction : BaseJavaCodeAction() {
override fun execAction(data: ActionData): Any {
val diagnostic = data[DiagnosticItem::class.java]!!
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return Any())
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
val file = data.requirePath()
return compiler.compile(file).get { task ->
val needsThrow = CodeActionUtils.findMethod(task, diagnostic.range)
@@ -27,7 +27,7 @@ import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
import com.itsaky.androidide.lsp.java.rewrite.CreateMissingMethod
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.utils.ILogger
/** @author Akash Yadav */
@@ -60,7 +60,8 @@ class CreateMissingMethodAction : BaseJavaCodeAction() {
override fun execAction(data: ActionData): Any {
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return Any())
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
val file = data.requirePath()
return compiler.compile(file).get {
CreateMissingMethod(file, findPosition(it, diagnostic.range.start))
@@ -28,7 +28,7 @@ import com.itsaky.androidide.lsp.java.models.DiagnosticCode
import com.itsaky.androidide.lsp.java.rewrite.ConvertFieldToBlock
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
import com.itsaky.androidide.lsp.models.DiagnosticItem
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.utils.ILogger
/** @author Akash Yadav */
@@ -62,7 +62,8 @@ class FieldToBlockAction : BaseJavaCodeAction() {
override fun execAction(data: ActionData): Any {
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return Any())
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
val diagnostic = data[DiagnosticItem::class.java]!!
val file = data.requirePath()
@@ -27,7 +27,7 @@ import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
import com.itsaky.androidide.lsp.java.rewrite.RemoveClass
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.utils.ILogger
/** @author Akash Yadav */
@@ -57,7 +57,8 @@ class RemoveClassAction : BaseJavaCodeAction() {
override fun execAction(data: ActionData): Any {
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return Any())
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
val file = data.requirePath()
return compiler.compile(file).get {
@@ -27,7 +27,7 @@ import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
import com.itsaky.androidide.lsp.java.rewrite.RemoveMethod
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findMethod
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.utils.ILogger
/** @author Akash Yadav */
@@ -57,7 +57,8 @@ class RemoveMethodAction : BaseJavaCodeAction() {
override fun execAction(data: ActionData): Any {
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return Any())
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
val file = data.requirePath()
return compiler.compile(file).get {
@@ -27,7 +27,7 @@ import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
import com.itsaky.androidide.lsp.java.rewrite.RemoveException
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.utils.ILogger
/** @author Akash Yadav */
@@ -60,7 +60,8 @@ class RemoveUnusedThrowsAction : BaseJavaCodeAction() {
override fun execAction(data: ActionData): Any {
val d = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return Any())
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
val file = data.requirePath()
return compiler.compile(file).get { task ->
val notThrown = CodeActionUtils.extractNotThrownExceptionName(d.message)
@@ -27,7 +27,7 @@ import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
import com.itsaky.androidide.lsp.java.rewrite.AddSuppressWarningAnnotation
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.utils.ILogger
/** @author Akash Yadav */
@@ -57,7 +57,8 @@ class SuppressUncheckedWarningAction : BaseJavaCodeAction() {
override fun execAction(data: ActionData): Any {
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return Any())
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
val file = data.requirePath()
return compiler.compile(file).get { task ->
val warnedMethod = CodeActionUtils.findMethod(task, diagnostic.range)
@@ -27,7 +27,7 @@ import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
import com.itsaky.androidide.lsp.java.rewrite.ConvertVariableToStatement
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.utils.ILogger
/** @author Akash Yadav */
@@ -65,7 +65,8 @@ class VariableToStatementAction : BaseJavaCodeAction() {
override fun execAction(data: ActionData): Any {
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return Any())
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
val path = data.requirePath()
return compiler.compile(path).get {
@@ -31,7 +31,7 @@ import com.itsaky.androidide.lsp.java.compiler.CompileTask
import com.itsaky.androidide.lsp.java.utils.EditHelper
import com.itsaky.androidide.lsp.java.utils.ShortTypePrinter.NO_PACKAGE
import com.itsaky.androidide.preferences.utils.indentationString
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.flashError
@@ -72,7 +72,8 @@ class GenerateConstructorAction : FieldBasedAction() {
private fun generateConstructor(data: ActionData, selected: MutableSet<String>) {
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return)
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return)
val range = data[com.itsaky.androidide.models.Range::class.java]!!
val file = data.requirePath()
@@ -27,7 +27,7 @@ import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
import com.itsaky.androidide.lsp.java.rewrite.GenerateRecordConstructor
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.utils.ILogger
/** @author Akash Yadav */
@@ -59,7 +59,8 @@ class GenerateMissingConstructorAction : BaseJavaCodeAction() {
override fun execAction(data: ActionData): Any {
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return Any())
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
val file = data.requirePath()
return compiler.compile(file).get { task ->
val needsConstructor =
@@ -36,7 +36,7 @@ import com.itsaky.androidide.lsp.java.utils.JavaParserUtils
import com.itsaky.androidide.lsp.java.utils.TypeUtils.toType
import com.itsaky.androidide.preferences.internal.tabSize
import com.itsaky.androidide.preferences.utils.indentationString
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.flashError
@@ -82,7 +82,8 @@ class GenerateSettersAndGettersAction : FieldBasedAction() {
private fun generateForFields(data: ActionData, names: MutableSet<String>) {
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return)
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return)
val range = data[com.itsaky.androidide.models.Range::class.java]!!
val file = data.requirePath()
@@ -31,7 +31,7 @@ import com.itsaky.androidide.lsp.java.compiler.CompileTask
import com.itsaky.androidide.lsp.java.utils.EditHelper
import com.itsaky.androidide.preferences.internal.tabSize
import com.itsaky.androidide.preferences.utils.indentationString
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.utils.ILogger
@@ -80,7 +80,8 @@ class GenerateToStringMethodAction : FieldBasedAction() {
private fun generateToString(data: ActionData, selected: MutableSet<String>) {
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return)
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return)
val range = data[com.itsaky.androidide.models.Range::class.java]!!
val file = data.requirePath()
@@ -38,7 +38,7 @@ import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarationAt
import com.itsaky.androidide.models.Position
import com.itsaky.androidide.preferences.internal.tabSize
import com.itsaky.androidide.preferences.utils.indentationString
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.flashError
@@ -90,7 +90,8 @@ class OverrideSuperclassMethodsAction : BaseJavaCodeAction() {
override fun execAction(data: ActionData): Any {
val range = data[com.itsaky.androidide.models.Range::class.java]!!
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return Any())
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
val file = data.requirePath()
return compiler.compile(file).get { task ->
@@ -197,7 +198,8 @@ class OverrideSuperclassMethodsAction : BaseJavaCodeAction() {
private fun overrideMethods(data: ActionData, checkedMethods: MutableList<MethodPtr>) {
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return)
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return)
val file = data.requirePath()
compiler.compile(file).run { task ->
@@ -263,7 +265,8 @@ class OverrideSuperclassMethodsAction : BaseJavaCodeAction() {
position: Position,
) {
val compiler =
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return)
JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return)
val editor = data[CodeEditor::class.java]!!
val file = data.requirePath()
val text = editor.text
@@ -20,7 +20,7 @@ package com.itsaky.androidide.lsp.java.edits
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.lsp.models.CompletionItem
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import io.github.rosemoe.sora.text.Content
import io.github.rosemoe.sora.widget.CodeEditor
import java.nio.file.Path
@@ -40,7 +40,8 @@ abstract class AdvancedJavaEditHandler(protected val file: Path) : BaseJavaEditH
column: Int,
index: Int
) {
val compiler = JavaCompilerProvider.get(ProjectManager.findModuleForFile(file) ?: return)
val compiler = JavaCompilerProvider.get(
IProjectManager.getInstance().findModuleForFile(file, false) ?: return)
performEdits(compiler, editor, item)
executeCommand(editor, item.command)
@@ -21,9 +21,17 @@ import com.itsaky.androidide.lsp.java.compiler.SourceFileManager;
import com.itsaky.androidide.lsp.java.compiler.SourceFileObject;
import com.itsaky.androidide.models.Position;
import com.itsaky.androidide.models.Range;
import com.itsaky.androidide.projects.ProjectManager;
import com.itsaky.androidide.projects.IProjectManager;
import com.itsaky.androidide.projects.api.ModuleProject;
import com.itsaky.androidide.utils.ILogger;
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;
import openjdk.source.tree.CompilationUnitTree;
import openjdk.source.tree.LineMap;
@@ -36,16 +44,6 @@ import openjdk.source.util.TreePath;
import openjdk.source.util.Trees;
import openjdk.tools.javac.api.JavacTool;
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;
public class Parser {
private static final JavaCompiler COMPILER = JavacTool.create();
@@ -75,9 +73,12 @@ public class Parser {
this.trees = Trees.instance(task);
}
/** Create a task that compiles a single file */
/**
* Create a task that compiles a single file
*/
private static JavacTask singleFileTask(JavaFileObject file) {
final ModuleProject module = ProjectManager.INSTANCE.findModuleForFile(Paths.get(file.toUri()));
final ModuleProject module = IProjectManager.getInstance()
.findModuleForFile(Paths.get(file.toUri()));
if (module != null) {
FILE_MANAGER = SourceFileManager.forModule(module);
}
@@ -24,7 +24,7 @@ import com.itsaky.androidide.lsp.models.DiagnosticResult
import com.itsaky.androidide.progress.ProgressManager
import com.itsaky.androidide.progress.ProgressManager.Companion.abortIfCancelled
import com.itsaky.androidide.projects.FileManager
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.utils.ILogger
import java.nio.file.Path
import java.time.Instant
@@ -45,7 +45,8 @@ class JavaDiagnosticProvider {
fun analyze(file: Path): DiagnosticResult {
val module = ProjectManager.findModuleForFile(file) ?: return DiagnosticResult.NO_UPDATE
val module = IProjectManager.getInstance().findModuleForFile(file, false)
?: return DiagnosticResult.NO_UPDATE
val compiler = JavaCompilerService(module)
abortIfCancelled()
@@ -19,7 +19,7 @@ package com.itsaky.androidide.lsp.java
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.api.ModuleProject
import org.junit.Before
import org.junit.Test
@@ -39,7 +39,7 @@ class JavaCompilerProviderTest {
@Test
fun `test module specific compilers`() {
val rootProject = ProjectManager.rootProject!!
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
@@ -20,7 +20,7 @@ package com.itsaky.androidide.lsp.java
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
import com.itsaky.androidide.lsp.api.LSPTest
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import org.junit.Before
import org.junit.Ignore
@@ -46,7 +46,7 @@ object JavaLSPTest : LSPTest() {
override fun getServerId() = JavaLanguageServer.SERVER_ID
fun getCompiler(): JavaCompilerService {
return JavaCompilerProvider.get(ProjectManager.app)
return JavaCompilerProvider.get(IProjectManager.getInstance().app)
}
override fun test() {}
@@ -31,7 +31,8 @@ import com.itsaky.androidide.models.Position
import com.itsaky.androidide.models.Range
import com.itsaky.androidide.preferences.internal.tabSize
import com.itsaky.androidide.projects.FileManager
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.IToolingApiServer
@@ -98,14 +99,14 @@ abstract class LSPTest {
Environment.ANDROID_JAR = FileProvider.resources().resolve("android.jar").toFile()
Environment.JAVA_HOME = File(System.getProperty("java.home")!!)
registerServer()
ProjectManager.register()
ProjectManager.setupProject()
ProjectManagerImpl.getInstance().register()
ProjectManagerImpl.getInstance().setupProject()
// We need to manually setup the language server with the project here
// ProjectManager.notifyProjectUpdate()
ILanguageServerRegistry.getDefault()
.getServer(getServerId())!!
.setupWithProject(ProjectManager.rootProject!!)
.setupWithProject(IProjectManager.getInstance().rootProject!!)
isInitialized = true
}
@@ -19,7 +19,7 @@ package com.itsaky.androidide.lsp.xml
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
import com.itsaky.androidide.lsp.api.LSPTest
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.api.ModuleProject
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import com.itsaky.androidide.xml.versions.ApiVersions
@@ -38,7 +38,7 @@ object XMLLSPTest : LSPTest() {
}
super.initProjectIfNeeded()
try {
val module = ProjectManager.app!!
val module = IProjectManager.getInstance().app!!
val lookup = Lookup.getDefault()
lookup.update(ModuleProject.COMPLETION_MODULE_KEY, module)
+35 -33
View File
@@ -1,44 +1,46 @@
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-parcelize")
id("kotlin-kapt")
id("com.android.library")
id("kotlin-android")
id("kotlin-parcelize")
id("kotlin-kapt")
}
android {
namespace = "${BuildConfig.packageName}.projects"
namespace = "${BuildConfig.packageName}.projects"
}
kapt {
arguments {
arg ("eventBusIndex", "${BuildConfig.packageName}.events.ProjectsApiEventsIndex")
}
arguments {
arg("eventBusIndex", "${BuildConfig.packageName}.events.ProjectsApiEventsIndex")
}
}
dependencies {
kapt(projects.annotationProcessors)
api(projects.eventbus)
api(projects.eventbusEvents)
api(projects.subprojects.toolingApi)
implementation(projects.common)
implementation(projects.logger)
implementation(projects.lookup)
implementation(projects.shared)
implementation(projects.subprojects.javacServices)
implementation(projects.subprojects.xmlUtils)
implementation(libs.common.io)
implementation(libs.google.guava)
testImplementation(projects.subprojects.toolingApiTesting)
testImplementation(projects.shared)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
testImplementation(libs.tests.robolectric)
androidTestImplementation(libs.tests.androidx.junit)
androidTestImplementation(libs.tests.androidx.espresso)
androidTestImplementation(libs.tests.google.truth)
kapt(projects.annotationProcessors)
kapt(libs.google.auto.service)
api(projects.eventbus)
api(projects.eventbusEvents)
api(projects.subprojects.toolingApi)
implementation(projects.common)
implementation(projects.logger)
implementation(projects.lookup)
implementation(projects.shared)
implementation(projects.subprojects.javacServices)
implementation(projects.subprojects.xmlUtils)
implementation(libs.common.io)
implementation(libs.google.auto.service.annotations)
implementation(libs.google.guava)
testImplementation(projects.subprojects.toolingApiTesting)
testImplementation(projects.shared)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
testImplementation(libs.tests.robolectric)
androidTestImplementation(libs.tests.androidx.junit)
androidTestImplementation(libs.tests.androidx.espresso)
androidTestImplementation(libs.tests.google.truth)
}
@@ -0,0 +1,145 @@
/*
* 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 androidx.annotation.RestrictTo
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.api.ModuleProject
import com.itsaky.androidide.projects.api.Project
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.utils.ServiceLoader
import java.io.File
import java.nio.file.Path
/**
* Project manager.
*
* @author Akash Yadav
*/
interface IProjectManager {
companion object {
private var projectManager: IProjectManager? = null
/**
* Get the project manager instance.
*/
@JvmStatic
fun getInstance(): IProjectManager {
return projectManager ?: ServiceLoader.load(IProjectManager::class.java).findFirstOrThrow()
.also {
projectManager = it
}
}
}
/**
* The root project model.
*/
val rootProject: Project?
/**
* The Android application module in the project.
*/
val app: AndroidModule?
/**
* The path to the project's root directory.
*/
val projectDirPath: String
/**
* The project's root directory.
*/
val projectDir: File
get() = File(projectDirPath)
/**
* Setup the project with the given [project proxy][project] from the Tooling API.
*
* @param project The project proxy.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
fun setupProject(
project: IProject = Lookup.getDefault().lookup(BuildService.KEY_PROJECT_PROXY)!!
)
/**
* 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
/**
* Destroy the project manager.
*/
fun destroy()
}
@@ -1,329 +1,318 @@
/*
* 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.itsaky.androidide.eventbus.events.EventReceiver
import com.itsaky.androidide.eventbus.events.editor.DocumentSaveEvent
import com.itsaky.androidide.eventbus.events.file.FileCreationEvent
import com.itsaky.androidide.eventbus.events.file.FileDeletionEvent
import com.itsaky.androidide.eventbus.events.file.FileEvent
import com.itsaky.androidide.eventbus.events.file.FileRenameEvent
import com.itsaky.androidide.eventbus.events.project.ProjectInitializedEvent
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.api.ModuleProject
import com.itsaky.androidide.projects.api.Project
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.projects.util.ProjectTransformer
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.messages.result.InitializeResult
import com.itsaky.androidide.tooling.api.models.BuildVariantInfo
import com.itsaky.androidide.utils.DocumentUtils
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.flashError
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode.ASYNC
import org.greenrobot.eventbus.ThreadMode.BACKGROUND
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.extension
import kotlin.io.path.isDirectory
import kotlin.io.path.pathString
/**
* Manages projects in AndroidIDE.
*
* @author Akash Yadav
*/
object ProjectManager : EventReceiver {
private val log = ILogger.newInstance(javaClass.simpleName)
lateinit var projectPath: String
var rootProject: Project? = null
var app: AndroidModule? = null
var projectInitialized: Boolean = false
var cachedInitResult: InitializeResult? = null
@JvmOverloads
fun setupProject(
project: IProject = Lookup.getDefault().lookup(BuildService.KEY_PROJECT_PROXY)!!,
isInitialized: Boolean = true
) {
this.rootProject = if (isInitialized) ProjectTransformer().transform(
CachingProject(project)) else null
if (this.rootProject != null) {
this.app = this.rootProject!!.findFirstAndroidAppModule()
this.rootProject!!.subProjects.filterIsInstance(ModuleProject::class.java).forEach {
it.indexSourcesAndClasspaths()
if (it is AndroidModule) {
it.readResources()
}
}
}
}
fun destroy() {
log.info("Destroying project manager")
this.rootProject = null
this.app = null
this.cachedInitResult = null
this.projectInitialized = false
}
fun getProjectDir(): File {
return File(getProjectDirPath())
}
fun getProjectDirPath(): String {
return projectPath
}
@JvmOverloads
fun generateSources(
builder: BuildService? = Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE)
) {
if (builder == null) {
log.warn("Cannot generate sources. BuildService is null.")
return
}
if (!builder.isToolingServerStarted()) {
flashError(R.string.msg_tooling_server_unavailable)
return
}
if (app == null) {
log.warn("Cannot run resource and source generation task. No application module found.")
return
}
val debug = app!!.getVariant("debug")
if (debug == null) {
log.warn("No debug variant found in application project ${app!!.name}")
return
}
val mainArtifact = debug.mainArtifact
val genResourcesTask = mainArtifact.resGenTaskName
val genSourcesTask = mainArtifact.sourceGenTaskName
val genDataBinding = // If view binding is enabled, generate the view binding classes too
if (app!!.viewBindingOptions.isEnabled) {
"dataBindingGenBaseClassesDebug"
} else {
""
}
builder
.executeProjectTasks(
app!!.path,
genResourcesTask ?: "",
genSourcesTask,
"processDebugResources",
genDataBinding
)
.whenComplete { result, taskErr ->
if (taskErr != null || !result.isSuccessful) {
log.warn(
"Execution for tasks '$genResourcesTask' and '$genSourcesTask' failed.",
taskErr ?: ""
)
} else {
notifyProjectUpdate()
}
}
}
fun getApplicationModule(): AndroidModule? {
return app
}
fun notifyProjectUpdate() {
executeAsync {
rootProject?.apply {
subProjects.forEach { subproject ->
if (subproject is ModuleProject) {
subproject.indexSources()
}
}
}
val event = ProjectInitializedEvent()
event.put(Project::class.java, rootProject)
EventBus.getDefault().post(event)
}
}
@JvmOverloads
fun findModuleForFile(file: File, checkExistance: Boolean = false): ModuleProject? {
if (!checkInit()) {
return null
}
return this.rootProject!!.findModuleForFile(file, checkExistance)
}
@JvmOverloads
fun findModuleForFile(file: Path, checkExistance: Boolean = false): ModuleProject? {
return findModuleForFile(file.toFile(), checkExistance)
}
fun containsSourceFile(file: Path): Boolean {
if (!checkInit()) {
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
}
private fun isInitialized() = rootProject != null
private fun checkInit(): Boolean {
if (isInitialized()) {
return true
}
log.warn("GradleProject is not initialized yet!")
return false
}
@Suppress("unused")
@Subscribe(threadMode = ASYNC)
fun onFileSaved(event: DocumentSaveEvent) {
event.file.apply {
if (isDirectory()) {
return@apply
}
if (extension != "xml") {
return@apply
}
val module = findModuleForFile(this) ?: return@apply
if (module !is AndroidModule) {
return@apply
}
val isResource =
module.mainSourceSet?.sourceProvider?.resDirectories?.any {
this.pathString.contains(it.path)
}
?: false
if (isResource) {
module.updateResourceTable()
}
}
}
@Suppress("unused")
@Subscribe(threadMode = BACKGROUND)
fun onFileCreated(event: FileCreationEvent) {
generateSourcesIfNecessary(event)
if (DocumentUtils.isJavaFile(event.file.toPath())) {
findModuleForFile(event.file)?.let {
val sourceRoot = it.findSourceRoot(event.file) ?: return@let
// add the source node entry
it.compileJavaSourceClasses.append(event.file.toPath(), sourceRoot)
}
}
}
@Suppress("unused")
@Subscribe(threadMode = BACKGROUND)
fun onFileDeleted(event: FileDeletionEvent) {
generateSourcesIfNecessary(event)
// Remove the source node entry
// 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)
?.compileJavaSourceClasses
?.findSource(event.file.toPath())
?.let { it.parent?.removeChild(it) }
}
}
@Suppress("unused")
@Subscribe(threadMode = BACKGROUND)
fun onFileRenamed(event: FileRenameEvent) {
generateSourcesIfNecessary(event)
// Do not check for Java file DocumentUtils.isJavaFile(...) as it checks for file existence as
// 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)
?.compileJavaSourceClasses
?.findSource(event.file.toPath())
?.let { it.parent?.removeChild(it) }
}
if (DocumentUtils.isJavaFile(event.newFile.toPath())) {
findModuleForFile(event.newFile)?.let {
val sourceRoot = it.findSourceRoot(event.newFile) ?: return@let
// add the new source node entry
it.compileJavaSourceClasses.append(event.newFile.toPath(), sourceRoot)
}
}
}
private fun generateSourcesIfNecessary(event: FileEvent) {
val builder = Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE) ?: return
val file = event.file
if (!isResource(file)) {
return
}
generateSources(builder)
}
private fun ModuleProject.findSourceRoot(file: File): Path? {
return getCompileSourceDirectories().find { file.path.startsWith(it.path) }?.toPath()
}
private fun isResource(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
}
}
/*
* 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 androidx.annotation.RestrictTo
import com.google.auto.service.AutoService
import com.itsaky.androidide.eventbus.events.EventReceiver
import com.itsaky.androidide.eventbus.events.editor.DocumentSaveEvent
import com.itsaky.androidide.eventbus.events.file.FileCreationEvent
import com.itsaky.androidide.eventbus.events.file.FileDeletionEvent
import com.itsaky.androidide.eventbus.events.file.FileEvent
import com.itsaky.androidide.eventbus.events.file.FileRenameEvent
import com.itsaky.androidide.eventbus.events.project.ProjectInitializedEvent
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.api.ModuleProject
import com.itsaky.androidide.projects.api.Project
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.projects.util.ProjectTransformer
import com.itsaky.androidide.tasks.executeAsync
import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.messages.result.InitializeResult
import com.itsaky.androidide.utils.DocumentUtils
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.flashError
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.extension
import kotlin.io.path.isDirectory
import kotlin.io.path.pathString
/**
* Internal implementation of [IProjectManager].
*
* @author Akash Yadav
*/
@AutoService(IProjectManager::class)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
class ProjectManagerImpl : IProjectManager, EventReceiver {
lateinit var projectPath: String
var projectInitialized: Boolean = false
var cachedInitResult: InitializeResult? = null
override var rootProject: Project? = null
override var app: AndroidModule? = null
override val projectDirPath: String
get() = projectPath
companion object {
private val log = ILogger.newInstance("ProjectManagerImpl")
@JvmStatic
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
fun getInstance(): ProjectManagerImpl {
return IProjectManager.getInstance() as ProjectManagerImpl
}
}
override fun setupProject(project: IProject) {
this.rootProject = ProjectTransformer().transform(CachingProject(project))
if (this.rootProject == null) {
return
}
this.app = this.rootProject!!.findFirstAndroidAppModule()
this.rootProject!!.subProjects.filterIsInstance(ModuleProject::class.java).forEach {
it.indexSourcesAndClasspaths()
if (it is AndroidModule) {
it.readResources()
}
}
}
override fun findModuleForFile(file: File, checkExistance: Boolean): ModuleProject? {
if (!checkInit()) {
return null
}
return this.rootProject!!.findModuleForFile(file, checkExistance)
}
override fun containsSourceFile(file: Path): Boolean {
if (!checkInit()) {
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.app = null
this.cachedInitResult = null
this.projectInitialized = false
}
@JvmOverloads
fun generateSources(
builder: BuildService? = Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE)
) {
if (builder == null) {
log.warn("Cannot generate sources. BuildService is null.")
return
}
if (!builder.isToolingServerStarted()) {
flashError(R.string.msg_tooling_server_unavailable)
return
}
if (app == null) {
log.warn("Cannot run resource and source generation task. No application module found.")
return
}
val debug = app!!.getVariant("debug")
if (debug == null) {
log.warn("No debug variant found in application project ${app!!.name}")
return
}
val mainArtifact = debug.mainArtifact
val genResourcesTask = mainArtifact.resGenTaskName
val genSourcesTask = mainArtifact.sourceGenTaskName
val genDataBinding = // If view binding is enabled, generate the view binding classes too
if (app!!.viewBindingOptions.isEnabled) {
"dataBindingGenBaseClassesDebug"
} else {
""
}
builder
.executeProjectTasks(
app!!.path,
genResourcesTask ?: "",
genSourcesTask,
"processDebugResources",
genDataBinding
)
.whenComplete { result, taskErr ->
if (taskErr != null || !result.isSuccessful) {
log.warn(
"Execution for tasks '$genResourcesTask' and '$genSourcesTask' failed.",
taskErr ?: ""
)
} else {
notifyProjectUpdate()
}
}
}
fun notifyProjectUpdate() {
executeAsync {
rootProject?.apply {
subProjects.forEach { subproject ->
if (subproject is ModuleProject) {
subproject.indexSources()
}
}
}
val event = ProjectInitializedEvent()
event.put(Project::class.java, rootProject)
EventBus.getDefault().post(event)
}
}
private fun isInitialized() = rootProject != null
private fun checkInit(): Boolean {
if (isInitialized()) {
return true
}
log.warn("GradleProject is not initialized yet!")
return false
}
private fun generateSourcesIfNecessary(event: FileEvent) {
val builder = Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE) ?: return
val file = event.file
if (!isAndroidResource(file)) {
return
}
generateSources(builder)
}
@Suppress("unused")
@Subscribe(threadMode = ThreadMode.ASYNC)
fun onFileSaved(event: DocumentSaveEvent) {
event.file.apply {
if (isDirectory()) {
return@apply
}
if (extension != "xml") {
return@apply
}
val module = IProjectManager.getInstance().findModuleForFile(this, false) ?: return@apply
if (module !is AndroidModule) {
return@apply
}
val isResource =
module.mainSourceSet?.sourceProvider?.resDirectories?.any {
this.pathString.contains(it.path)
}
?: false
if (isResource) {
module.updateResourceTable()
}
}
}
@Suppress("unused")
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onFileCreated(event: FileCreationEvent) {
generateSourcesIfNecessary(event)
if (DocumentUtils.isJavaFile(event.file.toPath())) {
IProjectManager.getInstance().findModuleForFile(event.file, false)?.let {
val sourceRoot = it.findSourceRoot(event.file) ?: return@let
// add the source node entry
it.compileJavaSourceClasses.append(event.file.toPath(), sourceRoot)
}
}
}
@Suppress("unused")
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onFileDeleted(event: FileDeletionEvent) {
generateSourcesIfNecessary(event)
// Remove the source node entry
// 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") {
IProjectManager.getInstance().findModuleForFile(event.file, false)
?.compileJavaSourceClasses
?.findSource(event.file.toPath())
?.let { it.parent?.removeChild(it) }
}
}
@Suppress("unused")
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onFileRenamed(event: FileRenameEvent) {
generateSourcesIfNecessary(event)
// Do not check for Java file DocumentUtils.isJavaFile(...) as it checks for file existence as
// 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
IProjectManager.getInstance().findModuleForFile(event.file, false)
?.compileJavaSourceClasses
?.findSource(event.file.toPath())
?.let { it.parent?.removeChild(it) }
}
if (DocumentUtils.isJavaFile(event.newFile.toPath())) {
IProjectManager.getInstance().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)
}
}
}
}
@@ -31,7 +31,7 @@ import com.itsaky.androidide.builder.model.DefaultModelSyncFile
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.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.tooling.api.ProjectType.Android
import com.itsaky.androidide.tooling.api.models.BasicAndroidVariantMetadata
import com.itsaky.androidide.tooling.api.models.GradleTask
@@ -190,7 +190,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
}
override fun getCompileClasspaths(): Set<File> {
val project = ProjectManager.rootProject ?: return emptySet()
val project = IProjectManager.getInstance().rootProject ?: return emptySet()
val result = mutableSetOf<File>()
result.addAll(getModuleClasspaths())
@@ -219,7 +219,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
}
override fun getCompileModuleProjects(): List<ModuleProject> {
val root = ProjectManager.rootProject ?: return emptyList()
val root = IProjectManager.getInstance().rootProject ?: return emptyList()
val result = mutableListOf<ModuleProject>()
for (library in this.libraries) {
@@ -18,7 +18,7 @@
package com.itsaky.androidide.projects.api
import com.itsaky.androidide.builder.model.IJavaCompilerSettings
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.tooling.api.ProjectType.Java
import com.itsaky.androidide.tooling.api.models.JavaModuleExternalDependency
import com.itsaky.androidide.tooling.api.models.JavaModuleProjectDependency
@@ -106,7 +106,7 @@ class JavaModule(
}
override fun getCompileModuleProjects(): List<ModuleProject> {
val root = ProjectManager.rootProject ?: return emptyList()
val root = IProjectManager.getInstance().rootProject ?: return emptyList()
return this.dependencies
.filterIsInstance(JavaModuleProjectDependency::class.java)
.filter { it.scope == SCOPE_COMPILE }
@@ -18,6 +18,7 @@
package com.itsaky.androidide.projects.api
import android.text.TextUtils
import androidx.annotation.RestrictTo
import com.itsaky.androidide.builder.model.IJavaCompilerSettings
import com.itsaky.androidide.javac.services.fs.CacheFSInfoSingleton
import com.itsaky.androidide.lookup.Lookup
@@ -62,6 +63,7 @@ abstract class ModuleProject(
@JvmField
val compileJavaSourceClasses = SourceClassTrie()
@JvmField
val compileClasspathClasses = ClassTrie()
@@ -84,7 +86,7 @@ abstract class ModuleProject(
* Get the classpaths for this module project. The returned list always included the
* `classes.jar`.
*/
abstract fun getClassPaths() : Set<File>
abstract fun getClassPaths(): Set<File>
/**
* Get the JAR files for this module. This does not include JAR files of any dependencies.
@@ -107,14 +109,26 @@ abstract class ModuleProject(
*/
abstract fun getCompileModuleProjects(): List<ModuleProject>
/**
* Find the source root of the given [file].
*
* @param file The file to find the source root for.
* @return The source root (directory) of the given file, or `null` if not found.
*/
fun findSourceRoot(file: File): Path? {
return getCompileSourceDirectories().find { file.path.startsWith(it.path) }?.toPath()
}
/** Finds the source files and classes from source directories and classpaths and indexes them. */
internal fun indexSourcesAndClasspaths() {
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
fun indexSourcesAndClasspaths() {
log.info("Indexing sources and classpaths for project:", path)
indexSources()
indexClasspaths()
}
internal fun indexClasspaths() {
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
fun indexClasspaths() {
this.compileClasspathClasses.clear()
@@ -138,7 +152,8 @@ abstract class ModuleProject(
}
}
internal fun indexSources() {
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
fun indexSources() {
this.compileJavaSourceClasses.clear()
@@ -22,7 +22,8 @@ import com.itsaky.androidide.eventbus.events.file.FileCreationEvent
import com.itsaky.androidide.eventbus.events.file.FileDeletionEvent
import com.itsaky.androidide.eventbus.events.file.FileRenameEvent
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.tooling.api.IAndroidProject
import com.itsaky.androidide.tooling.api.messages.InitializeProjectParams
@@ -54,7 +55,7 @@ class ModuleProjectTest {
assertThat(project).isNotNull()
val rootProject = ProjectManager.rootProject
val rootProject = IProjectManager.getInstance().rootProject
assertThat(rootProject).isNotNull()
val root = rootProject!!.rootProject
@@ -104,10 +105,10 @@ class ModuleProjectTest {
)
)
assertThat(
anotherAndroidLib.getCompileClasspaths().firstOrNull {
it.name == "nb-javac-android-17.0.0.0.jar"
}
)
anotherAndroidLib.getCompileClasspaths().firstOrNull {
it.name == "nb-javac-android-17.0.0.0.jar"
}
)
.isNotNull()
assertThat(anotherAndroidLib.getCompileSourceDirectories())
@@ -147,12 +148,12 @@ class ModuleProjectTest {
)
)
assertThat(
filter {
it.absolutePath.endsWith("appcompat-1.5.1/jars/classes.jar") ||
filter {
it.absolutePath.endsWith("appcompat-1.5.1/jars/classes.jar") ||
it.absolutePath.endsWith("material-1.8.0-alpha01/jars/classes.jar") ||
it.absolutePath.endsWith("coordinatorlayout-1.2.0/jars/classes.jar")
}
)
}
)
.hasSize(3)
}
@@ -203,9 +204,10 @@ class ModuleProjectTest {
Files.delete(testCls_renamed)
}
ProjectManager.setupProject()
val projectManager = ProjectManagerImpl.getInstance()
projectManager.setupProject()
val rootProject = ProjectManager.rootProject
val rootProject = IProjectManager.getInstance().rootProject
assertThat(rootProject).isNotNull()
val root = rootProject?.rootProject
@@ -229,28 +231,29 @@ class ModuleProjectTest {
assertThat(Files.isSameFile(source, sourceNode.file)).isTrue()
// make sure the files are not indexed
assertThat(ProjectManager.containsSourceFile(testCls)).isFalse()
assertThat(ProjectManager.containsSourceFile(testCls_renamed)).isFalse()
assertThat(IProjectManager.getInstance().containsSourceFile(testCls)).isFalse()
assertThat(IProjectManager.getInstance().containsSourceFile(testCls_renamed)).isFalse()
testCls.writeText("public class Test { }")
ProjectManager.onFileCreated(FileCreationEvent(testCls.toFile()))
assertThat(ProjectManager.containsSourceFile(testCls)).isTrue()
assertThat(ProjectManager.containsSourceFile(testCls_renamed)).isFalse()
assertThat(ProjectManager.findModuleForFile(testCls, true)).isEqualTo(module)
assertThat(ProjectManager.findModuleForFile(testCls_renamed, true)).isNull()
projectManager.onFileCreated(FileCreationEvent(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()
Files.move(testCls, testCls_renamed)
ProjectManager.onFileRenamed(FileRenameEvent(testCls.toFile(), testCls_renamed.toFile()))
assertThat(ProjectManager.containsSourceFile(testCls)).isFalse()
assertThat(ProjectManager.containsSourceFile(testCls_renamed)).isTrue()
assertThat(ProjectManager.findModuleForFile(testCls, true)).isNull()
assertThat(ProjectManager.findModuleForFile(testCls_renamed, true)).isEqualTo(module)
projectManager.onFileRenamed(FileRenameEvent(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(
module)
Files.delete(testCls_renamed)
ProjectManager.onFileDeleted(FileDeletionEvent(testCls_renamed.toFile()))
assertThat(ProjectManager.containsSourceFile(testCls)).isFalse()
assertThat(ProjectManager.containsSourceFile(testCls_renamed)).isFalse()
assertThat(ProjectManager.findModuleForFile(testCls, true)).isNull()
assertThat(ProjectManager.findModuleForFile(testCls_renamed, true)).isNull()
projectManager.onFileDeleted(FileDeletionEvent(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()
}
}
@@ -29,8 +29,8 @@ class ModuleProjectModelBuilder(initializationParams: InitializeProjectParams) :
AbstractModelBuilder<ModuleProjectModelBuilderParams, IModuleProject>(initializationParams) {
override fun build(param: ModuleProjectModelBuilderParams): IModuleProject {
val isAndroidProject = getAndroidVersions(param.module, param.controller) != null
return if (isAndroidProject) {
val versions = getAndroidVersions(param.module, param.controller)
return if (versions != null) {
AndroidProjectModelBuilder(initializationParams).build(
param.controller to param.module)
} else {
@@ -17,13 +17,10 @@
package com.itsaky.androidide.tooling.impl.sync
import com.itsaky.androidide.tooling.api.messages.InitializeProjectParams
import org.gradle.tooling.BuildController
import org.gradle.tooling.ProjectConnection
import org.gradle.tooling.model.idea.IdeaModule
import org.gradle.tooling.model.idea.IdeaProject
typealias BuildControllderAndIdeaModule = Pair<BuildController, IdeaModule>
class ModuleProjectModelBuilderParams(val controller: BuildController, project: IdeaProject,
@@ -34,7 +34,7 @@ import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.InflateException
import com.itsaky.androidide.inflater.internal.AttributeImpl
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.api.AndroidModule
import java.io.File
@@ -92,12 +92,12 @@ fun processXmlFile(file: File, expectedType: AaptResourceType): Pair<XmlProcesso
throw InflateException("File is not a layout file.")
}
if (ProjectManager.rootProject == null) {
if (IProjectManager.getInstance().rootProject == null) {
throw InflateException("GradleProject is not initialized!")
}
val module =
ProjectManager.findModuleForFile(file) as? AndroidModule
IProjectManager.getInstance().findModuleForFile(file, false) as? AndroidModule
?: throw InflateException("Cannot find module for given file. Is the project initialized?")
val resFile =
ResourceFile(
@@ -17,7 +17,7 @@
package com.itsaky.androidide.inflater.utils
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.api.AndroidModule
import java.io.File
@@ -33,7 +33,7 @@ fun startParse(file: File) {
if (isParsing) {
return
}
(ProjectManager.findModuleForFile(file) as? AndroidModule)?.let {
(IProjectManager.getInstance().findModuleForFile(file, false) as? AndroidModule)?.let {
startParse(it)
}
}
@@ -29,7 +29,7 @@ import com.itsaky.androidide.inflater.internal.ViewGroupImpl
import com.itsaky.androidide.inflater.internal.ViewImpl
import com.itsaky.androidide.inflater.internal.utils.IDTable
import com.itsaky.androidide.inflater.utils.newAttribute
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.api.AndroidModule
import org.junit.Before
import java.io.File
@@ -228,7 +228,7 @@ class LayoutInflaterTest {
}
private fun layoutFile(name: String): File {
val app = ProjectManager.app ?: throw IllegalStateException("GradleProject is not initialized")
val app = IProjectManager.getInstance().app ?: throw IllegalStateException("GradleProject is not initialized")
return File(app.projectDir, "src/main/res/layout/$name.xml")
}
@@ -21,7 +21,7 @@ import androidx.appcompat.app.AppCompatActivity
import com.itsaky.androidide.inflater.utils.endParse
import com.itsaky.androidide.inflater.utils.startParse
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.tooling.api.messages.InitializeProjectParams
@@ -48,15 +48,16 @@ object XmlInflaterTest {
server.initialize(InitializeProjectParams(FileProvider.testProjectRoot().pathString)).get()
Lookup.getDefault().register(BuildService.KEY_PROJECT_PROXY, project)
ProjectManager.setupProject()
IProjectManager.getInstance().setupProject()
init.set(true)
}
}
fun inflaterTest(block: (AndroidModule) -> Unit) {
XmlInflaterTest.initIfNeeded()
startParse(ProjectManager.app!!)
block(ProjectManager.app!!)
val app = IProjectManager.getInstance().app!!
startParse(app)
block(app)
endParse()
}