diff --git a/.idea/google-java-format.xml b/.idea/google-java-format.xml
index 05946a82a..8b57f4527 100644
--- a/.idea/google-java-format.xml
+++ b/.idea/google-java-format.xml
@@ -2,6 +2,5 @@
-
\ No newline at end of file
diff --git a/app/src/main/assets/data/common/tooling-api-all.jar b/app/src/main/assets/data/common/tooling-api-all.jar
index 242297626..a5502f9a5 100644
Binary files a/app/src/main/assets/data/common/tooling-api-all.jar and b/app/src/main/assets/data/common/tooling-api-all.jar differ
diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java
index 4ef7d10b0..4ca6e747e 100755
--- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java
+++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java
@@ -79,6 +79,7 @@ import com.itsaky.androidide.adapters.DiagnosticsAdapter;
import com.itsaky.androidide.adapters.EditorBottomSheetTabAdapter;
import com.itsaky.androidide.adapters.SearchListAdapter;
import com.itsaky.androidide.app.StudioActivity;
+import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.databinding.ActivityEditorBinding;
import com.itsaky.androidide.databinding.LayoutDiagnosticInfoBinding;
import com.itsaky.androidide.databinding.LayoutSearchProjectBinding;
@@ -109,6 +110,7 @@ import com.itsaky.androidide.projects.ProjectManager;
import com.itsaky.androidide.services.GradleBuildService;
import com.itsaky.androidide.services.LogReceiver;
import com.itsaky.androidide.shell.ShellServer;
+import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData;
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult;
import com.itsaky.androidide.tooling.api.model.IdeGradleProject;
import com.itsaky.androidide.utils.CharSequenceInputStream;
@@ -147,6 +149,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -157,1670 +160,1649 @@ import me.piruin.quickaction.ActionItem;
import me.piruin.quickaction.QuickAction;
public class EditorActivity extends StudioActivity
- implements FileTreeFragment.FileActionListener,
- TabLayout.OnTabSelectedListener,
- NavigationView.OnNavigationItemSelectedListener,
- DiagnosticClickListener,
- IDEHandler.Provider,
- EditorActivityProvider,
- OptionsListFragment.OnOptionsClickListener {
+ implements FileTreeFragment.FileActionListener,
+ TabLayout.OnTabSelectedListener,
+ NavigationView.OnNavigationItemSelectedListener,
+ DiagnosticClickListener,
+ IDEHandler.Provider,
+ EditorActivityProvider,
+ OptionsListFragment.OnOptionsClickListener {
- public static final String EXTRA_PROJECT_PATH = "project_path";
- public static final String KEY_BOTTOM_SHEET_SHOWN = "editor_bottomSheetShown";
- private static final String TAG_FILE_OPTIONS_FRAGMENT = "file_options_fragment";
- private static final int ACTION_ID_CLOSE = 100;
- private static final int ACTION_ID_OTHERS = 101;
- private static final int ACTION_ID_ALL = 102;
- private static final ILogger LOG = ILogger.newInstance("EditorActivity");
- private final EditorEventListener mBuildEventListener = new EditorEventListener();
- private ActivityEditorBinding mBinding;
- private LayoutDiagnosticInfoBinding mDiagnosticInfoBinding;
- private EditorBottomSheetTabAdapter bottomSheetTabAdapter;
- private final LogReceiver mLogReceiver = new LogReceiver().setLogListener(this::appendApkLog);
- private FileTreeFragment mFileTreeFragment;
- private TreeNode mLastHeld;
- private SymbolInputView symbolInput;
- private FileOptionsHandler mFileOptionsHandler;
- private QuickAction mTabCloseAction;
- private TextSheetFragment mDaemonStatusFragment;
- private OptionsListFragment mFileOptionsFragment;
- private ProgressSheet mSearchingProgress;
- private AlertDialog mFindInProjectDialog;
- private ActivityResultLauncher mUIDesignerLauncher;
- private EditorBottomSheetBehavior extends View> mEditorBottomSheet;
- private EditorViewModel mViewModel;
+ public static final String EXTRA_PROJECT_PATH = "project_path";
+ public static final String KEY_BOTTOM_SHEET_SHOWN = "editor_bottomSheetShown";
+ private static final String TAG_FILE_OPTIONS_FRAGMENT = "file_options_fragment";
+ private static final int ACTION_ID_CLOSE = 100;
+ private static final int ACTION_ID_OTHERS = 101;
+ private static final int ACTION_ID_ALL = 102;
+ private static final ILogger LOG = ILogger.newInstance("EditorActivity");
+ private final EditorEventListener mBuildEventListener = new EditorEventListener();
+ private ActivityEditorBinding mBinding;
+ private LayoutDiagnosticInfoBinding mDiagnosticInfoBinding;
+ private EditorBottomSheetTabAdapter bottomSheetTabAdapter;
+ private final LogReceiver mLogReceiver = new LogReceiver().setLogListener(this::appendApkLog);
+ private FileTreeFragment mFileTreeFragment;
+ private TreeNode mLastHeld;
+ private SymbolInputView symbolInput;
+ private FileOptionsHandler mFileOptionsHandler;
+ private QuickAction mTabCloseAction;
+ private TextSheetFragment mDaemonStatusFragment;
+ private OptionsListFragment mFileOptionsFragment;
+ private ProgressSheet mSearchingProgress;
+ private AlertDialog mFindInProjectDialog;
+ private ActivityResultLauncher mUIDesignerLauncher;
+ private EditorBottomSheetBehavior extends View> mEditorBottomSheet;
+ private EditorViewModel mViewModel;
- private IdeGradleProject mRootProject;
- private GradleBuildService mBuildService;
- private final ServiceConnection mGradleServiceConnection =
- new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mBuildService = ((GradleBuildService.GradleServiceBinder) service).getService();
- LOG.info("Gradle build service has been started...");
+ private GradleBuildService mBuildService;
+ private final ServiceConnection mGradleServiceConnection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mBuildService = ((GradleBuildService.GradleServiceBinder) service).getService();
+ LOG.info("Gradle build service has been started...");
- mBuildService
- .setEventListener(mBuildEventListener)
- .startToolingServer(() -> initializeProject());
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mBuildService = null;
- LOG.info("Disconnected from Gradle build service...");
- }
- };
-
- @Override
- public void onBackPressed() {
- if (mBinding.getRoot().isDrawerOpen(GravityCompat.END)) {
- mBinding.getRoot().closeDrawer(GravityCompat.END);
- } else if (mBinding.getRoot().isDrawerOpen(GravityCompat.START)) {
- mBinding.getRoot().closeDrawer(GravityCompat.START);
- } else if (getDaemonStatusFragment().isShowing()) {
- getDaemonStatusFragment().dismiss();
- } else if (mFileOptionsFragment != null && mFileOptionsFragment.isShowing()) {
- mFileOptionsFragment.dismiss();
- } else if (mEditorBottomSheet.getState() == BottomSheetBehavior.STATE_EXPANDED) {
- mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED);
- } else {
- confirmProjectClose();
- }
- }
-
- @SuppressLint("RestrictedApi")
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- if (menu instanceof MenuBuilder) {
- MenuBuilder builder = (MenuBuilder) menu;
- builder.setOptionalIconsVisible(true);
+ mBuildService
+ .setEventListener(mBuildEventListener)
+ .startToolingServer(() -> initializeProject());
}
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- ensureToolbarMenu(menu);
- return true;
- }
-
- public void ensureToolbarMenu(@NonNull Menu menu) {
- menu.clear();
-
- final var data = new ActionData();
- final var currentEditor = getCurrentEditor();
- data.put(Context.class, EditorActivity.this);
- data.put(CodeEditorView.class, currentEditor);
-
- if (currentEditor != null) {
- data.put(IDEEditor.class, currentEditor.getEditor());
- data.put(File.class, currentEditor.getEditor().getFile());
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mBuildService = null;
+ LOG.info("Disconnected from Gradle build service...");
}
+ };
- ActionsRegistry.getInstance()
- .fillMenu(
- data,
- com.itsaky.androidide.actions.ActionItem.Location.EDITOR_TOOLBAR,
- menu);
+ @Override
+ public void onBackPressed() {
+ if (mBinding.getRoot().isDrawerOpen(GravityCompat.END)) {
+ mBinding.getRoot().closeDrawer(GravityCompat.END);
+ } else if (mBinding.getRoot().isDrawerOpen(GravityCompat.START)) {
+ mBinding.getRoot().closeDrawer(GravityCompat.START);
+ } else if (getDaemonStatusFragment().isShowing()) {
+ getDaemonStatusFragment().dismiss();
+ } else if (mFileOptionsFragment != null && mFileOptionsFragment.isShowing()) {
+ mFileOptionsFragment.dismiss();
+ } else if (mEditorBottomSheet.getState() == BottomSheetBehavior.STATE_EXPANDED) {
+ mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED);
+ } else {
+ confirmProjectClose();
+ }
+ }
+
+ @SuppressLint("RestrictedApi")
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (menu instanceof MenuBuilder) {
+ MenuBuilder builder = (MenuBuilder) menu;
+ builder.setOptionalIconsVisible(true);
}
- @Nullable
- public CodeEditorView getCurrentEditor() {
- if (mViewModel.getCurrentFileIndex() != -1) {
- return getEditorAtIndex(mViewModel.getCurrentFileIndex());
- }
+ return true;
+ }
- return null;
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ ensureToolbarMenu(menu);
+ return true;
+ }
+
+ public void ensureToolbarMenu(@NonNull Menu menu) {
+ menu.clear();
+
+ final var data = new ActionData();
+ final var currentEditor = getCurrentEditor();
+ data.put(Context.class, EditorActivity.this);
+ data.put(CodeEditorView.class, currentEditor);
+
+ if (currentEditor != null) {
+ data.put(IDEEditor.class, currentEditor.getEditor());
+ data.put(File.class, currentEditor.getEditor().getFile());
}
- @Nullable
- public CodeEditorView getEditorAtIndex(final int index) {
- return (CodeEditorView) mBinding.editorContainer.getChildAt(index);
+ ActionsRegistry.getInstance()
+ .fillMenu(data, com.itsaky.androidide.actions.ActionItem.Location.EDITOR_TOOLBAR, menu);
+ }
+
+ @Nullable
+ public CodeEditorView getCurrentEditor() {
+ if (mViewModel.getCurrentFileIndex() != -1) {
+ return getEditorAtIndex(mViewModel.getCurrentFileIndex());
}
- @Override
- public EditorActivity provide() {
- return this;
+ return null;
+ }
+
+ @Nullable
+ public CodeEditorView getEditorAtIndex(final int index) {
+ return (CodeEditorView) mBinding.editorContainer.getChildAt(index);
+ }
+
+ @Override
+ public EditorActivity provide() {
+ return this;
+ }
+
+ @Override
+ public EditorActivity provideEditorActivity() {
+ return this;
+ }
+
+ public void handleSearchResults(Map> results) {
+ setSearchResultAdapter(
+ new com.itsaky.androidide.adapters.SearchListAdapter(
+ results,
+ file -> {
+ openFile(file);
+ hideViewOptions();
+ },
+ match -> {
+ openFileAndSelect(match.file, match);
+ hideViewOptions();
+ }));
+
+ showSearchResults();
+
+ if (mSearchingProgress != null && mSearchingProgress.isShowing()) {
+ mSearchingProgress.dismiss();
+ }
+ }
+
+ public void appendApkLog(LogLine line) {
+ final var logFragment = bottomSheetTabAdapter.getLogFragment();
+ if (logFragment != null) {
+ logFragment.appendLog(line);
+ }
+ }
+
+ public void showDaemonStatus() {
+ ShellServer shell = getApp().newShell(t -> getDaemonStatusFragment().append(t));
+ shell.bgAppend(String.format("echo '%s'", getString(R.string.msg_getting_daemom_status)));
+ shell.bgAppend(
+ String.format(
+ "cd '%s' && sh gradlew --status",
+ Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDirPath())));
+ if (!getDaemonStatusFragment().isShowing()) {
+ getDaemonStatusFragment().show(getSupportFragmentManager(), "daemon_status");
+ }
+ }
+
+ public TextSheetFragment getDaemonStatusFragment() {
+ return mDaemonStatusFragment == null
+ ? mDaemonStatusFragment =
+ new TextSheetFragment()
+ .setTextSelectable(true)
+ .setTitleText(R.string.gradle_daemon_status)
+ : mDaemonStatusFragment;
+ }
+
+ public void showSearchResults() {
+ if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_EXPANDED) {
+ mEditorBottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED);
}
- @Override
- public EditorActivity provideEditorActivity() {
- return this;
+ final int index = bottomSheetTabAdapter.findIndexOfFragmentByClass(SearchResultFragment.class);
+ if (index >= 0 && index < mBinding.bottomSheet.tabs.getTabCount()) {
+ final var tab = mBinding.bottomSheet.tabs.getTabAt(index);
+ if (tab != null) {
+ tab.select();
+ }
}
+ }
- public void handleSearchResults(Map> results) {
- setSearchResultAdapter(
- new com.itsaky.androidide.adapters.SearchListAdapter(
- results,
- file -> {
- openFile(file);
- hideViewOptions();
- },
- match -> {
- openFileAndSelect(match.file, match);
- hideViewOptions();
- }));
+ public void handleDiagnosticsResultVisibility(boolean errorVisible) {
+ runOnUiThread(
+ () -> {
+ final var diagnosticsFragment = bottomSheetTabAdapter.getDiagnosticsFragment();
+ if (diagnosticsFragment != null) {
+ diagnosticsFragment.handleResultVisibility(errorVisible);
+ }
+ });
+ }
- showSearchResults();
+ public void handleSearchResultVisibility(boolean errorVisible) {
+ runOnUiThread(
+ () -> {
+ final var searchResultFragment = bottomSheetTabAdapter.getSearchResultFragment();
+ if (searchResultFragment != null) {
+ searchResultFragment.handleResultVisibility(errorVisible);
+ }
+ });
+ }
- if (mSearchingProgress != null && mSearchingProgress.isShowing()) {
- mSearchingProgress.dismiss();
- }
+ public void showFirstBuildNotice() {
+ DialogUtils.newMaterialDialogBuilder(this)
+ .setPositiveButton(android.R.string.ok, null)
+ .setTitle(R.string.title_first_build)
+ .setMessage(R.string.msg_first_build)
+ .setCancelable(false)
+ .create()
+ .show();
+ }
+
+ @Override
+ public void onGroupClick(DiagnosticGroup group) {
+ if (group != null
+ && group.file != null
+ && group.file.exists()
+ && FileUtils.isUtf8(group.file)) {
+ openFile(group.file);
+ hideViewOptions();
}
+ }
- public void appendApkLog(LogLine line) {
- final var logFragment = bottomSheetTabAdapter.getLogFragment();
- if (logFragment != null) {
- logFragment.appendLog(line);
- }
- }
+ @Override
+ public void onDiagnosticClick(File file, @NonNull DiagnosticItem diagnostic) {
+ openFileAndSelect(file, diagnostic.getRange());
+ hideViewOptions();
+ }
- public void showDaemonStatus() {
- ShellServer shell = getApp().newShell(t -> getDaemonStatusFragment().append(t));
- shell.bgAppend(String.format("echo '%s'", getString(R.string.msg_getting_daemom_status)));
- shell.bgAppend(
- String.format(
- "cd '%s' && sh gradlew --status",
- Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDirPath())));
- if (!getDaemonStatusFragment().isShowing()) {
- getDaemonStatusFragment().show(getSupportFragmentManager(), "daemon_status");
- }
- }
-
- public TextSheetFragment getDaemonStatusFragment() {
- return mDaemonStatusFragment == null
- ? mDaemonStatusFragment =
- new TextSheetFragment()
- .setTextSelectable(true)
- .setTitleText(R.string.gradle_daemon_status)
- : mDaemonStatusFragment;
- }
-
- public void showSearchResults() {
- if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_EXPANDED) {
- mEditorBottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED);
- }
-
- final int index =
- bottomSheetTabAdapter.findIndexOfFragmentByClass(SearchResultFragment.class);
- if (index >= 0 && index < mBinding.bottomSheet.tabs.getTabCount()) {
- final var tab = mBinding.bottomSheet.tabs.getTabAt(index);
- if (tab != null) {
- tab.select();
- }
- }
- }
-
- public void handleDiagnosticsResultVisibility(boolean errorVisible) {
- runOnUiThread(
- () -> {
- final var diagnosticsFragment = bottomSheetTabAdapter.getDiagnosticsFragment();
- if (diagnosticsFragment != null) {
- diagnosticsFragment.handleResultVisibility(errorVisible);
- }
- });
- }
-
- public void handleSearchResultVisibility(boolean errorVisible) {
- runOnUiThread(
- () -> {
- final var searchResultFragment =
- bottomSheetTabAdapter.getSearchResultFragment();
- if (searchResultFragment != null) {
- searchResultFragment.handleResultVisibility(errorVisible);
- }
- });
- }
-
- public void showFirstBuildNotice() {
- DialogUtils.newMaterialDialogBuilder(this)
- .setPositiveButton(android.R.string.ok, null)
- .setTitle(R.string.title_first_build)
- .setMessage(R.string.msg_first_build)
- .setCancelable(false)
- .create()
- .show();
- }
-
- @Override
- public void onGroupClick(DiagnosticGroup group) {
- if (group != null
- && group.file != null
- && group.file.exists()
- && FileUtils.isUtf8(group.file)) {
- openFile(group.file);
- hideViewOptions();
- }
- }
-
- @Override
- public void onDiagnosticClick(File file, @NonNull DiagnosticItem diagnostic) {
- openFileAndSelect(file, diagnostic.getRange());
- hideViewOptions();
- }
-
- public void openFileAndSelect(File file, Range selection) {
- openFile(file, selection);
- final var opened = getEditorForFile(file);
- if (opened != null && opened.getEditor() != null) {
- final var editor = opened.getEditor();
- final var range = editor.validateRange(selection);
- editor.post(
- () -> {
- if (LSPUtils.isEqual(range.getStart(), range.getEnd())) {
- editor.setSelection(
- range.getStart().getLine(), range.getEnd().getColumn());
- } else {
- editor.setSelection(range);
- }
- });
- }
- }
-
- @Nullable
- public CodeEditorView getEditorForFile(@NonNull final File file) {
- for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
- final CodeEditorView editor = (CodeEditorView) mBinding.editorContainer.getChildAt(i);
- if (file.equals(editor.getFile())) {
- return editor;
- }
- }
-
- return null;
- }
-
- @Override
- public CodeEditorView openFile(File file) {
- return openFile(file, null);
- }
-
- public void hideViewOptions() {
- if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_COLLAPSED) {
- mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED);
- }
- }
-
- public CodeEditorView openFile(File file, com.itsaky.lsp.models.Range selection) {
- if (selection == null) {
- selection = com.itsaky.lsp.models.Range.NONE;
- }
-
- int index = openFileAndGetIndex(file, selection);
- final var tab = mBinding.tabs.getTabAt(index);
- if (tab != null && index >= 0 && !tab.isSelected()) {
- tab.select();
- }
-
- if (mBinding.editorDrawerLayout.isDrawerOpen(GravityCompat.END)) {
- mBinding.editorDrawerLayout.closeDrawer(GravityCompat.END);
- }
-
- mBinding.editorContainer.setDisplayedChild(index);
-
- try {
- return getEditorAtIndex(index);
- } catch (Throwable th) {
- LOG.error("Unable to get editor fragment at opened file index", index);
- LOG.error(th);
- return null;
- }
- }
-
- @SuppressLint("NotifyDataSetChanged")
- public int openFileAndGetIndex(File file, com.itsaky.lsp.models.Range selection) {
- final var openedFileIndex = findIndexOfEditorByFile(file);
-
- if (openedFileIndex != -1) {
- LOG.error("File is already opened. File: " + file);
- return openedFileIndex;
- }
-
- final var position = mViewModel.getOpenedFileCount();
-
- LOG.info("Opening file at index:", position, "file: ", file);
-
- final var editor = new CodeEditorView(this, file, selection);
- editor.getEditor().subscribeEvent(ContentChangeEvent.class, this::onEditorContentChanged);
- editor.setLayoutParams(
- new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
-
- mBinding.editorContainer.addView(editor);
- mBinding.editorContainer.setDisplayedChild(position);
- mBinding.tabs.addTab(mBinding.tabs.newTab().setText(file.getName()));
-
- mViewModel.addFile(file);
- mViewModel.setCurrentFile(position, file);
-
- return position;
- }
-
- public int findIndexOfEditorByFile(File file) {
- if (file == null) {
- LOG.error("Cannot find index of a null file.");
- return -1;
- }
-
- for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
- final var opened = mViewModel.getOpenedFile(i);
- if (opened.equals(file)) {
- return i;
- }
- }
-
- return -1;
- }
-
- private void onEditorContentChanged(ContentChangeEvent event, Unsubscribe unsubscribe) {
- if (event.getAction() != ContentChangeEvent.ACTION_SET_NEW_TEXT) {
- mViewModel.setFilesModified(true);
- }
- }
-
- @Override
- public void showFileOptions(File thisFile, TreeNode node) {
- mLastHeld = node;
- getFileOptionsFragment(thisFile)
- .show(getSupportFragmentManager(), TAG_FILE_OPTIONS_FRAGMENT);
- }
-
- public OptionsListFragment getFileOptionsFragment(File file) {
- mFileOptionsFragment = new OptionsListFragment();
- mFileOptionsFragment.addOption(
- new SheetOption(0, R.drawable.ic_file_copy_path, R.string.copy_path, file));
- mFileOptionsFragment.addOption(
- new SheetOption(1, R.drawable.ic_file_rename, R.string.rename_file, file));
- mFileOptionsFragment.addOption(
- new SheetOption(2, R.drawable.ic_delete, R.string.delete_file, file));
- if (file.isDirectory()) {
- mFileOptionsFragment.addOption(
- new SheetOption(3, R.drawable.ic_new_file, R.string.new_file, file));
- mFileOptionsFragment.addOption(
- new SheetOption(4, R.drawable.ic_new_folder, R.string.new_folder, file));
- }
-
- return mFileOptionsFragment;
- }
-
- public ActivityEditorBinding getBinding() {
- return mBinding;
- }
-
- public LayoutDiagnosticInfoBinding getDiagnosticBinding() {
- return mDiagnosticInfoBinding;
- }
-
- @Override
- public void onTabSelected(@NonNull TabLayout.Tab tab) {
- final var position = tab.getPosition();
- mBinding.editorContainer.setDisplayedChild(position);
-
- final var editorView = getEditorAtIndex(position);
- Objects.requireNonNull(editorView);
-
- editorView.onEditorSelected();
- mViewModel.setCurrentFile(position, editorView.getFile());
- refreshSymbolInput(editorView);
- }
-
- @Override
- public void onTabUnselected(@NonNull TabLayout.Tab tab) {
- final var position = tab.getPosition();
- final var editorView = getEditorAtIndex(position);
- if (editorView != null) {
- final var editor = editorView.getEditor();
- if (editor != null) {
- editor.ensureWindowsDismissed();
- }
- }
- }
-
- @Override
- public void onTabReselected(@NonNull TabLayout.Tab tab) {
- mTabCloseAction.show(tab.view);
- }
-
- private void refreshSymbolInput(@NonNull CodeEditorView frag) {
- symbolInput.bindEditor(frag.getEditor());
- symbolInput.setSymbols(Symbols.forFile(frag.getFile()));
- }
-
- @Override
- public boolean onNavigationItemSelected(@NonNull MenuItem p1) {
- final int id = p1.getItemId();
- if (id == R.id.editornav_discuss) {
- getApp().openTelegramGroup();
- } else if (id == R.id.editornav_suggest) {
- getApp().openGitHub();
- } else if (id == R.id.editornav_needHelp) {
- showNeedHelpDialog();
- } else if (id == R.id.editornav_settings) {
- startActivity(new Intent(this, PreferencesActivity.class));
- } else if (id == R.id.editornav_share) {
- startActivity(IntentUtils.getShareTextIntent(getString(R.string.msg_share_app)));
- } else if (id == R.id.editornav_close_project) {
- confirmProjectClose();
- } else if (id == R.id.editornav_terminal) {
- openTerminal();
- }
- mBinding.getRoot().closeDrawer(GravityCompat.START);
- return false;
- }
-
- public void loadFragment(Fragment fragment) {
- super.loadFragment(fragment, mBinding.editorFrameLayout.getId());
- }
-
- public FileTreeFragment getFileTreeFragment() {
- return mFileTreeFragment;
- }
-
- public TreeNode getLastHoldTreeNode() {
- return mLastHeld;
- }
-
- public ProgressSheet getProgressSheet(int msg) {
- if (mSearchingProgress != null && mSearchingProgress.isShowing()) {
- mSearchingProgress.dismiss();
- }
- mSearchingProgress = new ProgressSheet();
- mSearchingProgress.setCancelable(false);
- mSearchingProgress.setWelcomeTextEnabled(false);
- mSearchingProgress.setMessage(getString(msg));
- mSearchingProgress.setSubMessageEnabled(false);
- return mSearchingProgress;
- }
-
- public void setDiagnosticsAdapter(@NonNull final DiagnosticsAdapter adapter) {
- runOnUiThread(
- () -> {
- final var diagnosticsFragment = bottomSheetTabAdapter.getDiagnosticsFragment();
- if (diagnosticsFragment != null) {
- diagnosticsFragment.setAdapter(adapter);
- }
- });
- }
-
- public void setSearchResultAdapter(@NonNull final SearchListAdapter adapter) {
- runOnUiThread(
- () -> {
- final var searchResultFragment =
- bottomSheetTabAdapter.getSearchResultFragment();
- if (searchResultFragment != null) {
- searchResultFragment.setAdapter(adapter);
- }
- });
- }
-
- @SuppressWarnings("UnusedReturnValue")
- public boolean saveAll() {
- return saveAll(true);
- }
-
- public void closeFile(int index) {
- if (index >= 0 && index < mViewModel.getOpenedFileCount()) {
- var opened = mViewModel.getOpenedFile(index);
- LOG.info("Closing file:", opened);
- final var editor = getEditorAtIndex(index);
-
- if (editor != null && editor.isModified()) {
- notifyFilesUnsaved(Collections.singletonList(editor), () -> closeFile(index));
- return;
- }
-
- if (editor != null && editor.getEditor() != null) {
- editor.getEditor().close();
+ public void openFileAndSelect(File file, Range selection) {
+ openFile(file, selection);
+ final var opened = getEditorForFile(file);
+ if (opened != null && opened.getEditor() != null) {
+ final var editor = opened.getEditor();
+ final var range = editor.validateRange(selection);
+ editor.post(
+ () -> {
+ if (LSPUtils.isEqual(range.getStart(), range.getEnd())) {
+ editor.setSelection(range.getStart().getLine(), range.getEnd().getColumn());
} else {
- LOG.error("Cannot save file before close. Editor instance is null");
+ editor.setSelection(range);
}
+ });
+ }
+ }
- mViewModel.removeFile(index);
- mBinding.tabs.removeTabAt(index);
- mBinding.editorContainer.removeViewAt(index);
+ @Nullable
+ public CodeEditorView getEditorForFile(@NonNull final File file) {
+ for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
+ final CodeEditorView editor = (CodeEditorView) mBinding.editorContainer.getChildAt(i);
+ if (file.equals(editor.getFile())) {
+ return editor;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public CodeEditorView openFile(File file) {
+ return openFile(file, null);
+ }
+
+ public void hideViewOptions() {
+ if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_COLLAPSED) {
+ mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED);
+ }
+ }
+
+ public CodeEditorView openFile(File file, com.itsaky.lsp.models.Range selection) {
+ if (selection == null) {
+ selection = com.itsaky.lsp.models.Range.NONE;
+ }
+
+ int index = openFileAndGetIndex(file, selection);
+ final var tab = mBinding.tabs.getTabAt(index);
+ if (tab != null && index >= 0 && !tab.isSelected()) {
+ tab.select();
+ }
+
+ if (mBinding.editorDrawerLayout.isDrawerOpen(GravityCompat.END)) {
+ mBinding.editorDrawerLayout.closeDrawer(GravityCompat.END);
+ }
+
+ mBinding.editorContainer.setDisplayedChild(index);
+
+ try {
+ return getEditorAtIndex(index);
+ } catch (Throwable th) {
+ LOG.error("Unable to get editor fragment at opened file index", index);
+ LOG.error(th);
+ return null;
+ }
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ public int openFileAndGetIndex(File file, com.itsaky.lsp.models.Range selection) {
+ final var openedFileIndex = findIndexOfEditorByFile(file);
+
+ if (openedFileIndex != -1) {
+ LOG.error("File is already opened. File: " + file);
+ return openedFileIndex;
+ }
+
+ final var position = mViewModel.getOpenedFileCount();
+
+ LOG.info("Opening file at index:", position, "file: ", file);
+
+ final var editor = new CodeEditorView(this, file, selection);
+ editor.getEditor().subscribeEvent(ContentChangeEvent.class, this::onEditorContentChanged);
+ editor.setLayoutParams(
+ new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+ mBinding.editorContainer.addView(editor);
+ mBinding.editorContainer.setDisplayedChild(position);
+ mBinding.tabs.addTab(mBinding.tabs.newTab().setText(file.getName()));
+
+ mViewModel.addFile(file);
+ mViewModel.setCurrentFile(position, file);
+
+ return position;
+ }
+
+ public int findIndexOfEditorByFile(File file) {
+ if (file == null) {
+ LOG.error("Cannot find index of a null file.");
+ return -1;
+ }
+
+ for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
+ final var opened = mViewModel.getOpenedFile(i);
+ if (opened.equals(file)) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private void onEditorContentChanged(ContentChangeEvent event, Unsubscribe unsubscribe) {
+ if (event.getAction() != ContentChangeEvent.ACTION_SET_NEW_TEXT) {
+ mViewModel.setFilesModified(true);
+ }
+ }
+
+ @Override
+ public void showFileOptions(File thisFile, TreeNode node) {
+ mLastHeld = node;
+ getFileOptionsFragment(thisFile).show(getSupportFragmentManager(), TAG_FILE_OPTIONS_FRAGMENT);
+ }
+
+ public OptionsListFragment getFileOptionsFragment(File file) {
+ mFileOptionsFragment = new OptionsListFragment();
+ mFileOptionsFragment.addOption(
+ new SheetOption(0, R.drawable.ic_file_copy_path, R.string.copy_path, file));
+ mFileOptionsFragment.addOption(
+ new SheetOption(1, R.drawable.ic_file_rename, R.string.rename_file, file));
+ mFileOptionsFragment.addOption(
+ new SheetOption(2, R.drawable.ic_delete, R.string.delete_file, file));
+ if (file.isDirectory()) {
+ mFileOptionsFragment.addOption(
+ new SheetOption(3, R.drawable.ic_new_file, R.string.new_file, file));
+ mFileOptionsFragment.addOption(
+ new SheetOption(4, R.drawable.ic_new_folder, R.string.new_folder, file));
+ }
+
+ return mFileOptionsFragment;
+ }
+
+ public ActivityEditorBinding getBinding() {
+ return mBinding;
+ }
+
+ public LayoutDiagnosticInfoBinding getDiagnosticBinding() {
+ return mDiagnosticInfoBinding;
+ }
+
+ @Override
+ public void onTabSelected(@NonNull TabLayout.Tab tab) {
+ final var position = tab.getPosition();
+ mBinding.editorContainer.setDisplayedChild(position);
+
+ final var editorView = getEditorAtIndex(position);
+ Objects.requireNonNull(editorView);
+
+ editorView.onEditorSelected();
+ mViewModel.setCurrentFile(position, editorView.getFile());
+ refreshSymbolInput(editorView);
+ }
+
+ @Override
+ public void onTabUnselected(@NonNull TabLayout.Tab tab) {
+ final var position = tab.getPosition();
+ final var editorView = getEditorAtIndex(position);
+ if (editorView != null) {
+ final var editor = editorView.getEditor();
+ if (editor != null) {
+ editor.ensureWindowsDismissed();
+ }
+ }
+ }
+
+ @Override
+ public void onTabReselected(@NonNull TabLayout.Tab tab) {
+ mTabCloseAction.show(tab.view);
+ }
+
+ private void refreshSymbolInput(@NonNull CodeEditorView frag) {
+ symbolInput.bindEditor(frag.getEditor());
+ symbolInput.setSymbols(Symbols.forFile(frag.getFile()));
+ }
+
+ @Override
+ public boolean onNavigationItemSelected(@NonNull MenuItem p1) {
+ final int id = p1.getItemId();
+ if (id == R.id.editornav_discuss) {
+ getApp().openTelegramGroup();
+ } else if (id == R.id.editornav_suggest) {
+ getApp().openGitHub();
+ } else if (id == R.id.editornav_needHelp) {
+ showNeedHelpDialog();
+ } else if (id == R.id.editornav_settings) {
+ startActivity(new Intent(this, PreferencesActivity.class));
+ } else if (id == R.id.editornav_share) {
+ startActivity(IntentUtils.getShareTextIntent(getString(R.string.msg_share_app)));
+ } else if (id == R.id.editornav_close_project) {
+ confirmProjectClose();
+ } else if (id == R.id.editornav_terminal) {
+ openTerminal();
+ }
+ mBinding.getRoot().closeDrawer(GravityCompat.START);
+ return false;
+ }
+
+ public void loadFragment(Fragment fragment) {
+ super.loadFragment(fragment, mBinding.editorFrameLayout.getId());
+ }
+
+ public FileTreeFragment getFileTreeFragment() {
+ return mFileTreeFragment;
+ }
+
+ public TreeNode getLastHoldTreeNode() {
+ return mLastHeld;
+ }
+
+ public ProgressSheet getProgressSheet(int msg) {
+ if (mSearchingProgress != null && mSearchingProgress.isShowing()) {
+ mSearchingProgress.dismiss();
+ }
+ mSearchingProgress = new ProgressSheet();
+ mSearchingProgress.setCancelable(false);
+ mSearchingProgress.setWelcomeTextEnabled(false);
+ mSearchingProgress.setMessage(getString(msg));
+ mSearchingProgress.setSubMessageEnabled(false);
+ return mSearchingProgress;
+ }
+
+ public void setDiagnosticsAdapter(@NonNull final DiagnosticsAdapter adapter) {
+ runOnUiThread(
+ () -> {
+ final var diagnosticsFragment = bottomSheetTabAdapter.getDiagnosticsFragment();
+ if (diagnosticsFragment != null) {
+ diagnosticsFragment.setAdapter(adapter);
+ }
+ });
+ }
+
+ public void setSearchResultAdapter(@NonNull final SearchListAdapter adapter) {
+ runOnUiThread(
+ () -> {
+ final var searchResultFragment = bottomSheetTabAdapter.getSearchResultFragment();
+ if (searchResultFragment != null) {
+ searchResultFragment.setAdapter(adapter);
+ }
+ });
+ }
+
+ @SuppressWarnings("UnusedReturnValue")
+ public boolean saveAll() {
+ return saveAll(true);
+ }
+
+ public void closeFile(int index) {
+ if (index >= 0 && index < mViewModel.getOpenedFileCount()) {
+ var opened = mViewModel.getOpenedFile(index);
+ LOG.info("Closing file:", opened);
+ final var editor = getEditorAtIndex(index);
+
+ if (editor != null && editor.isModified()) {
+ notifyFilesUnsaved(Collections.singletonList(editor), () -> closeFile(index));
+ return;
+ }
+
+ if (editor != null && editor.getEditor() != null) {
+ editor.getEditor().close();
+ } else {
+ LOG.error("Cannot save file before close. Editor instance is null");
+ }
+
+ mViewModel.removeFile(index);
+ mBinding.tabs.removeTabAt(index);
+ mBinding.editorContainer.removeViewAt(index);
+ } else {
+ LOG.error("Invalid file index. Cannot close.");
+ return;
+ }
+
+ mBinding.tabs.requestLayout();
+ }
+
+ public void closeAll() {
+ closeAll(null);
+ }
+
+ /**
+ * Close all opened files. If all the files are saved successfully, then the provided {@link
+ * Runnable} will be called.
+ *
+ * @param onSaved The {@link Runnable} to run when all the files are saved.
+ */
+ public void closeAll(Runnable onSaved) {
+ final var count = mViewModel.getOpenedFileCount();
+ final var unsavedFiles =
+ mViewModel.getOpenedFiles().stream()
+ .map(this::getEditorForFile)
+ .filter(editor -> editor != null && editor.isModified())
+ .collect(Collectors.toList());
+
+ if (unsavedFiles.isEmpty()) {
+ // Files were already saved, close all files one by one
+ for (int i = 0; i < count; i++) {
+ final var editor = getEditorAtIndex(i);
+ if (editor != null && editor.getEditor() != null) {
+ editor.getEditor().close();
} else {
- LOG.error("Invalid file index. Cannot close.");
- return;
+ LOG.error("Unable to close file at index:", i);
}
+ }
- mBinding.tabs.requestLayout();
+ mViewModel.removeAllFiles();
+ mBinding.tabs.removeAllTabs();
+ mBinding.tabs.requestLayout();
+ mBinding.editorContainer.removeAllViews();
+
+ if (onSaved != null) {
+ onSaved.run();
+ }
+ } else {
+ // There are unsaved files
+ notifyFilesUnsaved(unsavedFiles, () -> closeAll(onSaved));
}
+ }
- public void closeAll() {
- closeAll(null);
- }
+ public boolean areFilesModified() {
+ return mViewModel.areFilesModified();
+ }
- /**
- * Close all opened files. If all the files are saved successfully, then the provided {@link
- * Runnable} will be called.
- *
- * @param onSaved The {@link Runnable} to run when all the files are saved.
- */
- public void closeAll(Runnable onSaved) {
- final var count = mViewModel.getOpenedFileCount();
- final var unsavedFiles =
- mViewModel.getOpenedFiles().stream()
- .map(this::getEditorForFile)
- .filter(editor -> editor != null && editor.isModified())
- .collect(Collectors.toList());
+ public EditorViewModel getViewModel() {
+ return mViewModel;
+ }
- if (unsavedFiles.isEmpty()) {
- // Files were already saved, close all files one by one
- for (int i = 0; i < count; i++) {
- final var editor = getEditorAtIndex(i);
- if (editor != null && editor.getEditor() != null) {
- editor.getEditor().close();
- } else {
- LOG.error("Unable to close file at index:", i);
- }
- }
+ public void closeOthers() {
+ final var unsavedFiles =
+ mViewModel.getOpenedFiles().stream()
+ .map(this::getEditorForFile)
+ .filter(editor -> editor != null && editor.isModified())
+ .collect(Collectors.toList());
- mViewModel.removeAllFiles();
- mBinding.tabs.removeAllTabs();
- mBinding.tabs.requestLayout();
- mBinding.editorContainer.removeAllViews();
+ if (unsavedFiles.isEmpty()) {
+ final var file = mViewModel.getCurrentFile();
+ for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
+ final var editor = getEditorAtIndex(i);
- if (onSaved != null) {
- onSaved.run();
- }
+ // Index of files changes as we keep close files
+ // So we compare the files instead of index
+ if (editor != null) {
+ if (!file.equals(editor.getFile())) {
+ closeFile(i);
+ }
} else {
- // There are unsaved files
- notifyFilesUnsaved(unsavedFiles, () -> closeAll(onSaved));
+ LOG.error("Unable to save file at index:", i);
}
+ }
+ } else {
+ notifyFilesUnsaved(unsavedFiles, this::closeOthers);
+ }
+ }
+
+ public void previewLayout() {
+ try {
+
+ if (getCurrentEditor() == null || getCurrentEditor().getFile() == null) {
+ LOG.error("No file is opened. Cannot preview layout.");
+ return;
+ }
+
+ saveAll(false);
+
+ getResourceDirPaths()
+ .whenComplete(
+ (dirs, error) -> {
+ runOnUiThread(
+ () -> {
+ final Intent intent = new Intent(this, DesignerActivity.class);
+ intent.putExtra(
+ DesignerActivity.KEY_LAYOUT_PATH,
+ getCurrentEditor().getFile().getAbsolutePath());
+ intent.putStringArrayListExtra(DesignerActivity.KEY_RES_DIRS, dirs);
+ LOG.info("Launching UI Designer...");
+ mUIDesignerLauncher.launch(intent);
+ });
+ });
+ } catch (Throwable th) {
+ LOG.error(getString(R.string.err_cannot_preview_layout), th);
+ getApp().toast(R.string.msg_cannot_preview_layout, Toaster.Type.ERROR);
+ }
+ }
+
+ public boolean saveAll(boolean notify) {
+ return saveAll(notify, false);
+ }
+
+ @NonNull
+ private CompletableFuture> getResourceDirPaths() {
+ return getResourceDirs()
+ .thenApply(
+ files ->
+ files.stream()
+ .filter(Objects::nonNull)
+ .map(File::getAbsolutePath)
+ .collect(Collectors.toCollection(ArrayList::new)));
+ }
+
+ public boolean saveAll(boolean notify, boolean canProcessResources) {
+ SaveResult result = saveAllResult();
+
+ if (notify) {
+ getApp().toast(R.string.all_saved, Toaster.Type.SUCCESS);
}
- public boolean areFilesModified() {
- return mViewModel.areFilesModified();
+ if (result.gradleSaved) {
+ notifySyncNeeded();
}
- public EditorViewModel getViewModel() {
- return mViewModel;
+ if (canProcessResources) {
+ ProjectManager.INSTANCE.generateSources(mBuildService);
}
- public void closeOthers() {
- final var unsavedFiles =
- mViewModel.getOpenedFiles().stream()
- .map(this::getEditorForFile)
- .filter(editor -> editor != null && editor.isModified())
- .collect(Collectors.toList());
+ return result.gradleSaved;
+ }
- if (unsavedFiles.isEmpty()) {
- final var file = mViewModel.getCurrentFile();
- for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
- final var editor = getEditorAtIndex(i);
+ private CompletableFuture> getResourceDirs() {
+ return ProjectManager.INSTANCE.getApplicationResDirectories();
+ }
- // Index of files changes as we keep close files
- // So we compare the files instead of index
- if (editor != null) {
- if (!file.equals(editor.getFile())) {
- closeFile(i);
- }
- } else {
- LOG.error("Unable to save file at index:", i);
- }
- }
- } else {
- notifyFilesUnsaved(unsavedFiles, this::closeOthers);
- }
+ public SaveResult saveAllResult() {
+ SaveResult result = new SaveResult();
+ for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
+ saveResult(i, result);
}
- public void previewLayout() {
- try {
+ return result;
+ }
- if (getCurrentEditor() == null || getCurrentEditor().getFile() == null) {
- LOG.error("No file is opened. Cannot preview layout.");
- return;
- }
+ public void notifySyncNeeded() {
+ if (mBuildService != null && !mBuildService.isBuildInProgress()) {
+ getSyncBanner()
+ .setNegative(android.R.string.cancel, null)
+ .setPositive(android.R.string.ok, v -> initializeProject())
+ .show();
+ }
+ }
- saveAll(false);
+ public void saveResult(int index, SaveResult result) {
+ if (index >= 0 && index < mViewModel.getOpenedFileCount()) {
+ var frag = getEditorAtIndex(index);
+ if (frag == null || frag.getFile() == null) {
+ return;
+ }
- final Intent intent = new Intent(this, DesignerActivity.class);
- intent.putExtra(
- DesignerActivity.KEY_LAYOUT_PATH,
- getCurrentEditor().getFile().getAbsolutePath());
- intent.putStringArrayListExtra(
- DesignerActivity.KEY_RES_DIRS, (ArrayList) getResourceDirectories());
- LOG.info("Launching UI Designer...");
- mUIDesignerLauncher.launch(intent);
- } catch (Throwable th) {
- LOG.error(getString(R.string.err_cannot_preview_layout), th);
- getApp().toast(R.string.msg_cannot_preview_layout, Toaster.Type.ERROR);
- }
+ // Must be called before frag.save()
+ // Otherwise, it'll always return false
+ final boolean modified = frag.isModified();
+
+ frag.save();
+
+ final boolean isGradle = frag.getFile().getName().endsWith(".gradle");
+ final boolean isXml = frag.getFile().getName().endsWith(".xml");
+
+ if (!result.gradleSaved) {
+ result.gradleSaved = modified && isGradle;
+ }
+
+ if (!result.xmlSaved) {
+ result.xmlSaved = modified && isXml;
+ }
}
- public boolean saveAll(boolean notify) {
- return saveAll(notify, false);
+ var modified = false;
+ for (var file : mViewModel.getOpenedFiles()) {
+ var editor = getEditorForFile(file);
+ if (editor == null) {
+ continue;
+ }
+ modified = modified || editor.isModified();
}
- @NonNull
- private List getResourceDirectories() {
- final var dirs = ProjectManager.INSTANCE.getApplicationResDirectories();
- if (dirs == null) {
- return Collections.emptyList();
- }
+ final boolean finalModified = modified;
+ ThreadUtils.runOnUiThread(() -> mViewModel.setFilesModified(finalModified));
+ }
- return dirs.stream().map(File::getAbsolutePath).collect(Collectors.toList());
+ public MaterialBanner getSyncBanner() {
+ return mBinding
+ .syncBanner
+ .setContentTextColor(ContextCompat.getColor(this, R.color.primaryTextColor))
+ .setBannerBackgroundColor(ContextCompat.getColor(this, R.color.primaryLightColor))
+ .setButtonTextColor(ContextCompat.getColor(this, R.color.secondaryColor))
+ .setIcon(R.drawable.ic_sync)
+ .setContentText(R.string.msg_sync_needed);
+ }
+
+ private void initializeProject() {
+ final var projectPath = ProjectManager.INSTANCE.getProjectDirPath();
+ if (projectPath == null) {
+ LOG.error("Cannot initialize project. Project model is null.");
+ return;
}
- public boolean saveAll(boolean notify, boolean canProcessResources) {
- SaveResult result = saveAllResult();
-
- if (notify) {
- getApp().toast(R.string.all_saved, Toaster.Type.SUCCESS);
- }
-
- if (result.gradleSaved) {
- notifySyncNeeded();
- }
-
- if (canProcessResources) {
- ProjectManager.INSTANCE.generateSources(mBuildService);
- }
-
- return result.gradleSaved;
+ final var projectDir = new File(projectPath);
+ if (!projectDir.exists()) {
+ LOG.error("Project directory does not exist. Cannot initialize project");
+ return;
}
- public SaveResult saveAllResult() {
- SaveResult result = new SaveResult();
- for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
- saveResult(i, result);
- }
+ ThreadUtils.runOnUiThread(
+ () -> {
+ setStatus(getString(R.string.msg_initializing_project));
+ mBinding.buildProgressIndicator.setVisibility(View.VISIBLE);
+ });
- return result;
- }
-
- public void notifySyncNeeded() {
- if (mBuildService != null && !mBuildService.isBuildInProgress()) {
- getSyncBanner()
- .setNegative(android.R.string.cancel, null)
- .setPositive(android.R.string.ok, v -> initializeProject())
- .show();
- }
- }
-
- public void saveResult(int index, SaveResult result) {
- if (index >= 0 && index < mViewModel.getOpenedFileCount()) {
- var frag = getEditorAtIndex(index);
- if (frag == null || frag.getFile() == null) {
- return;
- }
-
- // Must be called before frag.save()
- // Otherwise, it'll always return false
- final boolean modified = frag.isModified();
-
- frag.save();
-
- final boolean isGradle = frag.getFile().getName().endsWith(".gradle");
- final boolean isXml = frag.getFile().getName().endsWith(".xml");
-
- if (!result.gradleSaved) {
- result.gradleSaved = modified && isGradle;
- }
-
- if (!result.xmlSaved) {
- result.xmlSaved = modified && isXml;
- }
- }
-
- var modified = false;
- for (var file : mViewModel.getOpenedFiles()) {
- var editor = getEditorForFile(file);
- if (editor == null) {
- continue;
- }
- modified = modified || editor.isModified();
- }
-
- final boolean finalModified = modified;
- ThreadUtils.runOnUiThread(() -> mViewModel.setFilesModified(finalModified));
- }
-
- public MaterialBanner getSyncBanner() {
- return mBinding.syncBanner
- .setContentTextColor(ContextCompat.getColor(this, R.color.primaryTextColor))
- .setBannerBackgroundColor(ContextCompat.getColor(this, R.color.primaryLightColor))
- .setButtonTextColor(ContextCompat.getColor(this, R.color.secondaryColor))
- .setIcon(R.drawable.ic_sync)
- .setContentText(R.string.msg_sync_needed);
- }
-
- private void initializeProject() {
- final var projectPath = ProjectManager.INSTANCE.getProjectDirPath();
- if (projectPath == null) {
- LOG.error("Cannot initialize project. Project model is null.");
+ final var future = mBuildService.initializeProject(projectDir.getAbsolutePath());
+ future.whenComplete(
+ (result, error) -> {
+ final var root = result == null ? null : result.getProject();
+ if (result == null || root == null || error != null) {
+ LOG.error("An error occurred initializing the project with Tooling API", error);
+ setStatus(getString(R.string.msg_project_initialization_failed));
return;
- }
+ }
- final var projectDir = new File(projectPath);
- if (!projectDir.exists()) {
- LOG.error("Project directory does not exist. Cannot initialize project");
- return;
- }
+ onProjectInitialized(root);
+ });
+ }
- ThreadUtils.runOnUiThread(
- () -> {
- setStatus(getString(R.string.msg_initializing_project));
- mBinding.buildProgressIndicator.setVisibility(View.VISIBLE);
- });
+ public void setStatus(final CharSequence text) {
+ setStatus(text, Gravity.CENTER);
+ }
- final var future = mBuildService.initializeProject(projectDir.getAbsolutePath());
+ protected void onProjectInitialized(IdeGradleProject root) {
+ ProjectManager.INSTANCE.setRootProject(root);
+ ProjectManager.INSTANCE.notifyProjectUpdate();
+ ThreadUtils.runOnUiThread(
+ () -> {
+ setStatus(getString(R.string.msg_project_initialized));
+ mBinding.buildProgressIndicator.setVisibility(View.GONE);
+ if (mFindInProjectDialog != null && mFindInProjectDialog.isShowing()) {
+ mFindInProjectDialog.dismiss();
+ }
+
+ mFindInProjectDialog = null; // Create the dialog again if needed
+ });
+ }
+
+ public void setStatus(final CharSequence text, @GravityInt int gravity) {
+ try {
+ runOnUiThread(
+ () -> {
+ mBinding.bottomSheet.statusText.setGravity(gravity);
+ mBinding.bottomSheet.statusText.setText(text);
+ });
+ } catch (Throwable th) {
+ LOG.error("Failed to update status text", th);
+ }
+ }
+
+ public void assembleDebug(boolean installApk) {
+ execTasks(installApk ? installableTaskResultConsumer("debug") : null, "assembleDebug");
+ }
+
+ @SuppressWarnings("UnusedReturnValue")
+ @NonNull
+ public CompletableFuture execTasks(
+ Consumer resultHandler, String... tasks) {
+ ThreadUtils.runOnUiThread(
+ () -> appendBuildOut("Executing tasks: " + TextUtils.join(", ", tasks)));
+ return mBuildService
+ .executeTasks(tasks)
+ .whenComplete(
+ ((executionResult, throwable) -> {
+ if (executionResult == null || throwable != null) {
+ LOG.error("Tasks failed to execute", TextUtils.join(", ", tasks));
+ }
+
+ if (resultHandler != null) {
+ resultHandler.accept(executionResult);
+ }
+ }));
+ }
+
+ public Consumer installableTaskResultConsumer(@NonNull String variantName) {
+ return task -> {
+ if (task != null) {
+ LOG.debug("Installing APK(s) for variant:", variantName);
+ // TODO Handle multiple application modules
+ final var future = ProjectManager.INSTANCE.getApplicationModule();
future.whenComplete(
- (result, error) -> {
- final var root = result == null ? null : result.getProject();
- if (result == null || root == null || error != null) {
- LOG.error(
- "An error occurred initializing the project with Tooling API",
- error);
- setStatus(getString(R.string.msg_project_initialization_failed));
- return;
- }
+ (app, error) -> {
+ if (app == null) {
+ return;
+ }
- onProjectInitialized(root);
- });
- }
+ final var variants = app.getVariants();
+ final var foundVariant =
+ variants.stream()
+ .filter(variant -> variant.getName().equals(variantName))
+ .findFirst();
- public void setStatus(final CharSequence text) {
- setStatus(text, Gravity.CENTER);
- }
-
- protected void onProjectInitialized(IdeGradleProject root) {
- mRootProject = root;
- ProjectManager.INSTANCE.setRootProject(mRootProject);
- ProjectManager.INSTANCE.notifyProjectUpdate();
- ThreadUtils.runOnUiThread(
- () -> {
- setStatus(getString(R.string.msg_project_initialized));
- mBinding.buildProgressIndicator.setVisibility(View.GONE);
- if (mFindInProjectDialog != null && mFindInProjectDialog.isShowing()) {
- mFindInProjectDialog.dismiss();
- }
-
- mFindInProjectDialog = null; // Create the dialog again if needed
- });
- }
-
- public void setStatus(final CharSequence text, @GravityInt int gravity) {
- try {
- runOnUiThread(
- () -> {
- mBinding.bottomSheet.statusText.setGravity(gravity);
- mBinding.bottomSheet.statusText.setText(text);
- });
- } catch (Throwable th) {
- LOG.error("Failed to update status text", th);
- }
- }
-
- public void assembleDebug(boolean installApk) {
- execTasks(installApk ? installableTaskResultConsumer("debug") : null, "assembleDebug");
- }
-
- @SuppressWarnings("UnusedReturnValue")
- @NonNull
- public CompletableFuture execTasks(
- Consumer resultHandler, String... tasks) {
- ThreadUtils.runOnUiThread(
- () -> appendBuildOut("Executing tasks: " + TextUtils.join(", ", tasks)));
- return mBuildService
- .executeTasks(tasks)
- .whenComplete(
- ((executionResult, throwable) -> {
- if (executionResult == null || throwable != null) {
- LOG.error("Tasks failed to execute", TextUtils.join(", ", tasks));
- }
-
- if (resultHandler != null) {
- resultHandler.accept(executionResult);
- }
- }));
- }
-
- public Consumer installableTaskResultConsumer(
- @NonNull String variantName) {
- return task -> {
- if (task != null) {
- LOG.debug("Installing APK(s) for variant:", variantName);
- // TODO Handle multiple application modules
- final var app = mRootProject.findFirstAndroidModule();
- if (app == null) {
- LOG.error("No application module found in root project. Cannot install APKs");
- return;
+ if (foundVariant.isPresent()) {
+ final var variant = foundVariant.get();
+ final var main = variant.getMainArtifact();
+ final var outputListingFile = main.getAssembleTaskOutputListingFile();
+ if (outputListingFile == null) {
+ LOG.error("No output listing file provided with project model");
+ return;
}
- final var variants = app.getVariants();
- final var foundVariant =
- variants.stream()
- .filter(variant -> variant.getName().equals(variantName))
- .findFirst();
-
- if (foundVariant.isPresent()) {
- final var variant = foundVariant.get();
- final var main = variant.getMainArtifact();
- final var outputListingFile = main.getAssembleTaskOutputListingFile();
- if (outputListingFile == null) {
- LOG.error("No output listing file provided with project model");
- return;
- }
-
- final var apkFile = ApkMetadata.findApkFile(outputListingFile);
- if (apkFile == null) {
- LOG.error(
- "No apk file specified in output listing file:", outputListingFile);
- return;
- }
-
- if (!apkFile.exists()) {
- LOG.error(
- "APK file specified in output listing file does not exist!",
- apkFile);
- return;
- }
-
- install(apkFile);
- } else {
- LOG.error(
- "No",
- variantName,
- "variant found in application module",
- app.getProjectPath());
+ final var apkFile = ApkMetadata.findApkFile(outputListingFile);
+ if (apkFile == null) {
+ LOG.error("No apk file specified in output listing file:", outputListingFile);
+ return;
}
- }
- };
+
+ if (!apkFile.exists()) {
+ LOG.error("APK file specified in output listing file does not exist!", apkFile);
+ return;
+ }
+
+ install(apkFile);
+ } else {
+ LOG.error(
+ "No", variantName, "variant found in application module", app.getProjectPath());
+ }
+ });
+ }
+ };
+ }
+
+ public void appendBuildOut(final String str) {
+ final var frag = bottomSheetTabAdapter.getBuildOutputFragment();
+
+ if (frag != null) {
+ frag.appendOutput(str);
}
-
- public void appendBuildOut(final String str) {
- final var frag = bottomSheetTabAdapter.getBuildOutputFragment();
-
- if (frag != null) {
- frag.appendOutput(str);
- }
- }
-
- public void install(@NonNull File apk) {
- ThreadUtils.runOnUiThread(
- () -> {
- LOG.debug("Installing APK:", apk);
- if (apk.exists()) {
- Intent i = IntentUtils.getInstallAppIntent(apk);
- if (i != null) {
- startActivity(i);
- } else {
- getApp().toast(
- R.string.msg_apk_install_intent_failed,
- Toaster.Type.ERROR);
- }
- } else {
- LOG.error("APK file does not exist!");
- }
- });
- }
-
- public void assembleRelease() {
- execTasks(null, "assembleRelease");
- }
-
- public void build() {
- execTasks(null, "build");
- }
-
- public void clean() {
- execTasks(null, "clean");
- }
-
- public void bundle() {
- execTasks(null, "bundle");
- }
-
- public void lint() {
- execTasks(null, "lint");
- }
-
- public void lintDebug() {
- execTasks(null, "lintDebug");
- }
-
- public void lintRelease() {
- execTasks(null, "lintRelease");
- }
-
- public void cleanAndRebuild() {
- execTasks(null, "clean", "build");
- }
-
- /////////////////////////////////////////////////
- ////////////// PRIVATE APIS /////////////////////
- /////////////////////////////////////////////////
-
- public AlertDialog getFindInProjectDialog() {
- return mFindInProjectDialog == null ? createFindInProjectDialog() : mFindInProjectDialog;
- }
-
- @Override
- public void onOptionsClick(SheetOption option) {
- if (mFileOptionsHandler != null) {
- mFileOptionsHandler.onOptionsClick(option);
- }
- }
-
- public GradleBuildService getBuildService() {
- return mBuildService;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setSupportActionBar(mBinding.editorToolbar);
-
- mViewModel = new ViewModelProvider(this).get(EditorViewModel.class);
- getProjectFromIntent();
-
- mFileTreeFragment = FileTreeFragment.newInstance();
- mDaemonStatusFragment = new TextSheetFragment().setTextSelectable(true);
-
- setupDrawerToggle();
- loadFragment(mFileTreeFragment);
-
- symbolInput = new SymbolInputView(this);
- mBinding.bottomSheet.textContainer.addView(
- symbolInput, 0, new ViewGroup.LayoutParams(-1, -2));
- mBinding.tabs.addOnTabSelectedListener(this);
-
- setupEditorBottomSheet();
- createQuickActions();
-
- mBuildEventListener.setActivity(this);
- mFileOptionsHandler = new FileOptionsHandler(this);
-
- startServices();
-
- KeyboardUtils.registerSoftInputChangedListener(this, __ -> onSoftInputChanged());
- registerLogReceiver();
- setupContainers();
- setupDiagnosticInfo();
-
- mUIDesignerLauncher =
- registerForActivityResult(
- new ActivityResultContracts.StartActivityForResult(),
- this::onGetUIDesignerResult);
-
- EditorActivityActions.register(this);
- }
-
- @Override
- protected View bindLayout() {
- mBinding = ActivityEditorBinding.inflate(getLayoutInflater());
- mDiagnosticInfoBinding = mBinding.diagnosticInfo;
- return mBinding.getRoot();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- dispatchOnPauseToEditors();
- EditorActivityActions.clear();
-
- if (mFileTreeFragment != null) {
- mFileTreeFragment.saveTreeState();
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- EditorActivityActions.register(this);
-
- try {
- checkForCompilerModule();
- dispatchOnResumeToEditors();
-
- if (mFileTreeFragment != null) {
- mFileTreeFragment.listProjectFiles();
- }
- } catch (Throwable th) {
- LOG.error("Failed to update files list", th);
- getApp().toast(R.string.msg_failed_list_files, Toaster.Type.ERROR);
- }
- }
-
- @SuppressWarnings("deprecation")
- private void checkForCompilerModule() {
- if (!Environment.isCompilerModuleInstalled()) {
- final var pd =
- ProgressDialog.show(
- this,
- getString(R.string.title_compiler_module_install),
- getString(R.string.msg_compiler_module_install),
- true,
- false);
-
- final CompletableFuture future =
- CompletableFuture.supplyAsync(
- () -> {
- final var tmpModule =
- new File(Environment.TMP_DIR, "compiler-module.zip");
- if (!ResourceUtils.copyFileFromAssets(
- ToolsManager.getCommonAsset("compiler-module.zip"),
- tmpModule.getAbsolutePath())) {
- throw new CompletionException(
- new RuntimeException(
- "Unable to copy compiler-module.zip"));
- }
-
- try {
- ZipUtils.unzipFile(tmpModule, Environment.COMPILER_MODULE);
- } catch (Throwable e) {
- throw new CompletionException(e);
- }
-
- if (!Environment.isCompilerModuleInstalled()) {
- throw new CompletionException(
- new RuntimeException("Unknown error"));
- }
-
- try {
- FileUtils.delete(tmpModule);
- } catch (Exception e) {
- // ignored
- }
-
- return true;
- });
-
- future.whenComplete(
- (result, error) -> {
- pd.dismiss();
-
- if (error != null) {
- showCompilerModuleInstallError(error);
- return;
- }
-
- getApp().toast(
- getString(R.string.msg_compiler_module_installed),
- Toaster.Type.SUCCESS);
- });
- }
- }
-
- private void dispatchOnResumeToEditors() {
- CompletableFuture.runAsync(
- () -> {
- for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
- final var editor = getEditorAtIndex(i);
- if (editor != null) {
- editor.onResume();
- }
- }
- });
- }
-
- private void showCompilerModuleInstallError(Throwable error) {
- final var stacktrace = ThrowableUtils.getFullStackTrace(error);
- final var builder = DialogUtils.newMaterialDialogBuilder(this);
- builder.setTitle(R.string.title_installation_failed);
- builder.setMessage(getString(R.string.msg_compiler_module_install_failed, stacktrace));
- builder.setCancelable(false);
- builder.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
- builder.setNegativeButton(
- R.string.copy,
- (dialog, which) -> {
- ClipboardUtils.copyText(stacktrace);
- dialog.dismiss();
- });
- builder.show();
- }
-
- private void dispatchOnPauseToEditors() {
- CompletableFuture.runAsync(
- () -> {
- for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
- final var editor = getEditorAtIndex(i);
- if (editor != null) {
- editor.onPause();
- }
- }
- });
- }
-
- @Override
- protected void onDestroy() {
- closeProject(false);
- try {
- unregisterReceiver(mLogReceiver);
- } catch (Throwable th) {
- LOG.error("Failed to unregister LogReceiver", th);
- }
- unbindService(mGradleServiceConnection);
- super.onDestroy();
- }
-
- private void setupDrawerToggle() {
- ActionBarDrawerToggle toggle =
- new ActionBarDrawerToggle(
- this,
- mBinding.editorDrawerLayout,
- mBinding.editorToolbar,
- R.string.app_name,
- R.string.app_name);
- mBinding.editorDrawerLayout.addDrawerListener(toggle);
- mBinding.startNav.setNavigationItemSelectedListener(this);
- toggle.syncState();
- }
-
- private void setupEditorBottomSheet() {
- bottomSheetTabAdapter = new EditorBottomSheetTabAdapter(this);
- mBinding.bottomSheet.pager.setAdapter(bottomSheetTabAdapter);
-
- final var mediator =
- new TabLayoutMediator(
- mBinding.bottomSheet.tabs,
- mBinding.bottomSheet.pager,
- true,
- true,
- (tab, position) -> tab.setText(bottomSheetTabAdapter.getTitle(position)));
-
- mediator.attach();
- mBinding.bottomSheet.pager.setUserInputEnabled(false);
- mBinding.bottomSheet.pager.setOffscreenPageLimit(
- bottomSheetTabAdapter.getItemCount() - 1); // DO not remove any views
-
- mEditorBottomSheet =
- (EditorBottomSheetBehavior extends View>)
- EditorBottomSheetBehavior.from(mBinding.bottomSheet.getRoot());
- mEditorBottomSheet.setBinding(mBinding.bottomSheet);
- mEditorBottomSheet.addBottomSheetCallback(
- new BottomSheetBehavior.BottomSheetCallback() {
- @Override
- public void onStateChanged(@NonNull View bottomSheet, int newState) {
- mBinding.bottomSheet.textContainer.setVisibility(
- newState == BottomSheetBehavior.STATE_EXPANDED
- ? View.INVISIBLE
- : View.VISIBLE);
-
- if (newState == BottomSheetBehavior.STATE_EXPANDED) {
- final var editor = getCurrentEditor();
- if (editor != null && editor.getEditor() != null) {
- editor.getEditor().ensureWindowsDismissed();
- }
- }
- }
-
- @Override
- public void onSlide(@NonNull View bottomSheet, float slideOffset) {
- mBinding.bottomSheet.textContainer.setAlpha(1f - slideOffset);
- }
- });
-
- mBinding.bottomSheet.tabs.addOnTabSelectedListener(
- new TabLayout.OnTabSelectedListener() {
-
- @Override
- public void onTabSelected(TabLayout.Tab tab) {
- final var fragment =
- bottomSheetTabAdapter.getFragmentAtIndex(tab.getPosition());
- if (fragment instanceof NonEditableEditorFragment) {
- mBinding.bottomSheet.clearFab.show();
- mBinding.bottomSheet.shareOutputFab.show();
- } else {
- mBinding.bottomSheet.clearFab.hide();
- mBinding.bottomSheet.shareOutputFab.hide();
- }
- }
-
- @Override
- public void onTabUnselected(TabLayout.Tab tab) {}
-
- @Override
- public void onTabReselected(TabLayout.Tab tab) {}
- });
-
- TooltipCompat.setTooltipText(
- mBinding.bottomSheet.clearFab, getString(R.string.title_clear_output));
- mBinding.bottomSheet.clearFab.setOnClickListener(
- v -> {
- final var fragment =
- bottomSheetTabAdapter.getFragmentAtIndex(
- mBinding.bottomSheet.tabs.getSelectedTabPosition());
- if (fragment instanceof NonEditableEditorFragment) {
- final var editor = (NonEditableEditorFragment) fragment;
- if (editor.getEditor() != null) {
- editor.getEditor().setText("");
- }
- }
- });
-
- mBinding.bottomSheet.shareOutputFab.setOnClickListener(
- v -> {
- final var fragment =
- bottomSheetTabAdapter.getFragmentAtIndex(
- mBinding.bottomSheet.tabs.getSelectedTabPosition());
- if (fragment instanceof NonEditableEditorFragment) {
- final var editor = (NonEditableEditorFragment) fragment;
- if (editor.getEditor() != null) {
- final var text = editor.getEditor().getText();
- final var type =
- editor instanceof SimpleOutputFragment
- ? "build_output"
- : editor instanceof IDELogFragment
- ? "ide_logs"
- : editor instanceof LogViewFragment
- ? "app_logs"
- : "unknown_type";
- shareText(text, type);
- }
- }
- });
-
- if (!getApp().getPrefManager().getBoolean(KEY_BOTTOM_SHEET_SHOWN)
- && mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_EXPANDED) {
- mEditorBottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED);
- new Handler(Looper.getMainLooper())
- .postDelayed(
- () -> {
- mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED);
- getApp().getPrefManager().putBoolean(KEY_BOTTOM_SHEET_SHOWN, true);
- },
- 1500);
- }
- }
-
- @SuppressWarnings("deprecation")
- private void shareText(CharSequence text, String type) {
- final var pd =
- ProgressDialog.show(this, null, getString(R.string.please_wait), true, false);
- CompletableFuture.supplyAsync(() -> writeTempFile(text, type))
- .whenComplete(
- (result, error) -> {
- ThreadUtils.runOnUiThread(pd::dismiss);
- if (result == null || error != null) {
- LOG.warn("Unable to share output", error);
- return;
- }
-
- ThreadUtils.runOnUiThread(
- () -> {
- shareFile(result);
- });
- });
- }
-
- private void shareFile(File file) {
- final var uri =
- FileProvider.getUriForFile(
- this, BuildConfig.APPLICATION_ID + ".utilcode.fileprovider", file);
- final var intent = new Intent(Intent.ACTION_SEND);
- intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.setType("text/plain");
- intent.putExtra(Intent.EXTRA_STREAM, uri);
-
- startActivity(Intent.createChooser(intent, null));
- }
-
- @NonNull
- private File writeTempFile(CharSequence text, String type) {
- final var in = new CharSequenceInputStream(text, StandardCharsets.UTF_8);
- final var file = new File(getFilesDir(), type + ".txt"); // use a common name to avoid multiple files
- if (!FileIOUtils.writeFileFromIS(file, in)) {
- LOG.error("Unable to write output of type", type, "to temporary file", file);
- throw new RuntimeException(new IOException("Cannot write output to temp file"));
- }
-
- return file;
- }
-
- private void notifyFilesUnsaved(List unsavedEditors, Runnable invokeAfter) {
- //noinspection ConstantConditions
- final var mapped =
- unsavedEditors.stream()
- .map(CodeEditorView::getFile)
- .filter(Objects::nonNull)
- .map(File::getAbsolutePath)
- .collect(Collectors.toList());
- final var builder =
- DialogUtils.newYesNoDialog(
- this,
- getString(R.string.title_files_unsaved), // title
- getString(
- R.string.msg_files_unsaved,
- TextUtils.join("\n", mapped)), // message
- (dialog, which) -> { // 'yes' click
- dialog.dismiss();
- saveAll(true);
- invokeAfter.run();
- },
- (dialog, which) -> { // 'no' click
- dialog.dismiss();
- // Mark all the files as saved, then try to close them all
- for (var editor : unsavedEditors) {
- editor.markAsSaved();
- }
-
- invokeAfter.run();
- });
- builder.show();
- }
-
- @Contract(pure = true)
- private void onGetUIDesignerResult(@NonNull ActivityResult result) {
- final var index = mBinding.editorContainer.getDisplayedChild();
- final var editor = getEditorAtIndex(index);
- if (editor != null && result.getResultCode() == RESULT_OK) {
- final var data = result.getData();
- if (data != null && data.hasExtra(DesignerActivity.KEY_GENERATED_CODE)) {
- final var code = data.getStringExtra(DesignerActivity.KEY_GENERATED_CODE);
- editor.getEditor().setText(code);
- saveAll();
+ }
+
+ public void install(@NonNull File apk) {
+ ThreadUtils.runOnUiThread(
+ () -> {
+ LOG.debug("Installing APK:", apk);
+ if (apk.exists()) {
+ Intent i = IntentUtils.getInstallAppIntent(apk);
+ if (i != null) {
+ startActivity(i);
} else {
- final var msg = getString(R.string.msg_invalid_designer_result);
- getApp().toast(msg, Toaster.Type.ERROR);
- LOG.error(msg, "Data returned by UI Designer is null or is invalid.");
+ getApp().toast(R.string.msg_apk_install_intent_failed, Toaster.Type.ERROR);
}
- } else {
- LOG.error(
- "UI Designer returned an invalid result code.",
- "Result code: " + result.getResultCode());
- }
+ } else {
+ LOG.error("APK file does not exist!");
+ }
+ });
+ }
+
+ public void assembleRelease() {
+ execTasks(null, "assembleRelease");
+ }
+
+ public void build() {
+ execTasks(null, "build");
+ }
+
+ public void clean() {
+ execTasks(null, "clean");
+ }
+
+ public void bundle() {
+ execTasks(null, "bundle");
+ }
+
+ public void lint() {
+ execTasks(null, "lint");
+ }
+
+ public void lintDebug() {
+ execTasks(null, "lintDebug");
+ }
+
+ public void lintRelease() {
+ execTasks(null, "lintRelease");
+ }
+
+ public void cleanAndRebuild() {
+ execTasks(null, "clean", "build");
+ }
+
+ /////////////////////////////////////////////////
+ ////////////// PRIVATE APIS /////////////////////
+ /////////////////////////////////////////////////
+
+ public AlertDialog getFindInProjectDialog() {
+ return mFindInProjectDialog == null
+ ? createFindInProjectDialog()
+ : mFindInProjectDialog;
+ }
+
+ @Override
+ public void onOptionsClick(SheetOption option) {
+ if (mFileOptionsHandler != null) {
+ mFileOptionsHandler.onOptionsClick(option);
}
+ }
- private void openTerminal() {
- final Intent intent = new Intent(this, TerminalActivity.class);
- intent.putExtra(
- TerminalActivity.KEY_WORKING_DIRECTORY,
- Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDirPath()));
- startActivity(intent);
+ public GradleBuildService getBuildService() {
+ return mBuildService;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setSupportActionBar(mBinding.editorToolbar);
+
+ mViewModel = new ViewModelProvider(this).get(EditorViewModel.class);
+ getProjectFromIntent();
+
+ mFileTreeFragment = FileTreeFragment.newInstance();
+ mDaemonStatusFragment = new TextSheetFragment().setTextSelectable(true);
+
+ setupDrawerToggle();
+ loadFragment(mFileTreeFragment);
+
+ symbolInput = new SymbolInputView(this);
+ mBinding.bottomSheet.textContainer.addView(symbolInput, 0, new ViewGroup.LayoutParams(-1, -2));
+ mBinding.tabs.addOnTabSelectedListener(this);
+
+ setupEditorBottomSheet();
+ createQuickActions();
+
+ mBuildEventListener.setActivity(this);
+ mFileOptionsHandler = new FileOptionsHandler(this);
+
+ startServices();
+
+ KeyboardUtils.registerSoftInputChangedListener(this, __ -> onSoftInputChanged());
+ registerLogReceiver();
+ setupContainers();
+ setupDiagnosticInfo();
+
+ mUIDesignerLauncher =
+ registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(), this::onGetUIDesignerResult);
+
+ EditorActivityActions.register(this);
+ }
+
+ @Override
+ protected View bindLayout() {
+ mBinding = ActivityEditorBinding.inflate(getLayoutInflater());
+ mDiagnosticInfoBinding = mBinding.diagnosticInfo;
+ return mBinding.getRoot();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ dispatchOnPauseToEditors();
+ EditorActivityActions.clear();
+
+ if (mFileTreeFragment != null) {
+ mFileTreeFragment.saveTreeState();
}
+ }
- private void getProjectFromIntent() {
- final var project = getIntent().getStringExtra(EXTRA_PROJECT_PATH);
- ProjectManager.INSTANCE.setProjectPath(project);
+ @Override
+ protected void onResume() {
+ super.onResume();
- getApp().getPrefManager()
- .setOpenedProject(
- Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDirPath()));
+ EditorActivityActions.register(this);
- try {
- //noinspection ConstantConditions
- getSupportActionBar()
- .setSubtitle(new File(ProjectManager.INSTANCE.getProjectDirPath()).getName());
- } catch (Throwable th) {
- // ignored
- }
+ try {
+ checkForCompilerModule();
+ dispatchOnResumeToEditors();
- CompletableFuture.runAsync(
- () ->
- ValuesTableFactory.setupWithResDirectories(
- getResourceDirectories().stream()
- .map(File::new)
- .toArray(File[]::new)));
+ if (mFileTreeFragment != null) {
+ mFileTreeFragment.listProjectFiles();
+ }
+ } catch (Throwable th) {
+ LOG.error("Failed to update files list", th);
+ getApp().toast(R.string.msg_failed_list_files, Toaster.Type.ERROR);
}
+ }
- private void setupDiagnosticInfo() {
- GradientDrawable gd = new GradientDrawable();
- gd.setShape(GradientDrawable.RECTANGLE);
- gd.setColor(0xff212121);
- gd.setStroke(1, 0xffffffff);
- gd.setCornerRadius(8);
- mDiagnosticInfoBinding.getRoot().setBackground(gd);
- mDiagnosticInfoBinding.getRoot().setVisibility(View.GONE);
- }
+ @SuppressWarnings("deprecation")
+ private void checkForCompilerModule() {
+ if (!Environment.isCompilerModuleInstalled()) {
+ final var pd =
+ ProgressDialog.show(
+ this,
+ getString(R.string.title_compiler_module_install),
+ getString(R.string.msg_compiler_module_install),
+ true,
+ false);
- private void setupContainers() {
- handleDiagnosticsResultVisibility(true);
- handleSearchResultVisibility(true);
- }
+ final CompletableFuture future =
+ CompletableFuture.supplyAsync(
+ () -> {
+ final var tmpModule = new File(Environment.TMP_DIR, "compiler-module.zip");
+ if (!ResourceUtils.copyFileFromAssets(
+ ToolsManager.getCommonAsset("compiler-module.zip"),
+ tmpModule.getAbsolutePath())) {
+ throw new CompletionException(
+ new RuntimeException("Unable to copy compiler-module.zip"));
+ }
- private void startServices() {
+ try {
+ ZipUtils.unzipFile(tmpModule, Environment.COMPILER_MODULE);
+ } catch (Throwable e) {
+ throw new CompletionException(e);
+ }
- if (bindService(
- new Intent(this, GradleBuildService.class),
- mGradleServiceConnection,
- Context.BIND_AUTO_CREATE)) {
- LOG.info("Bind request for Gradle build service was successful...");
- } else {
- LOG.error("Gradle build service doesn't exist or the IDE is not allowed to access it.");
- }
+ if (!Environment.isCompilerModuleInstalled()) {
+ throw new CompletionException(new RuntimeException("Unknown error"));
+ }
- if (!IDELanguageClientImpl.isInitialized()) {
- IDELanguageClientImpl.initialize(this);
- }
+ try {
+ FileUtils.delete(tmpModule);
+ } catch (Exception e) {
+ // ignored
+ }
- initializeLanguageServers();
+ return true;
+ });
- // Actually, we don't need to start FileOptionsHandler
- // Because it would work anyway
- // But still we do...
- mFileOptionsHandler.start();
- }
+ future.whenComplete(
+ (result, error) -> {
+ pd.dismiss();
- private void initializeLanguageServers() {
- final var client = IDELanguageClientImpl.getInstance();
- final var javaLanguageServer = getApp().getJavaLanguageServer();
- final var workspaceRoots = new HashSet();
- workspaceRoots.add(
- new File(Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDirPath()))
- .toPath());
- workspaceRoots.add(Environment.HOME.toPath().resolve("logsender"));
-
- final var params = new InitializeParams(workspaceRoots);
-
- javaLanguageServer.connectClient(client);
- javaLanguageServer.applySettings(JavaServerSettings.getInstance());
- javaLanguageServer.initialize(params);
- }
-
- private void stopServices() {
-
- if (IDELanguageClientImpl.isInitialized()) {
- IDELanguageClientImpl.shutdown();
- }
-
- shutdownLanguageServers();
- if (mFileOptionsHandler != null) {
- mFileOptionsHandler.stop();
- }
- }
-
- private void shutdownLanguageServers() {
- final var javaServer = getApp().getJavaLanguageServer();
- javaServer.shutdown();
- }
-
- private void onSoftInputChanged() {
- invalidateOptionsMenu();
- if (KeyboardUtils.isSoftInputVisible(this)) {
- TransitionManager.beginDelayedTransition(mBinding.getRoot(), new Slide(Gravity.TOP));
- symbolInput.setVisibility(View.VISIBLE);
- mBinding.bottomSheet.statusText.setVisibility(View.GONE);
- mBinding.bottomSheet.swipeHint.setVisibility(View.GONE);
- } else {
- TransitionManager.beginDelayedTransition(mBinding.getRoot(), new Slide(Gravity.BOTTOM));
- symbolInput.setVisibility(View.GONE);
- mBinding.bottomSheet.statusText.setVisibility(View.VISIBLE);
- mBinding.bottomSheet.swipeHint.setVisibility(View.VISIBLE);
- }
- }
-
- private void closeProject(boolean manualFinish) {
- stopServices();
-
- // Make sure we close files
- // This fill further make sure that file contents are not erased.
- closeAll(
- () -> {
- getApp().getPrefManager().setOpenedProject(PreferenceManager.NO_OPENED_PROJECT);
-
- if (manualFinish) {
- finish();
- }
- });
- }
-
- private void confirmProjectClose() {
- final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
- builder.setTitle(R.string.title_confirm_project_close);
- builder.setMessage(R.string.msg_confirm_project_close);
- builder.setNegativeButton(R.string.no, null);
- builder.setPositiveButton(
- R.string.yes,
- (d, w) -> {
- d.dismiss();
- closeProject(true);
- });
- builder.show();
- }
-
- @Nullable
- private AlertDialog createFindInProjectDialog() {
- final var rootProject = ProjectManager.INSTANCE.getRootProject();
- if (rootProject == null) {
- LOG.warn("No root project model found. Is the project initialized?");
- getApp().toast(getString(R.string.msg_project_not_initialized), Toaster.Type.ERROR);
- return null;
- }
-
- final var moduleDirs =
- rootProject.getModules().stream()
- .map(IdeGradleProject::getProjectDir)
- .collect(Collectors.toList());
-
- final List srcDirs = new ArrayList<>();
- final LayoutSearchProjectBinding binding =
- LayoutSearchProjectBinding.inflate(getLayoutInflater());
- binding.modulesContainer.removeAllViews();
- for (int i = 0; i < moduleDirs.size(); i++) {
- final File module = moduleDirs.get(i);
- final File src = new File(module, "src");
- if (!module.exists() || !module.isDirectory() || !src.exists() || !src.isDirectory()) {
- continue;
+ if (error != null) {
+ showCompilerModuleInstallError(error);
+ return;
}
- CheckBox check = new CheckBox(this);
- check.setText(module.getName());
- check.setChecked(true);
+ getApp().toast(getString(R.string.msg_compiler_module_installed), Toaster.Type.SUCCESS);
+ });
+ }
+ }
- LinearLayout.MarginLayoutParams params = new LinearLayout.MarginLayoutParams(-2, -2);
- params.bottomMargin = SizeUtils.dp2px(4);
- binding.modulesContainer.addView(check, params);
+ private void dispatchOnResumeToEditors() {
+ CompletableFuture.runAsync(
+ () -> {
+ for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
+ final var editor = getEditorAtIndex(i);
+ if (editor != null) {
+ editor.onResume();
+ }
+ }
+ });
+ }
- srcDirs.add(src);
- }
- final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
- builder.setTitle(R.string.menu_find_project);
- builder.setView(binding.getRoot());
- builder.setCancelable(false);
- builder.setPositiveButton(
- R.string.menu_find,
- (dialog, which) -> {
- final String text =
- Objects.requireNonNull(binding.input.getEditText())
- .getText()
- .toString()
- .trim();
- if (text.isEmpty()) {
- getApp().toast(R.string.msg_empty_search_query, Toaster.Type.ERROR);
- return;
- }
+ private void showCompilerModuleInstallError(Throwable error) {
+ final var stacktrace = ThrowableUtils.getFullStackTrace(error);
+ final var builder = DialogUtils.newMaterialDialogBuilder(this);
+ builder.setTitle(R.string.title_installation_failed);
+ builder.setMessage(getString(R.string.msg_compiler_module_install_failed, stacktrace));
+ builder.setCancelable(false);
+ builder.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
+ builder.setNegativeButton(
+ R.string.copy,
+ (dialog, which) -> {
+ ClipboardUtils.copyText(stacktrace);
+ dialog.dismiss();
+ });
+ builder.show();
+ }
- final List searchDirs = new ArrayList<>();
- for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
- CheckBox check = (CheckBox) binding.modulesContainer.getChildAt(i);
- if (check.isChecked()) {
- searchDirs.add(srcDirs.get(i));
- }
- }
+ private void dispatchOnPauseToEditors() {
+ CompletableFuture.runAsync(
+ () -> {
+ for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
+ final var editor = getEditorAtIndex(i);
+ if (editor != null) {
+ editor.onPause();
+ }
+ }
+ });
+ }
- final String extensions =
- Objects.requireNonNull(binding.filter.getEditText())
- .getText()
- .toString()
- .trim();
- final List extensionList = new ArrayList<>();
- if (!extensions.isEmpty()) {
- if (extensions.contains("|")) {
- for (String str : extensions.split(Pattern.quote("|"))) {
- if (str == null || str.trim().isEmpty()) {
- continue;
- }
+ @Override
+ protected void onDestroy() {
+ closeProject(false);
+ try {
+ unregisterReceiver(mLogReceiver);
+ } catch (Throwable th) {
+ LOG.error("Failed to unregister LogReceiver", th);
+ }
+ unbindService(mGradleServiceConnection);
+ super.onDestroy();
+ }
- extensionList.add(str);
- }
- } else {
- extensionList.add(extensions);
- }
- }
+ private void setupDrawerToggle() {
+ ActionBarDrawerToggle toggle =
+ new ActionBarDrawerToggle(
+ this,
+ mBinding.editorDrawerLayout,
+ mBinding.editorToolbar,
+ R.string.app_name,
+ R.string.app_name);
+ mBinding.editorDrawerLayout.addDrawerListener(toggle);
+ mBinding.startNav.setNavigationItemSelectedListener(this);
+ toggle.syncState();
+ }
- if (searchDirs.isEmpty()) {
- getApp().toast(R.string.msg_select_search_modules, Toaster.Type.ERROR);
- } else {
- dialog.dismiss();
- getProgressSheet(R.string.msg_searching_project)
- .show(getSupportFragmentManager(), "search_in_project_progress");
- RecursiveFileSearcher.searchRecursiveAsync(
- text, extensionList, searchDirs, this::handleSearchResults);
- }
- });
- builder.setNegativeButton(android.R.string.cancel, (__, ___) -> __.dismiss());
- mFindInProjectDialog = builder.create();
- return mFindInProjectDialog;
+ private void setupEditorBottomSheet() {
+ bottomSheetTabAdapter = new EditorBottomSheetTabAdapter(this);
+ mBinding.bottomSheet.pager.setAdapter(bottomSheetTabAdapter);
+
+ final var mediator =
+ new TabLayoutMediator(
+ mBinding.bottomSheet.tabs,
+ mBinding.bottomSheet.pager,
+ true,
+ true,
+ (tab, position) -> tab.setText(bottomSheetTabAdapter.getTitle(position)));
+
+ mediator.attach();
+ mBinding.bottomSheet.pager.setUserInputEnabled(false);
+ mBinding.bottomSheet.pager.setOffscreenPageLimit(
+ bottomSheetTabAdapter.getItemCount() - 1); // DO not remove any views
+
+ mEditorBottomSheet =
+ (EditorBottomSheetBehavior extends View>)
+ EditorBottomSheetBehavior.from(mBinding.bottomSheet.getRoot());
+ mEditorBottomSheet.setBinding(mBinding.bottomSheet);
+ mEditorBottomSheet.addBottomSheetCallback(
+ new BottomSheetBehavior.BottomSheetCallback() {
+ @Override
+ public void onStateChanged(@NonNull View bottomSheet, int newState) {
+ mBinding.bottomSheet.textContainer.setVisibility(
+ newState == BottomSheetBehavior.STATE_EXPANDED ? View.INVISIBLE : View.VISIBLE);
+
+ if (newState == BottomSheetBehavior.STATE_EXPANDED) {
+ final var editor = getCurrentEditor();
+ if (editor != null && editor.getEditor() != null) {
+ editor.getEditor().ensureWindowsDismissed();
+ }
+ }
+ }
+
+ @Override
+ public void onSlide(@NonNull View bottomSheet, float slideOffset) {
+ mBinding.bottomSheet.textContainer.setAlpha(1f - slideOffset);
+ }
+ });
+
+ mBinding.bottomSheet.tabs.addOnTabSelectedListener(
+ new TabLayout.OnTabSelectedListener() {
+
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ final var fragment = bottomSheetTabAdapter.getFragmentAtIndex(tab.getPosition());
+ if (fragment instanceof NonEditableEditorFragment) {
+ mBinding.bottomSheet.clearFab.show();
+ mBinding.bottomSheet.shareOutputFab.show();
+ } else {
+ mBinding.bottomSheet.clearFab.hide();
+ mBinding.bottomSheet.shareOutputFab.hide();
+ }
+ }
+
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {}
+
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {}
+ });
+
+ TooltipCompat.setTooltipText(
+ mBinding.bottomSheet.clearFab, getString(R.string.title_clear_output));
+ mBinding.bottomSheet.clearFab.setOnClickListener(
+ v -> {
+ final var fragment =
+ bottomSheetTabAdapter.getFragmentAtIndex(
+ mBinding.bottomSheet.tabs.getSelectedTabPosition());
+ if (fragment instanceof NonEditableEditorFragment) {
+ final var editor = (NonEditableEditorFragment) fragment;
+ if (editor.getEditor() != null) {
+ editor.getEditor().setText("");
+ }
+ }
+ });
+
+ mBinding.bottomSheet.shareOutputFab.setOnClickListener(
+ v -> {
+ final var fragment =
+ bottomSheetTabAdapter.getFragmentAtIndex(
+ mBinding.bottomSheet.tabs.getSelectedTabPosition());
+ if (fragment instanceof NonEditableEditorFragment) {
+ final var editor = (NonEditableEditorFragment) fragment;
+ if (editor.getEditor() != null) {
+ final var text = editor.getEditor().getText();
+ final var type =
+ editor instanceof SimpleOutputFragment
+ ? "build_output"
+ : editor instanceof IDELogFragment
+ ? "ide_logs"
+ : editor instanceof LogViewFragment ? "app_logs" : "unknown_type";
+ shareText(text, type);
+ }
+ }
+ });
+
+ if (!getApp().getPrefManager().getBoolean(KEY_BOTTOM_SHEET_SHOWN)
+ && mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_EXPANDED) {
+ mEditorBottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED);
+ new Handler(Looper.getMainLooper())
+ .postDelayed(
+ () -> {
+ mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED);
+ getApp().getPrefManager().putBoolean(KEY_BOTTOM_SHEET_SHOWN, true);
+ },
+ 1500);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private void shareText(CharSequence text, String type) {
+ final var pd = ProgressDialog.show(this, null, getString(R.string.please_wait), true, false);
+ CompletableFuture.supplyAsync(() -> writeTempFile(text, type))
+ .whenComplete(
+ (result, error) -> {
+ ThreadUtils.runOnUiThread(pd::dismiss);
+ if (result == null || error != null) {
+ LOG.warn("Unable to share output", error);
+ return;
+ }
+
+ ThreadUtils.runOnUiThread(
+ () -> {
+ shareFile(result);
+ });
+ });
+ }
+
+ private void shareFile(File file) {
+ final var uri =
+ FileProvider.getUriForFile(
+ this, BuildConfig.APPLICATION_ID + ".utilcode.fileprovider", file);
+ final var intent = new Intent(Intent.ACTION_SEND);
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_STREAM, uri);
+
+ startActivity(Intent.createChooser(intent, null));
+ }
+
+ @NonNull
+ private File writeTempFile(CharSequence text, String type) {
+ final var in = new CharSequenceInputStream(text, StandardCharsets.UTF_8);
+ final var file =
+ new File(getFilesDir(), type + ".txt"); // use a common name to avoid multiple files
+ if (!FileIOUtils.writeFileFromIS(file, in)) {
+ LOG.error("Unable to write output of type", type, "to temporary file", file);
+ throw new RuntimeException(new IOException("Cannot write output to temp file"));
}
- private void registerLogReceiver() {
- IntentFilter filter = new IntentFilter();
- filter.addAction(LogReceiver.APPEND_LOG);
- registerReceiver(mLogReceiver, filter);
+ return file;
+ }
+
+ private void notifyFilesUnsaved(List unsavedEditors, Runnable invokeAfter) {
+ //noinspection ConstantConditions
+ final var mapped =
+ unsavedEditors.stream()
+ .map(CodeEditorView::getFile)
+ .filter(Objects::nonNull)
+ .map(File::getAbsolutePath)
+ .collect(Collectors.toList());
+ final var builder =
+ DialogUtils.newYesNoDialog(
+ this,
+ getString(R.string.title_files_unsaved), // title
+ getString(R.string.msg_files_unsaved, TextUtils.join("\n", mapped)), // message
+ (dialog, which) -> { // 'yes' click
+ dialog.dismiss();
+ saveAll(true);
+ invokeAfter.run();
+ },
+ (dialog, which) -> { // 'no' click
+ dialog.dismiss();
+ // Mark all the files as saved, then try to close them all
+ for (var editor : unsavedEditors) {
+ editor.markAsSaved();
+ }
+
+ invokeAfter.run();
+ });
+ builder.show();
+ }
+
+ @Contract(pure = true)
+ private void onGetUIDesignerResult(@NonNull ActivityResult result) {
+ final var index = mBinding.editorContainer.getDisplayedChild();
+ final var editor = getEditorAtIndex(index);
+ if (editor != null && result.getResultCode() == RESULT_OK) {
+ final var data = result.getData();
+ if (data != null && data.hasExtra(DesignerActivity.KEY_GENERATED_CODE)) {
+ final var code = data.getStringExtra(DesignerActivity.KEY_GENERATED_CODE);
+ editor.getEditor().setText(code);
+ saveAll();
+ } else {
+ final var msg = getString(R.string.msg_invalid_designer_result);
+ getApp().toast(msg, Toaster.Type.ERROR);
+ LOG.error(msg, "Data returned by UI Designer is null or is invalid.");
+ }
+ } else {
+ LOG.error(
+ "UI Designer returned an invalid result code.", "Result code: " + result.getResultCode());
+ }
+ }
+
+ private void openTerminal() {
+ final Intent intent = new Intent(this, TerminalActivity.class);
+ intent.putExtra(
+ TerminalActivity.KEY_WORKING_DIRECTORY,
+ Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDirPath()));
+ startActivity(intent);
+ }
+
+ private void getProjectFromIntent() {
+ final var project = getIntent().getStringExtra(EXTRA_PROJECT_PATH);
+ ProjectManager.INSTANCE.setProjectPath(project);
+
+ getApp()
+ .getPrefManager()
+ .setOpenedProject(Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDirPath()));
+
+ try {
+ //noinspection ConstantConditions
+ getSupportActionBar()
+ .setSubtitle(new File(ProjectManager.INSTANCE.getProjectDirPath()).getName());
+ } catch (Throwable th) {
+ // ignored
}
- private void showNeedHelpDialog() {
- MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
- builder.setTitle(R.string.need_help);
- builder.setMessage(R.string.msg_need_help);
- builder.setPositiveButton(android.R.string.ok, null);
- builder.create().show();
+ getResourceDirs()
+ .thenAccept(
+ dirs -> {
+ dirs.removeIf(Objects::isNull);
+ ValuesTableFactory.setupWithResDirectories(dirs.toArray(new File[0]));
+ });
+ }
+
+ private void setupDiagnosticInfo() {
+ GradientDrawable gd = new GradientDrawable();
+ gd.setShape(GradientDrawable.RECTANGLE);
+ gd.setColor(0xff212121);
+ gd.setStroke(1, 0xffffffff);
+ gd.setCornerRadius(8);
+ mDiagnosticInfoBinding.getRoot().setBackground(gd);
+ mDiagnosticInfoBinding.getRoot().setVisibility(View.GONE);
+ }
+
+ private void setupContainers() {
+ handleDiagnosticsResultVisibility(true);
+ handleSearchResultVisibility(true);
+ }
+
+ private void startServices() {
+
+ if (bindService(
+ new Intent(this, GradleBuildService.class),
+ mGradleServiceConnection,
+ Context.BIND_AUTO_CREATE)) {
+ LOG.info("Bind request for Gradle build service was successful...");
+ } else {
+ LOG.error("Gradle build service doesn't exist or the IDE is not allowed to access it.");
}
- private void createQuickActions() {
- ActionItem closeThis =
- new ActionItem(
- ACTION_ID_CLOSE,
- getString(R.string.action_closeThis),
- R.drawable.ic_close_this);
- ActionItem closeOthers =
- new ActionItem(
- ACTION_ID_OTHERS,
- getString(R.string.action_closeOthers),
- R.drawable.ic_close_others);
- ActionItem closeAll =
- new ActionItem(
- ACTION_ID_ALL,
- getString(R.string.action_closeAll),
- R.drawable.ic_close_all);
- mTabCloseAction = new QuickAction(this, QuickAction.HORIZONTAL);
- mTabCloseAction.addActionItem(closeThis, closeOthers, closeAll);
- mTabCloseAction.setColorRes(R.color.tabAction_background);
- mTabCloseAction.setTextColorRes(R.color.tabAction_text);
-
- mTabCloseAction.setOnActionItemClickListener(
- (item) -> {
- final int id = item.getActionId();
- if (getApp().getPrefManager()
- .getBoolean(PreferenceManager.KEY_EDITOR_AUTO_SAVE, false)) {
- saveAll();
- }
-
- if (id == ACTION_ID_CLOSE) {
- closeFile(mBinding.tabs.getSelectedTabPosition());
- }
-
- if (id == ACTION_ID_OTHERS) {
- closeOthers();
- }
-
- if (id == ACTION_ID_ALL) {
- closeAll();
- }
- });
+ if (!IDELanguageClientImpl.isInitialized()) {
+ IDELanguageClientImpl.initialize(this);
}
+
+ initializeLanguageServers();
+
+ // Actually, we don't need to start FileOptionsHandler
+ // Because it would work anyway
+ // But still we do...
+ mFileOptionsHandler.start();
+ }
+
+ private void initializeLanguageServers() {
+ final var client = IDELanguageClientImpl.getInstance();
+ final var javaLanguageServer = getApp().getJavaLanguageServer();
+ final var workspaceRoots = new HashSet();
+ workspaceRoots.add(
+ new File(Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDirPath())).toPath());
+ workspaceRoots.add(Environment.HOME.toPath().resolve("logsender"));
+
+ final var params = new InitializeParams(workspaceRoots);
+
+ javaLanguageServer.connectClient(client);
+ javaLanguageServer.applySettings(JavaServerSettings.getInstance());
+ javaLanguageServer.initialize(params);
+ }
+
+ private void stopServices() {
+
+ if (IDELanguageClientImpl.isInitialized()) {
+ IDELanguageClientImpl.shutdown();
+ }
+
+ shutdownLanguageServers();
+ if (mFileOptionsHandler != null) {
+ mFileOptionsHandler.stop();
+ }
+ }
+
+ private void shutdownLanguageServers() {
+ final var javaServer = getApp().getJavaLanguageServer();
+ javaServer.shutdown();
+ }
+
+ private void onSoftInputChanged() {
+ invalidateOptionsMenu();
+ if (KeyboardUtils.isSoftInputVisible(this)) {
+ TransitionManager.beginDelayedTransition(mBinding.getRoot(), new Slide(Gravity.TOP));
+ symbolInput.setVisibility(View.VISIBLE);
+ mBinding.bottomSheet.statusText.setVisibility(View.GONE);
+ mBinding.bottomSheet.swipeHint.setVisibility(View.GONE);
+ } else {
+ TransitionManager.beginDelayedTransition(mBinding.getRoot(), new Slide(Gravity.BOTTOM));
+ symbolInput.setVisibility(View.GONE);
+ mBinding.bottomSheet.statusText.setVisibility(View.VISIBLE);
+ mBinding.bottomSheet.swipeHint.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void closeProject(boolean manualFinish) {
+ stopServices();
+
+ // Make sure we close files
+ // This fill further make sure that file contents are not erased.
+ closeAll(
+ () -> {
+ getApp().getPrefManager().setOpenedProject(PreferenceManager.NO_OPENED_PROJECT);
+
+ if (manualFinish) {
+ finish();
+ }
+ });
+ }
+
+ private void confirmProjectClose() {
+ final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
+ builder.setTitle(R.string.title_confirm_project_close);
+ builder.setMessage(R.string.msg_confirm_project_close);
+ builder.setNegativeButton(R.string.no, null);
+ builder.setPositiveButton(
+ R.string.yes,
+ (d, w) -> {
+ d.dismiss();
+ closeProject(true);
+ });
+ builder.show();
+ }
+
+ @Nullable
+ private AlertDialog createFindInProjectDialog() {
+ final var rootProject = ProjectManager.INSTANCE.getRootProject();
+ if (rootProject == null) {
+ LOG.warn("No root project model found. Is the project initialized?");
+ getApp().toast(getString(R.string.msg_project_not_initialized), Toaster.Type.ERROR);
+ return null;
+ }
+
+ List moduleDirs;
+ try {
+ moduleDirs =
+ rootProject.listModules().get().stream()
+ .map(SimpleModuleData::getProjectDir)
+ .collect(Collectors.toList());
+ } catch (Throwable e) {
+ StudioApp.getInstance().toast(getString(R.string.msg_no_modules), Toaster.Type.ERROR);
+ moduleDirs = Collections.emptyList();
+ }
+
+ return createFindInProjectDialog (moduleDirs);
+ }
+
+ private AlertDialog createFindInProjectDialog (List moduleDirs) {
+ final List srcDirs = new ArrayList<>();
+ final LayoutSearchProjectBinding binding =
+ LayoutSearchProjectBinding.inflate(getLayoutInflater());
+ binding.modulesContainer.removeAllViews();
+ for (int i = 0; i < moduleDirs.size(); i++) {
+ final File module = moduleDirs.get(i);
+ final File src = new File(module, "src");
+ if (!module.exists() || !module.isDirectory() || !src.exists() || !src.isDirectory()) {
+ continue;
+ }
+
+ CheckBox check = new CheckBox(this);
+ check.setText(module.getName());
+ check.setChecked(true);
+
+ LinearLayout.MarginLayoutParams params = new LinearLayout.MarginLayoutParams(-2, -2);
+ params.bottomMargin = SizeUtils.dp2px(4);
+ binding.modulesContainer.addView(check, params);
+
+ srcDirs.add(src);
+ }
+
+ final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
+ builder.setTitle(R.string.menu_find_project);
+ builder.setView(binding.getRoot());
+ builder.setCancelable(false);
+ builder.setPositiveButton(
+ R.string.menu_find,
+ (dialog, which) -> {
+ final String text =
+ Objects.requireNonNull(binding.input.getEditText()).getText().toString().trim();
+ if (text.isEmpty()) {
+ getApp().toast(R.string.msg_empty_search_query, Toaster.Type.ERROR);
+ return;
+ }
+
+ final List searchDirs = new ArrayList<>();
+ for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
+ CheckBox check = (CheckBox) binding.modulesContainer.getChildAt(i);
+ if (check.isChecked()) {
+ searchDirs.add(srcDirs.get(i));
+ }
+ }
+
+ final String extensions =
+ Objects.requireNonNull(binding.filter.getEditText())
+ .getText()
+ .toString()
+ .trim();
+ final List extensionList = new ArrayList<>();
+ if (!extensions.isEmpty()) {
+ if (extensions.contains("|")) {
+ for (String str : extensions.split(Pattern.quote("|"))) {
+ if (str == null || str.trim().isEmpty()) {
+ continue;
+ }
+
+ extensionList.add(str);
+ }
+ } else {
+ extensionList.add(extensions);
+ }
+ }
+
+ if (searchDirs.isEmpty()) {
+ getApp().toast(R.string.msg_select_search_modules, Toaster.Type.ERROR);
+ } else {
+ dialog.dismiss();
+ getProgressSheet(R.string.msg_searching_project)
+ .show(getSupportFragmentManager(), "search_in_project_progress");
+ RecursiveFileSearcher.searchRecursiveAsync(
+ text, extensionList, searchDirs, this::handleSearchResults);
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, (__, ___) -> __.dismiss());
+ mFindInProjectDialog = builder.create();
+ return mFindInProjectDialog;
+ }
+
+ private void registerLogReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(LogReceiver.APPEND_LOG);
+ registerReceiver(mLogReceiver, filter);
+ }
+
+ private void showNeedHelpDialog() {
+ MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
+ builder.setTitle(R.string.need_help);
+ builder.setMessage(R.string.msg_need_help);
+ builder.setPositiveButton(android.R.string.ok, null);
+ builder.create().show();
+ }
+
+ private void createQuickActions() {
+ ActionItem closeThis =
+ new ActionItem(
+ ACTION_ID_CLOSE, getString(R.string.action_closeThis), R.drawable.ic_close_this);
+ ActionItem closeOthers =
+ new ActionItem(
+ ACTION_ID_OTHERS, getString(R.string.action_closeOthers), R.drawable.ic_close_others);
+ ActionItem closeAll =
+ new ActionItem(ACTION_ID_ALL, getString(R.string.action_closeAll), R.drawable.ic_close_all);
+ mTabCloseAction = new QuickAction(this, QuickAction.HORIZONTAL);
+ mTabCloseAction.addActionItem(closeThis, closeOthers, closeAll);
+ mTabCloseAction.setColorRes(R.color.tabAction_background);
+ mTabCloseAction.setTextColorRes(R.color.tabAction_text);
+
+ mTabCloseAction.setOnActionItemClickListener(
+ (item) -> {
+ final int id = item.getActionId();
+ if (getApp().getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_AUTO_SAVE, false)) {
+ saveAll();
+ }
+
+ if (id == ACTION_ID_CLOSE) {
+ closeFile(mBinding.tabs.getSelectedTabPosition());
+ }
+
+ if (id == ACTION_ID_OTHERS) {
+ closeOthers();
+ }
+
+ if (id == ACTION_ID_ALL) {
+ closeAll();
+ }
+ });
+ }
}
diff --git a/app/src/main/java/com/itsaky/androidide/projects/CachingProject.kt b/app/src/main/java/com/itsaky/androidide/projects/CachingProject.kt
new file mode 100644
index 000000000..de722c6d2
--- /dev/null
+++ b/app/src/main/java/com/itsaky/androidide/projects/CachingProject.kt
@@ -0,0 +1,117 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.projects
+
+import com.itsaky.androidide.tooling.api.IProject
+import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData
+import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
+import com.itsaky.androidide.tooling.api.model.IdeGradleProject
+import com.itsaky.androidide.tooling.api.model.IdeGradleTask
+import com.itsaky.androidide.utils.ILogger
+import java.io.File
+import java.util.concurrent.*
+
+/**
+ * A project which lazily caches some required properties of the given project.
+ *
+ * @author Akash Yadav
+ */
+class CachingProject(val project: IProject) : IProject {
+
+ private val log = ILogger.newInstance(javaClass.simpleName)
+
+ private val mName: String by lazy { this.project.name.get() }
+ private val mDescription: String by lazy { this.project.description.get() }
+ private val mProjectPath: String by lazy { this.project.projectPath.get() }
+ private val mProjectDir: File by lazy { this.project.projectDir.get() }
+ private val mBuildDir: File by lazy { this.project.buildDir.get() }
+ private val mBuildScript: File by lazy { this.project.buildScript.get() }
+
+ private var mFirstAppModule: IdeAndroidModule? = null
+ private val mModules: MutableList = mutableListOf()
+
+ override fun getName(): CompletableFuture {
+ return CompletableFuture.completedFuture(this.mName)
+ }
+
+ override fun getDescription(): CompletableFuture {
+ return CompletableFuture.completedFuture(mDescription)
+ }
+
+ override fun getProjectPath(): CompletableFuture {
+ return CompletableFuture.completedFuture(mProjectPath)
+ }
+
+ override fun getProjectDir(): CompletableFuture {
+ return CompletableFuture.completedFuture(mProjectDir)
+ }
+
+ override fun getBuildDir(): CompletableFuture {
+ return CompletableFuture.completedFuture(mBuildDir)
+ }
+
+ override fun getBuildScript(): CompletableFuture {
+ return CompletableFuture.completedFuture(mBuildScript)
+ }
+
+ override fun getTasks(): CompletableFuture> {
+ return this.project.tasks
+ }
+
+ override fun getModules(): CompletableFuture> {
+ return this.project.modules
+ }
+
+ override fun listModules(): CompletableFuture> {
+ return if (this.mModules.isNotEmpty()) {
+ CompletableFuture.completedFuture(mModules)
+ } else {
+ this.project.listModules().whenComplete { modules, err ->
+ if (err != null || modules == null || modules.isEmpty()) {
+ log.warn("Unable to fetch module data", err)
+ return@whenComplete
+ }
+
+ this.mModules.clear()
+ this.mModules.addAll(modules.filterNotNull())
+ }
+ }
+ }
+
+ override fun findByPath(path: String): CompletableFuture {
+ return this.project.findByPath(path)
+ }
+
+ override fun findAndroidModules(): CompletableFuture> {
+ return this.project.findAndroidModules()
+ }
+
+ override fun findFirstAndroidModule(): CompletableFuture {
+ return this.project.findFirstAndroidModule()
+ }
+
+ override fun findFirstAndroidAppModule(): CompletableFuture {
+ if (mFirstAppModule != null) {
+ return CompletableFuture.completedFuture(mFirstAppModule)
+ }
+
+ return this.project.findFirstAndroidAppModule().whenComplete { module, _ ->
+ this.mFirstAppModule = module
+ }
+ }
+}
diff --git a/app/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt b/app/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt
index 47db018c1..a3573153b 100644
--- a/app/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt
+++ b/app/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt
@@ -21,29 +21,50 @@ import com.android.builder.model.v2.ide.LibraryType.JAVA_LIBRARY
import com.android.builder.model.v2.ide.LibraryType.PROJECT
import com.itsaky.androidide.app.StudioApp
import com.itsaky.androidide.services.BuildService
+import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
-import com.itsaky.androidide.tooling.api.model.IdeGradleProject
import com.itsaky.androidide.tooling.api.model.IdeJavaModule
import com.itsaky.androidide.tooling.api.model.IdeModule
+import com.itsaky.androidide.utils.ILogger
import com.itsaky.lsp.java.models.JavaServerConfiguration
import java.io.File
import java.nio.file.Path
+import java.util.concurrent.*
/**
* Manages projects in AndroidIDE.
*
* @author Akash Yadav
*/
+@Suppress("MemberVisibilityCanBePrivate")
object ProjectManager {
- private val log = com.itsaky.androidide.utils.ILogger.newInstance(javaClass.simpleName)
- var rootProject: IdeGradleProject? = null
+ private val log = ILogger.newInstance(javaClass.simpleName)
+ var rootProject: IProject? = null
+ set(value) {
+ field = if (value == null) null else CachingProject(value)
+
+ // Cache the module data in advance for future use
+ if (field != null) {
+ field!!.listModules()
+ }
+ }
var projectPath: String? = null
- fun getProjectDir(): File? =
- if (getProjectDirPath() == null) null else File(getProjectDirPath()!!)
+ fun isInitialized() = this.rootProject != null && this.rootProject!!.isInitialized.get()
+
+ fun checkInit(): Boolean {
+ if (isInitialized()) {
+ return true
+ }
+
+ log.warn("Project is not initialized yet!")
+ return false
+ }
+
+ fun getProjectDir(): File? = if (!checkInit()) null else File(getProjectDirPath()!!)
fun getProjectDirPath(): String? =
- if (rootProject == null) projectPath else rootProject!!.projectDir!!.absolutePath
+ if (!checkInit()) projectPath else rootProject!!.projectDir.get().absolutePath
fun generateSources(builder: BuildService?) {
if (builder == null) {
@@ -51,62 +72,63 @@ object ProjectManager {
return
}
- val app =
- getApplicationModule()
- ?: kotlin.run {
- log.error("Cannot generate resources...")
- return
- }
-
- val debug = app.variants.firstOrNull { it.name == "debug" }
- if (debug == null) {
- log.warn("No debug variant found in application project ${app.name}")
- return
- }
-
- val mainArtifact = app.variants.first { it.name == "debug" }.mainArtifact
- val genResourcesTask = mainArtifact.resGenTaskName
- val genSourcesTask = mainArtifact.sourceGenTaskName
- builder
- .executeProjectTasks(
- app.projectPath!!,
- genResourcesTask ?: "",
- genSourcesTask,
-
- // If view binding is enabled, generate the view binding classes too
- if (app.viewBindingOptions != null && app.viewBindingOptions!!.isEnabled)
- "dataBindingGenBaseClassesDebug"
- else "")
- .whenComplete { result, err ->
- if (!result.isSuccessful || err != null) {
- log.warn(
- "Execution for tasks '$genResourcesTask' and '$genSourcesTask' failed.",
- err ?: "")
- return@whenComplete
- }
-
- notifyProjectUpdate()
+ getApplicationModule().whenComplete { app, _ ->
+ if (app == null) {
+ log.warn(
+ "Cannot run resource and source generation task. No application module found.")
+ return@whenComplete
}
+
+ val debug = app.variants.firstOrNull { it.name == "debug" }
+ if (debug == null) {
+ log.warn("No debug variant found in application project ${app.name}")
+ return@whenComplete
+ }
+
+ val mainArtifact = app.variants.first { it.name == "debug" }.mainArtifact
+ val genResourcesTask = mainArtifact.resGenTaskName
+ val genSourcesTask = mainArtifact.sourceGenTaskName
+ builder
+ .executeProjectTasks(
+ app.projectPath,
+ genResourcesTask ?: "",
+ genSourcesTask,
+
+ // If view binding is enabled, generate the view binding classes too
+ if (app.viewBindingOptions != null && app.viewBindingOptions!!.isEnabled)
+ "dataBindingGenBaseClassesDebug"
+ else "")
+ .whenComplete { result, taskErr ->
+ if (!result.isSuccessful || taskErr != null) {
+ log.warn(
+ "Execution for tasks '$genResourcesTask' and '$genSourcesTask' failed.",
+ taskErr ?: "")
+ } else {
+ notifyProjectUpdate()
+ }
+ }
+ }
}
- fun getApplicationModule(): IdeAndroidModule? {
+ fun getApplicationModule(): CompletableFuture {
if (rootProject == null) {
log.error(
"No root project instance is set. Is the project initialization process finished?")
- return null
+ return CompletableFuture.completedFuture(null)
}
- val app = rootProject!!.findFirstAndroidModule()
- if (app == null) {
- log.error("No application module found in root project.")
- return null
+ return rootProject!!.findFirstAndroidAppModule().whenComplete { app, err ->
+ if (app == null || err != null) {
+ log.debug("An error occurred while fetching model for application module.")
+ return@whenComplete
+ }
}
-
- return app
}
- fun getApplicationResDirectories(): List? {
- return collectResDirectories(getApplicationModule() ?: return null)
+ fun getApplicationResDirectories(): CompletableFuture?> {
+ return getApplicationModule().thenApplyAsync {
+ if (it == null) null else collectResDirectories(it)
+ }
}
fun collectResDirectories(android: IdeAndroidModule): List {
@@ -133,15 +155,21 @@ object ProjectManager {
}
fun notifyProjectUpdate() {
- val server = StudioApp.getInstance().javaLanguageServer
- val sourceDirs = collectApplicationSourceDirs()
- val classPaths = collectApplicationClassPaths()
- val configuration = JavaServerConfiguration(classPaths, sourceDirs)
- server.configurationChanged(configuration)
+ getApplicationModule().thenAccept {
+ if (it == null) {
+ log.debug("Cannot find application module. Unable to update class paths to LSP.")
+ return@thenAccept
+ }
+
+ val server = StudioApp.getInstance().javaLanguageServer
+ val sourceDirs = collectSourceDirs(it)
+ val classPaths = collectClassPaths(it)
+ val configuration = JavaServerConfiguration(classPaths, sourceDirs)
+ server.configurationChanged(configuration)
+ }
}
- private fun collectApplicationClassPaths(): Set {
- val app = getApplicationModule() ?: return emptySet()
+ private fun collectClassPaths(app: IdeAndroidModule): Set {
val libraries = app.variantDependencies["debug"]!!.libraries
val paths = mutableSetOf()
@@ -153,7 +181,7 @@ object ProjectManager {
paths.add(value.artifact!!.toPath())
} else {
val project =
- rootProject!!.findByPath(value.projectInfo!!.projectPath)!! as IdeModule
+ rootProject!!.findByPath(value.projectInfo!!.projectPath).get() as IdeModule
paths.add(project.getGeneratedJar("debug").toPath())
if (project is IdeAndroidModule && project.projectPath != app.projectPath) {
@@ -171,8 +199,7 @@ object ProjectManager {
return paths
}
- private fun collectApplicationSourceDirs(): Set {
- val app = getApplicationModule() ?: return emptySet()
+ private fun collectSourceDirs(app: IdeAndroidModule): Set {
val sources = app.mainSourceSet ?: return emptySet()
val javaDirs = sources.sourceProvider.javaDirectories
val sourcePaths = javaDirs.toMutableSet()
@@ -181,15 +208,12 @@ object ProjectManager {
return sourcePaths.map { it.toPath() }.toSet()
}
- fun collectProjectDependencies(
- project: IdeGradleProject,
- app: IdeAndroidModule
- ): List {
+ fun collectProjectDependencies(project: IProject, app: IdeAndroidModule): List {
return app.variantDependencies["debug"]!!
.libraries.values
.filter { it.type == PROJECT }
- .mapNotNull { project.findByPath(it.projectInfo!!.projectPath) }
+ .map { project.findByPath(it.projectInfo!!.projectPath) }
.filterIsInstance(IdeModule::class.java)
}
@@ -245,38 +269,4 @@ object ProjectManager {
android.variants.first { it.name == "debug" }.mainArtifact.generatedSourceFolders)
return sources
}
-
- /**
- * Finds the module in which the given file exists.
- *
- * @return The module in which the given file exists or `null` if the module cannot be found.
- */
- fun findModuleForFile(file: File): IdeModule? {
- if (rootProject == null) {
- log.warn(
- "No root project instance is set. Is the project initialization process finished?")
- return null
- }
-
- val path = file.canonicalPath
- val modules = rootProject!!.getModules()
-
- var longestPath = ""
- var longestPathModule: IdeModule? = null
- for (module in modules) {
- val modulePath = module.projectDir!!.canonicalPath
- if (path.startsWith(modulePath)) {
- if (longestPath.length < modulePath.length && module is IdeModule) {
- longestPathModule = module
- longestPath = modulePath
- }
- }
- }
-
- if (longestPath.isEmpty()) {
- return null
- }
-
- return longestPathModule
- }
}
diff --git a/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java b/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java
index eff5919d6..9f6965e92 100644
--- a/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java
+++ b/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java
@@ -54,6 +54,7 @@ import com.itsaky.androidide.tooling.api.messages.result.BuildResult;
import com.itsaky.androidide.tooling.api.messages.result.GradleWrapperCheckResult;
import com.itsaky.androidide.tooling.api.messages.result.InitializeResult;
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult;
+import com.itsaky.androidide.tooling.api.IProject;
import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher;
import com.itsaky.androidide.tooling.events.ProgressEvent;
import com.itsaky.androidide.utils.Environment;
@@ -486,14 +487,16 @@ public class GradleBuildService extends Service implements BuildService, IToolin
Environment.TOOLING_API_JAR.getAbsolutePath());
final var launcher =
- ToolingApiLauncher.createClientLauncher(
+ ToolingApiLauncher.newClientLauncher(
GradleBuildService.this, serverStreams.in, serverStreams.out);
final var future = launcher.startListening();
GradleBuildService.this.startServerOutputReader(serverStreams.err);
- GradleBuildService.this.server = launcher.getRemoteProxy();
+ GradleBuildService.this.server = (IToolingApiServer) launcher.getRemoteProxy();
GradleBuildService.this.isToolingServerStarted = true;
+ ProjectManager.INSTANCE.setRootProject((IProject) launcher.getRemoteProxy());
+
if (listener != null) {
listener.onServerStarted();
}
@@ -505,7 +508,8 @@ public class GradleBuildService extends Service implements BuildService, IToolin
if (!(err instanceof CancellationException)
&& !(err instanceof InterruptedException)) {
LOG.error(
- "An error occurred while waiting for tooling API server to terminate", err);
+ "An error occurred while waiting for tooling API server to terminate",
+ err);
}
}
diff --git a/build-tools/builder-model-impl/.gitignore b/build-tools/builder-model-impl/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/build-tools/builder-model-impl/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/build-tools/builder-model-impl/build.gradle b/build-tools/builder-model-impl/build.gradle
new file mode 100644
index 000000000..b1466b94c
--- /dev/null
+++ b/build-tools/builder-model-impl/build.gradle
@@ -0,0 +1,31 @@
+/*
+ * 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 .
+ */
+
+plugins {
+ id 'java-library'
+ id 'org.jetbrains.kotlin.jvm'
+}
+
+java {
+ sourceCompatibility = rootProject.ext.javaSourceVersion
+ targetCompatibility = rootProject.ext.javaTargetVersion
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+ api "com.android.tools.build:builder-model:${project.property ("androidide.tooling.supportedAgpVersion")}"
+}
\ No newline at end of file
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultAndroidArtifact.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultAndroidArtifact.kt
similarity index 97%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultAndroidArtifact.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultAndroidArtifact.kt
index b2e678c1c..fa8e250ed 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultAndroidArtifact.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultAndroidArtifact.kt
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.AndroidArtifact
import com.android.builder.model.v2.ide.CodeShrinker
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultAndroidGradlePluginProjectFlags.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultAndroidGradlePluginProjectFlags.kt
similarity index 95%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultAndroidGradlePluginProjectFlags.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultAndroidGradlePluginProjectFlags.kt
index 432323b35..b6dc65fc8 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultAndroidGradlePluginProjectFlags.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultAndroidGradlePluginProjectFlags.kt
@@ -14,8 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.AndroidGradlePluginProjectFlags
import com.android.builder.model.v2.ide.AndroidGradlePluginProjectFlags.BooleanFlag
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultAndroidLibraryData.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultAndroidLibraryData.kt
similarity index 96%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultAndroidLibraryData.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultAndroidLibraryData.kt
index 30c73a7e5..14431aca8 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultAndroidLibraryData.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultAndroidLibraryData.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.AndroidLibraryData
import java.io.File
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultApiVersion.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultApiVersion.kt
similarity index 94%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultApiVersion.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultApiVersion.kt
index efb511379..687ed4f5a 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultApiVersion.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultApiVersion.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.ApiVersion
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultArtifactDependencies.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultArtifactDependencies.kt
similarity index 95%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultArtifactDependencies.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultArtifactDependencies.kt
index ce0ec0143..8be32f7eb 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultArtifactDependencies.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultArtifactDependencies.kt
@@ -14,8 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.ArtifactDependencies
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultBundleInfo.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultBundleInfo.kt
similarity index 95%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultBundleInfo.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultBundleInfo.kt
index 5b4582e73..d9c4f5f9e 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultBundleInfo.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultBundleInfo.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.BundleInfo
import java.io.File
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultCustomSourceDirectory.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultCustomSourceDirectory.kt
similarity index 94%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultCustomSourceDirectory.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultCustomSourceDirectory.kt
index 478a65fb4..bb3e04845 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultCustomSourceDirectory.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultCustomSourceDirectory.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.CustomSourceDirectory
import java.io.File
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultGraphItem.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultGraphItem.kt
similarity index 94%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultGraphItem.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultGraphItem.kt
index b972a6239..6168c857a 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultGraphItem.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultGraphItem.kt
@@ -14,8 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.GraphItem
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultJavaArtifact.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultJavaArtifact.kt
similarity index 96%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultJavaArtifact.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultJavaArtifact.kt
index 65a688557..5cb5b9a05 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultJavaArtifact.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultJavaArtifact.kt
@@ -14,8 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.JavaArtifact
import java.io.File
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultJavaCompileOptions.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultJavaCompileOptions.kt
similarity index 95%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultJavaCompileOptions.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultJavaCompileOptions.kt
index af4d86f05..900d8c603 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultJavaCompileOptions.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultJavaCompileOptions.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.JavaCompileOptions
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultLibrary.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultLibrary.kt
similarity index 95%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultLibrary.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultLibrary.kt
index 906cf2630..d40306ed0 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultLibrary.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultLibrary.kt
@@ -14,8 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.Library
import com.android.builder.model.v2.ide.LibraryType
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultLibraryInfo.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultLibraryInfo.kt
similarity index 95%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultLibraryInfo.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultLibraryInfo.kt
index 70f6efb1e..7676c45fb 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultLibraryInfo.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultLibraryInfo.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.LibraryInfo
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultModelBuilderParameter.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultModelBuilderParameter.kt
similarity index 94%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultModelBuilderParameter.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultModelBuilderParameter.kt
index 08b35ef52..a60685d19 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultModelBuilderParameter.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultModelBuilderParameter.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.models.ModelBuilderParameter
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultModelSyncFile.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultModelSyncFile.kt
similarity index 95%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultModelSyncFile.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultModelSyncFile.kt
index ed5b87882..ff088a62b 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultModelSyncFile.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultModelSyncFile.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ModelSyncFile
import com.android.builder.model.v2.ModelSyncFile.ModelSyncType
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultProjectInfo.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultProjectInfo.kt
similarity index 95%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultProjectInfo.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultProjectInfo.kt
index 4f58772f4..d6a4b8c1e 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultProjectInfo.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultProjectInfo.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.ProjectInfo
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultProjectSyncIssues.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultProjectSyncIssues.kt
similarity index 94%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultProjectSyncIssues.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultProjectSyncIssues.kt
index 4e6a06baa..5a5659cd3 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultProjectSyncIssues.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultProjectSyncIssues.kt
@@ -14,8 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.models.ProjectSyncIssues
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultSourceProvider.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSourceProvider.kt
similarity index 96%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultSourceProvider.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSourceProvider.kt
index 98c34148d..f41b131dc 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultSourceProvider.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSourceProvider.kt
@@ -14,8 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.SourceProvider
import java.io.File
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultSourceSetContainer.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSourceSetContainer.kt
similarity index 95%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultSourceSetContainer.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSourceSetContainer.kt
index ab3653ba6..4e587d5c8 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultSourceSetContainer.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSourceSetContainer.kt
@@ -14,8 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.SourceSetContainer
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultSyncIssue.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSyncIssue.kt
similarity index 94%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultSyncIssue.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSyncIssue.kt
index f494f27ee..e14820e54 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultSyncIssue.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSyncIssue.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.SyncIssue
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultTestInfo.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultTestInfo.kt
similarity index 95%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultTestInfo.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultTestInfo.kt
index 785446034..a531b87e9 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultTestInfo.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultTestInfo.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.TestInfo
import com.android.builder.model.v2.ide.TestInfo.Execution
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultTestedTargetVariant.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultTestedTargetVariant.kt
similarity index 94%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultTestedTargetVariant.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultTestedTargetVariant.kt
index ecd0a1d3a..faaa8e995 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultTestedTargetVariant.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultTestedTargetVariant.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.TestedTargetVariant
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultUnresolvedDependency.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultUnresolvedDependency.kt
similarity index 94%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultUnresolvedDependency.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultUnresolvedDependency.kt
index 7c1a946c3..9bc67e8bd 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultUnresolvedDependency.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultUnresolvedDependency.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.UnresolvedDependency
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultVariant.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultVariant.kt
similarity index 96%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultVariant.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultVariant.kt
index fc082d0c1..af1c70b4d 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultVariant.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultVariant.kt
@@ -14,8 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.Variant
import java.io.File
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultVariantDependencies.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultVariantDependencies.kt
similarity index 95%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultVariantDependencies.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultVariantDependencies.kt
index 8784b7d55..9b68d6ccf 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultVariantDependencies.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultVariantDependencies.kt
@@ -14,8 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.models.VariantDependencies
import java.io.Serializable
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultViewBindingOptions.kt b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultViewBindingOptions.kt
similarity index 94%
rename from build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultViewBindingOptions.kt
rename to build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultViewBindingOptions.kt
index ed218ac8b..1d3f52fcd 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/internal/DefaultViewBindingOptions.kt
+++ b/build-tools/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultViewBindingOptions.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api.model.internal
+package com.itsaky.androidide.builder.model
import com.android.builder.model.v2.ide.ViewBindingOptions
import java.io.Serializable
diff --git a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java
index 9330e310a..66097c4c0 100644
--- a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java
+++ b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java
@@ -21,7 +21,6 @@ import static com.itsaky.androidide.utils.ILogger.newInstance;
import com.itsaky.androidide.models.LogLine;
import com.itsaky.androidide.tooling.api.IToolingApiClient;
-import com.itsaky.androidide.tooling.api.model.IProject;
import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher;
import com.itsaky.androidide.tooling.impl.model.InternalForwardingProject;
import com.itsaky.androidide.tooling.impl.progress.ForwardingProgressListener;
diff --git a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt
index 605d831d5..9325aa9d6 100644
--- a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt
+++ b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt
@@ -37,7 +37,7 @@ import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Fai
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.UNSUPPORTED_CONFIGURATION
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.UNSUPPORTED_GRADLE_VERSION
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
-import com.itsaky.androidide.tooling.api.model.internal.DefaultProjectSyncIssues
+import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues
import com.itsaky.androidide.tooling.impl.model.InternalForwardingProject
import com.itsaky.androidide.tooling.impl.progress.ForwardingProgressListener
import com.itsaky.androidide.tooling.impl.util.ProjectReader
@@ -229,6 +229,7 @@ internal class ToolingApiServerImpl(private val forwardingProject: InternalForwa
this.client = null
this.project = null
this.buildCancellationToken = null
+ this.forwardingProject.project = null
Main.future = null
Main.client = null
diff --git a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/model/InternalForwardingProject.kt b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/model/InternalForwardingProject.kt
index 9a4c6d93d..a259e1542 100644
--- a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/model/InternalForwardingProject.kt
+++ b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/model/InternalForwardingProject.kt
@@ -17,8 +17,9 @@
package com.itsaky.androidide.tooling.impl.model
+import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.IToolingApiClient
-import com.itsaky.androidide.tooling.api.model.IProject
+import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
import com.itsaky.androidide.tooling.api.model.IdeGradleTask
@@ -40,6 +41,10 @@ class InternalForwardingProject(var project: IProject?) : IProject {
private val log = ILogger.newInstance(javaClass.simpleName)
+ override fun isInitialized(): CompletableFuture {
+ return CompletableFuture.completedFuture(this.project != null)
+ }
+
override fun getName(): CompletableFuture =
if (this.project != null) this.project!!.name else CompletableFuture.completedFuture("")
@@ -71,6 +76,10 @@ class InternalForwardingProject(var project: IProject?) : IProject {
if (this.project != null) this.project!!.modules
else CompletableFuture.completedFuture(mutableListOf())
+ override fun listModules(): CompletableFuture> =
+ if (this.project != null) project!!.listModules()
+ else CompletableFuture.completedFuture(mutableListOf())
+
override fun findByPath(path: String): CompletableFuture =
if (this.project != null) this.project!!.findByPath(path)
else CompletableFuture.completedFuture(null)
@@ -82,4 +91,8 @@ class InternalForwardingProject(var project: IProject?) : IProject {
override fun findFirstAndroidModule(): CompletableFuture =
if (this.project != null) this.project!!.findFirstAndroidModule()
else CompletableFuture.completedFuture(null)
+
+ override fun findFirstAndroidAppModule(): CompletableFuture =
+ if (this.project != null) this.project!!.findFirstAndroidAppModule()
+ else CompletableFuture.completedFuture(null)
}
diff --git a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ModelInfoContainer.kt b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ModelInfoContainer.kt
index 29c87a1cd..0268124bc 100644
--- a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ModelInfoContainer.kt
+++ b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ModelInfoContainer.kt
@@ -18,7 +18,7 @@
package com.itsaky.androidide.tooling.impl.util
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
-import com.itsaky.androidide.tooling.api.model.internal.DefaultProjectSyncIssues
+import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues
import java.io.Serializable
/** @author Akash Yadav */
diff --git a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java
index def052872..de7062272 100644
--- a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java
+++ b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java
@@ -36,7 +36,7 @@ import com.itsaky.androidide.tooling.api.model.JavaModuleDependency;
import com.itsaky.androidide.tooling.api.model.JavaModuleExternalDependency;
import com.itsaky.androidide.tooling.api.model.JavaModuleProjectDependency;
import com.itsaky.androidide.tooling.api.model.JavaSourceDirectory;
-import com.itsaky.androidide.tooling.api.model.internal.DefaultProjectSyncIssues;
+import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues;
import com.itsaky.androidide.tooling.api.model.util.AndroidModulePropertyCopier;
import com.itsaky.androidide.tooling.api.model.util.ProjectBuilder;
import com.itsaky.androidide.tooling.impl.Main;
diff --git a/build-tools/tooling-api-impl/src/test/java/com/itsaky/androidide/tooling/impl/ToolingApiImplTest.kt b/build-tools/tooling-api-impl/src/test/java/com/itsaky/androidide/tooling/impl/ToolingApiImplTest.kt
index 96cf7f5a5..a50a02ecd 100644
--- a/build-tools/tooling-api-impl/src/test/java/com/itsaky/androidide/tooling/impl/ToolingApiImplTest.kt
+++ b/build-tools/tooling-api-impl/src/test/java/com/itsaky/androidide/tooling/impl/ToolingApiImplTest.kt
@@ -27,7 +27,7 @@ import com.itsaky.androidide.tooling.api.IToolingApiServer
import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage
import com.itsaky.androidide.tooling.api.messages.result.BuildResult
import com.itsaky.androidide.tooling.api.messages.result.GradleWrapperCheckResult
-import com.itsaky.androidide.tooling.api.model.IProject
+import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
import com.itsaky.androidide.tooling.api.model.IdeJavaModule
import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher
diff --git a/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar b/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar
index 0a5f4759c..c056714b4 100644
Binary files a/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar and b/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar differ
diff --git a/build-tools/tooling-api-model/build.gradle b/build-tools/tooling-api-model/build.gradle
index 2e59871c7..8ab540808 100644
--- a/build-tools/tooling-api-model/build.gradle
+++ b/build-tools/tooling-api-model/build.gradle
@@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
-
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
@@ -34,9 +33,9 @@ repositories {
dependencies {
implementation project (path: ':build-tools:logger')
implementation project (path: ':build-tools:xml-dom')
+ api project (path: ':build-tools:builder-model-impl')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
- api "com.android.tools.build:builder-model:${project.property ("androidide.tooling.supportedAgpVersion")}"
}
task copyToTestDir (type: Copy) {
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.java b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.java
new file mode 100644
index 000000000..f30fb870f
--- /dev/null
+++ b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.java
@@ -0,0 +1,98 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.tooling.api;
+
+import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData;
+import com.itsaky.androidide.tooling.api.model.IdeAndroidModule;
+import com.itsaky.androidide.tooling.api.model.IdeGradleProject;
+import com.itsaky.androidide.tooling.api.model.IdeGradleTask;
+
+import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
+import org.eclipse.lsp4j.jsonrpc.services.JsonSegment;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Root Gradle project. The getter methods return completed {@link CompletableFuture}s.
+ *
+ * @author Akash Yadav
+ */
+@JsonSegment("project")
+public interface IProject {
+
+ @NotNull
+ @JsonRequest
+ default CompletableFuture isInitialized() {
+ return CompletableFuture.completedFuture(true);
+ }
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture getName();
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture getDescription();
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture getProjectPath();
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture getProjectDir();
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture getBuildDir();
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture getBuildScript();
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture> getTasks();
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture> getModules();
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture> listModules();
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture findByPath(@NotNull String path);
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture findFirstAndroidModule();
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture findFirstAndroidAppModule();
+
+ @NotNull
+ @JsonRequest
+ CompletableFuture> findAndroidModules();
+}
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleModuleData.kt b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleModuleData.kt
new file mode 100644
index 000000000..6dc9d4299
--- /dev/null
+++ b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleModuleData.kt
@@ -0,0 +1,27 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.tooling.api.messages.result
+
+import java.io.File
+
+/**
+ * Container for basic data about module projects.
+ *
+ * @author Akash Yadav
+ */
+data class SimpleModuleData(val name: String, val path: String, val projectDir: File)
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IProject.java b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IProject.java
deleted file mode 100644
index 74dbf04d6..000000000
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IProject.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * This file is part of AndroidIDE.
- *
- * AndroidIDE is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * AndroidIDE is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with AndroidIDE. If not, see .
- */
-
-package com.itsaky.androidide.tooling.api.model;
-
-import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
-import org.eclipse.lsp4j.jsonrpc.services.JsonSegment;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-
-/**
- * Root Gradle project. The getter methods return completed {@link CompletableFuture}s.
- *
- * @author Akash Yadav
- */
-@JsonSegment("project")
-public interface IProject {
-
- @NotNull
- @JsonRequest
- CompletableFuture getName();
-
- @NotNull
- @JsonRequest
- CompletableFuture getDescription();
-
- @NotNull
- @JsonRequest
- CompletableFuture getProjectPath();
-
- @NotNull
- @JsonRequest
- CompletableFuture getProjectDir();
-
- @NotNull
- @JsonRequest
- CompletableFuture getBuildDir();
-
- @NotNull
- @JsonRequest
- CompletableFuture getBuildScript();
-
- @NotNull
- @JsonRequest
- CompletableFuture> getTasks();
-
- @NotNull
- @JsonRequest
- CompletableFuture> getModules();
-
- @NotNull
- @JsonRequest
- CompletableFuture findByPath(@NotNull String path);
-
- @NotNull
- @JsonRequest
- CompletableFuture> findAndroidModules();
-
- @NotNull
- @JsonRequest
- CompletableFuture findFirstAndroidModule();
-}
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeAndroidModule.kt b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeAndroidModule.kt
index dfcf10ee0..4ff48f267 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeAndroidModule.kt
+++ b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeAndroidModule.kt
@@ -18,14 +18,14 @@ package com.itsaky.androidide.tooling.api.model
import com.android.builder.model.v2.ide.ProjectType
import com.android.builder.model.v2.models.AndroidProject
-import com.itsaky.androidide.tooling.api.model.internal.DefaultAndroidGradlePluginProjectFlags
-import com.itsaky.androidide.tooling.api.model.internal.DefaultJavaCompileOptions
-import com.itsaky.androidide.tooling.api.model.internal.DefaultModelSyncFile
-import com.itsaky.androidide.tooling.api.model.internal.DefaultSourceProvider
-import com.itsaky.androidide.tooling.api.model.internal.DefaultSourceSetContainer
-import com.itsaky.androidide.tooling.api.model.internal.DefaultVariant
-import com.itsaky.androidide.tooling.api.model.internal.DefaultVariantDependencies
-import com.itsaky.androidide.tooling.api.model.internal.DefaultViewBindingOptions
+import com.itsaky.androidide.builder.model.DefaultAndroidGradlePluginProjectFlags
+import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions
+import com.itsaky.androidide.builder.model.DefaultModelSyncFile
+import com.itsaky.androidide.builder.model.DefaultSourceProvider
+import com.itsaky.androidide.builder.model.DefaultSourceSetContainer
+import com.itsaky.androidide.builder.model.DefaultVariant
+import com.itsaky.androidide.builder.model.DefaultVariantDependencies
+import com.itsaky.androidide.builder.model.DefaultViewBindingOptions
import org.eclipse.lemminx.dom.DOMParser
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager
import java.io.File
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeGradleProject.kt b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeGradleProject.kt
index 6e583c87f..b96386371 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeGradleProject.kt
+++ b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeGradleProject.kt
@@ -16,10 +16,12 @@
*/
package com.itsaky.androidide.tooling.api.model
+import com.android.builder.model.v2.ide.ProjectType.APPLICATION
+import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData
+import org.eclipse.lsp4j.jsonrpc.CompletableFutures
import java.io.File
import java.io.Serializable
import java.util.concurrent.*
-import org.eclipse.lsp4j.jsonrpc.CompletableFutures
/**
* A root Gradle project.
@@ -34,7 +36,7 @@ open class IdeGradleProject(
@JvmField val buildScript: File,
@JvmField val parent: IdeGradleProject?,
@JvmField val tasks: List,
-) : IProject, Serializable {
+) : com.itsaky.androidide.tooling.api.IProject, Serializable {
private val serialVersionUID = 1L
private val gsonType: String = javaClass.name
@@ -72,6 +74,14 @@ open class IdeGradleProject(
return CompletableFutures.computeAsync { this.moduleProjects.toMutableList() }
}
+ override fun listModules(): CompletableFuture> {
+ return CompletableFutures.computeAsync {
+ return@computeAsync this.moduleProjects
+ .map { SimpleModuleData(it.name, it.projectPath, it.projectDir) }
+ .toMutableList()
+ }
+ }
+
override fun findByPath(path: String): CompletableFuture {
return CompletableFutures.computeAsync {
if (path == this.projectPath) {
@@ -91,4 +101,12 @@ open class IdeGradleProject(
override fun findFirstAndroidModule(): CompletableFuture {
return findAndroidModules().thenApply { it.firstOrNull() }
}
+
+ override fun findFirstAndroidAppModule(): CompletableFuture {
+ return findAndroidModules().thenApply { modules ->
+ val application =
+ modules.stream().filter { it != null && it.projectType == APPLICATION }.findFirst()
+ application.orElse(null)
+ }
+ }
}
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/AndroidModulePropertyCopier.kt b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/AndroidModulePropertyCopier.kt
index 7b089c211..8c2ff504a 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/AndroidModulePropertyCopier.kt
+++ b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/AndroidModulePropertyCopier.kt
@@ -43,30 +43,30 @@ import com.android.builder.model.v2.ide.ViewBindingOptions
import com.android.builder.model.v2.models.ProjectSyncIssues
import com.android.builder.model.v2.models.VariantDependencies
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
-import com.itsaky.androidide.tooling.api.model.internal.DefaultAndroidArtifact
-import com.itsaky.androidide.tooling.api.model.internal.DefaultAndroidGradlePluginProjectFlags
-import com.itsaky.androidide.tooling.api.model.internal.DefaultAndroidLibraryData
-import com.itsaky.androidide.tooling.api.model.internal.DefaultApiVersion
-import com.itsaky.androidide.tooling.api.model.internal.DefaultArtifactDependencies
-import com.itsaky.androidide.tooling.api.model.internal.DefaultBundleInfo
-import com.itsaky.androidide.tooling.api.model.internal.DefaultCustomSourceDirectory
-import com.itsaky.androidide.tooling.api.model.internal.DefaultGraphItem
-import com.itsaky.androidide.tooling.api.model.internal.DefaultJavaArtifact
-import com.itsaky.androidide.tooling.api.model.internal.DefaultJavaCompileOptions
-import com.itsaky.androidide.tooling.api.model.internal.DefaultLibrary
-import com.itsaky.androidide.tooling.api.model.internal.DefaultLibraryInfo
-import com.itsaky.androidide.tooling.api.model.internal.DefaultModelSyncFile
-import com.itsaky.androidide.tooling.api.model.internal.DefaultProjectInfo
-import com.itsaky.androidide.tooling.api.model.internal.DefaultProjectSyncIssues
-import com.itsaky.androidide.tooling.api.model.internal.DefaultSourceProvider
-import com.itsaky.androidide.tooling.api.model.internal.DefaultSourceSetContainer
-import com.itsaky.androidide.tooling.api.model.internal.DefaultSyncIssue
-import com.itsaky.androidide.tooling.api.model.internal.DefaultTestInfo
-import com.itsaky.androidide.tooling.api.model.internal.DefaultTestedTargetVariant
-import com.itsaky.androidide.tooling.api.model.internal.DefaultUnresolvedDependency
-import com.itsaky.androidide.tooling.api.model.internal.DefaultVariant
-import com.itsaky.androidide.tooling.api.model.internal.DefaultVariantDependencies
-import com.itsaky.androidide.tooling.api.model.internal.DefaultViewBindingOptions
+import com.itsaky.androidide.builder.model.DefaultAndroidArtifact
+import com.itsaky.androidide.builder.model.DefaultAndroidGradlePluginProjectFlags
+import com.itsaky.androidide.builder.model.DefaultAndroidLibraryData
+import com.itsaky.androidide.builder.model.DefaultApiVersion
+import com.itsaky.androidide.builder.model.DefaultArtifactDependencies
+import com.itsaky.androidide.builder.model.DefaultBundleInfo
+import com.itsaky.androidide.builder.model.DefaultCustomSourceDirectory
+import com.itsaky.androidide.builder.model.DefaultGraphItem
+import com.itsaky.androidide.builder.model.DefaultJavaArtifact
+import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions
+import com.itsaky.androidide.builder.model.DefaultLibrary
+import com.itsaky.androidide.builder.model.DefaultLibraryInfo
+import com.itsaky.androidide.builder.model.DefaultModelSyncFile
+import com.itsaky.androidide.builder.model.DefaultProjectInfo
+import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues
+import com.itsaky.androidide.builder.model.DefaultSourceProvider
+import com.itsaky.androidide.builder.model.DefaultSourceSetContainer
+import com.itsaky.androidide.builder.model.DefaultSyncIssue
+import com.itsaky.androidide.builder.model.DefaultTestInfo
+import com.itsaky.androidide.builder.model.DefaultTestedTargetVariant
+import com.itsaky.androidide.builder.model.DefaultUnresolvedDependency
+import com.itsaky.androidide.builder.model.DefaultVariant
+import com.itsaky.androidide.builder.model.DefaultVariantDependencies
+import com.itsaky.androidide.builder.model.DefaultViewBindingOptions
import com.itsaky.androidide.utils.ILogger
/**
diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/ProjectBuilder.kt b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/ProjectBuilder.kt
index bc8113332..85a5e5f9f 100644
--- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/ProjectBuilder.kt
+++ b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/ProjectBuilder.kt
@@ -23,11 +23,11 @@ import com.itsaky.androidide.tooling.api.model.IdeGradleTask
import com.itsaky.androidide.tooling.api.model.IdeJavaModule
import com.itsaky.androidide.tooling.api.model.JavaContentRoot
import com.itsaky.androidide.tooling.api.model.JavaModuleDependency
-import com.itsaky.androidide.tooling.api.model.internal.DefaultAndroidGradlePluginProjectFlags
-import com.itsaky.androidide.tooling.api.model.internal.DefaultJavaCompileOptions
-import com.itsaky.androidide.tooling.api.model.internal.DefaultModelSyncFile
-import com.itsaky.androidide.tooling.api.model.internal.DefaultVariant
-import com.itsaky.androidide.tooling.api.model.internal.DefaultViewBindingOptions
+import com.itsaky.androidide.builder.model.DefaultAndroidGradlePluginProjectFlags
+import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions
+import com.itsaky.androidide.builder.model.DefaultModelSyncFile
+import com.itsaky.androidide.builder.model.DefaultVariant
+import com.itsaky.androidide.builder.model.DefaultViewBindingOptions
import java.io.File
/**
diff --git a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/result/InitializeResult.kt b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/result/InitializeResult.kt
index e62c763c5..f9bf57eab 100644
--- a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/result/InitializeResult.kt
+++ b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/result/InitializeResult.kt
@@ -18,7 +18,7 @@
package com.itsaky.androidide.tooling.api.messages.result
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
-import com.itsaky.androidide.tooling.api.model.internal.DefaultProjectSyncIssues
+import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues
/**
* Result received after an initialize project request.
diff --git a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java
index 875a85dfd..a7eda1bd0 100644
--- a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java
+++ b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java
@@ -20,7 +20,7 @@ package com.itsaky.androidide.tooling.api.util;
import com.google.gson.GsonBuilder;
import com.itsaky.androidide.tooling.api.IToolingApiClient;
import com.itsaky.androidide.tooling.api.IToolingApiServer;
-import com.itsaky.androidide.tooling.api.model.IProject;
+import com.itsaky.androidide.tooling.api.IProject;
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule;
import com.itsaky.androidide.tooling.api.model.IdeGradleProject;
import com.itsaky.androidide.tooling.api.model.IdeGradleTask;
@@ -86,10 +86,6 @@ import java.util.concurrent.Executors;
* @author Akash Yadav
*/
public class ToolingApiLauncher {
- public static Launcher createClientLauncher(
- IToolingApiClient client, InputStream in, OutputStream out) {
- return createIOLauncher(client, IToolingApiServer.class, in, out);
- }
public static Launcher createIOLauncher(
Object local, Class remote, InputStream in, OutputStream out) {
@@ -265,9 +261,4 @@ public class ToolingApiLauncher {
return newIoLauncher(
new Object[] {server, project}, new Class[] {IToolingApiClient.class}, in, out);
}
-
- public static Launcher createServerLauncher(
- IToolingApiServer server, InputStream in, OutputStream out) {
- return createIOLauncher(server, IToolingApiClient.class, in, out);
- }
}
diff --git a/settings.gradle b/settings.gradle
index 00ce7150e..8df61a0cc 100755
--- a/settings.gradle
+++ b/settings.gradle
@@ -27,3 +27,4 @@ include ':build-tools:tooling-api-model'
include ':build-tools:xml-dom'
include ':build-tools:xml-formatter'
include ':build-tools:fuzzysearch'
+include ':build-tools:builder-model-impl'