diff --git a/app/build.gradle b/app/build.gradle index 95f2eaf4a..df768c689 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -114,11 +114,8 @@ dependencies { implementation 'com.github.piruin:quickaction:2.4.3' implementation 'com.github.Rosemoe:BlockLinkedList:1.0.0' implementation 'com.github.smart-fun:XmlToJson:1.5.1' - - // Dependencies for java completion implementation 'com.google.code.gson:gson:2.8.8' implementation 'com.google.guava:guava:30.1.1-android' - implementation 'org.antlr:antlr4-runtime:4.9.2' implementation 'org.jsoup:jsoup:1.14.2' @@ -143,12 +140,14 @@ dependencies { implementation 'com.google.firebase:firebase-crashlytics' implementation 'com.google.firebase:firebase-messaging' - // Local projects + // Local projects here implementation project(':treeview') implementation project(':emulatorview') implementation project(':javapoet') implementation project(':lsp') implementation project(':layout-inflater') + implementation project(':lexers') + implementation project(':shell') // LSP4J implementation 'org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6571c3dca..49561f4a6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -68,7 +68,7 @@ diff --git a/app/src/main/assets/data/arm64-v8a/busybox b/app/src/main/assets/data/arm64-v8a/busybox index 974853f0a..4c933322b 100644 Binary files a/app/src/main/assets/data/arm64-v8a/busybox and b/app/src/main/assets/data/arm64-v8a/busybox differ diff --git a/app/src/main/assets/data/armeabi-v7a/busybox b/app/src/main/assets/data/armeabi-v7a/busybox new file mode 100644 index 000000000..56c71c4a7 Binary files /dev/null and b/app/src/main/assets/data/armeabi-v7a/busybox differ diff --git a/app/src/main/java/com/itsaky/androidide/DownloadActivity.java b/app/src/main/java/com/itsaky/androidide/DownloadActivity.java index f805ea7bb..e880c9c74 100644 --- a/app/src/main/java/com/itsaky/androidide/DownloadActivity.java +++ b/app/src/main/java/com/itsaky/androidide/DownloadActivity.java @@ -9,22 +9,28 @@ import android.view.View; import android.view.View.OnClickListener; import androidx.core.content.ContextCompat; import androidx.transition.TransitionManager; +import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.SizeUtils; +import com.blankj.utilcode.util.ThreadUtils; import com.google.android.material.button.MaterialButton; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.itsaky.androidide.app.StudioActivity; import com.itsaky.androidide.databinding.ActivityDownloadBinding; import com.itsaky.androidide.fragments.sheets.ProgressSheet; import com.itsaky.androidide.managers.PreferenceManager; -import com.itsaky.androidide.shell.ShellServer; +import com.itsaky.androidide.shell.IProcessExecutor; +import com.itsaky.androidide.shell.ProcessExecutorFactory; +import com.itsaky.androidide.shell.ProcessStreamsHolder; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.callables.ListDirectoryCallable; +import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.FileUtil; +import com.itsaky.androidide.utils.InputStreamLineReader; import com.itsaky.toaster.Toaster; import java.io.File; import java.io.FileFilter; +import java.io.IOException; import java.util.ArrayList; -import com.itsaky.androidide.utils.Environment; public class DownloadActivity extends StudioActivity { @@ -68,7 +74,7 @@ public class DownloadActivity extends StudioActivity { .build() .forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE); } catch (Throwable th){ - getApp().writeException(th); + LOG.error("Unable to start file picker", th); } } else if(id == binding.install.getId()) { if(choosenDir == null) { @@ -86,47 +92,119 @@ public class DownloadActivity extends StudioActivity { } private final String DONE = "DONE"; + private void installAll() { showProgress(); getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true); - final ShellServer server = getApp().newShell(__ -> checkInstalled(__)); - final StringBuilder sb = new StringBuilder(); - sb.append("cd $HOME && "); - sb.append("echo 'Installing...' && "); - File[] files = choosenDir.listFiles(ARCHIVE_FILTER); - if(files != null) { - for(File f : files) { - if(f.getName().endsWith(".tar.xz")) { - if(f.getName().startsWith("androidide-sysroot")) { - sb.append("cd $SYSROOT/.. && "); - } - sb.append("$BUSYBOX tar xvJf '" + f.getAbsolutePath() + "' && "); - sb.append("cd $HOME && "); - } else if(f.getName().endsWith(".zip")) { - sb.append("$BUSYBOX unzip '" + f.getAbsolutePath() + "' && "); - } - } + + try { + final File script = createExtractScript(); + final ProcessStreamsHolder holder = new ProcessStreamsHolder (); + final IProcessExecutor executor = ProcessExecutorFactory.commonExecutor(); + + executor.execAsync(holder, + code -> onInstallProcessExit (code), + true, + Environment.BUSYBOX.getAbsolutePath(), "sh",// We use busybox's sh, because Environment.SHELL is not installed yet... + script.getAbsolutePath() + ); + + final InputStreamLineReader reader = new InputStreamLineReader (holder.in, line -> onInstallationOutput (line)); + new Thread (reader).start(); + + } catch (DownloadActivity.InstallationException e) { + LOG.error("Installation error", e); + onInstallationFailed(e.exitCode); + } catch (IOException e) { + LOG.error("Installation error", e); + onInstallationFailed(5); // Exit code 5 : I/O Error } - sb.append("echo 'Cleaning unsupported flags in binaries...' && $BUSYBOX find $JAVA_HOME -type f -exec androidide-cleaner {} \\; && "); - sb.append("echo " + DONE); - - server.bgAppend(sb.toString()); } - private void checkInstalled(CharSequence out) { - if(out != null) { - final String line = out.toString().trim(); - runOnUiThread(() -> { - getProgressSheet().setSubMessage(line); - if(line.contains(DONE)) { - getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_INSTALLED, true); - showRestartNeeded(); - if(getProgressSheet().isShowing()) - getProgressSheet().dismiss(); - } - }); - } - } + private void onInstallationOutput (final String line) { + ThreadUtils.runOnUiThread(() -> { + getProgressSheet().setSubMessage(line); + }); + } + + private void onInstallProcessExit (final int code) { + ThreadUtils.runOnUiThread(() -> { + if (code == 0) { // 0 = normal execution + getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_INSTALLED, true); + showRestartNeeded(); + if(getProgressSheet().isShowing()) { + getProgressSheet().dismiss(); + } + } else { + onInstallationFailed (code); + } + }); + } + + private void onInstallationFailed(int code) { + // TODO Can this be improved by adding some animations? + + if(getProgressSheet().isShowing()) { + getProgressSheet().dismiss(); + } + + final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder (this, R.style.AppTheme_MaterialAlertDialog); + builder.setTitle(R.string.title_installation_failed); + builder.setMessage(getString(R.string.msg_installation_failed, code)); + builder.setPositiveButton(android.R.string.ok, (d, w) -> { + d.dismiss(); + finishAffinity(); + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + } + + private File createExtractScript() throws DownloadActivity.InstallationException { + final StringBuilder sb = new StringBuilder (); + sb.append("cd"); + joiner(sb); + sb.append("echo 'Installing...'"); + joiner(sb); + + File[] files = choosenDir.listFiles(ARCHIVE_FILTER); + + if (files == null || files.length <= 0) { + throw new InstallationException (2); + } + + for (File f : files) { + if(f.getName().endsWith(".tar.xz")) { + if(f.getName().startsWith("androidide-sysroot")) { + sb.append("cd $SYSROOT/.."); + joiner(sb); + } + sb.append("$BUSYBOX tar xvJf '" + f.getAbsolutePath() + "'"); + joiner(sb); + sb.append("cd $HOME"); + joiner(sb); + } else if(f.getName().endsWith(".zip")) { + sb.append("$BUSYBOX unzip '" + f.getAbsolutePath() + "'"); + joiner(sb); + } + } + + sb.append("echo 'Cleaning unsupported flags in binaries...'"); + joiner(sb); + sb.append("$BUSYBOX find $JAVA_HOME -type f -exec androidide-cleaner {} \\;"); + joiner(sb); + sb.append("echo " + DONE); + + final File script = new File (Environment.TMP_DIR, "extract_tools.sh"); + if (!FileIOUtils.writeFileFromString(script, sb.toString())) { + throw new InstallationException (2); + } + + return script; + } + + private void joiner (StringBuilder sb) { + sb.append(" && "); + } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { @@ -200,4 +278,11 @@ public class DownloadActivity extends StudioActivity { return p1.isFile() && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip")); } }; + + private class InstallationException extends Exception { + private final int exitCode; + public InstallationException(int exitCode) { + this.exitCode = exitCode; + } + } } diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index 3681f4502..f1a9fc63b 100644 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -56,15 +56,15 @@ import com.itsaky.androidide.language.logs.LogLanguageImpl; import com.itsaky.androidide.lsp.LSP; import com.itsaky.androidide.lsp.LSPProvider; import com.itsaky.androidide.managers.PreferenceManager; -import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.project.AndroidProject; import com.itsaky.androidide.models.DiagnosticGroup; import com.itsaky.androidide.models.LogLine; import com.itsaky.androidide.models.SaveResult; import com.itsaky.androidide.models.SearchResult; import com.itsaky.androidide.models.SheetOption; -import com.itsaky.androidide.models.project.IDEProject; +import com.itsaky.androidide.project.IDEProject; import com.itsaky.androidide.project.ProjectResourceFinder; -import com.itsaky.androidide.receivers.LogReceiver; +import com.itsaky.androidide.services.LogReceiver; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.shell.ShellServer; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; @@ -98,6 +98,7 @@ import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DidChangeConfigurationParams; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.Position; +import com.itsaky.androidide.lsp.IDELanguageClientImpl; public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener, TabLayout.OnTabSelectedListener, @@ -1199,9 +1200,14 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F } private void startServices() { + + if (!IDELanguageClientImpl.isInitialized()) { + IDELanguageClientImpl.initialize(this); + } + // Actually, we don't need to start FileOptionsHandler // Because it would work anyway - // But it's a good practice to call the start() method + // But still we do... mFileOptionsHandler.start(); mBuildServiceHandler.start(); diff --git a/app/src/main/java/com/itsaky/androidide/MainActivity.java b/app/src/main/java/com/itsaky/androidide/MainActivity.java index aa6cdd616..79431b41c 100644 --- a/app/src/main/java/com/itsaky/androidide/MainActivity.java +++ b/app/src/main/java/com/itsaky/androidide/MainActivity.java @@ -14,10 +14,10 @@ import com.itsaky.androidide.databinding.LayoutCreateProjectContentBinding; import com.itsaky.androidide.fragments.sheets.ProgressSheet; import com.itsaky.androidide.interfaces.ProjectWriterCallback; import com.itsaky.androidide.managers.PreferenceManager; -import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.project.AndroidProject; import com.itsaky.androidide.models.NewProjectDetails; import com.itsaky.androidide.models.ProjectTemplate; -import com.itsaky.androidide.project.utils.ProjectFinder; +import com.itsaky.androidide.utils.ProjectFinder; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.callables.ProjectCreatorCallable; import com.itsaky.androidide.utils.TransformUtils; diff --git a/app/src/main/java/com/itsaky/androidide/adapters/EditorPagerAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/EditorPagerAdapter.java index ae457c7c5..1a87dd7a7 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/EditorPagerAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/EditorPagerAdapter.java @@ -4,7 +4,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentStatePagerAdapter; import com.itsaky.androidide.fragments.EditorFragment; -import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.project.AndroidProject; import com.itsaky.androidide.models.SaveResult; import com.itsaky.androidide.utils.Logger; import java.io.File; diff --git a/app/src/main/java/com/itsaky/androidide/adapters/ModuleInfoAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/ModuleInfoAdapter.java index 94da13de3..3880b8d67 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/ModuleInfoAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/ModuleInfoAdapter.java @@ -7,7 +7,7 @@ import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.recyclerview.widget.RecyclerView; import com.itsaky.androidide.databinding.LayoutModuleInfoItemBinding; -import com.itsaky.androidide.models.project.IDEModule; +import com.itsaky.androidide.project.IDEModule; import java.util.ArrayList; import java.util.List; import java.util.Locale; diff --git a/app/src/main/java/com/itsaky/androidide/adapters/ProjectsListAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/ProjectsListAdapter.java index e0636252c..e81062fe9 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/ProjectsListAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/ProjectsListAdapter.java @@ -8,7 +8,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.itsaky.androidide.R; import com.itsaky.androidide.databinding.LayoutProjectsItemBinding; -import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.project.AndroidProject; import java.io.File; import java.util.ArrayList; import java.util.Collections; diff --git a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java index f492c0071..e9d0575d8 100644 --- a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java @@ -213,7 +213,7 @@ public class StudioApp extends MultiDexApplication { } public ShellServer newShell(ShellServer.Callback callback, boolean redirectErrors) { - ShellServer shellServer = new ShellServer(callback, "sh", Environment.mkdirIfNotExits(getRootDir()).getAbsolutePath(), redirectErrors); + ShellServer shellServer = new ShellServer(callback, "sh", Environment.mkdirIfNotExits(getRootDir()).getAbsolutePath(), Environment.getEnvironment(true), redirectErrors); shellServer.start(); return shellServer; } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java index d4fa9f1bb..0b074e07f 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java @@ -12,9 +12,9 @@ import com.itsaky.androidide.fragments.preferences.EditorPreferences; import com.itsaky.androidide.language.groovy.GroovyLanguage; import com.itsaky.androidide.language.java.JavaLanguage; import com.itsaky.androidide.language.xml.XMLLanguage; -import com.itsaky.androidide.language.xml.lexer.XMLLexer; +import com.itsaky.androidide.lexers.xml.XMLLexer; import com.itsaky.androidide.managers.PreferenceManager; -import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.project.AndroidProject; import com.itsaky.androidide.models.ConstantsBridge; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; import com.itsaky.androidide.tasks.TaskExecutor; diff --git a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java index 27212bbe3..436b33a44 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java @@ -16,7 +16,7 @@ import com.itsaky.androidide.R; import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.databinding.LayoutEditorFileTreeBinding; -import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.project.AndroidProject; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.callables.FileTreeCallable; import com.itsaky.androidide.utils.Environment; diff --git a/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java index e11766a38..6282156be 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java @@ -14,7 +14,7 @@ import com.itsaky.androidide.EditorActivity; import com.itsaky.androidide.adapters.ProjectsListAdapter; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.databinding.LayoutProjectsBinding; -import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.project.AndroidProject; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.utils.FileUtil; import fr.arnaudguyon.xmltojsonlib.XmlToJson; @@ -26,7 +26,7 @@ import java.util.concurrent.Callable; import java.util.regex.Pattern; import org.json.JSONException; import org.json.JSONObject; -import com.itsaky.androidide.project.utils.ProjectFinder; +import com.itsaky.androidide.utils.ProjectFinder; public class ProjectsFragment extends BaseFragment { diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProjectInfoSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProjectInfoSheet.java index 8b798ae86..a9b99b653 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProjectInfoSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProjectInfoSheet.java @@ -12,7 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import com.bumptech.glide.Glide; import com.itsaky.androidide.adapters.ModuleInfoAdapter; import com.itsaky.androidide.databinding.LayoutProjectDetailsBinding; -import com.itsaky.androidide.models.project.IDEProject; +import com.itsaky.androidide.project.IDEProject; import java.io.File; import java.util.Locale; diff --git a/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java b/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java index b61f60bfd..8b88436c2 100644 --- a/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java +++ b/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java @@ -4,9 +4,9 @@ import android.view.View; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.itsaky.androidide.R; import com.itsaky.androidide.managers.PreferenceManager; -import com.itsaky.androidide.models.AndroidProject; -import com.itsaky.androidide.models.project.IDEModule; -import com.itsaky.androidide.models.project.IDEProject; +import com.itsaky.androidide.project.AndroidProject; +import com.itsaky.androidide.project.IDEModule; +import com.itsaky.androidide.project.IDEProject; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.tasks.GradleTask; import com.itsaky.androidide.tasks.gradle.build.ApkGeneratingTask; diff --git a/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java b/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java index d0eef31d6..cfd2af6f7 100644 --- a/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java +++ b/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java @@ -1,8 +1,8 @@ package com.itsaky.androidide.handlers; import com.itsaky.androidide.EditorActivity; -import com.itsaky.androidide.models.AndroidProject; -import com.itsaky.androidide.models.project.IDEProject; +import com.itsaky.androidide.project.AndroidProject; +import com.itsaky.androidide.project.IDEProject; import com.itsaky.androidide.utils.Logger; /** diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java index 4ded86d40..cb5036bd9 100644 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java @@ -14,18 +14,18 @@ import org.eclipse.lsp4j.Diagnostic; public class GroovyAnalyzer extends io.github.rosemoe.editor.langs.AbstractCodeAnalyzer { private final EditorLanguage language; + public GroovyAnalyzer (EditorLanguage language) { this.language = language; } - @Override public void analyze(IDELanguageServer server, File file, CharSequence content, TextAnalyzeResult colors, TextAnalyzer.AnalyzeThread.Delegate delegate) { try { GroovyLexerImpl lexer = new GroovyLexerImpl(this.language, (Content) content, colors); lexer.init(); - // Adding spans, hex colors, block lines and all is handled by JavaLexerImpl + // Adding spans, hex colors, block lines and all is handled by GroovyLexerImpl while (delegate.shouldAnalyze()) { // null = EOF if(lexer.nextToken() == null) diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java index e36bf7edc..35dbd1d1d 100644 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java @@ -1,8 +1,8 @@ package com.itsaky.androidide.language.groovy; import com.itsaky.androidide.language.BaseLanguage; -import com.itsaky.androidide.language.groovy.lexer.GroovyLexer; import com.itsaky.androidide.language.java.manager.JavaCharacter; +import com.itsaky.androidide.lexers.groovy.GroovyLexer; import com.itsaky.androidide.lsp.LSPProvider; import com.itsaky.lsp.services.IDELanguageServer; import io.github.rosemoe.editor.interfaces.AutoCompleteProvider; diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java index 6d7ce67ac..b20fd9c50 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java @@ -1,7 +1,7 @@ package com.itsaky.androidide.language.java; -import com.itsaky.androidide.antlr4.java.JavaLexer; -import com.itsaky.androidide.antlr4.java.JavaParser; +import com.itsaky.androidide.lexers.java.JavaLexer; +import com.itsaky.androidide.lexers.java.JavaParser; import com.itsaky.androidide.language.BaseLanguage; import com.itsaky.androidide.language.java.manager.JavaCharacter; import com.itsaky.androidide.lsp.LSPProvider; diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java index 8bea6bf9f..fe41e4786 100644 --- a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java @@ -1,9 +1,9 @@ package com.itsaky.androidide.language.xml; import android.graphics.Color; -import com.itsaky.androidide.language.xml.lexer.XMLLexer; +import com.itsaky.androidide.lexers.xml.XMLLexer; +import com.itsaky.lsp.SemanticHighlight; import com.itsaky.lsp.services.IDELanguageServer; -import io.github.rosemoe.editor.interfaces.CodeAnalyzer; import io.github.rosemoe.editor.struct.HexColor; import io.github.rosemoe.editor.text.CharPosition; import io.github.rosemoe.editor.text.TextAnalyzeResult; @@ -12,13 +12,12 @@ import io.github.rosemoe.editor.widget.EditorColorScheme; import java.io.File; import java.io.StringReader; import java.util.HashMap; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CodePointCharStream; import org.antlr.v4.runtime.Token; -import com.itsaky.lsp.SemanticHighlight; -import java.util.Map; import org.eclipse.lsp4j.Diagnostic; public class XMLAnalyzer extends io.github.rosemoe.editor.langs.AbstractCodeAnalyzer { @@ -87,15 +86,15 @@ public class XMLAnalyzer extends io.github.rosemoe.editor.langs.AbstractCodeAnal } catch (Throwable th) { } } - + @Override public void setSemanticHighlights(SemanticHighlight highlights) { } - + @Override public void updateDiagnostics(Map> diagnostics) { } - + private void addHexColorIfPresent(Token token, HashMap lineColors, int line, int column) { Matcher m = HEX.matcher(token.getText()); if (m.find()) { @@ -104,17 +103,11 @@ public class XMLAnalyzer extends io.github.rosemoe.editor.langs.AbstractCodeAnal final CharPosition end = new CharPosition(line, start.column + (m.end() - m.start())); final HexColor key = new HexColor(start, end); final Integer value = Color.parseColor(token.getText().substring(m.start(), m.end())); - - // Null checks - start.getClass(); - end.getClass(); - key.getClass(); - value.getClass(); - + lineColors.put(key, value); } catch (Throwable th) {} } } - + private final Pattern HEX = Pattern.compile("#[a-fA-F0-9]{3,8}"); } diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java b/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java index 6f5c995ff..c46796f74 100644 --- a/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java @@ -2,7 +2,7 @@ package com.itsaky.androidide.language.xml; import com.itsaky.androidide.language.BaseLanguage; import com.itsaky.androidide.language.java.manager.JavaCharacter; -import com.itsaky.androidide.language.xml.lexer.XMLLexer; +import com.itsaky.androidide.lexers.xml.XMLLexer; import com.itsaky.androidide.lsp.LSPProvider; import com.itsaky.lsp.services.IDELanguageServer; import io.github.rosemoe.editor.interfaces.AutoCompleteProvider; diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/completion/XMLCompletionService.java b/app/src/main/java/com/itsaky/androidide/language/xml/completion/XMLCompletionService.java index 7517afcbd..a437a69c8 100644 --- a/app/src/main/java/com/itsaky/androidide/language/xml/completion/XMLCompletionService.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/completion/XMLCompletionService.java @@ -2,7 +2,7 @@ package com.itsaky.androidide.language.xml.completion; import com.google.gson.JsonObject; import com.itsaky.androidide.app.StudioApp; -import com.itsaky.androidide.language.xml.lexer.XMLLexer; +import com.itsaky.androidide.lexers.xml.XMLLexer; import com.itsaky.attrinfo.AttrInfo; import com.itsaky.attrinfo.models.Attr; import com.itsaky.widgets.WidgetInfo; diff --git a/app/src/main/java/com/itsaky/androidide/lsp/AsynchronousSocketLauncher.java b/app/src/main/java/com/itsaky/androidide/lsp/AsynchronousSocketLauncher.java index 2b4a47b7f..f39a6c6cf 100644 --- a/app/src/main/java/com/itsaky/androidide/lsp/AsynchronousSocketLauncher.java +++ b/app/src/main/java/com/itsaky/androidide/lsp/AsynchronousSocketLauncher.java @@ -30,8 +30,6 @@ public class AsynchronousSocketLauncher extends LSPClientLauncher { serverSocket = AsynchronousServerSocketChannel.open(); serverSocket.bind(new InetSocketAddress(this.port)); - languageClient.startServerViaCommand(); - final AsynchronousSocketChannel server = serverSocket.accept().get(); final OutputStream outWriter = new OutputStreamWrapper(Channels.newOutputStream(server)); final InputStream inReader = Channels.newInputStream(server); @@ -40,8 +38,6 @@ public class AsynchronousSocketLauncher extends LSPClientLauncher { this.listeningFuture = launcher.startListening(); this.server = launcher.getRemoteProxy(); - this.languageClient.onServerConnected(this.server); - try { listeningFuture.get(); } catch (Throwable th) {} @@ -62,7 +58,5 @@ public class AsynchronousSocketLauncher extends LSPClientLauncher { } CloseUtils.closeIOQuietly(serverSocket); - - this.languageClient.onServerDisconnected(); } } diff --git a/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java b/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java index 78e385611..9ef7f2f71 100644 --- a/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java +++ b/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java @@ -61,28 +61,57 @@ import org.eclipse.lsp4j.SignatureHelp; import org.eclipse.lsp4j.SignatureInformation; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.SetTraceParams; +import org.eclipse.lsp4j.ApplyWorkspaceEditResponse; +import org.eclipse.lsp4j.WorkDoneProgressCreateParams; +import org.eclipse.lsp4j.ProgressParams; +import org.eclipse.lsp4j.ConfigurationParams; +import org.eclipse.lsp4j.RegistrationParams; +import org.eclipse.lsp4j.ApplyWorkspaceEditParams; +import org.eclipse.lsp4j.MessageActionItem; +import org.eclipse.lsp4j.WorkspaceFolder; +import org.eclipse.lsp4j.ShowMessageRequestParams; +import org.eclipse.lsp4j.UnregistrationParams; /** * AndroidIDE specific implementation of the LanguageClient */ -public abstract class IDELanguageClientImpl implements IDELanguageClient { - +public class IDELanguageClientImpl implements IDELanguageClient { + protected static final Gson gson = new Gson(); protected static final Logger LOG = Logger.instance("AbstractLanguageClient"); - - protected EditorActivityProvider activityProvider; + public static final int DIAGNOSTIC_TRANSITION_DURATION = 80; private final Map> diagnostics = new HashMap<>(); - private final StarterListener starterListener; - private final OnConnectedListener onConnectedListener; - + private static IDELanguageClientImpl mInstance; + + protected EditorActivityProvider activityProvider; private boolean isConnected; - public static final int DIAGNOSTIC_TRANSITION_DURATION = 80; - - public IDELanguageClientImpl(StarterListener starterListener, OnConnectedListener onConnectedListener) { - this.starterListener = starterListener; - this.onConnectedListener = onConnectedListener; + private IDELanguageClientImpl (EditorActivityProvider provider) { + setActivityProvider(provider); + } + + public static IDELanguageClientImpl getInstance () { + if (mInstance == null) { + throw new IllegalStateException ("Client not initialized"); + } + + return mInstance; + } + + public static IDELanguageClientImpl initialize (EditorActivityProvider provider) { + if (mInstance != null) { + throw new IllegalStateException ("Client is already initialized"); + } + + mInstance = new IDELanguageClientImpl (provider); + + return getInstance(); + } + + public static boolean isInitialized () { + return mInstance != null; } public void setActivityProvider(EditorActivityProvider provider) { @@ -95,33 +124,7 @@ public abstract class IDELanguageClientImpl implements IDELanguageClient { public boolean isConnected() { return isConnected; } - - /** - * Called when the LanguageServer is connected successfully - */ - protected void onServerConnected(IDELanguageServer server) { - this.isConnected = true; - if (onConnectedListener != null) - onConnectedListener.onConnected(server); - } - - /** - * Called when the connection to the LanguageServer was cancelled. - */ - protected void onServerDisconnected() { - this.isConnected = false; - } - - /** - * Called when the ServerSocket is started. At this point, the {@link LSPClient} waits for the server to connect.
- * This is (probably) the proper time to start your language server as ServerSocket is waiting for a client to connect. - */ - protected void startServerViaCommand() { - if (this.starterListener != null) { - starterListener.startServer(); - } - } - + protected EditorActivity activity() { if (activityProvider == null) return null; return activityProvider.provide(); @@ -602,25 +605,65 @@ public abstract class IDELanguageClientImpl implements IDELanguageClient { return new DiagnosticsAdapter(mapAsGroup(this.diagnostics), activity()); } - /** - * Reports connection progress - */ - protected void connectionReport(String message) { - + + /************************************************** + UNUSED METHODS + **************************************************/ + + @Override + public CompletableFuture applyEdit(ApplyWorkspaceEditParams p1) { + return null; } - /** - * Called when there was an error connecting to server. - */ - protected void connectionError(Throwable th) { - + @Override + public CompletableFuture> configuration(ConfigurationParams p1) { + return null; } - public static interface StarterListener { - void startServer(); + @Override + public CompletableFuture createProgress(WorkDoneProgressCreateParams p1) { + return null; } - public static interface OnConnectedListener { - void onConnected(IDELanguageServer server); + @Override + public void notifyProgress(ProgressParams p1) { + } + + @Override + public CompletableFuture refreshCodeLenses() { + return null; + } + + @Override + public CompletableFuture refreshSemanticTokens() { + return null; + } + + @Override + public CompletableFuture registerCapability(RegistrationParams p1) { + return null; + } + + @Override + public void setTrace(SetTraceParams p1) { + } + + @Override + public void showMessage(MessageParams p1) { + } + + @Override + public CompletableFuture showMessageRequest(ShowMessageRequestParams p1) { + return null; + } + + @Override + public CompletableFuture unregisterCapability(UnregistrationParams p1) { + return null; + } + + @Override + public CompletableFuture> workspaceFolders() { + return null; } } diff --git a/app/src/main/java/com/itsaky/androidide/lsp/LSPClientLauncher.java b/app/src/main/java/com/itsaky/androidide/lsp/LSPClientLauncher.java index 72942e26b..141ec2787 100644 --- a/app/src/main/java/com/itsaky/androidide/lsp/LSPClientLauncher.java +++ b/app/src/main/java/com/itsaky/androidide/lsp/LSPClientLauncher.java @@ -53,7 +53,7 @@ public abstract class LSPClientLauncher extends Thread { try { launch(); } catch (Throwable th) { - languageClient.connectionError(th); + // TODO Log this } } diff --git a/app/src/main/java/com/itsaky/androidide/lsp/LSPProvider.java b/app/src/main/java/com/itsaky/androidide/lsp/LSPProvider.java index 189923aa8..ce3b8413f 100644 --- a/app/src/main/java/com/itsaky/androidide/lsp/LSPProvider.java +++ b/app/src/main/java/com/itsaky/androidide/lsp/LSPProvider.java @@ -1,5 +1,6 @@ package com.itsaky.androidide.lsp; +import com.itsaky.lsp.services.IDELanguageClient; import com.itsaky.lsp.services.IDELanguageServer; import java.util.HashMap; import java.util.Map; @@ -54,10 +55,6 @@ public class LSPProvider { capabilitiesMap.put(languageCode, capabilities); } - public static void setClientForLanguage(String languageCode, IDELanguageClientImpl client) { - clientMap.put(languageCode, client); - } - public static Map getAvailableServers() { return serverMap; } @@ -66,10 +63,6 @@ public class LSPProvider { return capabilitiesMap; } - public static Map getAllServerClients() { - return clientMap; - } - public static IDELanguageServer getServerForLanguage(String languageCode) { return languageCode == null ? null : serverMap.get(languageCode); } @@ -78,7 +71,7 @@ public class LSPProvider { return language == null ? null : capabilitiesMap.get(language); } - public static IDELanguageClientImpl getClientForLanguage(String language) { - return language == null ? null : clientMap.get(language); + public static IDELanguageClientImpl getClient() { + return IDELanguageClientImpl.isInitialized() ? IDELanguageClientImpl.getInstance() : null; } } diff --git a/app/src/main/java/com/itsaky/androidide/lsp/StandardStreamsLauncher.java b/app/src/main/java/com/itsaky/androidide/lsp/StandardStreamsLauncher.java index 94ba446de..2f3b9cbbd 100644 --- a/app/src/main/java/com/itsaky/androidide/lsp/StandardStreamsLauncher.java +++ b/app/src/main/java/com/itsaky/androidide/lsp/StandardStreamsLauncher.java @@ -13,13 +13,15 @@ public class StandardStreamsLauncher extends LSPClientLauncher { private final InputStream in; private final OutputStream out; + private final String langCode; private Future listening; - public StandardStreamsLauncher(IDELanguageClientImpl client, InputStream in, OutputStream out) { + public StandardStreamsLauncher(IDELanguageClientImpl client, InputStream in, OutputStream out, String langCode) { super(client); this.in = new BufferedInputStream( in ); this.out = new BufferedOutputStream( out ); + this.langCode = langCode; } @Override @@ -29,7 +31,7 @@ public class StandardStreamsLauncher extends LSPClientLauncher { this.server = server.getRemoteProxy(); this.listening = server.startListening(); - languageClient.onServerConnected(this.server); + LSPProvider.setLanguageServer(this.langCode, this.server); try { listening.get(); @@ -52,7 +54,5 @@ public class StandardStreamsLauncher extends LSPClientLauncher { listening.cancel(true); } CloseUtils.closeIO(in, out); - - languageClient.onServerDisconnected(); } } diff --git a/app/src/main/java/com/itsaky/androidide/lsp/client/java/JavaLanguageClient.java b/app/src/main/java/com/itsaky/androidide/lsp/client/java/JavaLanguageClient.java deleted file mode 100644 index c79da87b3..000000000 --- a/app/src/main/java/com/itsaky/androidide/lsp/client/java/JavaLanguageClient.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.itsaky.androidide.lsp.client.java; - -import com.itsaky.androidide.lsp.IDELanguageClientImpl; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import org.eclipse.lsp4j.ApplyWorkspaceEditParams; -import org.eclipse.lsp4j.ApplyWorkspaceEditResponse; -import org.eclipse.lsp4j.ConfigurationParams; -import org.eclipse.lsp4j.LogTraceParams; -import org.eclipse.lsp4j.MessageActionItem; -import org.eclipse.lsp4j.MessageParams; -import org.eclipse.lsp4j.ProgressParams; -import org.eclipse.lsp4j.PublishDiagnosticsParams; -import org.eclipse.lsp4j.RegistrationParams; -import org.eclipse.lsp4j.SetTraceParams; -import org.eclipse.lsp4j.ShowDocumentParams; -import org.eclipse.lsp4j.ShowDocumentResult; -import org.eclipse.lsp4j.ShowMessageRequestParams; -import org.eclipse.lsp4j.UnregistrationParams; -import org.eclipse.lsp4j.WorkDoneProgressCreateParams; -import org.eclipse.lsp4j.WorkspaceFolder; - -public class JavaLanguageClient extends IDELanguageClientImpl { - - public JavaLanguageClient(StarterListener starterListener, OnConnectedListener onConnectedListener) { - super(starterListener, onConnectedListener); - } - - @Override - public CompletableFuture applyEdit(ApplyWorkspaceEditParams p1) { - return null; - } - - @Override - public CompletableFuture> configuration(ConfigurationParams p1) { - return null; - } - - @Override - public CompletableFuture createProgress(WorkDoneProgressCreateParams p1) { - return null; - } - - @Override - public void notifyProgress(ProgressParams p1) { - } - - @Override - public CompletableFuture refreshCodeLenses() { - return null; - } - - @Override - public CompletableFuture refreshSemanticTokens() { - return null; - } - - @Override - public CompletableFuture registerCapability(RegistrationParams p1) { - return null; - } - - public void setTrace(SetTraceParams p1) { - } - - @Override - public void showMessage(MessageParams p1) { - } - - @Override - public CompletableFuture showMessageRequest(ShowMessageRequestParams p1) { - return null; - } - - @Override - public CompletableFuture unregisterCapability(UnregistrationParams p1) { - return null; - } - - @Override - public CompletableFuture> workspaceFolders() { - return null; - } -} diff --git a/app/src/main/java/com/itsaky/androidide/lsp/handlers/JLSHandler.java b/app/src/main/java/com/itsaky/androidide/lsp/handlers/JLSHandler.java index d0fcc5304..44c3a5e32 100644 --- a/app/src/main/java/com/itsaky/androidide/lsp/handlers/JLSHandler.java +++ b/app/src/main/java/com/itsaky/androidide/lsp/handlers/JLSHandler.java @@ -1,15 +1,23 @@ package com.itsaky.androidide.lsp.handlers; import com.itsaky.androidide.app.StudioApp; +import com.itsaky.androidide.lsp.IDELanguageClientImpl; import com.itsaky.androidide.lsp.LSP; import com.itsaky.androidide.lsp.LSPClientLauncher; import com.itsaky.androidide.lsp.LSPProvider; import com.itsaky.androidide.lsp.StandardStreamsLauncher; -import com.itsaky.androidide.lsp.client.java.JavaLanguageClient; +import com.itsaky.androidide.shell.IProcessExecutor; +import com.itsaky.androidide.shell.IProcessExitListener; +import com.itsaky.androidide.shell.ProcessExecutorFactory; +import com.itsaky.androidide.shell.ProcessStreamsHolder; import com.itsaky.androidide.utils.Environment; +import com.itsaky.androidide.utils.FileUtil; +import com.itsaky.androidide.utils.InputStreamWriter; import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.services.IDELanguageServer; import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -21,18 +29,6 @@ import org.eclipse.lsp4j.WorkspaceFolder; public class JLSHandler implements LSPHandler { - /** - * Why not use {@link com.itsaky.androidide.shell.ShellServer ShellServer} instead? - * Because it will try to read from the process's input stream - * and at the same time, LSP4J will try to do the same. But, - * as {@code ShellServer} will be started first, LSP4J will not he able to read at all. - *

- * That's why we use a ProcessBuilder without redirecting {@code System.err} of the process - */ - private static ProcessBuilder mShellBuilder; - private static Process mShell; - - private static JavaLanguageClient javaClient; private static LSPClientLauncher mLauncher; private static final Logger LOG = Logger.instance("JLSHandler"); @@ -40,26 +36,32 @@ public class JLSHandler implements LSPHandler { @Override public void start(Runnable onStarted) { - if (mShell != null && mLauncher != null && mLauncher.isConnected()) { - return; - } - try { - mShellBuilder = new ProcessBuilder("/system/bin/sh"); - mShellBuilder.directory(Environment.HOME); - mShellBuilder.redirectErrorStream(false); - mShellBuilder.environment().putAll(Environment.getEnvironment(false)); + final IProcessExecutor executor = ProcessExecutorFactory.commonExecutor(); + final ProcessStreamsHolder holder = new ProcessStreamsHolder(); + final IProcessExitListener listener = new IProcessExitListener() { + @Override + public void onExit(int code) { + LOG.info("Java Language server terminated with exit code " + code); + shutdown(); + } + }; - mShell = mShellBuilder.start(); + executor.execAsync(holder, listener, false, Environment.JAVA.getAbsolutePath(), "-agentlib:hook2", "-jar", Environment.JLS_JAR.getAbsolutePath(), QUIET ? "--quiet" : ""); + startReader (holder.err); // holder.in is used by LSP4J - mShell.getOutputStream().write(("java -agentlib:hook2 -jar " + Environment.JLS_JAR.getAbsolutePath() + (QUIET ? " --quiet" : "") + "\n").getBytes()); - mShell.getOutputStream().flush(); + if (!IDELanguageClientImpl.isInitialized()) { + IDELanguageClientImpl.initialize(LSP.PROVIDER); + } - javaClient = new JavaLanguageClient(null, server -> storeServerInfoAndNotify(server, onStarted)); - javaClient.setActivityProvider(LSP.PROVIDER); - mLauncher = new StandardStreamsLauncher(javaClient, mShell.getInputStream(), mShell.getOutputStream()); + IDELanguageClientImpl client = IDELanguageClientImpl.getInstance(); + + mLauncher = new StandardStreamsLauncher(client, holder.in, holder.out, LSPProvider.LANGUAGE_JAVA); mLauncher.start(); - } catch (Throwable th) {} + + } catch (Throwable th) { + LOG.error("Unable to start Java Language server", th); + } } @Override @@ -89,6 +91,7 @@ public class JLSHandler implements LSPHandler { params.setCapabilities(LSP.getCapabilities()); CompletableFuture initResult = server.initialize(params); + try { InitializeResult result = initResult.get(); if(result.getCapabilities() != null) { @@ -106,8 +109,6 @@ public class JLSHandler implements LSPHandler { final IDELanguageServer server = LSPProvider.getServerForLanguage(LSPProvider.LANGUAGE_JAVA); if (server == null) return; - - // Initialized params do not have any parameters server.initialized(new InitializedParams()); } @@ -117,19 +118,16 @@ public class JLSHandler implements LSPHandler { if(mLauncher != null) { mLauncher.shutdown(); } - if(mShell != null) { - mShell.destroy(); - } + } + + private void startReader(InputStream in) throws FileNotFoundException { + final InputStreamWriter writer = new InputStreamWriter (in, getOutputFile()); + final Thread readerThread = new Thread (writer, "JavaLanguageServerOutputReader"); + readerThread.setDaemon(true); + readerThread.start(); } - /** - * Store the instance of Java Language Server in LSPProvider - */ - private void storeServerInfoAndNotify(IDELanguageServer server, Runnable startedListener) { - LSPProvider.setLanguageServer(LSPProvider.LANGUAGE_JAVA, server); - LSPProvider.setClientForLanguage(LSPProvider.LANGUAGE_JAVA, javaClient); - - if(startedListener != null) - startedListener.run(); + private File getOutputFile () { + return new File (FileUtil.getExternalStorageDir(), "ide_xlog/jls_output.txt"); } } diff --git a/app/src/main/java/com/itsaky/androidide/models/AndroidProject.java b/app/src/main/java/com/itsaky/androidide/project/AndroidProject.java similarity index 98% rename from app/src/main/java/com/itsaky/androidide/models/AndroidProject.java rename to app/src/main/java/com/itsaky/androidide/project/AndroidProject.java index 5f7461796..2cca97199 100644 --- a/app/src/main/java/com/itsaky/androidide/models/AndroidProject.java +++ b/app/src/main/java/com/itsaky/androidide/project/AndroidProject.java @@ -1,8 +1,7 @@ -package com.itsaky.androidide.models; +package com.itsaky.androidide.project; import android.os.Parcel; import android.os.Parcelable; -import androidx.core.util.Pair; import com.itsaky.androidide.app.StudioApp; import java.io.File; import java.util.ArrayList; diff --git a/app/src/main/java/com/itsaky/androidide/models/project/IDEModule.java b/app/src/main/java/com/itsaky/androidide/project/IDEModule.java similarity index 97% rename from app/src/main/java/com/itsaky/androidide/models/project/IDEModule.java rename to app/src/main/java/com/itsaky/androidide/project/IDEModule.java index 529098d6e..810cbe8f5 100644 --- a/app/src/main/java/com/itsaky/androidide/models/project/IDEModule.java +++ b/app/src/main/java/com/itsaky/androidide/project/IDEModule.java @@ -1,4 +1,4 @@ -package com.itsaky.androidide.models.project; +package com.itsaky.androidide.project; import com.google.gson.annotations.SerializedName; diff --git a/app/src/main/java/com/itsaky/androidide/models/project/IDEProject.java b/app/src/main/java/com/itsaky/androidide/project/IDEProject.java similarity index 98% rename from app/src/main/java/com/itsaky/androidide/models/project/IDEProject.java rename to app/src/main/java/com/itsaky/androidide/project/IDEProject.java index 2d3cfc628..13c099687 100644 --- a/app/src/main/java/com/itsaky/androidide/models/project/IDEProject.java +++ b/app/src/main/java/com/itsaky/androidide/project/IDEProject.java @@ -1,4 +1,5 @@ -package com.itsaky.androidide.models.project; +package com.itsaky.androidide.project; + import com.google.gson.annotations.SerializedName; import java.util.List; import java.util.ArrayList; diff --git a/app/src/main/java/com/itsaky/androidide/models/project/IDETask.java b/app/src/main/java/com/itsaky/androidide/project/IDETask.java similarity index 93% rename from app/src/main/java/com/itsaky/androidide/models/project/IDETask.java rename to app/src/main/java/com/itsaky/androidide/project/IDETask.java index 49e0d923e..b8b943d2b 100644 --- a/app/src/main/java/com/itsaky/androidide/models/project/IDETask.java +++ b/app/src/main/java/com/itsaky/androidide/project/IDETask.java @@ -1,4 +1,4 @@ -package com.itsaky.androidide.models.project; +package com.itsaky.androidide.project; import com.google.gson.annotations.SerializedName; diff --git a/app/src/main/java/com/itsaky/androidide/services/IDEService.java b/app/src/main/java/com/itsaky/androidide/services/IDEService.java index fa676365f..c3c885715 100644 --- a/app/src/main/java/com/itsaky/androidide/services/IDEService.java +++ b/app/src/main/java/com/itsaky/androidide/services/IDEService.java @@ -12,8 +12,8 @@ import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.lsp.LSP; import com.itsaky.androidide.managers.PreferenceManager; -import com.itsaky.androidide.models.project.IDEModule; -import com.itsaky.androidide.models.project.IDEProject; +import com.itsaky.androidide.project.IDEModule; +import com.itsaky.androidide.project.IDEProject; import com.itsaky.androidide.shell.ShellServer; import com.itsaky.androidide.tasks.GradleTask; import com.itsaky.androidide.tasks.gradle.BaseGradleTasks; diff --git a/app/src/main/java/com/itsaky/androidide/receivers/LogReceiver.java b/app/src/main/java/com/itsaky/androidide/services/LogReceiver.java similarity index 97% rename from app/src/main/java/com/itsaky/androidide/receivers/LogReceiver.java rename to app/src/main/java/com/itsaky/androidide/services/LogReceiver.java index a2a4cd33a..2b74857c4 100644 --- a/app/src/main/java/com/itsaky/androidide/receivers/LogReceiver.java +++ b/app/src/main/java/com/itsaky/androidide/services/LogReceiver.java @@ -1,4 +1,4 @@ -package com.itsaky.androidide.receivers; +package com.itsaky.androidide.services; import android.content.BroadcastReceiver; import android.content.Context; diff --git a/app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/JavaHighlighter.java b/app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/JavaHighlighter.java index dc31a582f..2397392af 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/JavaHighlighter.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/JavaHighlighter.java @@ -3,7 +3,7 @@ package com.itsaky.androidide.syntax.lexer.highlighters; import android.text.SpannableStringBuilder; import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; -import com.itsaky.androidide.antlr4.java.JavaLexer; +import com.itsaky.androidide.lexers.java.JavaLexer; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; import io.github.rosemoe.editor.widget.EditorColorScheme; import java.io.StringReader; diff --git a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/groovy/GroovyLexerImpl.java b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/groovy/GroovyLexerImpl.java index 0b8edcba8..01c563ede 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/groovy/GroovyLexerImpl.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/groovy/GroovyLexerImpl.java @@ -1,11 +1,12 @@ package com.itsaky.androidide.syntax.lexer.impls.groovy; -import com.itsaky.androidide.language.groovy.lexer.GroovyLexer; +import com.itsaky.androidide.lexers.groovy.GroovyLexer; import com.itsaky.androidide.syntax.lexer.Lexer; import com.itsaky.androidide.syntax.lexer.impls.BaseJavaLexer; import com.itsaky.androidide.syntax.lexer.tokens.Token; import com.itsaky.androidide.syntax.lexer.tokens.TokenType; import com.itsaky.androidide.utils.LSPUtils; +import io.github.rosemoe.editor.interfaces.EditorLanguage; import io.github.rosemoe.editor.interfaces.NewlineHandler; import io.github.rosemoe.editor.struct.BlockLine; import io.github.rosemoe.editor.text.CharPosition; @@ -21,7 +22,6 @@ import java.util.List; import java.util.Map; import org.antlr.v4.runtime.CharStreams; import org.eclipse.lsp4j.Range; -import io.github.rosemoe.editor.interfaces.EditorLanguage; public class GroovyLexerImpl extends BaseJavaLexer implements Lexer { @@ -54,7 +54,7 @@ public class GroovyLexerImpl extends BaseJavaLexer implements Lexer { @Override public void init() throws IOException { - lexer = new GroovyLexer(CharStreams.fromReader(new StringReader(content.toString()))); + lexer = new GroovyLexer(content.toString()); } @Override diff --git a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java index 57aebc379..1d57b1c9e 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java @@ -1,7 +1,7 @@ package com.itsaky.androidide.syntax.lexer.impls.java; import androidx.core.util.Pair; -import com.itsaky.androidide.antlr4.java.JavaLexer; +import com.itsaky.androidide.lexers.java.JavaLexer; import com.itsaky.androidide.models.ConstantsBridge; import com.itsaky.androidide.syntax.lexer.Lexer; import com.itsaky.androidide.syntax.lexer.impls.BaseJavaLexer; diff --git a/app/src/main/java/com/itsaky/androidide/utils/Environment.java b/app/src/main/java/com/itsaky/androidide/utils/Environment.java index 8611c3b0a..504d5dd32 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/Environment.java +++ b/app/src/main/java/com/itsaky/androidide/utils/Environment.java @@ -40,6 +40,7 @@ public final class Environment { public static final File JAVA; public static final File BUSYBOX; public static final File SHELL; + public static final File BUSYBOX_SH; public static File BOOTCLASSPATH; @@ -83,6 +84,7 @@ public final class Environment { JAVA = new File(JAVA_HOME, "bin/java"); BUSYBOX = new File(BINDIR, "busybox"); SHELL = new File(BINDIR, "sh.sh"); + BUSYBOX_SH = new File (BINDIR, "sh"); if(!JAVA.canExecute()) { JAVA.setExecutable(true); @@ -96,6 +98,7 @@ public final class Environment { SHELL.setExecutable(true); } + System.setProperty("user.home", HOME.getAbsolutePath()); System.setProperty("java.home", JAVA_HOME.getAbsolutePath()); } diff --git a/app/src/main/java/com/itsaky/androidide/utils/InputStreamLineReader.java b/app/src/main/java/com/itsaky/androidide/utils/InputStreamLineReader.java new file mode 100644 index 000000000..7cf1a762b --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/utils/InputStreamLineReader.java @@ -0,0 +1,36 @@ +package com.itsaky.androidide.utils; + +import java.io.InputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; + +/** + * Reads an input stream line by line + */ +public class InputStreamLineReader implements Runnable { + + private final InputStream in; + private final OnReadListener listener; + + public InputStreamLineReader(InputStream in, OnReadListener listener) { + this.in = in; + this.listener = listener; + } + + @Override + public void run() { + try { + final BufferedReader reader = new BufferedReader (new InputStreamReader (in)); + String line; + while ((line = reader.readLine()) != null && listener != null) { + listener.onRead(line); + } + } catch (Throwable th) { + // ignored + } + } + + public static interface OnReadListener { + void onRead (String line); + } +} diff --git a/app/src/main/java/com/itsaky/androidide/utils/InputStreamWriter.java b/app/src/main/java/com/itsaky/androidide/utils/InputStreamWriter.java new file mode 100644 index 000000000..2110891dc --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/utils/InputStreamWriter.java @@ -0,0 +1,46 @@ +package com.itsaky.androidide.utils; + +import java.io.File; +import java.io.InputStream; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.FileNotFoundException; +import java.io.BufferedReader; +import java.io.InputStreamReader; + +/** + * Writes an InputStream to a file + */ +public class InputStreamWriter implements Runnable { + + private final InputStream in; + private final File out; + + private final BufferedOutputStream os; + + public InputStreamWriter(InputStream in, File out) throws FileNotFoundException { + this.in = in; + this.out = out; + + this.os = new BufferedOutputStream (new FileOutputStream (out, true)); + } + + @Override + public void run() { + try { + final BufferedReader reader = new BufferedReader (new InputStreamReader (in)); + while (true) { + final int data = reader.read(); + if (data == -1) { + break; + } + + os.write(data); + os.flush(); + } + } catch (Throwable th) { + // ignored + } + } + +} diff --git a/app/src/main/java/com/itsaky/androidide/language/java/manager/JavaCharacter.java b/app/src/main/java/com/itsaky/androidide/utils/JavaCharacter.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/language/java/manager/JavaCharacter.java rename to app/src/main/java/com/itsaky/androidide/utils/JavaCharacter.java diff --git a/app/src/main/java/com/itsaky/androidide/project/utils/ProjectFinder.java b/app/src/main/java/com/itsaky/androidide/utils/ProjectFinder.java similarity index 98% rename from app/src/main/java/com/itsaky/androidide/project/utils/ProjectFinder.java rename to app/src/main/java/com/itsaky/androidide/utils/ProjectFinder.java index 5ee2a910a..4ce6424f4 100644 --- a/app/src/main/java/com/itsaky/androidide/project/utils/ProjectFinder.java +++ b/app/src/main/java/com/itsaky/androidide/utils/ProjectFinder.java @@ -1,8 +1,8 @@ -package com.itsaky.androidide.project.utils; +package com.itsaky.androidide.utils; import android.text.TextUtils; import com.blankj.utilcode.util.FileIOUtils; -import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.project.AndroidProject; import com.itsaky.androidide.utils.FileUtil; import fr.arnaudguyon.xmltojsonlib.XmlToJson; import java.io.File; diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java index 383df01a5..91d354266 100644 --- a/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java @@ -761,7 +761,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca } this.mLanguage = lang; this.mLanguageServer = mLanguage.getLanguageServer(); - this.mLanguageClient = LSPProvider.getClientForLanguage(mLanguage.getLanguageCode()); + this.mLanguageClient = LSPProvider.getClient(); // Update spanner if (mSpanner != null) { diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java index d8683420c..8be809005 100644 --- a/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java @@ -244,6 +244,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow { // Trigger signature help request if included in CompletionItem // TODO Don't rely on CompletionItem for requesting signature help + // If the insert text contains '(', automatically trigger this action mEditor.signatureHelp("("); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3e79c7082..e30b527ae 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,6 @@ + Installation failed + Installation process terminated with exit code %1$d. Make sure you downloaded the build tools from original sources.\n\nYou could try to clear data of AndroidIDE and install again. Failed to load SDK information Unable to preview layout! Preview Layout diff --git a/build.gradle b/build.gradle index 0f2d56ee2..0d896bf96 100644 --- a/build.gradle +++ b/build.gradle @@ -4,8 +4,8 @@ buildscript { buildTools = "31.0.0" minSdk = 24 targetSdk = 28 - versionCode = 1031211101 - versionName = "1.0.3.1-alpha" + versionCode = 2001211120 + versionName = "2.0-beta" packageName = "com.itsaky.androidide" javaSourceVersion = JavaVersion.VERSION_1_8 diff --git a/app/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButton.java b/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButton.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButton.java rename to emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButton.java diff --git a/app/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButtonState.java b/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButtonState.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButtonState.java rename to emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButtonState.java diff --git a/app/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeyButton.java b/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeyButton.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeyButton.java rename to emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeyButton.java diff --git a/app/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysConstants.java b/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysConstants.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysConstants.java rename to emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysConstants.java diff --git a/app/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysInfo.java b/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysInfo.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysInfo.java rename to emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysInfo.java diff --git a/app/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysView.java b/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysView.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysView.java rename to emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/VirtualKeysView.java diff --git a/lexers/.gitignore b/lexers/.gitignore new file mode 100644 index 000000000..56cc6425e --- /dev/null +++ b/lexers/.gitignore @@ -0,0 +1,85 @@ +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ diff --git a/lexers/build.gradle b/lexers/build.gradle new file mode 100644 index 000000000..cf219316b --- /dev/null +++ b/lexers/build.gradle @@ -0,0 +1,30 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdk + buildToolsVersion rootProject.ext.buildTools + + defaultConfig { + minSdkVersion rootProject.ext.minSdk + targetSdkVersion rootProject.ext.targetSdk + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + } + + compileOptions { + sourceCompatibility rootProject.ext.javaSourceVersion + targetCompatibility rootProject.ext.javaTargetVersion + } + + buildTypes { + release { + minifyEnabled true + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + api 'org.antlr:antlr4-runtime:4.9.2' +} diff --git a/lexers/proguard-rules.pro b/lexers/proguard-rules.pro new file mode 100644 index 000000000..0448f7080 --- /dev/null +++ b/lexers/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/bogdan/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/lexers/src/main/AndroidManifest.xml b/lexers/src/main/AndroidManifest.xml new file mode 100644 index 000000000..25aa8b741 --- /dev/null +++ b/lexers/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/lexer/GroovyLexer.g4 b/lexers/src/main/java/com/itsaky/androidide/lexers/groovy/GroovyLexer.g4 similarity index 100% rename from app/src/main/java/com/itsaky/androidide/language/groovy/lexer/GroovyLexer.g4 rename to lexers/src/main/java/com/itsaky/androidide/lexers/groovy/GroovyLexer.g4 diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/lexer/GroovyLexer.java b/lexers/src/main/java/com/itsaky/androidide/lexers/groovy/GroovyLexer.java similarity index 98% rename from app/src/main/java/com/itsaky/androidide/language/groovy/lexer/GroovyLexer.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/groovy/GroovyLexer.java index d891af434..8fba0b606 100644 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/lexer/GroovyLexer.java +++ b/lexers/src/main/java/com/itsaky/androidide/lexers/groovy/GroovyLexer.java @@ -1,14 +1,19 @@ // Generated from GroovyLexer.g4 by ANTLR 4.9.2 -package com.itsaky.androidide.language.groovy.lexer; +package com.itsaky.androidide.lexers.groovy; -import org.antlr.v4.runtime.Lexer; +import java.io.StringReader; import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.RuntimeMetaData; +import org.antlr.v4.runtime.Vocabulary; +import org.antlr.v4.runtime.VocabularyImpl; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.LexerATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.misc.*; +import java.io.IOException; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class GroovyLexer extends Lexer { @@ -141,8 +146,11 @@ public class GroovyLexer extends Lexer { public Vocabulary getVocabulary() { return VOCABULARY; } - - + + public GroovyLexer (String input) throws IOException { + this (CharStreams.fromReader(new StringReader(input))); + } + public GroovyLexer(CharStream input) { super(input); _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaLexer.g4 b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaLexer.g4 similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaLexer.g4 rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaLexer.g4 diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaLexer.interp b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaLexer.interp similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaLexer.interp rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaLexer.interp diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaLexer.java b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaLexer.java similarity index 99% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaLexer.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaLexer.java index 0473fb446..738a70a4a 100644 --- a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaLexer.java +++ b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaLexer.java @@ -1,5 +1,5 @@ // Generated from JavaLexer.g4 by ANTLR 4.9.2 -package com.itsaky.androidide.antlr4.java; +package com.itsaky.androidide.lexers.java; import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Token; @@ -506,4 +506,4 @@ public class JavaLexer extends Lexer { _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaLexer.tokens b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaLexer.tokens similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaLexer.tokens rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaLexer.tokens diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParser.g4 b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParser.g4 similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParser.g4 rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParser.g4 diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParser.interp b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParser.interp similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParser.interp rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParser.interp diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParser.java b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParser.java similarity index 99% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParser.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParser.java index c63c47f2d..c0fe0a468 100644 --- a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParser.java +++ b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParser.java @@ -1,5 +1,5 @@ // Generated from JavaParser.g4 by ANTLR 4.9.2 -package com.itsaky.androidide.antlr4.java; +package com.itsaky.androidide.lexers.java; import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; import org.antlr.v4.runtime.*; @@ -10215,4 +10215,4 @@ public class JavaParser extends Parser { _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParser.tokens b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParser.tokens similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParser.tokens rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParser.tokens diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserBaseListener.java b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserBaseListener.java similarity index 99% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserBaseListener.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserBaseListener.java index de7f5e68e..3b5db04f3 100644 --- a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserBaseListener.java +++ b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserBaseListener.java @@ -1,5 +1,5 @@ // Generated from JavaParser.g4 by ANTLR 4.9.2 -package com.itsaky.androidide.antlr4.java; +package com.itsaky.androidide.lexers.java; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ErrorNode; @@ -1296,4 +1296,4 @@ public class JavaParserBaseListener implements JavaParserListener { *

The default implementation does nothing.

*/ @Override public void visitErrorNode(ErrorNode node) { } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserBaseVisitor.java b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserBaseVisitor.java similarity index 99% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserBaseVisitor.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserBaseVisitor.java index 48b556bec..d542243b8 100644 --- a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserBaseVisitor.java +++ b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserBaseVisitor.java @@ -1,5 +1,5 @@ // Generated from JavaParser.g4 by ANTLR 4.9.2 -package com.itsaky.androidide.antlr4.java; +package com.itsaky.androidide.lexers.java; import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; /** @@ -746,4 +746,4 @@ public class JavaParserBaseVisitor extends AbstractParseTreeVisitor implem * {@link #visitChildren} on {@code ctx}.

*/ @Override public T visitArguments(JavaParser.ArgumentsContext ctx) { return visitChildren(ctx); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserListener.java b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserListener.java similarity index 99% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserListener.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserListener.java index f5a0aaf72..64bf852d0 100644 --- a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserListener.java +++ b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserListener.java @@ -1,5 +1,5 @@ // Generated from JavaParser.g4 by ANTLR 4.9.2 -package com.itsaky.androidide.antlr4.java; +package com.itsaky.androidide.lexers.java; import org.antlr.v4.runtime.tree.ParseTreeListener; /** @@ -1057,4 +1057,4 @@ public interface JavaParserListener extends ParseTreeListener { * @param ctx the parse tree */ void exitArguments(JavaParser.ArgumentsContext ctx); -} \ No newline at end of file +} diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserVisitor.java b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserVisitor.java similarity index 99% rename from app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserVisitor.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserVisitor.java index 6811b741a..8353ebcb4 100644 --- a/app/src/main/java/com/itsaky/androidide/antlr4/java/JavaParserVisitor.java +++ b/lexers/src/main/java/com/itsaky/androidide/lexers/java/JavaParserVisitor.java @@ -1,5 +1,5 @@ // Generated from JavaParser.g4 by ANTLR 4.9.2 -package com.itsaky.androidide.antlr4.java; +package com.itsaky.androidide.lexers.java; import org.antlr.v4.runtime.tree.ParseTreeVisitor; /** @@ -640,4 +640,4 @@ public interface JavaParserVisitor extends ParseTreeVisitor { * @return the visitor result */ T visitArguments(JavaParser.ArgumentsContext ctx); -} \ No newline at end of file +} diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocLexer.g4 b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocLexer.g4 similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocLexer.g4 rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocLexer.g4 diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocLexer.interp b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocLexer.interp similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocLexer.interp rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocLexer.interp diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocLexer.java b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocLexer.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocLexer.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocLexer.java diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocLexer.tokens b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocLexer.tokens similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocLexer.tokens rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocLexer.tokens diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParser.g4 b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParser.g4 similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParser.g4 rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParser.g4 diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParser.interp b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParser.interp similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParser.interp rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParser.interp diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParser.java b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParser.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParser.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParser.java diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParser.tokens b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParser.tokens similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParser.tokens rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParser.tokens diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParserBaseListener.java b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParserBaseListener.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParserBaseListener.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParserBaseListener.java diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParserBaseVisitor.java b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParserBaseVisitor.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParserBaseVisitor.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParserBaseVisitor.java diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParserListener.java b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParserListener.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParserListener.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParserListener.java diff --git a/app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParserVisitor.java b/lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParserVisitor.java similarity index 100% rename from app/src/main/java/com/itsaky/androidide/antlr4/javadoc/JavadocParserVisitor.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/javadoc/JavadocParserVisitor.java diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/lexer/XMLLexer.java b/lexers/src/main/java/com/itsaky/androidide/lexers/xml/XMLLexer.java similarity index 94% rename from app/src/main/java/com/itsaky/androidide/language/xml/lexer/XMLLexer.java rename to lexers/src/main/java/com/itsaky/androidide/lexers/xml/XMLLexer.java index 15549c503..469463dcb 100644 --- a/app/src/main/java/com/itsaky/androidide/language/xml/lexer/XMLLexer.java +++ b/lexers/src/main/java/com/itsaky/androidide/lexers/xml/XMLLexer.java @@ -1,14 +1,19 @@ -package com.itsaky.androidide.language.xml.lexer; +package com.itsaky.androidide.lexers.xml; // Generated from XMLLexer.g4 by ANTLR 4.9.2 -import org.antlr.v4.runtime.Lexer; +import java.io.StringReader; import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.RuntimeMetaData; +import org.antlr.v4.runtime.Vocabulary; +import org.antlr.v4.runtime.VocabularyImpl; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.LexerATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.misc.*; +import java.io.IOException; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class XMLLexer extends Lexer { @@ -88,8 +93,11 @@ public class XMLLexer extends Lexer { public Vocabulary getVocabulary() { return VOCABULARY; } - - + + public XMLLexer (String input) throws IOException { + this (CharStreams.fromReader(new StringReader(input))); + } + public XMLLexer(CharStream input) { super(input); _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); diff --git a/settings.gradle b/settings.gradle index b12623d19..6a0246d40 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,5 +5,7 @@ include ':javapoet' include ':lsp' include ':sdk-info' include ':layout-inflater' +include ':lexers' +include ':shell' rootProject.name = "AndroidIDE" diff --git a/shell/.gitignore b/shell/.gitignore new file mode 100644 index 000000000..56cc6425e --- /dev/null +++ b/shell/.gitignore @@ -0,0 +1,85 @@ +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ diff --git a/shell/build.gradle b/shell/build.gradle new file mode 100644 index 000000000..912323779 --- /dev/null +++ b/shell/build.gradle @@ -0,0 +1,28 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdk + buildToolsVersion rootProject.ext.buildTools + + defaultConfig { + minSdkVersion rootProject.ext.minSdk + targetSdkVersion rootProject.ext.targetSdk + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + } + + compileOptions { + sourceCompatibility rootProject.ext.javaSourceVersion + targetCompatibility rootProject.ext.javaTargetVersion + } + + buildTypes { + release { + minifyEnabled true + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/shell/proguard-rules.pro b/shell/proguard-rules.pro new file mode 100644 index 000000000..0448f7080 --- /dev/null +++ b/shell/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/bogdan/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/shell/src/main/AndroidManifest.xml b/shell/src/main/AndroidManifest.xml new file mode 100644 index 000000000..7ef69d377 --- /dev/null +++ b/shell/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/shell/src/main/java/com/itsaky/androidide/shell/CommonProcessExecutor.java b/shell/src/main/java/com/itsaky/androidide/shell/CommonProcessExecutor.java new file mode 100644 index 000000000..34a0977cf --- /dev/null +++ b/shell/src/main/java/com/itsaky/androidide/shell/CommonProcessExecutor.java @@ -0,0 +1,98 @@ +package com.itsaky.androidide.shell; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * Creates subprocesses using a {@link ProcessBuilder} + */ +public class CommonProcessExecutor implements IProcessExecutor { + + final File HOME = getHome(); + + @Override + public int exec(ProcessStreamsHolder holder, String... args) throws IOException, InterruptedException { + return exec(holder, true, args); + } + + @Override + public int exec(ProcessStreamsHolder holder, boolean redirectErr, String... args) throws IOException, InterruptedException { + final Process proc = newProcess(args, redirectErr, holder); + return proc.waitFor(); + } + + @Override + public void execAsync(ProcessStreamsHolder holder, IProcessExitListener onExit, boolean redirectErr, String[] args) throws IOException { + final Process proc = newProcess(args, redirectErr, holder); + final Thread exitListener = new Thread(new ExitListenerRunnable (proc, onExit), "ProcessExitListener"); + exitListener.start(); + } + + private Process newProcess(String[] args, boolean redirectErr, ProcessStreamsHolder holder) throws IOException { + final ProcessBuilder builder = new ProcessBuilder(args); + builder.directory(HOME); + builder.redirectErrorStream(redirectErr); + builder.environment().putAll(getEnv()); + + final Process proc = builder.start(); + holder.in = proc.getInputStream(); + holder.err = proc.getErrorStream(); + holder.out = proc.getOutputStream(); + + return proc; + } + + private static File getHome() { + try { + final Class env = CommonProcessExecutor.class.getClassLoader().loadClass("com.itsaky.androidide.utils.Environment"); + final Field home = env.getDeclaredField("HOME"); + home.setAccessible(true); + return (File) home.get(null); + } catch (Throwable th) { + // ignored + } + + // Return this if we cannot get it from Environment class + return new File("/data/data/com.itsaky.androidide/files/framework"); + } + + private static Map getEnv() { + try { + final Class env = CommonProcessExecutor.class.getClassLoader().loadClass("com.itsaky.androidide.utils.Environment"); + final Method method = env.getDeclaredMethod("getEnvironment", boolean.class); + method.setAccessible(true); + return (Map) method.invoke(null, true); + } catch (Throwable th) { + // ignored + } + + // Return this if we cannot get it from Environment class + return new HashMap(); + } + + private class ExitListenerRunnable implements Runnable { + + private final Process proc; + private final IProcessExitListener listener; + + public ExitListenerRunnable(Process proc, IProcessExitListener listener) { + this.proc = proc; + this.listener = listener; + } + + @Override + public void run() { + try { + proc.waitFor(); + } catch (InterruptedException e) { + // ignored + } + + listener.onExit(proc.exitValue()); + } + } +} diff --git a/shell/src/main/java/com/itsaky/androidide/shell/IProcessExecutor.java b/shell/src/main/java/com/itsaky/androidide/shell/IProcessExecutor.java new file mode 100644 index 000000000..db8b1ca8a --- /dev/null +++ b/shell/src/main/java/com/itsaky/androidide/shell/IProcessExecutor.java @@ -0,0 +1,41 @@ +package com.itsaky.androidide.shell; + +import java.io.IOException; + +/** + * An executor to execute a command. + *

+ * Subclasses of this interface can decide how to execute the command. + * For example, you could use {@link ProcessBuilder} or you could + * use {@link com.itsaky.terminal.JNI JNI} to create subprocesses. + *

+ */ +public interface IProcessExecutor { + + /** + * @see exec(ProcessStreamsHolder, boolean, String[]) + */ + int exec (ProcessStreamsHolder holder, String... args) throws IOException, InterruptedException; + + /** + * Executes the given command with its attributes. + * This method WILL block the current thread. + * + * @param holder A holder which will be provided the process's input, error and output stream + * @param redirectErr Whether to redirect error stream to output stream + * @param args Arguments for {@code cmd} + * @return The exit code of the command + */ + int exec (ProcessStreamsHolder holder, boolean redirectErr, String... args) throws IOException, InterruptedException; + + /** + * Executes the given command with its attributes. + * This method will not block the current thread. + * + * @param holder A holder which will be provided the process's input, error and output stream + * @param redirectErr Whether to redirect error stream to output stream + * @param onExit A listener that will be called once the process exits + * @param args Arguments for {@code cmd} + */ + void execAsync (ProcessStreamsHolder holder, IProcessExitListener onExit, boolean redirectErr, String... args) throws IOException; +} diff --git a/shell/src/main/java/com/itsaky/androidide/shell/IProcessExitListener.java b/shell/src/main/java/com/itsaky/androidide/shell/IProcessExitListener.java new file mode 100644 index 000000000..ef470e8b0 --- /dev/null +++ b/shell/src/main/java/com/itsaky/androidide/shell/IProcessExitListener.java @@ -0,0 +1,5 @@ +package com.itsaky.androidide.shell; + +public interface IProcessExitListener { + void onExit (int code); +} diff --git a/shell/src/main/java/com/itsaky/androidide/shell/ProcessExecutorFactory.java b/shell/src/main/java/com/itsaky/androidide/shell/ProcessExecutorFactory.java new file mode 100644 index 000000000..dc2449ba8 --- /dev/null +++ b/shell/src/main/java/com/itsaky/androidide/shell/ProcessExecutorFactory.java @@ -0,0 +1,27 @@ +package com.itsaky.androidide.shell; + +import java.io.IOException; + +/** + * Provides static access to the process executor + */ +public class ProcessExecutorFactory { + + private static final CommonProcessExecutor executor = new CommonProcessExecutor (); + + public static IProcessExecutor commonExecutor () { + return executor; + } + + public static int exec (ProcessStreamsHolder holder, String ... args) throws InterruptedException, IOException { + return executor.exec(holder, args); + } + + public static int exec (ProcessStreamsHolder holder, boolean redirectErr, String ... args) throws InterruptedException, IOException { + return executor.exec(holder, redirectErr, args); + } + + public static void execAsync (ProcessStreamsHolder holder, IProcessExitListener exitListener, boolean redirectErr, String ... args) throws IOException { + executor.execAsync(holder, exitListener, redirectErr, args); + } +} diff --git a/shell/src/main/java/com/itsaky/androidide/shell/ProcessStreamsHolder.java b/shell/src/main/java/com/itsaky/androidide/shell/ProcessStreamsHolder.java new file mode 100644 index 000000000..729afd66d --- /dev/null +++ b/shell/src/main/java/com/itsaky/androidide/shell/ProcessStreamsHolder.java @@ -0,0 +1,12 @@ +package com.itsaky.androidide.shell; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Holder input, error and output streams for a {@link java.lang.Process} + */ +public class ProcessStreamsHolder { + public InputStream in; + public InputStream err; + public OutputStream out; +} diff --git a/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java b/shell/src/main/java/com/itsaky/androidide/shell/ShellServer.java similarity index 58% rename from app/src/main/java/com/itsaky/androidide/shell/ShellServer.java rename to shell/src/main/java/com/itsaky/androidide/shell/ShellServer.java index 1182c9717..6d068b2e3 100644 --- a/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java +++ b/shell/src/main/java/com/itsaky/androidide/shell/ShellServer.java @@ -1,9 +1,5 @@ package com.itsaky.androidide.shell; -import com.blankj.utilcode.util.CloseUtils; -import com.blankj.utilcode.util.ThrowableUtils; -import com.itsaky.androidide.utils.Environment; -import com.itsaky.androidide.utils.Logger; import java.io.BufferedReader; import java.io.Closeable; import java.io.File; @@ -11,62 +7,42 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.blankj.utilcode.util.ThreadUtils; public class ShellServer extends Thread { - private final List callbacks = Collections.synchronizedList(new ArrayList()); + private final Callback callback; private BufferedReader output; private Process process; - private static final Logger LOG = Logger.instance("ShellServer"); - - public ShellServer(Callback callback, String command, String dirPath) { - this(callback, command, dirPath, true); - } - - public ShellServer(Callback callback, String command, String dirPath, boolean redirectErrors) { - addCallback(callback); + public ShellServer(Callback callback, String command, String dirPath, Map env, boolean redirectErrors) { + this.callback = callback; ProcessBuilder processBuilder = new ProcessBuilder(new String[]{command}); processBuilder.directory(new File(dirPath)); processBuilder.redirectErrorStream(redirectErrors); - processBuilder.environment().putAll(Environment.getEnvironment(false)); + processBuilder.environment().putAll(env); + try { this.process = processBuilder.start(); this.output = new BufferedReader(new InputStreamReader(process.getInputStream())); } catch (Throwable th) { if (callback != null) { - String out = ThrowableUtils.getFullStackTrace(th).concat("\n"); + String out = getFullStackTrace(th).concat("\n"); callback.output(out); } } } - public int getPid() { - int pid = -1; - try { - Field f = process.getClass().getDeclaredField("pid"); - f.setAccessible(true); - pid = f.getInt(process); - f.setAccessible(false); - } catch (Throwable ignored) { - try { - Matcher m = Pattern.compile("pid=(\\d+)").matcher(process.toString()); - pid = m.find() ? Integer.parseInt(m.group(1)) : -1; - } catch (Throwable ignored2) { - pid = -1; - } - } - return pid; - } - public InputStream getProcessInputStream() { return process.getInputStream(); } @@ -75,13 +51,6 @@ public class ShellServer extends Thread { return process.getOutputStream(); } - public ShellServer addCallback(Callback callback) { - if (callback != null) { - this.callbacks.add(callback); - } - return this; - } - public void append(String str) { append(str, true); } @@ -129,23 +98,11 @@ public class ShellServer extends Thread { } public void output(String str) { - int i = 0; - if (this.callbacks != null && this.callbacks.size() != 0) { - Callback[] callbackArr = this.callbacks.toArray(new Callback[0]); - while (i < callbackArr.length) { - callbackArr[i].output(str); - i++; - } + if (this.callback != null) { + this.callback.output(str); } } - public ShellServer removeCallback(Callback callback) { - if (callback != null) { - this.callbacks.remove(callback); - } - return this; - } - @Override public void run() { while (true) { @@ -159,9 +116,25 @@ public class ShellServer extends Thread { output(th.toString().concat("\n")); } } - CloseUtils.closeIOQuietly(new Closeable[]{this.output, this.process.getInputStream(), this.process.getErrorStream(), this.process.getOutputStream()}); + closeIOQuietly(new Closeable[]{this.output, this.process.getInputStream(), this.process.getErrorStream(), this.process.getOutputStream()}); this.process.destroy(); } + + private String getFullStackTrace (Throwable th) { + StringWriter sw = new StringWriter (); + th.printStackTrace( new PrintWriter (sw)); + return sw.toString(); + } + + private void closeIOQuietly (Closeable ... toClose) { + for (Closeable c : toClose) { + try { + c.close(); + } catch (IOException e) { + // ignored + } + } + } public interface Callback { public void output(CharSequence charSequence);