Refactor code

This commit is contained in:
Akash Yadav
2021-11-21 14:11:47 +05:30
parent 14c3a328e8
commit 471c53820e
96 changed files with 943 additions and 390 deletions
+3 -4
View File
@@ -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'
+1 -1
View File
@@ -68,7 +68,7 @@
</intent-filter>
</service>
<receiver
android:name="com.itsaky.androidide.receivers.LogReceiver"
android:name="com.itsaky.androidide.services.LogReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
Binary file not shown.
Binary file not shown.
@@ -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;
}
}
}
@@ -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();
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
}
@@ -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;
@@ -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;
@@ -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 {
@@ -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;
@@ -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;
@@ -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;
/**
@@ -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)
@@ -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;
@@ -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;
@@ -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<Integer, Map<Integer, Diagnostic>> diagnostics) {
}
private void addHexColorIfPresent(Token token, HashMap<HexColor, Integer> 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}");
}
@@ -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;
@@ -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;
@@ -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();
}
}
@@ -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<File, List<Diagnostic>> 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. <br>
* 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<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams p1) {
return null;
}
/**
* Called when there was an error connecting to server.
*/
protected void connectionError(Throwable th) {
@Override
public CompletableFuture<List<Object>> configuration(ConfigurationParams p1) {
return null;
}
public static interface StarterListener {
void startServer();
@Override
public CompletableFuture<Void> createProgress(WorkDoneProgressCreateParams p1) {
return null;
}
public static interface OnConnectedListener {
void onConnected(IDELanguageServer server);
@Override
public void notifyProgress(ProgressParams p1) {
}
@Override
public CompletableFuture<Void> refreshCodeLenses() {
return null;
}
@Override
public CompletableFuture<Void> refreshSemanticTokens() {
return null;
}
@Override
public CompletableFuture<Void> registerCapability(RegistrationParams p1) {
return null;
}
@Override
public void setTrace(SetTraceParams p1) {
}
@Override
public void showMessage(MessageParams p1) {
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams p1) {
return null;
}
@Override
public CompletableFuture<Void> unregisterCapability(UnregistrationParams p1) {
return null;
}
@Override
public CompletableFuture<List<WorkspaceFolder>> workspaceFolders() {
return null;
}
}
@@ -53,7 +53,7 @@ public abstract class LSPClientLauncher extends Thread {
try {
launch();
} catch (Throwable th) {
languageClient.connectionError(th);
// TODO Log this
}
}
@@ -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<String, IDELanguageServer> getAvailableServers() {
return serverMap;
}
@@ -66,10 +63,6 @@ public class LSPProvider {
return capabilitiesMap;
}
public static Map<String, IDELanguageClientImpl> 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;
}
}
@@ -13,13 +13,15 @@ public class StandardStreamsLauncher extends LSPClientLauncher {
private final InputStream in;
private final OutputStream out;
private final String langCode;
private Future<Void> 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();
}
}
@@ -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<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams p1) {
return null;
}
@Override
public CompletableFuture<List<Object>> configuration(ConfigurationParams p1) {
return null;
}
@Override
public CompletableFuture<Void> createProgress(WorkDoneProgressCreateParams p1) {
return null;
}
@Override
public void notifyProgress(ProgressParams p1) {
}
@Override
public CompletableFuture<Void> refreshCodeLenses() {
return null;
}
@Override
public CompletableFuture<Void> refreshSemanticTokens() {
return null;
}
@Override
public CompletableFuture<Void> registerCapability(RegistrationParams p1) {
return null;
}
public void setTrace(SetTraceParams p1) {
}
@Override
public void showMessage(MessageParams p1) {
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams p1) {
return null;
}
@Override
public CompletableFuture<Void> unregisterCapability(UnregistrationParams p1) {
return null;
}
@Override
public CompletableFuture<List<WorkspaceFolder>> workspaceFolders() {
return null;
}
}
@@ -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.
* <p>
* 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<InitializeResult> 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");
}
}
@@ -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;
@@ -1,4 +1,4 @@
package com.itsaky.androidide.models.project;
package com.itsaky.androidide.project;
import com.google.gson.annotations.SerializedName;
@@ -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;
@@ -1,4 +1,4 @@
package com.itsaky.androidide.models.project;
package com.itsaky.androidide.project;
import com.google.gson.annotations.SerializedName;
@@ -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;
@@ -1,4 +1,4 @@
package com.itsaky.androidide.receivers;
package com.itsaky.androidide.services;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -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;
@@ -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
@@ -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;
@@ -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());
}
@@ -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);
}
}
@@ -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
}
}
}
@@ -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;
@@ -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) {
@@ -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("(");
}
+2
View File
@@ -1,4 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="title_installation_failed">Installation failed</string>
<string name="msg_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.</string>
<string name="msg_sdk_info_load_failed">Failed to load SDK information</string>
<string name="msg_cannot_preview_layout">Unable to preview layout!</string>
<string name="title_preview_layout">Preview Layout</string>
+2 -2
View File
@@ -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
+85
View File
@@ -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/
+30
View File
@@ -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'
}
+17
View File
@@ -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 *;
#}
+1
View File
@@ -0,0 +1 @@
<manifest package="com.itsaky.androidide.lexers"/>
@@ -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);
@@ -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);
}
}
}
}
@@ -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);
}
}
}
}
@@ -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 {
* <p>The default implementation does nothing.</p>
*/
@Override public void visitErrorNode(ErrorNode node) { }
}
}
@@ -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<T> extends AbstractParseTreeVisitor<T> implem
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitArguments(JavaParser.ArgumentsContext ctx) { return visitChildren(ctx); }
}
}
@@ -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);
}
}
@@ -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<T> extends ParseTreeVisitor<T> {
* @return the visitor result
*/
T visitArguments(JavaParser.ArgumentsContext ctx);
}
}
@@ -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);
+2
View File
@@ -5,5 +5,7 @@ include ':javapoet'
include ':lsp'
include ':sdk-info'
include ':layout-inflater'
include ':lexers'
include ':shell'
rootProject.name = "AndroidIDE"
+85
View File
@@ -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/
+28
View File
@@ -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'])
}
+17
View File
@@ -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 *;
#}
+1
View File
@@ -0,0 +1 @@
<manifest package="com.itsaky.androidide.shell"/>
@@ -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<String, String> 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<String, String>) method.invoke(null, true);
} catch (Throwable th) {
// ignored
}
// Return this if we cannot get it from Environment class
return new HashMap<String, String>();
}
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());
}
}
}
@@ -0,0 +1,41 @@
package com.itsaky.androidide.shell;
import java.io.IOException;
/**
* An executor to execute a command.
* <p>
* 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.
* </p>
*/
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;
}
@@ -0,0 +1,5 @@
package com.itsaky.androidide.shell;
public interface IProcessExitListener {
void onExit (int code);
}
@@ -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);
}
}
@@ -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;
}
@@ -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<Callback> callbacks = Collections.synchronizedList(new ArrayList<Callback>());
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<String, String> 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);