- Implmented Diagnostics

This commit is contained in:
itsaky
2021-07-29 13:56:28 +05:30
parent e5cdf40c2a
commit a6db8d0c86
158 changed files with 5911 additions and 732 deletions
+4 -3
View File
@@ -59,7 +59,7 @@ android {
stringfog.enable false
}
release {
minifyEnabled true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
stringfog.enable true
@@ -75,8 +75,9 @@ android {
}
stringfog {
def isRelease = getGradle().getStartParameter().getTaskRequests().toString().contains("release")
key 'WVV0aGMwaEFZVzVFVW05SlJFbGtaUT09'
enable true
enable false
implementation 'com.github.megatronking.stringfog.xor.StringFogImpl'
}
@@ -90,6 +91,7 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'com.github.megatronking.stringfog:xor:1.1.0'
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
@@ -119,7 +121,6 @@ dependencies {
// Local projects
implementation project(':treeview')
implementation project(':editor')
implementation project(':spinkit')
implementation project(':slidr')
implementation project(':javadocparser')
+4
View File
@@ -0,0 +1,4 @@
o0OoO0
0oO00Oo0
oO0o0O0O
0O0o00
Binary file not shown.
+9 -9
View File
@@ -1,5 +1,8 @@
-keepattributes SourceFile, LineNumberTable
-renamesourcefileattribute FuckYou
-obfuscationdictionary dictionary.txt
-packageobfuscationdictionary dictionary.txt
-classobfuscationdictionary dictionary.txt
-ignorewarnings
-dontwarn
@@ -14,17 +17,14 @@
-dontwarn androidx.lifecycle.**
-keep class androidx.arch.** { *; }
-keep class androidx.lifecycle.** { *; }
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
-keep class org.eclipse.jdt.** { <init>(...); }
-keep class org.eclipse.jdt.** { static *; }
-keepclasseswithmembernames,includedescriptorclasses class * { native <methods>; }
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep class * extends com.bumptech.glide.module.AppGlideModule {
<init>(...);
}
-keep class * extends com.bumptech.glide.module.AppGlideModule { <init>(...); }
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
*** rewind();
}
-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { *** rewind(); }
@@ -17,6 +17,8 @@ import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.transition.Slide;
import androidx.transition.TransitionManager;
import com.blankj.utilcode.util.ClipboardUtils;
@@ -29,6 +31,7 @@ import com.google.android.material.navigation.NavigationView;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.transition.MaterialArcMotion;
import com.google.android.material.transition.MaterialContainerTransform;
import com.itsaky.androidide.adapters.DiagnosticsAdapter;
import com.itsaky.androidide.adapters.EditorPagerAdapter;
import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder;
import com.itsaky.androidide.app.StudioActivity;
@@ -41,9 +44,11 @@ import com.itsaky.androidide.fragments.sheets.OptionsListFragment;
import com.itsaky.androidide.fragments.sheets.TextSheetFragment;
import com.itsaky.androidide.interfaces.CompileListener;
import com.itsaky.androidide.interfaces.DexResultListener;
import com.itsaky.androidide.interfaces.DiagnosticClickListener;
import com.itsaky.androidide.language.logs.LogLanguageImpl;
import com.itsaky.androidide.models.AndroidProject;
import com.itsaky.androidide.models.ConstantsBridge;
import com.itsaky.androidide.models.DiagnosticGroup;
import com.itsaky.androidide.models.LogLine;
import com.itsaky.androidide.models.SheetOption;
import com.itsaky.androidide.receivers.LogReceiver;
@@ -64,6 +69,7 @@ import com.unnamed.b.atv.model.TreeNode;
import io.github.rosemoe.editor.langs.EmptyLanguage;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -71,14 +77,14 @@ import java.util.Map;
import java.util.regex.Pattern;
import me.piruin.quickaction.ActionItem;
import me.piruin.quickaction.QuickAction;
import com.elvishew.xlog.XLog;
public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener,
IDEService.BuildListener,
TabLayout.OnTabSelectedListener,
NavigationView.OnNavigationItemSelectedListener,
DexResultListener,
CompileListener {
CompileListener,
DiagnosticClickListener {
private ActivityEditorBinding mBinding;
private EditorPagerAdapter mPagerAdapter;
@@ -89,6 +95,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
private TreeNode mLastHolded;
private CodeEditor buildView;
private CodeEditor logView;
private RecyclerView diagnosticList;
private LogLanguageImpl mLogLanguageImpl;
@@ -97,6 +104,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
private OptionsListFragment mFileOptionsFragment;
private boolean recreateCompletionServices = false;
private final Map<File, List<CompilerDiagnostic>> diagnostics = new HashMap<>();
private final String RES_PATH_REGEX = "/.*/src/.*/res";
private final String LAYOUTRES_PATH_REGEX = "/.*/src/.*/res/layout";
@@ -111,9 +120,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
private static final int ACTIONID_CLOSE = 100;
private static final int ACTIONID_OTHERS = 101;
private static final int ACTIONID_ALL = 102;
private static final HashMap<File, List<CompilerDiagnostic>> mDiagnosticsMap = new HashMap<>();
private LogReceiver mLogReceiver = new LogReceiver().setLogListener(line -> appendApkLog(line));
private BroadcastReceiver mServiceStartReceiver = new BroadcastReceiver(){
@@ -186,7 +193,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
KeyboardUtils.registerSoftInputChangedListener(this, __ -> onSoftInputChanged());
registerLogReceiver();
setupContainers();
}
@@ -198,6 +204,10 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
if(getLogView().getParent() != null && getLogView().getParent() instanceof ViewGroup) {
((ViewGroup) getLogView().getParent()).removeView(getLogView());
}
if(getDiagnosticsList().getParent() != null && getDiagnosticsList().getParent() instanceof ViewGroup) {
((ViewGroup) getDiagnosticsList().getParent()).removeView(getDiagnosticsList());
}
mBinding.buildOutcontainer.addView(getBuildView(), new ViewGroup.LayoutParams(-1, -1));
mBinding.buildContainer.setVisibility(View.GONE);
@@ -208,12 +218,18 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
mBinding.logContainer.setVisibility(View.GONE);
mBinding.logOutToolbar.setNavigationOnClickListener(v -> hideLogResult());
mBinding.logOutToolbar.setOnClickListener(v -> hideLogResult());
mBinding.diagListContainer.addView(getDiagnosticsList(), new ViewGroup.LayoutParams(-1, -1));
mBinding.diagContainer.setVisibility(View.GONE);
mBinding.diagToolbar.setNavigationOnClickListener(v -> hideDiagnostics());
mBinding.diagToolbar.setOnClickListener(v -> hideDiagnostics());
mBinding.viewOptionsCard.setVisibility(View.GONE);
mBinding.transformScrim.setVisibility(View.GONE);
mBinding.transformScrim.setOnClickListener(v -> hideViewOptions());
mBinding.viewBuildOut.setOnClickListener(v -> showBuildResult());
mBinding.viewDiags.setOnClickListener(v -> showDiagnostics());
mBinding.viewLogs.setOnClickListener(v -> showLogResult());
mBinding.viewFiles.setOnClickListener(v -> showFiles());
mBinding.viewDaemonStatus.setOnClickListener(v -> showDaemonStatus());
@@ -250,18 +266,20 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
@Override
public void onBackPressed() {
if(mBinding.buildContainer.getVisibility() == View.VISIBLE) {
hideBuildResult();
} else if(mBinding.logContainer.getVisibility() == View.VISIBLE) {
hideLogResult();
} else if(mBinding.viewOptionsCard.getVisibility() == View.VISIBLE) {
hideViewOptions();
} else if(mBinding.getRoot().isDrawerOpen(GravityCompat.END)) {
if(mBinding.getRoot().isDrawerOpen(GravityCompat.END)) {
mBinding.getRoot().closeDrawer(GravityCompat.END);
} else if(mBinding.getRoot().isDrawerOpen(GravityCompat.START)) {
mBinding.getRoot().closeDrawer(GravityCompat.START);
} else if(getDaemonStatusFragment().isShowing()) {
getDaemonStatusFragment().dismiss();
} else if(mBinding.buildContainer.getVisibility() == View.VISIBLE) {
hideBuildResult();
} else if(mBinding.logContainer.getVisibility() == View.VISIBLE) {
hideLogResult();
} else if(mBinding.viewOptionsCard.getVisibility() == View.VISIBLE) {
hideViewOptions();
} else if(mBinding.diagContainer.getVisibility() == View.VISIBLE) {
hideDiagnostics();
} else if(mFileOptionsFragment != null && mFileOptionsFragment.isShowing()) {
mFileOptionsFragment.dismiss();
} else {
@@ -411,6 +429,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
if(recreateCompletionServices) {
getApp().createCompletionService(mProject);
} else if(task.getType() == GradleTask.Type.BUILD) {
getApp().createJavaCompiler(mProject);
getApp().getJavaCompiler().compileAllAsync(this);
if(task.getTaskID() == IDEService.TASK_ASSEMBLE_DEBUG) {
if(task.buildsApk()) {
install(task.getApk(new File(mProject.getMainModulePath(), "build").getAbsolutePath(), mProject.getMainModule()));
@@ -429,6 +449,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
if(task.canOutput()) {
setStatus(msg);
}
if(task.getType() == GradleTask.Type.BUILD) {
getApp().createJavaCompiler(mProject);
getApp().getJavaCompiler().compileAllAsync(this);
}
showBuildResult();
@@ -441,9 +466,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
public void onGetDependencies(List<String> dependencies) {
setStatus(getString(R.string.msg_starting_completion));
mProject.setClassPaths(dependencies);
getApp().createJavaCompiler(mProject, this);
getApp().getJavaCompiler().compileAll();
new Jar2DexService(getApp().getIndexedDir(), dependencies, this).startDexing();
createCompletionServices();
}
@Override
@@ -464,19 +487,31 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
public void saveFiles() {
saveAll();
}
/**
* @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them.
*/
@Deprecated
@Override
public void onDex(String name) {
setStatus(getString(R.string.msg_indexing, name));
}
/**
* @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them.
*/
@Deprecated
@Override
public void onDexSuccess(List<String> dexes) {
ConstantsBridge.PROJECT_DEXES = dexes;
setStatus(getString(R.string.msg_indexing_success));
createCompletionServices();
}
/**
* @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them.
*/
@Deprecated
@Override
public void onDexFailed(String message) {
appendBuildOut(message);
@@ -484,14 +519,20 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
showBuildResult();
}
private void appendBuildOut(String str) {
str = str.endsWith("\n") ? str : str.concat("\n");
getBuildView(true).getText().append(str);
// Can be called from a different different, better to run on UI thread
private void appendBuildOut(final String str) {
runOnUiThread(() -> {
String strFinal = str.endsWith("\n") ? str : str.concat("\n");
getBuildView(true).getText().append(strFinal);
});
}
private void appendLogOut(String str) {
str = str.endsWith("\n") ? str : str.concat("\n");
getLogView(true).getText().append(str);
// Can be called from a different different, better to run on UI thread
private void appendLogOut(final String str) {
runOnUiThread(() -> {
String strFinal = str.endsWith("\n") ? str : str.concat("\n");
getLogView(true).getText().append(strFinal);
});
}
private void showFiles() {
@@ -563,6 +604,20 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
mBinding.logContainer.setVisibility(View.GONE);
mBinding.viewOptionsCard.setVisibility(View.VISIBLE);
}
private void showDiagnostics() {
TransitionManager.beginDelayedTransition(mBinding.getRoot(), createContainerTransformFor(mBinding.viewDiags, mBinding.diagContainer));
mBinding.diagContainer.setVisibility(View.VISIBLE);
mBinding.viewOptionsCard.setVisibility(View.GONE);
hideToolbarAndTabs();
}
private void hideDiagnostics() {
showToolbarAndTabs();
TransitionManager.beginDelayedTransition(mBinding.getRoot(), createContainerTransformFor(mBinding.diagContainer, mBinding.viewDiags));
mBinding.diagContainer.setVisibility(View.GONE);
mBinding.viewOptionsCard.setVisibility(View.VISIBLE);
}
private MaterialContainerTransform createContainerTransformFor(View start, View end) {
return createContainerTransformFor(start, end, mBinding.realContainer);
@@ -632,7 +687,12 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
}
@Override
public void onCompilationResult(boolean success, Map<File, List<CompilerDiagnostic>> diagnostics) {
public void onCompilationResult(boolean singleFile, Map<File, List<CompilerDiagnostic>> diagnostics) {
if(getDiagnosticsList().getParent() == null || !(getDiagnosticsList().getParent() instanceof ViewGroup))
setupContainers();
getDiagnosticsList().setAdapter(new DiagnosticsAdapter(mapAsGroup(diagnostics), this));
this.diagnostics.clear();
this.diagnostics.putAll(diagnostics);
provideDiagnosticsToEditors(diagnostics);
}
@@ -650,6 +710,21 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
}
}
}
@Override
public void onGroupClick(DiagnosticGroup group) {
if(group != null
&& group.file != null
&& group.file.exists()
&& FileUtils.isUtf8(group.file))
{
openFile(group.file);
}
}
@Override
public void onDiagnosticClick(CompilerDiagnostic diagnostic) {
}
/*****************************
**** Tab Related ************
@@ -690,8 +765,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
final int id = p1.getItemId();
if(id == R.id.editornav_discuss) {
getApp().openTelegramGroup();
// } else if(id == R.id.editornav_suggest) {
// getApp().openIssueTracker();
} else if(id == R.id.editornav_suggest) {
getApp().openIssueTracker();
} else if(id == R.id.editornav_needHelp) {
showNeedHelpDialog();
} else if(id == R.id.editornav_settings) {
@@ -796,7 +871,32 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
logView.setTextSize(12);
return logView;
}
private RecyclerView getDiagnosticsList() {
return diagnosticList == null ? createDiagnosticsList() : diagnosticList;
}
private RecyclerView createDiagnosticsList() {
diagnosticList = new RecyclerView(this);
diagnosticList.setLayoutManager(new LinearLayoutManager(this));
diagnosticList.setAdapter(new DiagnosticsAdapter(mapAsGroup(this.diagnostics), this));
return diagnosticList;
}
private List<DiagnosticGroup> mapAsGroup(Map<File, List<CompilerDiagnostic>> diags) {
List<DiagnosticGroup> groups = new ArrayList<>();
if(diags == null || diags.size() <= 0)
return groups;
for(File file : diags.keySet()) {
List<CompilerDiagnostic> fileDiags = diags.get(file);
if(fileDiags == null || fileDiags.size() <= 0)
continue;
DiagnosticGroup group = new DiagnosticGroup(R.drawable.ic_language_java, file, fileDiags);
groups.add(group);
}
return groups;
}
private LogLanguageImpl getLogLanguage() {
return mLogLanguageImpl == null ? mLogLanguageImpl = new LogLanguageImpl() : mLogLanguageImpl;
}
@@ -823,6 +923,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
boolean isGradleSaved = mPagerAdapter.saveAll();
getApp().toast(R.string.all_saved, Toaster.Type.SUCCESS);
if(isGradleSaved) notifySyncNeeded();
if(getApp().getJavaCompiler() != null)
getApp().getJavaCompiler().compileAllAsync(this);
return isGradleSaved;
}
@@ -1148,7 +1250,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
mBinding.tabs.setupWithViewPager(mBinding.editorViewPager);
mBinding.tabs.addOnTabSelectedListener(this);
}
private void closeFile(int index) {
closeFile(index, true);
}
@@ -0,0 +1,65 @@
package com.itsaky.androidide.adapters;
import android.graphics.PorterDuff;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.itsaky.androidide.R;
import com.itsaky.androidide.databinding.LayoutDiagnosticItemBinding;
import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic;
import java.util.List;
import javax.tools.Diagnostic;
import com.itsaky.androidide.interfaces.DiagnosticClickListener;
public class DiagnosticItemAdpater extends RecyclerView.Adapter<DiagnosticItemAdpater.VH> {
private List<CompilerDiagnostic> diags;
private DiagnosticClickListener listener;
public DiagnosticItemAdpater(List<CompilerDiagnostic> diags, DiagnosticClickListener listener) {
this.diags = diags;
this.listener = listener;
}
@Override
public DiagnosticItemAdpater.VH onCreateViewHolder(ViewGroup p1, int p2) {
return new VH(LayoutDiagnosticItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false));
}
@Override
public void onBindViewHolder(DiagnosticItemAdpater.VH p1, int p2) {
final CompilerDiagnostic diagnostic = diags.get(p2);
final LayoutDiagnosticItemBinding binding = p1.binding;
binding.icon.setImageResource(getDiagnosticIconId(diagnostic));
binding.icon.setColorFilter(ContextCompat.getColor(binding.icon.getContext(), diagnostic.kind() == Diagnostic.Kind.ERROR ? R.color.diagnostic_error : R.color.diagnostic_warning), PorterDuff.Mode.SRC_ATOP);
binding.title.setText(diagnostic.message());
binding.getRoot().setOnClickListener(v -> {
if(listener != null) {
listener.onDiagnosticClick(diagnostic);
}
});
}
@Override
public int getItemCount() {
return diags.size();
}
private int getDiagnosticIconId(CompilerDiagnostic diagnostic) {
if(diagnostic.kind() == Diagnostic.Kind.ERROR)
return R.drawable.ic_compilation_error;
return R.drawable.ic_info;
}
public class VH extends RecyclerView.ViewHolder {
private LayoutDiagnosticItemBinding binding;
public VH(LayoutDiagnosticItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}
@@ -0,0 +1,63 @@
package com.itsaky.androidide.adapters;
import android.graphics.PorterDuff;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.itsaky.androidide.R;
import com.itsaky.androidide.databinding.LayoutDiagnosticGroupBinding;
import com.itsaky.androidide.interfaces.DiagnosticClickListener;
import com.itsaky.androidide.models.DiagnosticGroup;
import java.util.List;
public class DiagnosticsAdapter extends RecyclerView.Adapter<DiagnosticsAdapter.VH> {
private final List<DiagnosticGroup> diags;
private final DiagnosticClickListener listener;
public DiagnosticsAdapter(List<DiagnosticGroup> diags, DiagnosticClickListener listener) {
this.diags = diags;
this.listener = listener;
}
@Override
public DiagnosticsAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) {
return new VH(LayoutDiagnosticGroupBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false));
}
@Override
public void onBindViewHolder(DiagnosticsAdapter.VH p1, int p2) {
final DiagnosticGroup group = diags.get(p2);
final LayoutDiagnosticGroupBinding binding = p1.binding;
final int color = ContextCompat.getColor(binding.info.icon.getContext(), R.color.secondaryColor);
binding.info.icon.setImageResource(group.icon);
binding.info.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
binding.info.title.setText(group.text);
binding.info.title.setTextColor(color);
binding.diagnostics.setLayoutManager(new LinearLayoutManager(binding.diagnostics.getContext()));
binding.diagnostics.setAdapter(new DiagnosticItemAdpater(group.diagnostics, listener));
binding.info.getRoot().setOnClickListener(v -> {
if(listener != null) {
listener.onGroupClick(group);
}
});
}
@Override
public int getItemCount() {
return diags.size();
}
public class VH extends RecyclerView.ViewHolder {
private LayoutDiagnosticGroupBinding binding;
public VH(LayoutDiagnosticGroupBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}
@@ -11,15 +11,10 @@ import android.text.TextUtils;
import androidx.core.app.NotificationManagerCompat;
import androidx.multidex.MultiDexApplication;
import com.blankj.utilcode.util.EncodeUtils;
import com.blankj.utilcode.util.FileUtils;
import com.blankj.utilcode.util.ResourceUtils;
import com.blankj.utilcode.util.SPStaticUtils;
import com.blankj.utilcode.util.ThrowableUtils;
import com.elvishew.xlog.LogConfiguration;
import com.elvishew.xlog.LogLevel;
import com.elvishew.xlog.XLog;
import com.elvishew.xlog.printer.Printer;
import com.elvishew.xlog.printer.file.FilePrinter;
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator;
import com.google.firebase.messaging.FirebaseMessaging;
import com.itsaky.androidide.R;
import com.itsaky.androidide.app.StudioApp;
@@ -28,8 +23,10 @@ import com.itsaky.androidide.language.java.manager.JavaCharacter;
import com.itsaky.androidide.language.java.provider.JavaCompletionProvider;
import com.itsaky.androidide.language.xml.completion.XMLCompletionService;
import com.itsaky.androidide.models.AndroidProject;
import com.itsaky.androidide.models.ConstantsBridge;
import com.itsaky.androidide.services.IDEService;
import com.itsaky.androidide.services.MessagingService;
import com.itsaky.androidide.services.compiler.JavaCompilerService;
import com.itsaky.androidide.shell.ShellServer;
import com.itsaky.androidide.utils.Environment;
import com.itsaky.androidide.utils.FileUtil;
@@ -37,16 +34,11 @@ import com.itsaky.androidide.utils.PreferenceManager;
import com.itsaky.androidide.utils.StudioUtils;
import com.itsaky.toaster.Toaster;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.itsaky.androidide.models.ConstantsBridge;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.io.FileOutputStream;
import java.io.IOException;
import com.itsaky.androidide.services.compiler.JavaCompilerService;
import com.itsaky.androidide.interfaces.CompileListener;
import com.blankj.utilcode.util.FileUtils;
public class StudioApp extends MultiDexApplication
{
@@ -93,10 +85,6 @@ public class StudioApp extends MultiDexApplication
newShell(line -> handleLog(line)).bgAppend("logcat -v threadtime");
setupLibsIfNeeded();
if(isFrameworkInstalled()) {
newShell(t -> XLog.i(t)).bgAppend("printenv > /sdcard/androidideenv.txt && java -version");
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannels();
}
@@ -153,41 +141,28 @@ public class StudioApp extends MultiDexApplication
}
private void initLogger() {
LogConfiguration config = new LogConfiguration.Builder()
.logLevel(DEBUG ? LogLevel.ALL : LogLevel.NONE)
.tag("Android IDE")
.enableThreadInfo()
.enableStackTrace(2)
.enableBorder()
.build();
Printer filePrinter = new FilePrinter
.Builder(FileUtil.getExternalStorageDir() + "/ide_xlog")
.fileNameGenerator(new DateFileNameGenerator())
.build();
XLog.init(
config,
filePrinter);
}
private boolean setupLibsIfNeeded() {
File lib = Environment.LIBDIR;
return extractLibs(lib);
// if(lib != null && lib.exists() && lib.isDirectory()) {
// boolean libhook, librt, libgcc, libgcc_real, libpthread, libz;
// libhook = librt = libgcc = libgcc_real = libpthread = libz = false;
// File[] list = lib.listFiles();
// if(list != null) {
// for(File f : list) {
// libhook = libhook == false && f.getName().equals("libhook.so");
// librt = librt == false && f.getName().equals("librt.so");
// libpthread = libpthread == false && f.getName().equals("libpthread.so");
// libgcc = libgcc == false && f.getName().equals("libgcc.so");
// libgcc_real = libgcc_real = false & f.getName().equals("libgcc_real.so");
// libz = libz == false && f.getName().equals("libz.so.1");
// }
// } else return extractLibs(lib);
// return !(libhook && librt && libgcc && libgcc_real && libpthread && libz) ? extractLibs(lib) : true;
// } else return extractLibs(lib);
if(lib != null && lib.exists() && lib.isDirectory()) {
boolean libhook, librt, libgcc, libgcc_real, libpthread, libz, libcpp_shared;
libhook = librt = libgcc = libgcc_real = libpthread = libz = libcpp_shared = false;
File[] list = lib.listFiles();
if(list != null) {
for(File f : list) {
libhook = libhook == false ? f.getName().equals("libhook.so") : libhook;
librt = librt == false ? f.getName().equals("librt.so") : librt;
libpthread = libpthread == false ? f.getName().equals("libpthread.so") : libpthread;
libgcc = libgcc == false ? f.getName().equals("libgcc.so") : libgcc;
libgcc_real = libgcc_real = false ? f.getName().equals("libgcc_real.so") : libgcc_real;
libz = libz == false ? f.getName().equals("libz.so.1") : libz;
libcpp_shared = libcpp_shared == false ? f.getName().equals("libc++_shared.so") : libcpp_shared;
}
} else return extractLibs(lib);
return !(libhook && librt && libgcc && libgcc_real && libpthread && libz && libcpp_shared) ? extractLibs(lib) : true;
} else return extractLibs(lib);
}
private boolean extractLibs(File lib) {
@@ -248,10 +223,9 @@ public class StudioApp extends MultiDexApplication
this.mCompletionProvider.loadProject(project);
}
public void createJavaCompiler(AndroidProject project, CompileListener listener) {
public void createJavaCompiler(AndroidProject project) {
this.mJavaCompilerService =
new JavaCompilerService(project)
.setListener(listener);
new JavaCompilerService(project);
}
public JavaCompilerService getJavaCompiler() {
@@ -5,21 +5,26 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.itsaky.androidide.adapters.CompletionListAdapter;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.databinding.FragmentEditorBinding;
import com.itsaky.androidide.fragments.preferences.EditorPreferences;
import com.itsaky.androidide.interfaces.CompileListener;
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.models.AndroidProject;
import com.itsaky.androidide.models.ConstantsBridge;
import com.itsaky.androidide.services.compiler.JavaCompilerService;
import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic;
import com.itsaky.androidide.syntax.colorschemes.SchemeWombat;
import com.itsaky.androidide.tasks.TaskExecutor;
import com.itsaky.androidide.tasks.callables.ReadFileTask;
import com.itsaky.androidide.tools.SourceJavaFileObject;
import com.itsaky.androidide.utils.FileUtil;
import com.itsaky.androidide.utils.PreferenceManager;
import com.itsaky.androidide.utils.TypefaceUtils;
import com.sun.tools.javac.resources.compiler;
import io.github.rosemoe.editor.interfaces.EditorEventListener;
import io.github.rosemoe.editor.langs.EmptyLanguage;
import io.github.rosemoe.editor.widget.CodeEditor;
@@ -28,13 +33,11 @@ import java.io.File;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token;
import com.elvishew.xlog.XLog;
import android.text.TextUtils;
import com.itsaky.androidide.services.compiler.JavaCompilerService;
public class EditorFragment extends BaseFragment implements EditorEventListener {
public class EditorFragment extends BaseFragment implements EditorEventListener, CompileListener {
public FragmentEditorBinding binding;
private File mFile;
@@ -43,9 +46,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
private boolean isFirstCreate = false;
private static AndroidProject project;
private List<CompilerDiagnostic> diags;
public static final String KEY_FILE_PATH = "file_path";
public static final String KEY_PROJECT = "project";
public static final String EXT_JAVA = ".java";
@@ -55,7 +56,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
public static final String EXT_GROOVY = ".groovy";
public static final String EXT_KOTLIN = ".kt";
public static final String EXT_JSON = ".json";
public static EditorFragment newInstance(File file, AndroidProject project) {
Bundle bundle = new Bundle();
bundle.putString(KEY_FILE_PATH, file.getAbsolutePath());
@@ -100,11 +101,30 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
postRead();
});
}
@Override
public void onCompilationResult(boolean singleFile, Map<File, List<CompilerDiagnostic>> diagnostics) {
if(diagnostics != null
&& getFile() != null
&& diagnostics.containsKey(getFile()))
{
binding.editorCodeEditor.setDiagnostics(diagnostics.get(getFile()));
}
}
private void requestCompilation() {
final long s = System.currentTimeMillis();
final SourceJavaFileObject source = new SourceJavaFileObject(getFile(), binding.editorCodeEditor.getText().toString());
JavaCompilerService compiler = StudioApp.getInstance().getJavaCompiler();
if(compiler != null) {
compiler.compileSingleAsync(source, this);
}
}
public void setDiagnostics(List<CompilerDiagnostic> diags) {
this.diags = diags;
XLog.i("[EditorFragment] Diagnostics for file: " + getFile().getAbsolutePath()
+ "\ndiagnostics: " + TextUtils.join("\n", diags));
if(binding.editorCodeEditor != null) {
binding.editorCodeEditor.setDiagnostics(diags);
}
}
private void configureEditorIfNeeded() {
@@ -176,10 +196,10 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
private void postRead() {
if (mFile.isFile() && mFile.getName().endsWith(EXT_JAVA)) {
binding.editorCodeEditor.setEditorLanguage(new JavaLanguage(binding.editorCodeEditor, project));
binding.editorCodeEditor.setEditorLanguage(new JavaLanguage(project));
binding.editorCodeEditor.setColorScheme(new SchemeVS2019());
} else if (mFile.isFile() && mFile.getName().endsWith(EXT_XML)) {
binding.editorCodeEditor.setEditorLanguage(new XMLLanguage(binding.editorCodeEditor));
binding.editorCodeEditor.setEditorLanguage(new XMLLanguage());
binding.editorCodeEditor.setColorScheme(new SchemeWombat());
} else if (mFile.isFile() && mFile.getName().endsWith(EXT_GRADLE)) {
binding.editorCodeEditor.setEditorLanguage(new GroovyLanguage());
@@ -251,6 +271,8 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
@Override
public void afterDelete(CodeEditor editor, CharSequence content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) {
isModified = true;
requestCompilation();
}
@Override
@@ -263,11 +285,8 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
if(isOpen && insertedContent.toString().equals("/")) {
closeCurrentTag(editor.getText().toString(), endLine, endColumn);
}
JavaCompilerService compiler = getStudioActivity().getApp().getJavaCompiler();
if(compiler != null) {
compiler.compileAll();
}
requestCompilation();
}
@Override
@@ -26,6 +26,7 @@ import com.unnamed.b.atv.view.AndroidTreeView;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import com.blankj.utilcode.util.FileUtils;
public class FileTreeFragment extends BottomSheetDialogFragment implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener {
@@ -86,13 +87,8 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN
public void onClick(TreeNode node, Object p2) {
final File f = (File) p2;
if(f.isFile()) {
if (mFileActionListener != null && f.isFile()) {
for(String ext : compatibleExtensions) {
if(f.getName().endsWith(ext)) {
mFileActionListener.openFile(f);
break;
}
}
if (mFileActionListener != null && FileUtils.isUtf8(f)) {
mFileActionListener.openFile(f);
}
} else if(f.isDirectory() && f.exists()) {
if(node.isExpanded()) {
@@ -181,7 +177,6 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN
final File gradleHome = Environment.GRADLE_PROPS_DIR;
final File root = StudioApp.getInstance().getRootDir().getParentFile();
File projectDir = new File(mProject.getProjectPath());
projectDir = root;
mRoot = TreeNode.root(projectDir);
if(gradleHome.exists() && gradleHome.isDirectory()) {
if(!gradleProps.exists())
@@ -84,7 +84,7 @@ public class IDEPreferences extends BasePreferenceFragment {
screen.addPreference(editor);
screen.addPreference(build);
// screen.addPreference(issueTracker);
screen.addPreference(issueTracker);
screen.addPreference(telegram);
screen.addPreference(changelog);
@@ -8,7 +8,6 @@ import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.blankj.utilcode.util.ThrowableUtils;
import com.elvishew.xlog.XLog;
import com.itsaky.androidide.R;
import com.itsaky.androidide.models.LogLine;
@@ -6,5 +6,5 @@ import java.util.List;
import java.util.Map;
public interface CompileListener {
void onCompilationResult(boolean success, Map<File, List<CompilerDiagnostic>> diagnostics);
void onCompilationResult(boolean singleFile, Map<File, List<CompilerDiagnostic>> diagnostics);
}
@@ -3,6 +3,13 @@ package com.itsaky.androidide.interfaces;
import androidx.core.util.Pair;
import java.util.List;
/**
* A callback to listen for updates while dexing JAR files
*
* @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them.
*/
@Deprecated
public interface DexResultListener {
public void onDex(String name);
public void onDexSuccess(List<String> dexes);
@@ -0,0 +1,9 @@
package com.itsaky.androidide.interfaces;
import com.itsaky.androidide.models.DiagnosticGroup;
import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic;
public interface DiagnosticClickListener {
void onGroupClick(DiagnosticGroup group);
void onDiagnosticClick(CompilerDiagnostic diagnostic);
}
@@ -6,6 +6,7 @@ import com.itsaky.androidide.utils.Either;
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
import io.github.rosemoe.editor.struct.CompletionItem;
import io.github.rosemoe.editor.text.TextAnalyzeResult;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -17,7 +18,7 @@ public class GroovyAutoComplete implements AutoCompleteProvider {
private static final List<String> OTHERS = createOtherCompletions();
@Override
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
List<Either<SuggestItem, CompletionItem>> result = new ArrayList<>();
for(String artifact : ANDROIDX_ARTIFACTS) {
@@ -15,22 +15,23 @@ import java.util.ArrayList;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import com.itsaky.androidide.utils.Logger;
import java.util.stream.Collectors;
import com.itsaky.androidide.services.compiler.model.CompilerDiagnosticImpl;
public class JavaAutoComplete implements AutoCompleteProvider {
private CodeEditor editor;
private JavaLanguageAnalyzer analyzer;
public JavaAutoComplete(CodeEditor editor, JavaLanguageAnalyzer analyzer) {
this.editor = editor;
public JavaAutoComplete(JavaLanguageAnalyzer analyzer) {
this.analyzer = analyzer;
}
@Override
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
Pair<ArrayList<SuggestItem>, List<Diagnostic<? extends JavaFileObject>>> pair = StudioApp.getInstance().getCompletionProvider().getSuggestions(editor);
final ArrayList<SuggestItem> suggestions = pair.first;
final List<Diagnostic<? extends JavaFileObject>> diags = pair.second;
// final List<Diagnostic<? extends JavaFileObject>> diags = pair.second;
// editor.setDiagnostics(diags.stream().map(CompilerDiagnosticImpl::fromDiagnostic).collect(Collectors.toList()));
if(suggestions != null) {
List<Either<SuggestItem, CompletionItem>> result = new ArrayList<>();
for(SuggestItem item : suggestions) {
@@ -21,14 +21,12 @@ public class JavaLanguage implements EditorLanguage
private JavaLanguageAnalyzer analyzer;
private JavaAutoComplete complete;
private AndroidProject project;
private CodeEditor editor;
public JavaLanguage(CodeEditor editor, AndroidProject project)
public JavaLanguage(AndroidProject project)
{
this.analyzer = new JavaLanguageAnalyzer();
this.complete = new JavaAutoComplete(editor, analyzer);
this.complete = new JavaAutoComplete(analyzer);
this.project = project;
this.editor = editor;
}
@Override
@@ -49,7 +49,7 @@ public class CompleteThisKeyword extends JavaCompleteMatcherImpl {
MethodDescription desc = new MethodDescription(
method.getName().toString(),
returnType,
method.getModifiers().flags,
(int) method.getModifiers().flags,
paramsStr);
setInfo(desc, editor, incomplete);
result.add(desc);
@@ -1,55 +0,0 @@
package com.itsaky.androidide.language.java.parser.internal;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.elvishew.xlog.XLog;
import com.itsaky.androidide.models.AndroidProject;
import com.itsaky.androidide.models.ConstantsBridge;
import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;
import java.io.File;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
class CompiledClassLoader {
@NonNull
public ArrayList<Class> getCompiledClassesFromProject(@NonNull AndroidProject project) {
ArrayList<Class> classes = new ArrayList<>();
try {
final List<String> dexes = ConstantsBridge.PROJECT_DEXES;
final PathClassLoader loader = new PathClassLoader(TextUtils.join(File.pathSeparator, dexes), ClassLoader.getSystemClassLoader());
if (dexes == null || dexes.size() <= 0) return classes;
for (String dex : dexes) {
final DexFile d = new DexFile(new File(dex));
Enumeration<String> entries = d.entries();
String name = null;
while (entries.hasMoreElements() && (name = entries.nextElement()) != null) {
while(name.startsWith(File.separator))
name = name.substring(1);
if(name.contains(File.separator))
name = name.replace(File.separator, ".");
Class<?> load = tryLoad(loader, name);
if (load != null)
classes.add(load);
}
d.close();
}
ConstantsBridge.CLASS_LOAD_SUCCESS = true;
return classes;
} catch (Throwable th) {
ConstantsBridge.CLASS_LOAD_SUCCESS = false;
return classes;
}
}
private Class<?> tryLoad(ClassLoader loader, String name) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException e) {
return null;
}
}
}
@@ -1,26 +1,47 @@
package com.itsaky.androidide.language.java.parser.internal;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.FileUtils;
import com.itsaky.androidide.interfaces.Filter;
import com.itsaky.androidide.language.java.parser.JavacParser;
import com.itsaky.androidide.language.java.parser.model.ClassDescription;
import com.itsaky.androidide.language.java.parser.model.FieldDescription;
import com.itsaky.androidide.language.java.parser.model.MethodDescription;
import com.itsaky.androidide.models.AndroidProject;
import com.itsaky.androidide.models.ConstantsBridge;
import com.itsaky.androidide.utils.Logger;
import com.sun.tools.javac.tree.JCTree;
import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.classfmt.FieldInfo;
import org.eclipse.jdt.internal.compiler.env.IBinaryField;
import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import com.itsaky.androidide.language.java.parser.model.ConstructorDescription;
import com.itsaky.androidide.utils.Either;
import com.blankj.utilcode.util.ThrowableUtils;
import com.itsaky.androidide.utils.Environment;
public class JavaClassManager implements IClassManager {
private static final String TAG = "JavaClassReader";
private static final String JAVA_DOT_LANG_DOT = "java.lang.";
private static JavaClassManager INSTANCE;
@@ -65,11 +86,9 @@ public class JavaClassManager implements IClassManager {
public void loadFromProject(AndroidProject project) {
try {
CompiledClassLoader classLoader = new CompiledClassLoader();
ArrayList<Class> classes = classLoader.getCompiledClassesFromProject(project);
for (Class clazz : classes) {
getClassWrapper(clazz);
}
try {
loadClasspaths(project);
} catch (Throwable e) {}
JavacParser parser = new JavacParser();
List<String> javaSrcDirs = project.getSourcePaths();
for (String dir : javaSrcDirs) {
@@ -84,9 +103,7 @@ public class JavaClassManager implements IClassManager {
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
} catch (Throwable e) {}
}
/**
@@ -249,5 +266,214 @@ public class JavaClassManager implements IClassManager {
}
return result;
}
private void loadClasspaths(AndroidProject project) throws IOException, ClassFormatException, ClassNotFoundException {
final List<String> classpaths = project.getClassPaths();
if(!classpaths.contains(Environment.BOOTCLASSPATH))
classpaths.add(Environment.BOOTCLASSPATH.getAbsolutePath());
for(int i=0;i<classpaths.size();i++) {
final File cp = new File(classpaths.get(i));
if(!(cp.exists() && cp.getName().endsWith(".jar"))) continue;
final ZipFile zip = new ZipFile(cp);
final Enumeration<? extends ZipEntry> entries = zip.entries();
ZipEntry entry = null;
while(entries.hasMoreElements() && ((entry = entries.nextElement()) != null)) {
if(!entry.getName().endsWith(".class"))
continue;
try {
ClassFileReader reader = ClassFileReader.read(zip, entry.getName());
describe(zip, reader);
} catch (Throwable th) {}
}
}
}
private ClassDescription describe(ZipFile zip, IBinaryType type) throws IOException, ClassFormatException, ClassNotFoundException {
if(type == null)
return null;
final char[] name = type.getName();
final char[] superClassName = type.getSuperclassName();
if(name == null) return null;
final int mods = type.getModifiers();
final boolean isPremitive = false;
final boolean isAnnotation = false;
final boolean isEnum = false;
final boolean isMember = type.getEnclosingTypeName() != null;
final IBinaryField[] fields = type.getFields();
final IBinaryMethod[] methods = type.getMethods();
final char[][] interfaces = type.getInterfaceNames();
ClassDescription clazz = new ClassDescription(bytecodeToSourceType(new String(name)), mods, isPremitive, isAnnotation, isEnum, isMember);
int index = 0;
while(fields != null && index < fields.length) {
IBinaryField f = fields[index];
if(f == null) continue;
FieldDescription field = describe(zip, f);
if(field == null) continue;
clazz.addField(field);
++index;
}
index = 0;
while(methods != null && index < methods.length) {
IBinaryMethod m = methods[index];
if(m == null) continue;
Either<MethodDescription, ConstructorDescription> result = describe(zip, m);
if(result == null) continue;
if(result.isLeft())
clazz.addMethod(result.getLeft());
else clazz.addConstructor(result.getRight());
++index;
}
// TODO: Add interfaces
clazz.setSuperclass(superClassName == null ? ClassDescription.ofObject() : findType(zip, new String(superClassName)));
update(clazz);
return clazz;
}
private Either<MethodDescription, ConstructorDescription> describe(ZipFile zip, IBinaryMethod m) throws IOException, IllegalArgumentException, ClassFormatException, ClassNotFoundException {
if(zip == null || m == null) return null;
final char[] descriptor = m.getMethodDescriptor();
final char[] signature = m.getGenericSignature();
final char[] name = m.getSelector();
final int mods = m.getModifiers();
if(name == null || descriptor == null) return null;
IClass returns = findType(zip, Signature.getReturnType(new String(descriptor)));
String[] paramTypes = Signature.getParameterTypes(new String(descriptor));
ArrayList<String> params = new ArrayList<>();
for(String type : paramTypes) {
params.add(bytecodeToSourceType(type));
}
return m.isConstructor() ? Either.forRight(new ConstructorDescription(new String(name).replace("/", "."), params)) : Either.forLeft(new MethodDescription(new String(name).replace("/", "."), returns, mods, params));
}
private FieldDescription describe(ZipFile zip, IBinaryField field) throws IOException, ClassFormatException, ClassNotFoundException {
if(field == null)
return null;
final char[] n = field.getName();
final char[] t = field.getTypeName();
if(n == null || t == null) return null;
final String name = new String(n);
final String type = new String(t);
final int mods = field.getModifiers();
final String value = field instanceof FieldInfo && ((FieldInfo) field).hasConstant() ? field.getConstant().stringValue() : "";
IClass typeClass = findType(zip, type);
if(typeClass == null)
typeClass = new ClassDescription(Object.class);
FieldDescription f = new FieldDescription(mods, typeClass, name.replace("/", "."), value);
return f;
}
private IClass findType(ZipFile zip, String name) throws IOException, ClassFormatException, ClassNotFoundException {
if(zip == null || name == null)
return null;
final int arrayCount = Signature.getArrayCount(name);
if(arrayCount == 0) {
if(name.length() == 1) {
// A primitive type
final Class load = primitiveType(name.charAt(0));
if(load != null) {
ClassDescription desc = new ClassDescription(load);
desc.initMembers(load);
update(desc);
return desc;
}
}
IClass found = getParsedClass(bytecodeToSourceType(name));
if(found != null) {
return found;
} else {
ClassFileReader reader = ClassFileReader.read(zip, name);
if(reader == null)
return null;
else {
// This class will be updated in describe() method
return describe(zip, reader);
}
}
} else {
if(Signature.getElementType(name).length() == 1) {
// This type may be an array of primitive
Class load = Class.forName(name);
if(load != null) {
ClassDescription desc = new ClassDescription(load);
desc.initMembers(load);
update(desc);
return desc;
}
}
// TODO: Load array type classes of objects
// Return without loading members of this class
return new ClassDescription(Void.TYPE);
}
}
private String bytecodeToSourceType(String type) {
type = Signature.getElementType(type);
if(type.startsWith("L") && type.endsWith(";")) {
type = type.substring(1, type.lastIndexOf(";"));
}
return type.replace("/", ".");
}
public Class primitiveType(char typeId) {
switch (typeId) {
case 'I' : return int.class;
case 'B' : return byte.class;
case 'S' : return short.class;
case 'C' : return char.class;
case 'F' : return float.class;
case 'D' : return double.class;
case 'Z' : return boolean.class;
case 'J' : return long.class;
default : return null;
}
}
/**
* Kept as a backup if loading jar files fails for some reasons
*/
private ArrayList<Class> loadFromDexes(AndroidProject project) {
ArrayList<Class> classes = new ArrayList<>();
try {
final List<String> dexes = ConstantsBridge.PROJECT_DEXES;
final PathClassLoader loader = new PathClassLoader(TextUtils.join(File.pathSeparator, dexes), ClassLoader.getSystemClassLoader());
if (dexes == null || dexes.size() <= 0) return classes;
for (String dex : dexes) {
final DexFile d = new DexFile(new File(dex));
Enumeration<String> entries = d.entries();
String name = null;
while (entries.hasMoreElements() && (name = entries.nextElement()) != null) {
while(name.startsWith(File.separator))
name = name.substring(1);
if(name.contains(File.separator))
name = name.replace(File.separator, ".");
Class<?> load = tryLoad(loader, name);
if (load != null)
classes.add(load);
}
d.close();
}
ConstantsBridge.CLASS_LOAD_SUCCESS = true;
return classes;
} catch (Throwable th) {
ConstantsBridge.CLASS_LOAD_SUCCESS = false;
return classes;
}
}
private Class<?> tryLoad(ClassLoader loader, String name) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException e) {
return null;
}
}
}
@@ -56,6 +56,12 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass {
this.isMemberClass = isMember;
this.mEum = isEnum;
}
public static ClassDescription ofObject() {
ClassDescription desc = new ClassDescription(Object.class);
desc.initMembers(Object.class);
return desc;
}
@Override
public boolean isMemberClass() {
@@ -251,7 +257,6 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass {
for (Class anInterface : interfaces) {
IClass implement = JavaClassManager.getInstance().getClassWrapper(anInterface);
mImplements.add(implement);
// initClassMembers(implement);
}
if (c.getSuperclass() != null) {
@@ -262,8 +267,6 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass {
mSuperClass = JavaClassManager.getInstance().getParsedClass(Object.class.getName());
}
}
// initClassMembers(mSuperClass);
for (Constructor constructor : c.getConstructors()) {
if (Modifier.isPublic(constructor.getModifiers())) {
@@ -288,26 +291,5 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass {
public void setSuperclass(IClass superclass) {
this.mSuperClass = superclass;
// initClassMembers(mSuperClass);
}
// private void initClassMembers(IClass clazz) {
// if(clazz.getFields() != null) {
// for(IField f : clazz.getFields()) {
// addField(f);
// }
// }
//
// if(clazz.getMethods() != null) {
// for(IMethod method : clazz.getMethods()) {
// addMethod(method);
// }
// }
//
// if(clazz.getConstructors() != null) {
// for(ConstructorDescription desc : clazz.getConstructors()) {
// addConstructor(desc);
// }
// }
// }
}
@@ -13,21 +13,26 @@ import io.github.rosemoe.editor.widget.CodeEditor;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ConstructorDescription extends JavaSuggestItemImpl implements IJavaDocCommentable {
private ArrayList<IClass> mParameterTypes = new ArrayList<>();
private ArrayList<String> mParameterTypes = new ArrayList<>();
private String mConstructorName;
public ConstructorDescription(Constructor constructor) {
mConstructorName = constructor.getName();
Class[] parameterTypes = constructor.getParameterTypes();
for (Class parameterType : parameterTypes) {
IClass type = JavaClassManager.getInstance().getClassWrapper(parameterType);
mParameterTypes.add(type);
mParameterTypes.add(parameterType.getName());
}
}
public ConstructorDescription(String name, List<IClass> paramTypes) {
mConstructorName = name;
mParameterTypes.addAll(paramTypes.stream().filter(c -> c != null).map(c -> c.getFullClassName()).collect(Collectors.toList()));
}
public ConstructorDescription(String name, ArrayList<String> paramTypes) {
mConstructorName = name;
mParameterTypes.addAll(paramTypes);
}
@@ -95,21 +100,21 @@ public class ConstructorDescription extends JavaSuggestItemImpl implements IJava
return getSimpleName() + "()";
}
private String paramsToString(@NonNull ArrayList<IClass> parameterTypes) {
private String paramsToString(@NonNull ArrayList<String> parameterTypes) {
StringBuilder result = new StringBuilder();
boolean firstTime = true;
for (IClass parameterType : parameterTypes) {
for (String parameterType : parameterTypes) {
if (firstTime) {
firstTime = false;
} else {
result.append(",");
}
result.append(parameterType.getSimpleName());
result.append(parameterType);
}
return result.toString();
}
public ArrayList<IClass> getParameterTypes() {
public ArrayList<String> getParameterTypes() {
return mParameterTypes;
}
@@ -28,10 +28,10 @@ public class MethodDescription extends JavaSuggestItemImpl implements Member, Su
private IClass mReturnType;
public MethodDescription(String name, IClass returnType,
long modifiers, ArrayList<String> parameterTypes) {
int modifiers, ArrayList<String> parameterTypes) {
mMethodName = name;
mReturnType = returnType;
mModifiers = (int) modifiers;
mModifiers = modifiers;
mParameterTypes = parameterTypes;
}
@@ -44,7 +44,7 @@ public class JavaCompletionProvider {
this.mPackageManager = new PackageManager();
this.mJavaParser = new JavacParser();
addAutoComplete();
addMatchers();
}
public boolean loadProject(AndroidProject project) {
@@ -57,7 +57,7 @@ public class JavaCompletionProvider {
return initiated;
}
private void addAutoComplete() {
private void addMatchers() {
mCompletors.add(new CompleteExpression(mClassLoader));
mCompletors.add(new CompleteNewKeyword(mClassLoader));
mCompletors.add(new CompletePackage(mPackageManager));
@@ -10,6 +10,7 @@ import io.github.rosemoe.editor.interfaces.NewlineHandler;
import io.github.rosemoe.editor.struct.CompletionItem;
import io.github.rosemoe.editor.text.TextAnalyzeResult;
import io.github.rosemoe.editor.text.TextAnalyzer;
import io.github.rosemoe.editor.widget.CodeEditor;
import io.github.rosemoe.editor.widget.SymbolPairMatch;
import java.util.ArrayList;
import java.util.List;
@@ -90,7 +91,7 @@ public class LogLanguageImpl implements EditorLanguage {
private static class LogCompletor implements AutoCompleteProvider {
@Override
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
return new ArrayList<Either<SuggestItem, CompletionItem>>();
}
}
@@ -18,8 +18,6 @@ import com.itsaky.androidide.utils.EitherOfResult;
public class XMLAutoComplete implements AutoCompleteProvider {
private CodeEditor editor;
private Comparator<CompletionItem> RESULTS_SORTER = new Comparator<CompletionItem>(){
@Override
@@ -27,13 +25,9 @@ public class XMLAutoComplete implements AutoCompleteProvider {
return ((CompletionListItem) p1).getSortText().compareTo(((CompletionListItem) p2).getSortText());
}
};
public XMLAutoComplete(CodeEditor editor) {
this.editor = editor;
}
@Override
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
// We do not store the completion service here in this class
// This is because, if we modify the instance of the service in app,
// it'll be updated here
@@ -19,8 +19,8 @@ public class XMLLanguage implements EditorLanguage {
private XMLAutoComplete completer;
private NewlineHandler[] newlineHandlers = new NewlineHandler[] {};
public XMLLanguage(CodeEditor editor) {
this.completer = new XMLAutoComplete(editor);
public XMLLanguage() {
this.completer = new XMLAutoComplete();
this.analyzer = new XMLAnalyzer();
this.newlineHandlers = new NewlineHandler[]{new IndentHandler()};
}
@@ -0,0 +1,18 @@
package com.itsaky.androidide.models;
import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic;
import java.io.File;
import java.util.List;
public class DiagnosticGroup {
public int icon;
public String text;
public File file;
public List<CompilerDiagnostic> diagnostics;
public DiagnosticGroup(int icon, File file, List<CompilerDiagnostic> diagnostics) {
this.icon = icon;
this.file = file;
this.text = file.getName();
this.diagnostics = diagnostics;
}
}
@@ -4,9 +4,9 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.itsaky.androidide.models.LogLine;
import com.itsaky.androidide.utils.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.elvishew.xlog.XLog;
public class LogReceiver extends BroadcastReceiver {
@@ -37,7 +37,7 @@ public class LogReceiver extends BroadcastReceiver {
Matcher m = LOG_HEAD_PATTERN.matcher(line);
LogLine log = null;
final boolean find = line != null && line.trim().length() > 2 /*if length() == 2 then only [] is available*/ && m.find();
XLog.i("line -> " + line + "\nlogTag find() -> " + find);
Logger.instance().i("line -> " + line + "\nlogTag find() -> " + find);
if(find) {
String date = "";
String time = "";
@@ -84,9 +84,9 @@ public class LogReceiver extends BroadcastReceiver {
log = new LogLine("", "", "", "", "", "", line);
}
XLog.i("log = " + log + "\nlogListener = " + listener);
Logger.instance().i("log = " + log + "\nlogListener = " + listener);
if(log != null && listener != null) {
XLog.i("listener.appendLogLine(...) log ->\n" + log);
Logger.instance().i("listener.appendLogLine(...) log ->\n" + log);
listener.appendLogLine(log);
logTag = null;
}
@@ -207,6 +207,8 @@ public class IDEService extends Service implements ShellServer.Callback {
@Override
public void onDestroy() {
super.onDestroy();
// Delete the tmp directory. This directory is not needed anymore...
FileUtils.delete(Environment.TMP_DIR);
isRunning = false;
}
@@ -12,6 +12,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* A utility class to convert JAR files to DEX
*
* @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them.
*/
@Deprecated
public class Jar2DexService implements ShellServer.Callback {
private final List<String> mClasspaths;
@@ -0,0 +1,33 @@
package com.itsaky.androidide.services.compiler;
import org.eclipse.jdt.core.compiler.CompilationProgress;
public class CompilerProgress extends CompilationProgress {
private boolean canceled = false;
public void setCanceled(boolean canceled) {
this.canceled = canceled;
}
@Override
public void begin(int p1) {
}
@Override
public void done() {
}
@Override
public boolean isCanceled() {
return canceled;
}
@Override
public void setTaskName(String p1) {
}
@Override
public void worked(int p1, int p2) {
}
}
@@ -0,0 +1,101 @@
package com.itsaky.androidide.services.compiler;
import java.io.File;
import java.nio.charset.Charset;
import java.util.Locale;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileObject;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
import org.eclipse.jdt.internal.compiler.tool.EclipseCompilerImpl;
import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager;
import org.eclipse.jdt.internal.compiler.tool.EclipseFileObject;
public class CompilerRequestor implements ICompilerRequestor {
private DiagnosticListener<? super JavaFileObject> listener;
public CompilerRequestor(DiagnosticListener<? super JavaFileObject> listener) {
this.listener = listener;
}
@Override
public void acceptResult(CompilationResult p1) {
if (p1 != null && p1.hasProblems()) {
for (CategorizedProblem problem : p1.getAllProblems()) {
report(problem);
}
}
}
private void report(final CategorizedProblem problem) {
this.listener.report(new Diagnostic<JavaFileObject>() {
@Override
public String getCode() {
return null;
}
@Override
public long getColumnNumber() {
if (problem instanceof DefaultProblem) {
return ((DefaultProblem) problem).column;
}
return Diagnostic.NOPOS;
}
@Override
public long getEndPosition() {
if (problem instanceof DefaultProblem) {
return ((DefaultProblem) problem).getSourceEnd();
}
return Diagnostic.NOPOS;
}
@Override
public Kind getKind() {
if (problem.isError()) {
return Diagnostic.Kind.ERROR;
}
if (problem.isWarning()) {
return Diagnostic.Kind.WARNING;
} else if (problem instanceof DefaultProblem && ((DefaultProblem) problem).isInfo()) {
return Diagnostic.Kind.NOTE;
}
return Diagnostic.Kind.OTHER;
}
@Override
public long getLineNumber() {
if (problem instanceof DefaultProblem) {
return ((DefaultProblem) problem).getSourceLineNumber();
}
return Diagnostic.NOPOS;
}
@Override
public String getMessage(Locale locale) {
return problem.getMessage();
}
@Override
public long getPosition() {
if (problem instanceof DefaultProblem) {
return ((DefaultProblem) problem).getSourceStart();
}
return Diagnostic.NOPOS;
}
@Override
public JavaFileObject getSource() {
if (problem instanceof DefaultProblem) {
File f = new File(new String(problem.getOriginatingFileName()));
if (f.exists()) {
return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null);
}
return null;
}
return null;
}
@Override
public long getStartPosition() {
return getPosition();
}
});
}
}
@@ -0,0 +1,138 @@
package com.itsaky.androidide.services.compiler;
import java.io.PrintWriter;
import org.eclipse.jdt.core.compiler.CompilationProgress;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.ReadManager;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.util.Messages;
public class CompilerWrapper extends Compiler {
public CompilerWrapper(INameEnvironment env,
IErrorHandlingPolicy errorHandlingPolicy,
CompilerOptions options,
ICompilerRequestor compilerRequestor,
IProblemFactory problemFactory,
PrintWriter out,
CompilationProgress progress) {
super(env, errorHandlingPolicy, options, compilerRequestor, problemFactory, out, progress);
this.useSingleThread = true;
}
public CompilerWrapper setProgress(CompilationProgress progress) {
this.progress = progress;
return this;
}
@Override
public void compile(ICompilationUnit[] sourceUnits) {
compileInternal(sourceUnits, false);
}
private void compileInternal(ICompilationUnit[] sourceUnits, boolean lastRound) {
this.stats.startTime = System.currentTimeMillis();
try {
reportProgress(Messages.compilation_beginningToCompile);
if (this.annotationProcessorManager == null) {
beginToCompile(sourceUnits);
} else {
ICompilationUnit[] originalUnits = sourceUnits.clone(); // remember source units in case a source type collision occurs
try {
beginToCompile(sourceUnits);
if (!lastRound) processAnnotations();
if (!this.options.generateClassFiles) return;
} catch (SourceTypeCollisionException e) {
backupAptProblems();
reset();
int originalLength = originalUnits.length;
int newProcessedLength = e.newAnnotationProcessorUnits.length;
ICompilationUnit[] combinedUnits = new ICompilationUnit[originalLength + newProcessedLength];
System.arraycopy(originalUnits, 0, combinedUnits, 0, originalLength);
System.arraycopy(e.newAnnotationProcessorUnits, 0, combinedUnits, originalLength, newProcessedLength);
this.annotationProcessorStartIndex = originalLength;
compileInternal(combinedUnits, e.isLastRound);
return;
}
}
restoreAptProblems();
processCompiledUnits(0, lastRound);
} catch (AbortCompilation e) {
this.handleInternalException(e, null);
} catch (Throwable th) {
AbortCompilation a = new AbortCompilation();
a.isSilent = true;
handleInternalException(a, null);
}
if (this.options.verbose) {
if (this.totalUnits > 1) {
this.out.println(Messages.bind(Messages.compilation_units, String.valueOf(this.totalUnits)));
} else {
this.out.println(Messages.bind(Messages.compilation_unit, String.valueOf(this.totalUnits)));
}
}
}
@Override
protected void internalBeginToCompile(ICompilationUnit[] sourceUnits, int maxUnits) {
if (!this.useSingleThread && maxUnits >= ReadManager.THRESHOLD)
this.parser.readManager = new ReadManager(sourceUnits, maxUnits);
for (int i = 0; i < maxUnits; i++) {
CompilationResult unitResult = null;
try {
if (this.options.verbose) {
this.out.println(
Messages.bind(Messages.compilation_request,
new String[] {
String.valueOf(i + 1),
String.valueOf(maxUnits),
new String(sourceUnits[i].getFileName())
}));
}
CompilationUnitDeclaration parsedUnit;
unitResult = new CompilationResult(sourceUnits[i], i, maxUnits, this.options.maxProblemsPerUnit);
long parseStart = System.currentTimeMillis();
if (this.totalUnits < this.parseThreshold) {
parsedUnit = this.parser.parse(sourceUnits[i], unitResult);
} else {
parsedUnit = this.parser.dietParse(sourceUnits[i], unitResult);
}
long resolveStart = System.currentTimeMillis();
this.stats.parseTime += resolveStart - parseStart;
this.lookupEnvironment.buildTypeBindings(parsedUnit, null);
this.stats.resolveTime += System.currentTimeMillis() - resolveStart;
addCompilationUnit(sourceUnits[i], parsedUnit);
ImportReference currentPackage = parsedUnit.currentPackage;
if (currentPackage != null) {
unitResult.recordPackageName(currentPackage.tokens);
}
if (this.parser.readManager != null) {
this.parser.readManager.shutdown();
this.parser.readManager = null;
}
this.lookupEnvironment.completeTypeBindings();
} catch (AbortCompilation a) {
if (a.compilationResult == null)
a.compilationResult = unitResult;
throw a;
} catch (Throwable th) {
AbortCompilation a = new AbortCompilation();
a.isSilent = true;
throw a;
} finally {
sourceUnits[i] = null;
}
}
}
}
@@ -2,13 +2,18 @@ package com.itsaky.androidide.services.compiler;
import android.text.TextUtils;
import com.blankj.utilcode.util.FileUtils;
import com.blankj.utilcode.util.ThreadUtils;
import com.itsaky.androidide.interfaces.CompileListener;
import com.itsaky.androidide.models.AndroidProject;
import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic;
import com.itsaky.androidide.services.compiler.model.CompilerDiagnosticImpl;
import com.itsaky.androidide.services.compiler.request.CompilationRequest;
import com.itsaky.androidide.tools.SourceJavaFileObject;
import com.itsaky.androidide.utils.Environment;
import com.itsaky.androidide.utils.Logger;
import java.io.File;
import java.io.FileFilter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -18,20 +23,41 @@ import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
import org.eclipse.jdt.internal.compiler.batch.FileSystem;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager;
import com.elvishew.xlog.XLog;
import org.eclipse.jdt.internal.compiler.tool.EclipseFileObject;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
public class JavaCompilerService implements DiagnosticListener<JavaFileObject> {
private AndroidProject project;
private CompileListener listener;
private EclipseCompiler compiler;
private EclipseFileManager fileManager;
private List<String> options;
private final Map<File, List<CompilerDiagnostic>> diagnostics = new HashMap<>();
private AndroidProject mProject;
private EclipseCompiler mCompiler;
private CompilerWrapper mSingleFileCompiler;
private EclipseFileManager mFileManager;
private FileSystem sf_nameEnvironment;
private CompilerOptions sf_options;
private IProblemFactory sf_problemFactory;
private ICompilerRequestor sf_compilerRequest;
private CompilerProgress sf_progress;
private IErrorHandlingPolicy sf_policy;
private PrintWriter sf_outWriter;
private List<String> mOptions;
private List<File> mSources;
private static final Map<File, List<CompilerDiagnostic>> mDiagnostics = new HashMap<>();
private FileFilter JAVA_FILTER = new FileFilter(){
@@ -42,90 +68,344 @@ public class JavaCompilerService implements DiagnosticListener<JavaFileObject> {
};
public JavaCompilerService(AndroidProject project) {
this.project = project;
getCompiler();
this.mProject = project;
this.createOptions();
}
public JavaCompilerService setListener(CompileListener listener) {
this.listener = listener;
return this;
private void createOptions() {
this.mOptions = new ArrayList<>();
mOptions.add("-1.8");
mOptions.add("-d");
mOptions.add(Environment.TMP_DIR.getAbsolutePath());
mOptions.add("-proc:none");
mOptions.add("-warn:all");
mOptions.add("-warn:-nls");
mOptions.add("-g");
mOptions.add("-bootclasspath");
mOptions.add(Environment.BOOTCLASSPATH.getAbsolutePath());
mOptions.add("-cp");
mOptions.add(TextUtils.join(File.pathSeparator, mProject.getClassPaths()));
}
private EclipseCompiler getCompiler() {
return getCompiler(false);
}
private EclipseCompiler getCompiler() {
if(compiler == null) {
this.compiler = new EclipseCompiler();
this.fileManager = (EclipseFileManager) compiler.getStandardFileManager(this, Locale.US, null);
this.options = new ArrayList<>();
options.add("-1.8");
options.add("-d");
options.add("none");
options.add("-proc:none");
options.add("-warn:all");
options.add("-g");
options.add("-bootclasspath");
options.add(Environment.BOOTCLASSPATH.getAbsolutePath());
options.add("-cp");
options.add(TextUtils.join(File.pathSeparator, project.getClassPaths()));
}
return compiler;
private EclipseCompiler getCompiler(boolean forceCreate) {
if(mCompiler == null || forceCreate) {
this.mCompiler = new EclipseCompiler();
this.mFileManager = (EclipseFileManager) mCompiler.getStandardFileManager(this, Locale.US, null);
}
return mCompiler;
}
public boolean compileSingle(File file) {
List<String> ops = new ArrayList<String>(options);
ops.add("-sourcepath");
ops.add(TextUtils.join(File.pathSeparator, project.getSourcePaths()));
JavaCompiler.CompilationTask task = getCompiler().getTask(null, fileManager, this, ops, null, fileManager.getJavaFileObjects(file));
diagnostics.clear();
boolean success = task.call();
if(this.listener != null) {
this.listener.onCompilationResult(success, diagnostics);
}
private EclipseFileManager getFileManager() {
if(mFileManager == null)
getCompiler(true);
return mFileManager;
}
private CompilerWrapper getSingleFileCompiler() {
if(this.sf_nameEnvironment == null)
this.sf_nameEnvironment = new FileSystem(createClasspaths(mProject.getClassPaths()), null, null);
if(this.sf_compilerRequest == null)
this.sf_compilerRequest = new CompilerRequestor(this);
if(this.sf_problemFactory == null)
this.sf_problemFactory = getProblemFactory();
if(this.sf_options == null) {
this.sf_options = new CompilerOptions();
putOptions(sf_options);
}
if(sf_policy == null)
this.sf_policy = DefaultErrorHandlingPolicies.exitAfterAllProblems();
if(sf_outWriter == null)
sf_outWriter = new PrintWriter(System.out);
if(mSingleFileCompiler == null) {
mSingleFileCompiler = new CompilerWrapper(
sf_nameEnvironment,
sf_policy,
sf_options,
sf_compilerRequest,
sf_problemFactory,
sf_outWriter,
sf_progress = new CompilerProgress()
);
}
return mSingleFileCompiler;
}
private void putOptions(CompilerOptions options) {
final long languageLevel = ClassFileConstants.JDK1_8;
options.sourceLevel = options.targetJDK = options.complianceLevel = languageLevel;
options.processAnnotations = false;
Map<String, String> map = options.getMap();
for(String warn : CompilerOptions.warningTokens) {
map.put(warn, CompilerOptions.ENABLED);
}
map.put(CompilerOptions.OPTION_ReportNonExternalizedStringLiteral, CompilerOptions.DISABLED);
map.put(CompilerOptions.OPTION_LocalVariableAttribute, CompilerOptions.DO_NOT_GENERATE);
map.put(CompilerOptions.OPTION_LineNumberAttribute, CompilerOptions.DO_NOT_GENERATE);
map.put(CompilerOptions.OPTION_SourceFileAttribute, CompilerOptions.DO_NOT_GENERATE);
options.set(map);
}
private String[] createClasspaths(List<String> classpaths) {
if (classpaths == null) {
classpaths = new ArrayList<>();
}
if(!classpaths.contains(Environment.BOOTCLASSPATH.getAbsolutePath())) {
classpaths.add(Environment.BOOTCLASSPATH.getAbsolutePath());
}
String[] result = new String[classpaths.size()];
return classpaths.toArray(result);
}
private CompilationRequest createCompilationRequestForAll(CompileListener listener) {
if(mSources == null || mSources.size() <= 0) {
loadSources();
}
return CompilationRequest.Builder
.newInstance()
.withCallback(listener)
.withFiles(mSources)
.build();
}
private void notifyCompiled(CompileListener listener, Map<File, List<CompilerDiagnostic>> diags) {
ThreadUtils.runOnUiThread(() -> {
listener.onCompilationResult(true, diags);
});
}
protected void cancelCompilation() {
getCompiler().setCompilationCancelled(true);
if(sf_progress != null) {
sf_progress.setCanceled(true);
}
}
public List<String> getClasspaths() {
return mProject.getClassPaths();
}
public void loadSources() {
mSources = new ArrayList<File>();
for(String path : mProject.getSourcePaths()) {
if(path == null || path.trim().length() <= 0) continue;
File p = new File(path);
if(!p.exists() || !p.isDirectory()) continue;
mSources.addAll(FileUtils.listFilesInDirWithFilter(p, JAVA_FILTER, true));
}
}
public void compileAllAsync(CompileListener listener) {
new Thread(() -> {
compile(createCompilationRequestForAll(listener));
}).start();
}
public void compileAsync(CompilationRequest request) {
new Thread(() -> {
compile(request);
}).start();
}
public void compileSingleAsync(SourceJavaFileObject source, CompileListener listener) {
new Thread(() -> {
compileSingle(source, listener);
}).start();
}
public boolean compile(final CompilationRequest request) {
this.mDiagnostics.clear();
final List<JavaFileObject> fileObjects = (ArrayList<JavaFileObject>) getFileManager().getJavaFileObjectsFromFiles(request.files());
JavaCompiler.CompilationTask task = getCompiler()
.setCompilationCancelled()
.newProgress()
.getTask(null, getFileManager(), this, mOptions, null, fileObjects);
boolean success = task.call();
notifyCompiled(request.callback(), mDiagnostics);
return success;
}
public boolean compileAll() {
List<File> files = new ArrayList<File>();
for(String path : project.getSourcePaths()) {
if(path == null || path.trim().length() <= 0) continue;
File p = new File(path);
if(!p.exists() || !p.isDirectory()) continue;
files.addAll(FileUtils.listFilesInDirWithFilter(p, JAVA_FILTER, true));
}
return compile(files);
}
public boolean compile(List<File> files) {
JavaCompiler.CompilationTask task = getCompiler().getTask(null, fileManager, this, options, null, fileManager.getJavaFileObjectsFromFiles(files));
diagnostics.clear();
final long s = System.nanoTime();
boolean success = task.call();
XLog.i("[JavaCompiler]"
+ "\ncompiled: " + files.size() + " files"
+ "\ntimeElapsed: " + (System.nanoTime() - s) + "ns");
return success;
}
public Map<File, List<CompilerDiagnostic>> getDiagnostics() {
return diagnostics;
}
}
public void compileSingle(final SourceJavaFileObject source, final CompileListener listener) {
mDiagnostics.clear();
cancelCompilation();
if(mDiagnostics != null) mDiagnostics.clear();
getSingleFileCompiler()
.setProgress(this.sf_progress = new CompilerProgress())
.compile(new CompilationUnit[]{source.asUnit()});
notifyCompiled(listener, mDiagnostics);
}
@Override
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
CompilerDiagnostic d = CompilerDiagnosticImpl.fromDiagnostic(diagnostic);
if(diagnostics.containsKey(d.file())) {
List<CompilerDiagnostic> diags = diagnostics.get(d.file());
if(mDiagnostics.containsKey(d.file())) {
List<CompilerDiagnostic> diags = mDiagnostics.get(d.file());
if(diags == null)
diags = new ArrayList<>();
diags.add(d);
diagnostics.put(d.file(), diags);
mDiagnostics.put(d.file(), diags);
} else {
List<CompilerDiagnostic> diags = new ArrayList<>();
diags.add(d);
diagnostics.put(d.file(), diags);
mDiagnostics.put(d.file(), diags);
}
}
private IProblemFactory getProblemFactory() {
return new DefaultProblemFactory() {
@Override
public CategorizedProblem createProblem(
final char[] originatingFileName,
final int problemId,
final String[] problemArguments,
final String[] messageArguments,
final int severity,
final int startPosition,
final int endPosition,
final int lineNumber,
final int columnNumber) {
report(new Diagnostic<JavaFileObject>() {
@Override
public String getCode() {
return Integer.toString(problemId);
}
@Override
public long getColumnNumber() {
return columnNumber;
}
@Override
public long getEndPosition() {
return endPosition;
}
@Override
public Kind getKind() {
if ((severity & ProblemSeverities.Error) != 0) {
return Diagnostic.Kind.ERROR;
}
if ((severity & ProblemSeverities.Optional) != 0) {
return Diagnostic.Kind.WARNING;
}
if ((severity & ProblemSeverities.Warning) != 0) {
return Diagnostic.Kind.MANDATORY_WARNING;
}
return Diagnostic.Kind.OTHER;
}
@Override
public long getLineNumber() {
return lineNumber;
}
@Override
public String getMessage(Locale locale) {
if (locale != null) {
setLocale(locale);
}
return getLocalizedMessage(problemId, problemArguments);
}
@Override
public long getPosition() {
return startPosition;
}
@Override
public JavaFileObject getSource() {
File f = new File(new String(originatingFileName));
if (f.exists()) {
return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null);
}
return null;
}
@Override
public long getStartPosition() {
return startPosition;
}
});
return super.createProblem(originatingFileName, problemId, problemArguments, messageArguments, severity, startPosition, endPosition, lineNumber, columnNumber);
}
@Override
public CategorizedProblem createProblem(
final char[] originatingFileName,
final int problemId,
final String[] problemArguments,
final int elaborationID,
final String[] messageArguments,
final int severity,
final int startPosition,
final int endPosition,
final int lineNumber,
final int columnNumber) {
report(new Diagnostic<JavaFileObject>() {
@Override
public String getCode() {
return Integer.toString(problemId);
}
@Override
public long getColumnNumber() {
return columnNumber;
}
@Override
public long getEndPosition() {
return endPosition;
}
@Override
public Kind getKind() {
if ((severity & ProblemSeverities.Error) != 0) {
return Diagnostic.Kind.ERROR;
}
if ((severity & ProblemSeverities.Info) != 0) {
return Diagnostic.Kind.NOTE;
}
if ((severity & ProblemSeverities.Optional) != 0) {
return Diagnostic.Kind.WARNING;
}
if ((severity & ProblemSeverities.Warning) != 0) {
return Diagnostic.Kind.MANDATORY_WARNING;
}
return Diagnostic.Kind.OTHER;
}
@Override
public long getLineNumber() {
return lineNumber;
}
@Override
public String getMessage(Locale locale) {
if (locale != null) {
setLocale(locale);
}
return getLocalizedMessage(problemId, problemArguments);
}
@Override
public long getPosition() {
return startPosition;
}
@Override
public JavaFileObject getSource() {
File f = new File(new String(originatingFileName));
if (f.exists()) {
return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null);
}
return null;
}
@Override
public long getStartPosition() {
return startPosition;
}
});
return super.createProblem(originatingFileName, problemId, problemArguments, elaborationID, messageArguments, severity, startPosition, endPosition, lineNumber, columnNumber);
}
};
}
}
@@ -4,11 +4,13 @@ import java.io.File;
import javax.tools.Diagnostic;
public interface CompilerDiagnostic {
File file();
long line();
long column();
long start();
long end();
String message();
Diagnostic.Kind kind();
File file();
long line();
long column();
long start();
long end();
String message();
Diagnostic.Kind kind();
int color();
}
@@ -15,16 +15,20 @@ public class CompilerDiagnosticImpl implements CompilerDiagnostic {
protected long end = 0;
protected Diagnostic.Kind kind;
public static final int COLOR_ERROR = 0xfff44336;
public static final int COLOR_WARNING = 0xffffb300;
public static final int COLOR_NOTE = 0xffffffff;
private CompilerDiagnosticImpl(){}
private CompilerDiagnosticImpl(Diagnostic<? extends JavaFileObject> diagnostic) {
this.file = new File(diagnostic.getSource().toUri());
this.message = diagnostic.getMessage(Locale.US);
this.line = diagnostic.getLineNumber();
this.line = diagnostic.getLineNumber() - 1;
this.column = diagnostic.getColumnNumber();
this.start = diagnostic.getStartPosition();
this.end = diagnostic.getEndPosition();
this.end = diagnostic.getEndPosition() + 1;
this.kind = diagnostic.getKind();
}
@@ -80,9 +84,34 @@ public class CompilerDiagnosticImpl implements CompilerDiagnostic {
public String message() {
return message;
}
@Override
public int color() {
if(kind() == Diagnostic.Kind.ERROR) {
return COLOR_ERROR;
} else if(kind() == Diagnostic.Kind.WARNING || kind() == Diagnostic.Kind.MANDATORY_WARNING) {
return COLOR_WARNING;
}
return COLOR_NOTE;
}
@Override
public String toString() {
return "CompilerDiagnosticImpl(file=" + file() + ", line=" + line() + ", column=" + column() + ", start=" + start() + ", end=" + end() + ", message=" + message() + ")";
return "CompilerDiagnosticImpl(file=" + file() + ", line=" + line() + ", column=" + column() + ", start=" + start() + ", end=" + end() + ", message=" + message() + ", kind=" + kind().toString() + ")";
}
@Override
public boolean equals(Object obj) {
if(obj instanceof CompilerDiagnosticImpl) {
CompilerDiagnosticImpl that = (CompilerDiagnosticImpl) obj;
return this.file().equals(that.file()) &&
this.line() == that.line() &&
this.column() == that.column() &&
this.start() == that.start() &&
this.end() == that.end() &&
this.message().equals(that.message()) &&
this.kind() == that.kind();
}
return false;
}
}
@@ -0,0 +1,71 @@
package com.itsaky.androidide.services.compiler.request;
import com.itsaky.androidide.interfaces.CompileListener;
import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic;
import com.itsaky.androidide.tools.SourceJavaFileObject;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public interface CompilationRequest {
List<File> files();
CompileListener callback();
public static class Builder {
private List<File> files;
private CompileListener callback;
private Builder(){
files = new ArrayList<>();
}
public static Builder newInstance() {
return new Builder();
}
public Builder withFiles(File... files) {
if(files == null) {
this.files = new ArrayList<>();
return this;
}
this.files = new ArrayList<>();
for(File file : files) {
this.files.add(file);
}
return this;
}
public Builder withFiles(List<File> files) {
if(files == null) {
this.files = new ArrayList<>();
return this;
}
this.files = files;
return this;
}
public Builder withCallback(CompileListener callback) {
this.callback = callback;
return this;
}
public CompilationRequest build() {
return new CompilationRequest(){
@Override
public List<File> files() {
return files;
}
@Override
public CompileListener callback() {
return callback;
}
};
}
}
}
@@ -2,7 +2,6 @@ package com.itsaky.androidide.shell;
import com.blankj.utilcode.util.CloseUtils;
import com.blankj.utilcode.util.ThrowableUtils;
import com.elvishew.xlog.XLog;
import com.itsaky.androidide.utils.Environment;
import java.io.BufferedReader;
import java.io.Closeable;
@@ -38,25 +37,30 @@ public class ShellServer extends Thread {
stringBuilder.append("\nexport PATH=$PATH:" + Environment.path(Environment.ANDROID_HOME) + "/cmdline-tools/latest/bin");
stringBuilder.append("\nexport PATH=$PATH:" + Environment.path(Environment.ANDROID_HOME) + "/cmake/bin");
// Provide paths to .so files needed to execute JDK
final String lib = Environment.LIBDIR.getAbsolutePath();
stringBuilder.append("\nexport LD_LIBRARY_PATH=" + lib);
stringBuilder.append(System.getenv().containsKey("LD_LIBRARY_PATH") ? ":" + System.getenv().get("LD_LIBRARY_PATH") : "");
if(android.os.Build.VERSION.SDK_INT >= 30) {
// Hook calls to malloc() in native binaries
// Required only in Android 11 and up
stringBuilder.append("\nexport LD_PRELOAD=" + Environment.LIBHOOKSO.getAbsolutePath());
}
// Monotonic clock fix, needed for JDK
stringBuilder.append("\nls -l /system/lib64/libc.so && " +
"ln -sf /apex/com.android.runtime/lib64/bionic/libc.so " + lib + "/librt.so && " +
"ln -sf /apex/com.android.runtime/lib64/bionic/libc.so " + lib + "/libpthread.so");
// Make sure there are proper permissions set to the $HOME directory
stringBuilder.append("\nchmod -R 777 $HOME");
stringBuilder.append("\njava -XshowSettings:all > /sdcard/androidide_jdk_settings.txt");
// Execute all commands...
append(stringBuilder.toString(), false);
} catch (Throwable th) {
if (callback != null) {
String out = ThrowableUtils.getFullStackTrace(th).concat("\n");
XLog.e("[ShellServer] error setting up server ->\n" + out);
callback.output(out);
}
}
@@ -0,0 +1,39 @@
package com.itsaky.androidide.tools;
import java.io.File;
import java.net.URI;
import javax.tools.SimpleJavaFileObject;
import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
public class SourceJavaFileObject extends SimpleJavaFileObject {
private File file;
private String source;
public SourceJavaFileObject(File file, String source) {
super(URI.create("source"), Kind.SOURCE);
this.file = file;
this.source = source;
}
public File getFile() {
return this.file;
}
@Override
public String getName() {
return file.getAbsolutePath();
}
@Override
public CharSequence getCharContent(boolean p1) {
return source;
}
public CompilationUnit asUnit() {
return new CompilationUnit(
getCharContent(true).toString().toCharArray(),
getFile().getAbsolutePath(),
"UTF-8");
}
}
@@ -0,0 +1,119 @@
package com.itsaky.androidide.utils;
import com.elvishew.xlog.LogConfiguration;
import com.elvishew.xlog.LogLevel;
import com.elvishew.xlog.XLog;
import com.elvishew.xlog.printer.Printer;
import com.elvishew.xlog.printer.file.FilePrinter;
import com.elvishew.xlog.printer.file.naming.LevelFileNameGenerator;
public class Logger {
private static Logger instance;
private static com.elvishew.xlog.Logger xLogger;
private static String TAG = "AndroidIDE";
public static Logger instance() {
return instance == null ? createInstance(TAG) : instance;
}
public static Logger instance(String tag) {
return instance == null ? createInstance(tag) : instance;
}
private Logger(String tag) {
this.TAG = tag;
}
private static Logger createInstance(String tag) {
LogConfiguration config = new LogConfiguration.Builder()
.logLevel(com.itsaky.androidide.BuildConfig.DEBUG ? LogLevel.ALL : LogLevel.NONE)
.tag("AndroidIDE")
.enableThreadInfo()
.enableStackTrace(10)
.disableBorder()
.build();
Printer filePrinter = new FilePrinter
.Builder(FileUtil.getExternalStorageDir() + "/ide_xlog")
.fileNameGenerator(new LevelFileNameGenerator())
.cleanStrategy(f -> false)
.build();
XLog.init(config, filePrinter);
xLogger = XLog.tag("AndroidIDE").build();
instance = new Logger(tag);
return instance;
}
public Logger d(Object... messages) {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(TAG);
sb.append("]");
sb.append("\n");
if(messages == null) {
xLogger.d("null");
return this;
}
for(Object msg : messages) {
sb.append(msg);
sb.append("\n");
}
xLogger.d(sb.toString());
return this;
}
public Logger e(Object... messages) {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(TAG);
sb.append("]");
sb.append("\n");
if(messages == null) {
xLogger.e("null");
return this;
}
for(Object msg : messages) {
sb.append(msg);
sb.append("\n");
}
xLogger.e(sb.toString());
return this;
}
public Logger v(Object... messages) {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(TAG);
sb.append("]");
sb.append("\n");
if(messages == null) {
xLogger.v("null");
return this;
}
for(Object msg : messages) {
sb.append(msg);
sb.append("\n");
}
xLogger.v(sb.toString());
return this;
}
public Logger i(Object... messages) {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(TAG);
sb.append("]");
sb.append("\n");
if(messages == null) {
xLogger.i("null");
return this;
}
for(Object msg : messages) {
sb.append(msg);
sb.append("\n");
}
xLogger.i(sb.toString());
return this;
}
}
@@ -19,6 +19,7 @@ import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.utils.Either;
import io.github.rosemoe.editor.struct.CompletionItem;
import io.github.rosemoe.editor.text.TextAnalyzeResult;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.util.List;
/**
@@ -37,7 +38,7 @@ public interface AutoCompleteProvider {
* @param line The line of cursor
* @return Analyzed items
*/
List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line);
List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line);
}
@@ -24,6 +24,7 @@ import io.github.rosemoe.editor.interfaces.NewlineHandler;
import io.github.rosemoe.editor.struct.CompletionItem;
import io.github.rosemoe.editor.text.TextAnalyzeResult;
import io.github.rosemoe.editor.text.TextAnalyzer.AnalyzeThread.Delegate;
import io.github.rosemoe.editor.widget.CodeEditor;
import io.github.rosemoe.editor.widget.SymbolPairMatch;
import java.util.ArrayList;
import java.util.List;
@@ -79,7 +80,7 @@ public class EmptyLanguage implements EditorLanguage {
public static class EmptyAutoCompleteProvider implements AutoCompleteProvider {
@Override
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
return new ArrayList<Either<SuggestItem, CompletionItem>>();
}
@@ -56,7 +56,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import io.github.rosemoe.editor.R;
import com.itsaky.androidide.R;
import io.github.rosemoe.editor.interfaces.EditorEventListener;
import io.github.rosemoe.editor.interfaces.EditorLanguage;
import io.github.rosemoe.editor.interfaces.NewlineHandler;
@@ -82,6 +82,13 @@ import java.util.LinkedList;
import java.util.List;
import com.blankj.utilcode.util.DeviceUtils;
import io.github.rosemoe.editor.struct.HexColor;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic;
import javax.tools.Diagnostic;
import java.util.stream.Collectors;
import com.itsaky.androidide.utils.Logger;
/**
* CodeEditor is a editor that can highlight text regions by doing basic syntax analyzing
@@ -219,6 +226,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
private EditorLanguage mLanguage;
private long mLastMakeVisible = 0;
private EditorAutoCompleteWindow mCompletionWindow;
private DiagnosticWindow mDiagnosticWindow;
private EditorTouchEventHandler mEventHandler;
private Paint.Align mLineNumberAlign;
private GestureDetector mBasicDetector;
@@ -243,7 +251,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
KeyMetaStates mKeyMetaStates = new KeyMetaStates(this);
private HashMap<HexColor, Integer> mLineColors;
private HashMap<Long, List<CompilerDiagnostic>> mDiagnostics;
public CodeEditor(Context context) {
this(context, null);
}
@@ -431,6 +440,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
setFocusableInTouchMode(true);
mConnection = new EditorInputConnection(this);
mCompletionWindow = new EditorAutoCompleteWindow(this);
mDiagnosticWindow = new DiagnosticWindow(this);
mVerticalEdgeGlow = new MaterialEdgeEffect();
mHorizontalGlow = new MaterialEdgeEffect();
mOverrideSymbolPairs = new SymbolPairMatch();
@@ -462,6 +472,57 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
mErrorColor = Color.parseColor("#FF5252");
mWarningColor = Color.parseColor("#FFD740");
mRect2.set(-1, -1, -1, -1);
mDiagnostics = new HashMap<>();
}
public void showOrHideDiagnosticWindow() {
int line = getCursor().getLeftLine();
int index = getCursor().getLeft();
boolean hide = false;
boolean has = mDiagnostics.containsKey(Long.valueOf(line));
if(has) {
List<CompilerDiagnostic> diags = mDiagnostics.get(Long.valueOf(line));
if(diags != null) {
diags = diags.stream().filter(d -> d.start() <= index && d.end() >= index).collect(Collectors.toList());
if(diags != null && diags.size() > 0){
CompilerDiagnostic d = diags.get(0);
mDiagnosticWindow.setDiagnostic(d);
if(!mDiagnosticWindow.isShowing())
mDiagnosticWindow.show();
else updateDiagnosticWindowPosition();
} else hide = true;
} else hide = true;
} else hide = true;
if(hide && mDiagnosticWindow.isShowing()) {
mDiagnosticWindow.hide();
}
}
public void setDiagnostics(List<CompilerDiagnostic> diags) {
this.mDiagnostics = new HashMap<>();
mapDiagnostics(diags);
}
private void mapDiagnostics(List<CompilerDiagnostic> diags) {
if(diags == null)
return;
for(int i=0;i<diags.size();i++) {
CompilerDiagnostic d = diags.get(i);
if(d == null)
continue;
if(mDiagnostics.containsKey(d.line())) {
List<CompilerDiagnostic> ds = mDiagnostics.get(d.line());
if(ds == null)
ds = new ArrayList<>();
if(!ds.contains(d))
ds.add(d);
} else {
List<CompilerDiagnostic> ds = new ArrayList<>();
ds.add(d);
mDiagnostics.put(Long.valueOf(d.line()), ds);
}
}
}
public CodeEditor setLineColors(HashMap<HexColor, Integer> map) {
@@ -1141,7 +1202,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
}
}
// Draw hex color values
// Draw hex colors
if(isLineColorsEnabled() && mLineColors != null && mLineColors.size() > 0) {
for(HexColor c : mLineColors.keySet()) {
if(c != null && c.isSameLine(line) && mLineColors.get(c) != null) {
@@ -1159,7 +1220,35 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
}
}
}
// Draw errors and warnings
if(mDiagnostics != null && mDiagnostics.containsKey(Long.valueOf(line))) {
List<CompilerDiagnostic> diags = mDiagnostics.get(Long.valueOf(line));
if(diags != null && diags.size() > 0) {
for(int i=0;i<diags.size();i++) {
CompilerDiagnostic d = diags.get(i);
if(d == null) continue;
CharPosition start = null, end = null;
try {
start = getText().getIndexer().getCharPosition((int) d.start());
end = getText().getIndexer().getCharPosition((int) d.end());
} catch (Throwable th) {
continue;
}
final int paintStart = Math.max(firstVisibleChar, start.column);
final int paintEnd = Math.min(lastVisibleChar, end.column);
final float width = measureText(mBuffer, paintStart, paintEnd - paintStart);
final RectF r = new RectF();
r.bottom = getRowBottom(line) - getOffsetY() - mDpUnit * 1;
r.top = r.bottom - getRowHeight() * 0.2f;
r.left = paintingOffset + measureText(mBuffer, firstVisibleChar, paintStart - firstVisibleChar);
r.right = r.left + width;
mPaint.setColor(d.color());
drawZigzagUnderline(r, canvas, mPaint);
}
}
}
// Draw cursors
if (mCursor.isSelected()) {
if (mTextActionPresenter.shouldShowCursor()) {
@@ -1179,6 +1268,34 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
}
}
private void drawZigzagUnderline(RectF boundaries, Canvas canvas, Paint paint) {
Path path = new Path();
path.moveTo(boundaries.right, boundaries.bottom);
float left = boundaries.left;
float right = boundaries.right;
float y = boundaries.bottom;
float h = boundaries.height();
float seed = 2 * h;
float width = right - left;
int count = (int) (width / seed);
float diff = width - seed * count;
float sideDiff = diff / 2;
float halfSeed = seed / 2;
float innerHeight = y - h;
for (int i=count;i>=1;i--) {
float startSeed = i * seed + sideDiff + left;
float endSeed = startSeed - seed;
if (i == 1) {
endSeed -= sideDiff;
}
path.lineTo(startSeed - halfSeed, innerHeight);
path.lineTo(endSeed, y);
}
canvas.drawPath(path, paint);
}
/**
* Draw code block lines on screen
@@ -2141,6 +2258,36 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
mTextActionPresenter.onExit();
}
}
private void updateDiagnosticWindowPosition() {
float panelX = updateCursorAnchor() + mDpUnit * 20;
float[] rightLayoutOffset = mLayout.getCharLayoutOffset(mCursor.getRightLine(), mCursor.getRightColumn());
float panelY = rightLayoutOffset[0] - getOffsetY() + getRowHeight() / 2f;
float restY = getHeight() - panelY;
if (restY > mDpUnit * 120) {
restY = mDpUnit * 120;
} else if (restY < mDpUnit * 100) {
float offset = 0;
while (restY < mDpUnit * 100) {
restY += getRowHeight();
panelY -= getRowHeight();
offset += getRowHeight();
}
getScroller().startScroll(getOffsetX(), getOffsetY(), 0, (int) offset, 0);
}
mDiagnosticWindow.setExtendedX(panelX);
mDiagnosticWindow.setExtendedY(panelY);
if (getWidth() < 500 * mDpUnit) {
mDiagnosticWindow.setWidth(getWidth() * 7 / 8);
mDiagnosticWindow.setExtendedX(getWidth() / 8f / 2f);
} else {
int w = getWidth() / 3;
mDiagnosticWindow.setWidth(w * 2);
}
mDiagnosticWindow.setHeight((int) restY);
mDiagnosticWindow.updatePosition();
}
/**
* Apply new position of auto completion window
@@ -3227,6 +3374,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
if (mTextActionPresenter != null) {
mTextActionPresenter.onExit();
}
showOrHideDiagnosticWindow();
}
/**
@@ -3302,6 +3451,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
mTextActionPresenter.onBeginTextSelect();
}
mEventHandler.notifyTouchedSelectionHandlerLater();
showOrHideDiagnosticWindow();
}
/**
@@ -3767,7 +3917,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
| EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS
| EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI|EditorInfo.IME_FLAG_NO_FULLSCREEN;
outAttrs.initialSelStart = getCursor() != null ? getCursor().getLeft() : 0;
outAttrs.initialSelEnd = getCursor() != null ? getCursor().getRight() : 0;
outAttrs.initialCapsMode = mConnection.getCursorCapsMode(0);
@@ -4075,6 +4227,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
@Override
public void afterInsert(Content content, int startLine, int startColumn, int endLine, int endColumn, CharSequence insertedContent) {
if(mDiagnosticWindow != null)
mDiagnosticWindow.hide();
// Update spans
if (isSpanMapPrepared(true, endLine - startLine)) {
if (startLine == endLine) {
@@ -4135,6 +4290,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
@Override
public void afterDelete(Content content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) {
if(mDiagnosticWindow != null)
mDiagnosticWindow.hide();
if (isSpanMapPrepared(false, endLine - startLine)) {
if (startLine == endLine) {
SpanMapUpdater.shiftSpansOnSingleLineDelete(mSpanner.getResult().getSpanMap(), startLine, startColumn, endColumn);
@@ -24,7 +24,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.utils.Either;
import io.github.rosemoe.editor.R;
import com.itsaky.androidide.R;
import io.github.rosemoe.editor.struct.CompletionItem;
import android.graphics.drawable.Drawable;
import androidx.core.content.ContextCompat;
@@ -0,0 +1,79 @@
package io.github.rosemoe.editor.widget;
import android.graphics.PorterDuff;
import android.view.LayoutInflater;
import androidx.core.content.ContextCompat;
import com.itsaky.androidide.R;
import com.itsaky.androidide.databinding.LayoutDiagnosticInfoBinding;
import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic;
import javax.tools.Diagnostic;
public class DiagnosticWindow extends EditorBasePopupWindow {
private CodeEditor editor;
private CompilerDiagnostic diagnostic;
private LayoutDiagnosticInfoBinding binding;
public DiagnosticWindow(CodeEditor editor) {
super(editor);
this.editor = editor;
this.binding = LayoutDiagnosticInfoBinding.inflate(LayoutInflater.from(editor.getContext()));
setContentView(this.binding.getRoot());
}
@Override
public void show() {
super.show();
setupData();
}
public void setMaxHeight(int height) {
}
@Override
public void updatePosition() {
super.updatePosition();
setupData();
}
private void setupData() {
if(diagnostic != null) {
binding.icon.setImageResource(getDiagnosticIconId());
binding.icon.setColorFilter(ContextCompat.getColor(binding.icon.getContext(), R.color.primaryIconColor), PorterDuff.Mode.SRC_ATOP);
binding.errorType.setText(getDiagnosticTypeString());
binding.errorPosition.setText(
editor.getContext().getString(
com.itsaky.androidide.R.string.diagnostic_position,
""+ diagnostic.line(),
""+ diagnostic.column()
)
);
binding.msg.setText(diagnostic.message());
binding.msg.setTextIsSelectable(true);
}
}
public DiagnosticWindow setDiagnostic(CompilerDiagnostic diag) {
this.diagnostic = diag;
return this;
}
private int getDiagnosticIconId() {
if(diagnostic.kind() == Diagnostic.Kind.ERROR)
return R.drawable.ic_compilation_error;
return R.drawable.ic_info;
}
private String getDiagnosticTypeString() {
if(diagnostic.kind() == Diagnostic.Kind.ERROR) {
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_error);
} else if(diagnostic.kind() == Diagnostic.Kind.WARNING || diagnostic.kind() == Diagnostic.Kind.MANDATORY_WARNING) {
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_warning);
}
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_note);
}
}
@@ -19,14 +19,12 @@ import android.content.Context;
import android.graphics.drawable.GradientDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.blankj.utilcode.util.DeviceUtils;
import com.itsaky.androidide.databinding.LayoutCompletionWindowBinding;
import com.itsaky.androidide.language.java.parser.internal.SuggestItem;
import com.itsaky.androidide.models.CompletionEdit;
import com.itsaky.androidide.models.CompletionListItem;
import com.itsaky.androidide.utils.Either;
import io.github.rosemoe.editor.databinding.LayoutCompletionWindowBinding;
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
import io.github.rosemoe.editor.struct.CompletionItem;
import io.github.rosemoe.editor.text.CharPosition;
@@ -276,7 +274,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
mAdapter.attachAttributes(this, results);
mBinding.list.setAdapter(mAdapter);
mCurrent = 0;
float newHeight = mEditor.getDpUnit() * 40 * results.size();
float newHeight = mEditor.getDpUnit() * mAdapter.getItemHeight() * results.size();
if (isShowing()) {
update(getWidth(), (int) Math.min(newHeight, mMaxHeight));
}
@@ -307,7 +305,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
@Override
public void run() {
try {
displayResults(mLocalProvider.getAutoCompleteItems(mPrefix, mInner, mColors, mLine), mTime);
displayResults(mLocalProvider.getAutoCompleteItems(mEditor, mPrefix, mInner, mColors, mLine), mTime);
} catch (Exception e) {
e.printStackTrace();
displayResults(new ArrayList<>(), mTime);
@@ -17,7 +17,7 @@ package io.github.rosemoe.editor.widget;
import android.view.Gravity;
import android.widget.PopupWindow;
import io.github.rosemoe.editor.R;
import com.itsaky.androidide.R;
/**
* Editor base panel class
@@ -24,7 +24,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import io.github.rosemoe.editor.R;
import com.itsaky.androidide.R;
/**
* This will show when selecting text

Some files were not shown because too many files have changed in this diff Show More