v1.0.3-alpha

- Implemented
    • Find all references
    • Comment/Uncomment line
    • Find in file
    • Find in project
This commit is contained in:
Akash Yadav
2021-08-13 13:54:12 +05:30
parent 28a48b9a0c
commit fb2c6313ec
43 changed files with 1343 additions and 156 deletions
Binary file not shown.
@@ -4,7 +4,6 @@ import abhishekti7.unicorn.filepicker.UnicornFilePicker;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
@@ -25,9 +24,6 @@ import com.itsaky.toaster.Toaster;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import com.itsaky.androidide.utils.Logger;
public class DownloadActivity extends StudioActivity {
@@ -88,7 +84,7 @@ public class DownloadActivity extends StudioActivity {
}
}
private final String DONE = "DONE";
private final String DONE = "'Installed successfully.'";
private void installAll() {
showProgress();
getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true);
@@ -106,7 +102,8 @@ public class DownloadActivity extends StudioActivity {
}
}
sb.append("rm -rf '" + getApp().getToolsDownloadDirectory().getAbsolutePath() + "' && ");
sb.append("echo " + DONE);
sb.append("echo 'Cleaning unsupported flags in binaries...' && $BUSYBOX find $JAVA_HOME/bin -type f -exec androidide-cleaner {} \\; && ");
sb.append("echo " + DONE);
server.bgAppend(sb.toString());
}
@@ -51,6 +51,7 @@ import com.itsaky.androidide.databinding.LayoutDialogTextInputBinding;
import com.itsaky.androidide.fragments.EditorFragment;
import com.itsaky.androidide.fragments.FileTreeFragment;
import com.itsaky.androidide.fragments.sheets.OptionsListFragment;
import com.itsaky.androidide.fragments.sheets.ProgressSheet;
import com.itsaky.androidide.fragments.sheets.TextSheetFragment;
import com.itsaky.androidide.interfaces.DiagnosticClickListener;
import com.itsaky.androidide.interfaces.JLSRequestor;
@@ -59,6 +60,7 @@ import com.itsaky.androidide.language.logs.LogLanguageImpl;
import com.itsaky.androidide.models.AndroidProject;
import com.itsaky.androidide.models.DiagnosticGroup;
import com.itsaky.androidide.models.LogLine;
import com.itsaky.androidide.models.SearchResult;
import com.itsaky.androidide.models.SheetOption;
import com.itsaky.androidide.receivers.LogReceiver;
import com.itsaky.androidide.services.IDEService;
@@ -102,10 +104,10 @@ import com.itsaky.lsp.TextDocumentPositionParams;
import com.itsaky.toaster.Toaster;
import com.unnamed.b.atv.model.TreeNode;
import io.github.rosemoe.editor.langs.EmptyLanguage;
import io.github.rosemoe.editor.text.Content;
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;
import java.util.Map;
@@ -113,6 +115,11 @@ import java.util.Stack;
import java.util.regex.Pattern;
import me.piruin.quickaction.ActionItem;
import me.piruin.quickaction.QuickAction;
import androidx.appcompat.app.AlertDialog;
import com.itsaky.androidide.databinding.LayoutSearchProjectBinding;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import com.itsaky.androidide.utils.RecursiveFileSearcher;
public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener,
IDEService.BuildListener,
@@ -141,6 +148,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
private QuickAction mTabCloseAction;
private TextSheetFragment mDaemonStatusFragment;
private OptionsListFragment mFileOptionsFragment;
private ProgressSheet mSearchingProgress;
private AlertDialog mFindInProjectDialog;
private boolean recreateCompletionServices = false;
@@ -256,21 +265,10 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
}
private void setupContainers() {
if(getBuildView().getParent() != null && getBuildView().getParent() instanceof ViewGroup) {
((ViewGroup) getBuildView().getParent()).removeView(getBuildView());
}
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());
}
if(getSearchResultList().getParent() != null && getSearchResultList().getParent() instanceof ViewGroup) {
((ViewGroup) getSearchResultList().getParent()).removeView(getSearchResultList());
}
removeFromParent(getBuildView());
removeFromParent(getLogView());
removeFromParent(getDiagnosticsList());
removeFromParent(getSearchResultList());
mBinding.buildOutcontainer.addView(getBuildView(), new ViewGroup.LayoutParams(-1, -1));
mBinding.buildContainer.setVisibility(View.GONE);
@@ -303,6 +301,12 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
mBinding.viewFiles.setOnClickListener(v -> showFiles());
mBinding.viewDaemonStatus.setOnClickListener(v -> showDaemonStatus());
}
private void removeFromParent(View v) {
if(v.getParent() != null && v.getParent() instanceof ViewGroup) {
((ViewGroup) v.getParent()).removeView(v);
}
}
private void onSoftInputChanged() {
invalidateOptionsMenu();
@@ -388,14 +392,21 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
if (item != null) {
boolean enabled = getBuildService() != null && !getBuildService().isBuilding();
item.setEnabled(enabled);
item.getIcon().setAlpha(enabled ? 255 : 130);
item.getIcon().setAlpha(enabled ? 255 : 76);
}
}
MenuItem run1 = menu.findItem(R.id.menuEditor_quickRun);
MenuItem run2 = menu.findItem(R.id.menuEditor_run);
MenuItem undo = menu.findItem(R.id.menuEditor_undo);
MenuItem redo = menu.findItem(R.id.menuEditor_redo);
MenuItem save = menu.findItem(R.id.menuEditor_save);
MenuItem def = menu.findItem(R.id.menuEditor_gotoDefinition);
MenuItem ref = menu.findItem(R.id.menuEditor_findReferences);
MenuItem comment = menu.findItem(R.id.menuEditor_commentLine);
MenuItem uncomment = menu.findItem(R.id.menuEditor_uncommentLine);
MenuItem findFile = menu.findItem(R.id.menuEditor_findFile);
if(KeyboardUtils.isSoftInputVisible(this)) {
run1.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
run2.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
@@ -408,25 +419,28 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
redo.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
if(mPagerAdapter != null && mPagerAdapter.getCount() <= 0) {
undo.setEnabled(false);
redo.setEnabled(false);
save.setEnabled(false);
final int alpha = 76;
undo.getIcon().setAlpha(alpha);
redo.getIcon().setAlpha(alpha);
save.getIcon().setAlpha(alpha);
} else {
undo.setEnabled(true);
redo.setEnabled(true);
save.setEnabled(true);
final int alpha = 255;
undo.getIcon().setAlpha(alpha);
redo.getIcon().setAlpha(alpha);
save.getIcon().setAlpha(alpha);
}
final boolean enabled1 = mCurrentFile != null;
final boolean enabled2 = enabled1 && mCurrentFile.getName().endsWith(".java");
final int alpha1 = enabled1 ? 255 : 76;
final int alpha2 = enabled2 ? 255 : 76;
undo.setEnabled(enabled1);
redo.setEnabled(enabled1);
save.setEnabled(enabled1);
comment.setEnabled(enabled1);
uncomment.setEnabled(enabled1);
findFile.setEnabled(enabled1);
def.setEnabled(enabled2);
ref.setEnabled(enabled2);
undo.getIcon().setAlpha(alpha1);
redo.getIcon().setAlpha(alpha1);
save.getIcon().setAlpha(alpha1);
comment.getIcon().setAlpha(alpha1);
uncomment.getIcon().setAlpha(alpha1);
findFile.getIcon().setAlpha(alpha1);
def.getIcon().setAlpha(alpha2);
ref.getIcon().setAlpha(alpha2);
return true;
}
@@ -467,10 +481,113 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
this.mCurrentFragment.undo();
} else if (id == R.id.menuEditor_redo && this.mCurrentFragment != null && this.mCurrentFragment.isVisible()) {
this.mCurrentFragment.redo();
}
} else if(id == R.id.menuEditor_gotoDefinition && mCurrentFragment != null && this.mCurrentFragment.isVisible()) {
this.mCurrentFragment.findDefinition();
} else if(id == R.id.menuEditor_findReferences && mCurrentFragment != null && this.mCurrentFragment.isVisible()) {
this.mCurrentFragment.findReferences();
} else if(id == R.id.menuEditor_commentLine && mCurrentFragment != null && this.mCurrentFragment.isVisible()) {
this.mCurrentFragment.commentLine();
} else if(id == R.id.menuEditor_uncommentLine && mCurrentFragment != null && this.mCurrentFragment.isVisible()) {
this.mCurrentFragment.uncommentLine();
} else if(id == R.id.menuEditor_findFile && mCurrentFragment != null && this.mCurrentFragment.isVisible()) {
this.mCurrentFragment.beginSearch();
} else if(id == R.id.menuEditor_findProject) {
AlertDialog d = getFindInProjectDialog();
if(d != null) d.show();
}
invalidateOptionsMenu();
return true;
}
private AlertDialog getFindInProjectDialog() {
return mFindInProjectDialog == null ? createFindInProjectDialog() : mFindInProjectDialog;
}
private AlertDialog createFindInProjectDialog() {
if(mProject == null
|| mProject.getModulePaths() == null
|| mProject.getModulePaths().size() <= 0) {
getApp().toast(R.string.msg_no_modules, Toaster.Type.ERROR);
return null;
}
final List<String> modules = mProject.getModulePaths();
final List<File> srcDirs = new ArrayList<>();
final LayoutSearchProjectBinding binding = LayoutSearchProjectBinding.inflate(getLayoutInflater());
binding.modulesContainer.removeAllViews();
for(int i=0;i<modules.size();i++) {
final File file = new File(modules.get(i));
final File src = new File(file, "src");
if(!file.exists() || !file.isDirectory() || !src.exists() || !src.isDirectory()) continue;
CheckBox check = new CheckBox(this);
check.setText(file.getName());
check.setChecked(true);
LinearLayout.MarginLayoutParams params = new LinearLayout.MarginLayoutParams(-2, -2);
params.bottomMargin = SizeUtils.dp2px(4);
binding.modulesContainer.addView(check, params);
srcDirs.add(src);
}
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this, R.style.AppTheme_MaterialAlertDialog);
builder.setTitle(R.string.menu_find_project);
builder.setView(binding.getRoot());
builder.setPositiveButton(R.string.menu_find, (dialog, which) -> {
final String text = binding.input.getEditText().getText().toString().trim();
if(text == null || text.isEmpty()) {
getApp().toast(R.string.msg_empty_search_query, Toaster.Type.ERROR);
return;
}
final List<File> searchDirs = new ArrayList<>();
for(int i=0;i<binding.modulesContainer.getChildCount();i++) {
CheckBox check = (CheckBox) binding.modulesContainer.getChildAt(i);
if(check.isChecked())
searchDirs.add(srcDirs.get(i));
}
final String extensions = binding.filter.getEditText().getText().toString().trim();
final List<String> exts = new ArrayList<>();
if(extensions != null && !extensions.isEmpty()) {
if(extensions.contains("|")) {
for(String str : extensions.split(Pattern.quote("|"))) {
if(str == null || str.trim().isEmpty())
continue;
exts.add(str);
}
} else
exts.add(extensions);
}
if(searchDirs == null || searchDirs.isEmpty()) {
getApp().toast(R.string.msg_select_search_modules, Toaster.Type.ERROR);
} else {
dialog.dismiss();
getProgressSheet(R.string.msg_searching_project).show(getSupportFragmentManager(), "search_in_project_progress");
RecursiveFileSearcher.searchRecursiveAsync(text, exts, searchDirs, result -> handleSearchResults(result));
}
});
builder.setNegativeButton(android.R.string.cancel, (__, ___) -> __.dismiss());
mFindInProjectDialog = builder.create();
return mFindInProjectDialog;
}
private void handleSearchResults(Map<File, List<SearchResult>> results) {
getSearchResultList().setAdapter(new SearchListAdapter(results, file -> {
openFile(file);
hideSearchResults();
}, match -> {
openFileAndSelect(match.file, match);
hideSearchResults();
}));
mBinding.transformScrim.setVisibility(View.VISIBLE);
mBinding.fabView.setVisibility(View.GONE);
showSearchResults();
if(mSearchingProgress != null && mSearchingProgress.isShowing())
mSearchingProgress.dismiss();
}
/************************************
* Build Related *
@@ -681,7 +798,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
private void showSearchResults() {
TransitionManager.beginDelayedTransition(mBinding.getRoot(), createContainerTransformFor(mBinding.viewSearchResults, mBinding.searchResultsContainer));
mBinding.diagContainer.setVisibility(View.VISIBLE);
mBinding.searchResultsContainer.setVisibility(View.VISIBLE);
mBinding.viewOptionsCard.setVisibility(View.GONE);
hideToolbarAndTabs();
}
@@ -689,7 +806,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
private void hideSearchResults() {
showToolbarAndTabs();
TransitionManager.beginDelayedTransition(mBinding.getRoot(), createContainerTransformFor(mBinding.searchResultsContainer, mBinding.viewSearchResults));
mBinding.diagContainer.setVisibility(View.GONE);
mBinding.searchResultsContainer.setVisibility(View.GONE);
mBinding.viewOptionsCard.setVisibility(View.VISIBLE);
}
@@ -772,7 +889,9 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
}
@Override
public void onDiagnosticClick(Diagnostic diagnostic) {
public void onDiagnosticClick(File file, Diagnostic diagnostic) {
openFileAndSelect(file, diagnostic.range);
hideDiagnostics();
}
@Override
@@ -954,10 +1073,13 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
final JavaLanguageServer server = getApp().getJavaLanguageServer();
if(server == null) return;
server.findDefinition(params);
getProgressSheet(R.string.msg_finding_definition).show(getSupportFragmentManager(), "definition_progress");
}
@Override
public void gotoDefinition(List<Location> locations) {
if(mSearchingProgress != null && mSearchingProgress.isShowing())
mSearchingProgress.dismiss();
if(locations == null || locations.size() <= 0) {
getApp().toast(R.string.msg_no_definition, Toaster.Type.ERROR);
return;
@@ -977,42 +1099,89 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
&& frag.getEditor() != null
&& frag.getFile().getAbsolutePath().equals(file.getAbsolutePath())) {
if(range.start.equals(range.end)) {
frag.getEditor().setSelection(range.start.line, range.start.character);
frag.getEditor().setSelection(range.start.line, range.start.column);
} else {
frag.getEditor().setSelectionRegion(range.start.line, range.start.character, range.end.line, range.end.character);
frag.getEditor().setSelectionRegion(range.start.line, range.start.column, range.end.line, range.end.column);
}
} else {
openFile(file, range);
EditorFragment opened = mPagerAdapter.findEditorByFile(file);
if(opened != null && opened.getEditor() != null) {
CodeEditor editor = opened.getEditor();
editor.post(() -> {
if(range.start.equals(range.end)) {
editor.setSelection(range.start.line, range.start.character);
} else {
editor.setSelectionRegion(range.start.line, range.start.character, range.end.line, range.end.character);
}
});
}
openFileAndSelect(file, range);
}
} catch (Throwable th) {
Logger.instance().e(ThrowableUtils.getFullStackTrace(th));
}
}
private void openFileAndSelect(File file, Range range) {
openFile(file, range);
EditorFragment opened = mPagerAdapter.findEditorByFile(file);
if (opened != null && opened.getEditor() != null) {
CodeEditor editor = opened.getEditor();
editor.post(() -> {
if (range.start.equals(range.end)) {
editor.setSelection(range.start.line, range.start.column);
} else {
editor.setSelectionRegion(range.start.line, range.start.column, range.end.line, range.end.column);
}
});
}
}
@Override
public void findReferences(ReferenceParams params) {
final JavaLanguageServer server = getApp().getJavaLanguageServer();
if(server == null) return;
server.findReferences(params);
getProgressSheet(R.string.msg_finding_references).show(getSupportFragmentManager(), "references_progress");
}
@Override
public void references(List<Location> references) {
if(references == null || references.size() <= 0) {
if(mSearchingProgress != null && mSearchingProgress.isShowing())
mSearchingProgress.dismiss();
if(references == null) {
getApp().toast(R.string.msg_no_references, Toaster.Type.INFO);
return;
}
if(references.size() <= 0) {
getApp().toast(R.string.msg_no_references, Toaster.Type.INFO);
getSearchResultList().setAdapter(new SearchListAdapter(null, null, null));
return;
}
final Map<File, List<SearchResult>> results = new HashMap<>();
for(int i=0;i<references.size();i++) {
try {
final Location loc = references.get(i);
if(loc == null || loc.uri == null || loc.range == null) continue;
final File file = new File(loc.uri);
if(!file.exists() || !file.isFile()) continue;
EditorFragment frag = mPagerAdapter.findEditorByFile(file);
Content content;
if(frag != null && frag.getEditor() != null)
content = frag.getEditor().getText();
else content = new Content(null, FileIOUtils.readFile2String(file));
final List<SearchResult> matches = results.containsKey(file) ? results.get(file) : new ArrayList<>();
matches.add(
new SearchResult(
loc.range,
file,
content.getLineString(loc.range.start.line),
content.subContent(
loc.range.start.line,
loc.range.start.column,
loc.range.end.line,
loc.range.end.column
).toString()
)
);
results.put(file, matches);
} catch (Throwable th) {
Logger.instance().e(ThrowableUtils.getFullStackTrace(th));
}
}
handleSearchResults(results);
}
private void addPendingMessage(Message msg) {
@@ -1052,9 +1221,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
EditorFragment current = mPagerAdapter.getFrag(p1.getPosition());
if(current != null && current.getFile() != null) {
this.mCurrentFragment = current;
mCurrentFile = current.getFile();
this.mCurrentFile = current.getFile();
refreshSymbolInput(current);
}
invalidateOptionsMenu();
}
@Override
@@ -1102,9 +1273,10 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
builder.create().show();
}
// -----------------------------
// ---- Fragment Related
// -----------------------------
// -----------------------------
// ---- Fragment Related
// -------------------------------
public void loadFragment(Fragment fragment) {
super.loadFragment(fragment, mBinding.editorFrameLayout.getId());
@@ -1122,6 +1294,18 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
mFileOptionsFragment.setOnOptionsClickListener(o -> handleOptionClick(o));
return mFileOptionsFragment;
}
private ProgressSheet getProgressSheet(int msg) {
if(mSearchingProgress != null && mSearchingProgress.isShowing()) {
mSearchingProgress.dismiss();
}
mSearchingProgress = new ProgressSheet();
mSearchingProgress.setCancelable(false);
mSearchingProgress.setWelcomeTextEnabled(false);
mSearchingProgress.setMessage(getString(msg));
mSearchingProgress.setSubMessageEnabled(false);
return mSearchingProgress;
}
private MaterialBanner getSyncBanner() {
return mBinding.syncBanner.setContentTextColor(ContextCompat.getColor(this, R.color.primaryTextColor))
@@ -1209,7 +1393,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
private RecyclerView createSearchResultList() {
searchResultList = new RecyclerView(this);
searchResultList.setLayoutManager(new LinearLayoutManager(this));
searchResultList.setAdapter(new SearchListAdapter());
return searchResultList;
}
@@ -1602,12 +1785,14 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
}
private void closeOthers() {
final Fragment frag = mPagerAdapter.getFragments().get(mBinding.tabs.getSelectedTabPosition());
final EditorFragment frag = mPagerAdapter.getFrag(mBinding.tabs.getSelectedTabPosition());
final File file = mPagerAdapter.getOpenedFiles().get(mBinding.tabs.getSelectedTabPosition());
mPagerAdapter = new EditorPagerAdapter(getSupportFragmentManager(), mProject, Arrays.asList(frag), Arrays.asList(file));
try { frag.save(); } catch (Throwable th) { }
mPagerAdapter = new EditorPagerAdapter(getSupportFragmentManager(), mProject);
mBinding.editorViewPager.setAdapter(mPagerAdapter);
mBinding.tabs.setupWithViewPager(mBinding.editorViewPager);
mBinding.tabs.addOnTabSelectedListener(this);
openFile(file);
}
private void closeFile(int index) {
@@ -11,14 +11,17 @@ import com.itsaky.androidide.interfaces.DiagnosticClickListener;
import com.itsaky.lsp.Diagnostic;
import com.itsaky.lsp.DiagnosticSeverity;
import java.util.List;
import java.io.File;
public class DiagnosticItemAdpater extends RecyclerView.Adapter<DiagnosticItemAdpater.VH> {
private List<Diagnostic> diags;
private final File file;
private DiagnosticClickListener listener;
public DiagnosticItemAdpater(List<Diagnostic> diags, DiagnosticClickListener listener) {
public DiagnosticItemAdpater(List<Diagnostic> diags, File file, DiagnosticClickListener listener) {
this.diags = diags;
this.file = file;
this.listener = listener;
}
@@ -38,7 +41,7 @@ public class DiagnosticItemAdpater extends RecyclerView.Adapter<DiagnosticItemAd
binding.getRoot().setOnClickListener(v -> {
if(listener != null) {
listener.onDiagnosticClick(diagnostic);
listener.onDiagnosticClick(file, diagnostic);
}
});
}
@@ -38,7 +38,7 @@ public class DiagnosticsAdapter extends RecyclerView.Adapter<DiagnosticsAdapter.
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.diagnostics.setAdapter(new DiagnosticItemAdpater(group.diagnostics, group.file, listener));
binding.info.getRoot().setOnClickListener(v -> {
if(listener != null) {
@@ -1,7 +1,133 @@
package com.itsaky.androidide.adapters;
public class SearchListAdapter {
import android.graphics.PorterDuff;
import android.text.SpannableStringBuilder;
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.blankj.utilcode.util.ThreadUtils;
import com.blankj.utilcode.util.ThrowableUtils;
import com.itsaky.androidide.databinding.LayoutSearchResultGroupBinding;
import com.itsaky.androidide.databinding.LayoutSearchResultItemBinding;
import com.itsaky.androidide.models.SearchResult;
import com.itsaky.androidide.syntax.lexer.highlighters.JavaHighlighter;
import com.itsaky.androidide.utils.Logger;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.VH> {
private final Map<File, List<SearchResult>> results;
private final List<File> keys;
private final OnFileClickListener fileListener;
private final OnMatchClickListener matchListener;
public SearchListAdapter(Map<File, List<SearchResult>> results, OnFileClickListener fileListener, OnMatchClickListener matchListener) {
if (results == null)
results = new HashMap<>();
this.results = results;
this.keys = new ArrayList<File>(results.keySet());
this.fileListener = fileListener;
this.matchListener = matchListener;
}
@Override
public SearchListAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) {
return new VH(LayoutSearchResultGroupBinding.inflate(LayoutInflater.from(p1.getContext())));
}
@Override
public void onBindViewHolder(SearchListAdapter.VH p1, int p2) {
final LayoutSearchResultGroupBinding binding = p1.binding;
final File file = keys.get(p2);
final List<SearchResult> matches = results.get(file);
final int color = ContextCompat.getColor(binding.icon.getContext(), com.itsaky.androidide.R.color.secondaryColor);
binding.title.setText(file.getName());
binding.icon.setImageResource(com.itsaky.androidide.R.drawable.ic_language_java);
binding.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
binding.items.setLayoutManager(new LinearLayoutManager(binding.items.getContext()));
binding.items.setAdapter(new ChildAdapter(matches));
binding.getRoot().setOnClickListener(v -> {
if(fileListener != null)
fileListener.onClick(file);
});
}
@Override
public int getItemCount() {
return results.size();
}
public class ChildAdapter extends RecyclerView.Adapter<ChildVH> {
private final List<SearchResult> matches;
public ChildAdapter(List<SearchResult> matches) {
if (matches == null)
matches = new ArrayList<>();
this.matches = matches;
}
@Override
public SearchListAdapter.ChildVH onCreateViewHolder(ViewGroup p1, int p2) {
return new ChildVH(LayoutSearchResultItemBinding.inflate(LayoutInflater.from(p1.getContext())));
}
@Override
public void onBindViewHolder(SearchListAdapter.ChildVH p1, int p2) {
final SearchResult match = matches.get(p2);
final LayoutSearchResultItemBinding binding = p1.binding;
new Thread(() -> {
try {
final SpannableStringBuilder sb = new JavaHighlighter().highlight(match.line, match.match);
ThreadUtils.runOnUiThread(() -> binding.text.setText(sb));
} catch (Exception e) {
Logger.instance().e(ThrowableUtils.getFullStackTrace(e));
ThreadUtils.runOnUiThread(() -> binding.text.setText(match.match));
}
}).start();
binding.getRoot().setOnClickListener(v -> {
if(matchListener != null) {
matchListener.onClick(match);
}
});
}
@Override
public int getItemCount() {
return matches.size();
}
}
public class VH extends RecyclerView.ViewHolder {
private LayoutSearchResultGroupBinding binding;
public VH(LayoutSearchResultGroupBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}
public class ChildVH extends RecyclerView.ViewHolder {
private LayoutSearchResultItemBinding binding;
public ChildVH(LayoutSearchResultItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
public static interface OnFileClickListener {
void onClick(File file);
}
public static interface OnMatchClickListener {
void onClick(SearchResult match);
}
}
@@ -85,10 +85,12 @@ public class StudioApp extends MultiDexApplication
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
JavaCharacter.initMap();
newShell(line -> handleLog(line)).bgAppend("logcat -v threadtime");
setupLibsIfNeeded();
extractJlsIfNeeded();
extractLogsenderIfNeeded();
extractCleanerIfNeeded();
newShell(line -> handleLog(line)).bgAppend("androidide-cleaner && logcat -v threadtime");
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannels();
@@ -97,6 +99,20 @@ public class StudioApp extends MultiDexApplication
FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_UPDATE);
FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_DEV_MSGS);
}
private void extractCleanerIfNeeded() {
File cleanerZip = new File(Environment.TMP_DIR, "bin.zip");
File cleaner = new File(Environment.BINDIR, "androidide-cleaner");
if(!cleaner.exists()) {
ResourceUtils.copyFileFromAssets("data/bin.zip", cleanerZip.getAbsolutePath());
try {
ZipUtils.unzipFile(cleanerZip, Environment.BINDIR);
} catch (Throwable e) {}
}
if(cleaner.exists() && !cleaner.canExecute())
cleaner.setExecutable(true);
}
private void handleLog(CharSequence seq) {
if(seq == null)
@@ -88,8 +88,8 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
bundle.putString(KEY_FILE_PATH, file.getAbsolutePath());
bundle.putInt(KEY_LINE_START, selection.start.line);
bundle.putInt(KEY_LINE_END, selection.end.line);
bundle.putInt(KEY_COLUMN_START, selection.start.character);
bundle.putInt(KEY_COLUMN_END, selection.end.character);
bundle.putInt(KEY_COLUMN_START, selection.start.column);
bundle.putInt(KEY_COLUMN_END, selection.end.column);
bundle.putParcelable(KEY_PROJECT, project);
EditorFragment frag = new EditorFragment();
frag.setArguments(bundle);
@@ -149,9 +149,9 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
postRead();
binding.editor.post(() -> {
if(range.start.equals(range.end)) {
getEditor().setSelection(range.start.line, range.start.character);
getEditor().setSelection(range.start.line, range.start.column);
} else {
getEditor().setSelectionRegion(range.start.line, range.start.character, range.end.line, range.end.character);
getEditor().setSelectionRegion(range.start.line, range.start.column, range.end.line, range.end.column);
}
});
});
@@ -241,6 +241,26 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
if(binding.editor.canRedo())
binding.editor.redo();
}
public void findDefinition() {
binding.editor.findDefinition();
}
public void findReferences() {
binding.editor.findReferences();
}
public void commentLine() {
binding.editor.commentLine();
}
public void uncommentLine() {
binding.editor.uncommentLine();
}
public void beginSearch() {
binding.editor.beginSearchMode();
}
public void save() {
if(mFile == null || binding == null || binding.editor == null || binding.editor.getText() == null) return;
@@ -26,6 +26,8 @@ import org.json.JSONException;
import org.json.JSONObject;
import com.blankj.utilcode.util.FileUtils;
import java.util.stream.Collectors;
import java.util.regex.Pattern;
import com.blankj.utilcode.util.FileIOUtils;
public class ProjectsFragment extends BaseFragment {
@@ -85,40 +87,35 @@ public class ProjectsFragment extends BaseFragment {
if (!settings.exists()) continue;
String content = FileUtil.readFile(settings.getAbsolutePath());
if (TextUtils.isEmpty(content)) continue;
boolean foundIcon = false;
AndroidProject project = new AndroidProject();
for (File module : file.listFiles()) {
String[] result;
if (module.isDirectory()
&& !module.isHidden()
&& (result = proceedModule(file, module, project)) != null
&& result.length == 2
&& result[0] != null
&& result[1] != null
&& !foundIcon) {
foundIcon = true;
String packageName = result[0];
String icon = result[1];
project.setPackageName(packageName)
.setIconPath(icon)
.setMainModule(module.getName())
.setMainSourcePath(new File(module, "src/main/java").getAbsolutePath());
if (module.isDirectory() && !module.isHidden()) {
proceedModule(file, module, project);
}
}
project.setAppName(file.getName());
project.setProjectPath(file.getAbsolutePath());
if(project.getMainModule() == null
&& project.getModulePaths().size() > 0
&& project.getModulePaths().get(0) != null) {
File f = new File(project.getModulePaths().get(0));
if(!f.exists() || !f.isDirectory()) continue;
project.setMainModule(f.getName());
project.setMainSourcePath(new File(f, "src/main/java").getAbsolutePath());
}
mProjects.add(project);
}
}
private String[] proceedModule(File file, File module, AndroidProject project) throws JSONException {
private void proceedModule(File file, File module, AndroidProject project) throws JSONException {
final Pattern appPattern = Pattern.compile("apply\\s+plugin\\s*:\\s*(\'|\")com.android.application(\'|\")");
File projectGradle = new File(file, "build.gradle");
File moduleGradle = new File(module, "build.gradle");
File manifest = new File(module, "src/main/AndroidManifest.xml");
File res = new File(module, "src/main/res");
if (!module.exists() || !projectGradle.exists() || !moduleGradle.exists() || !manifest.exists()) return null;
if (!module.isDirectory() || !moduleGradle.isFile() || !projectGradle.isFile() || !manifest.isFile()) return null;
if (!module.exists() || !projectGradle.exists() || !moduleGradle.exists() || !manifest.exists()) return;
if (!module.isDirectory() || !moduleGradle.isFile() || !projectGradle.isFile() || !manifest.isFile()) return;
File java = new File(module, "src/main/java");
if (java.exists() && java.isDirectory())
@@ -139,7 +136,7 @@ public class ProjectsFragment extends BaseFragment {
project.addSource(new File(module, "build/generated/data_binding_base_class_source_out/debug/out").getAbsolutePath());
JSONObject obj = new XmlToJson.Builder(FileUtil.readFile(manifest.getAbsolutePath())).build().toJson();
if (!obj.has("manifest")) return null;
if (!obj.has("manifest")) return;
String iconPath = null;
if (obj.has("manifest") && obj.getJSONObject("manifest").has("application") && obj.getJSONObject("manifest").getJSONObject("application").has("android:icon")) {
String iconRes = obj.getJSONObject("manifest").getJSONObject("application").getString("android:icon");
@@ -172,8 +169,25 @@ public class ProjectsFragment extends BaseFragment {
}
}
final String packageName = obj.getJSONObject("manifest").getString("package");
if (iconPath == null) return null;
return new String[]{ packageName, iconPath };
final String mainModuleName = module.getName();
boolean isMainModule = false;
if(project.getMainModule() == null) {
String content = FileIOUtils.readFile2String(moduleGradle);
if(content != null && appPattern.matcher(content).find()) {
isMainModule = true;
project.setMainModule(mainModuleName);
project.setMainSourcePath(new File(module, "src/main/java").getAbsolutePath());
}
}
if (iconPath != null) {
project.setIconPath(iconPath);
}
if(packageName != null && isMainModule) {
project.setPackageName(packageName);
}
}
private File[] listDrawables(File file) {
@@ -2,8 +2,9 @@ package com.itsaky.androidide.interfaces;
import com.itsaky.androidide.models.DiagnosticGroup;
import com.itsaky.lsp.Diagnostic;
import java.io.File;
public interface DiagnosticClickListener {
void onGroupClick(DiagnosticGroup group);
void onDiagnosticClick(Diagnostic diagnostic);
void onDiagnosticClick(File file, Diagnostic diagnostic);
}
@@ -88,9 +88,9 @@ public class CompletionItemWrapper implements SuggestItem, Comparable {
final Position end = edit.range.end;
if(start == null || end == null) continue;
if(start.equals(end)) {
editor.getText().insert(start.line, start.character, edit.newText);
editor.getText().insert(start.line, start.column, edit.newText);
} else {
editor.getText().replace(start.line, start.character, end.line, end.character, edit.newText);
editor.getText().replace(start.line, start.column, end.line, end.column, edit.newText);
}
}
}
@@ -0,0 +1,18 @@
package com.itsaky.androidide.models;
import com.itsaky.lsp.Range;
import java.io.File;
public class SearchResult extends Range {
public File file;
public String line;
public String match;
public SearchResult(Range src, File file, String line, String match) {
this.start = src.start;
this.end = src.end;
this.file = file;
this.line = line;
this.match = match;
}
}
@@ -36,6 +36,7 @@ public class ShellServer extends Thread {
stringBuilder.append("\nexport PATH=$PATH:" + Environment.path(Environment.GRADLE_DIR) + "/bin");
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");
stringBuilder.append("\nexport PATH=$PATH:" + Environment.path(Environment.BINDIR));
stringBuilder.append("\nexport JLS_HOME=" + Environment.path(Environment.JLS_HOME));
// Provide paths to .so files needed to execute JDK
@@ -50,15 +51,14 @@ public class ShellServer extends Thread {
}
// Monotonic clock fix, needed for JDK
stringBuilder.append( // "\nls -l /system/lib64/libc.so && " +
"\nln -sf /apex/com.android.runtime/lib64/bionic/libc.so " + lib + "/librt.so && " +
stringBuilder.append("\nln -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");
// Execute all commands...
append(stringBuilder.toString(), false);
// Make sure there are proper permissions set to the $HOME directory
append("chmod -R 777 $HOME");
} catch (Throwable th) {
if (callback != null) {
String out = ThrowableUtils.getFullStackTrace(th).concat("\n");
@@ -27,7 +27,7 @@ public class SchemeAndroidIDE extends EditorColorScheme {
setColor(BLOCK_LINE_CURRENT, 0xffffffff);
setColor(AUTO_COMP_PANEL_BG, 0xff757575);
setColor(AUTO_COMP_PANEL_CORNER, 0xff9e9e9e);
setColor(MATCHED_TEXT_BACKGROUND, 0xffffff00);
setColor(MATCHED_TEXT_BACKGROUND, 0xffFF8F00);
setColor(NON_PRINTABLE_CHAR, 0xffdddddd);
setColor(KEYWORD, 0xffff6060);
setColor(COMMENT, 0xffbdbdbd);
@@ -0,0 +1,13 @@
package com.itsaky.androidide.syntax.lexer.highlighters;
import android.text.SpannableStringBuilder;
/**
* A highlighter is used to highlight a small piece of code
*
* Implementations of these class shouldn't be used in CodeEditor
* For CodeEditor usage, implement an EditorLanguage instead
*/
public interface Highlighter {
SpannableStringBuilder highlight(String code, String match) throws Exception;
}
@@ -0,0 +1,166 @@
package com.itsaky.androidide.syntax.lexer.highlighters;
import android.text.SpannableStringBuilder;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import com.itsaky.androidide.language.java.parser.JavaLexer;
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
import io.github.rosemoe.editor.widget.EditorColorScheme;
import java.io.StringReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token;
public class JavaHighlighter implements Highlighter {
@Override
public SpannableStringBuilder highlight(String code, String match) throws Exception {
final JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(code)));
final SpannableStringBuilder sb = new SpannableStringBuilder();
final EditorColorScheme scheme = new SchemeAndroidIDE();
Token token;
while((token = lexer.nextToken()) != null) {
if(token.getType() == JavaLexer.EOF)
break;
switch(token.getType()) {
case JavaLexer.WS :
sb.append(token.getText());
break;
case JavaLexer.ABSTRACT:
case JavaLexer.ASSERT:
case JavaLexer.BREAK:
case JavaLexer.CASE:
case JavaLexer.CATCH:
case JavaLexer.CLASS:
case JavaLexer.CONST:
case JavaLexer.CONTINUE:
case JavaLexer.DEFAULT:
case JavaLexer.DO:
case JavaLexer.ELSE:
case JavaLexer.EXTENDS:
case JavaLexer.FINAL:
case JavaLexer.FINALLY:
case JavaLexer.FOR:
case JavaLexer.IF:
case JavaLexer.GOTO:
case JavaLexer.IMPLEMENTS:
case JavaLexer.IMPORT:
case JavaLexer.INSTANCEOF:
case JavaLexer.INTERFACE:
case JavaLexer.NATIVE:
case JavaLexer.NEW:
case JavaLexer.PACKAGE:
case JavaLexer.PRIVATE:
case JavaLexer.PROTECTED:
case JavaLexer.PUBLIC:
case JavaLexer.RETURN:
case JavaLexer.STATIC:
case JavaLexer.STRICTFP:
case JavaLexer.SUPER:
case JavaLexer.SWITCH:
case JavaLexer.SYNCHRONIZED:
case JavaLexer.THIS:
case JavaLexer.THROW:
case JavaLexer.THROWS:
case JavaLexer.TRANSIENT:
case JavaLexer.TRY:
case JavaLexer.VOID:
case JavaLexer.VOLATILE:
case JavaLexer.WHILE:
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(EditorColorScheme.KEYWORD)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case JavaLexer.DECIMAL_LITERAL:
case JavaLexer.HEX_LITERAL:
case JavaLexer.OCT_LITERAL:
case JavaLexer.BINARY_LITERAL:
case JavaLexer.FLOAT_LITERAL:
case JavaLexer.HEX_FLOAT_LITERAL:
case JavaLexer.BOOL_LITERAL:
case JavaLexer.CHAR_LITERAL:
case JavaLexer.NULL_LITERAL:
case JavaLexer.STRING_LITERAL :
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(EditorColorScheme.LITERAL)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case JavaLexer.LPAREN :
case JavaLexer.RPAREN :
case JavaLexer.LBRACK :
case JavaLexer.RBRACK :
case JavaLexer.SEMI :
case JavaLexer.COMMA :
case JavaLexer.ASSIGN :
case JavaLexer.GT :
case JavaLexer.LT :
case JavaLexer.BANG :
case JavaLexer.TILDE :
case JavaLexer.QUESTION :
case JavaLexer.COLON :
case JavaLexer.EQUAL :
case JavaLexer.GE :
case JavaLexer.LE :
case JavaLexer.NOTEQUAL :
case JavaLexer.AND :
case JavaLexer.OR :
case JavaLexer.INC :
case JavaLexer.DEC :
case JavaLexer.ADD :
case JavaLexer.SUB :
case JavaLexer.MUL :
case JavaLexer.DIV :
case JavaLexer.BITAND :
case JavaLexer.BITOR :
case JavaLexer.CARET :
case JavaLexer.MOD :
case JavaLexer.ADD_ASSIGN :
case JavaLexer.SUB_ASSIGN :
case JavaLexer.MUL_ASSIGN :
case JavaLexer.DIV_ASSIGN :
case JavaLexer.AND_ASSIGN :
case JavaLexer.OR_ASSIGN :
case JavaLexer.XOR_ASSIGN :
case JavaLexer.MOD_ASSIGN :
case JavaLexer.LSHIFT_ASSIGN :
case JavaLexer.RSHIFT_ASSIGN :
case JavaLexer.URSHIFT_ASSIGN :
case JavaLexer.ARROW :
case JavaLexer.COLONCOLON :
case JavaLexer.ELLIPSIS :
case JavaLexer.DOT :
case JavaLexer.LBRACE :
case JavaLexer.RBRACE :
case JavaLexer.AT :
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(EditorColorScheme.OPERATOR)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case JavaLexer.BOOLEAN:
case JavaLexer.BYTE :
case JavaLexer.CHAR:
case JavaLexer.DOUBLE:
case JavaLexer.ENUM :
case JavaLexer.FLOAT:
case JavaLexer.INT :
case JavaLexer.LONG:
case JavaLexer.SHORT:
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(EditorColorScheme.TYPE_NAME)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case JavaLexer.COMMENT :
case JavaLexer.LINE_COMMENT :
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(EditorColorScheme.COMMENT)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
default:
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(EditorColorScheme.TEXT_NORMAL)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
}
}
final BackgroundColorSpan bg = new BackgroundColorSpan(0xffffff00);
final ForegroundColorSpan fg = new ForegroundColorSpan(0xff000000);
Pattern pattern = Pattern.compile(Pattern.quote(match));
Matcher matcher = pattern.matcher(code);
while(matcher.find()) {
sb.setSpan(bg, matcher.start(), matcher.end(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.setSpan(fg, matcher.start(), matcher.end(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return sb;
}
}
@@ -31,18 +31,6 @@ public class JavaLexerImpl extends BaseJavaLexer implements Lexer {
this.previous = -1;
this.wasClassName = false;
this.isFirst = true;
builtinTypes = new ArrayList<>();
builtinTypes.add(JavaLexer.BOOLEAN);
builtinTypes.add(JavaLexer.BYTE);
builtinTypes.add(JavaLexer.CHAR);
builtinTypes.add(JavaLexer.DOUBLE);
builtinTypes.add(JavaLexer.ENUM);
builtinTypes.add(JavaLexer.FLOAT);
builtinTypes.add(JavaLexer.INT);
builtinTypes.add(JavaLexer.LONG);
builtinTypes.add(JavaLexer.SHORT);
}
public void setJavaColors(JavaColors colors) {
@@ -463,7 +451,7 @@ public class JavaLexerImpl extends BaseJavaLexer implements Lexer {
for(int i=0;i<ranges.size();i++) {
final Range range = ranges.get(i);
if(range == null) continue;
if(range.start.line == line && range.start.character == column)
if(range.start.line == line && range.start.column == column)
return true;
}
}
@@ -6,6 +6,7 @@ import com.blankj.utilcode.util.ThrowableUtils;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import com.itsaky.androidide.utils.Logger;
public class TaskExecutor {
private final Executor executor = Executors.newSingleThreadExecutor(); // change according to your requirements
@@ -22,8 +23,7 @@ public class TaskExecutor {
handler.post(() -> {
callback.onComplete(result);
});
} catch (Throwable th) {
}
} catch (Throwable th) {}
});
}
}
@@ -19,6 +19,7 @@ public final class Environment {
public static File TMP_DIR;
public static File USRDIR;
public static File LIBDIR;
public static File BINDIR;
public static File GRADLE_DIR;
public static File GRADLE_PROPS;
@@ -65,6 +66,7 @@ public final class Environment {
GRADLE_DIR = new File(HOME, GRADLE_FOLDER_NAME);
USRDIR = mkdirIfNotExits(new File(HOME.getParentFile(), "usr"));
LIBDIR = mkdirIfNotExits(new File(USRDIR, "lib"));
BINDIR = mkdirIfNotExits(new File(USRDIR, "bin"));
LIBHOOKSO = new File(LIBDIR, "libhook.so");
CACHES_DIR = new File(HOME, "ide-caches");
@@ -0,0 +1,131 @@
package com.itsaky.androidide.utils;
import com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.FileUtils;
import com.itsaky.androidide.models.SearchResult;
import com.itsaky.androidide.tasks.TaskExecutor;
import com.itsaky.lsp.Position;
import com.itsaky.lsp.Range;
import io.github.rosemoe.editor.text.CharPosition;
import io.github.rosemoe.editor.text.Content;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.HashMap;
/**
* This class provides API to search in files recursively
*
* @author Akash Yadav
*/
public class RecursiveFileSearcher {
private static final Logger logger = Logger.instance("RecursiveFileSearcher");
/**
* Search the given text in files recursively in given search directories
*
* @param text Text to search
* @param exts Extentions of file to search. Maybe null.
* @param searchDirs Directories to search in. Subdirectories will be included
* @param callback A listener that will listen to the search result
*/
public static void searchRecursiveAsync(String text, List<String> exts, List<File> searchDirs, Callback callback) {
// Cannot search empty or null text
if(text == null || text.isEmpty()) {
return;
}
// If there is no listener to the search, search is meaningless
if(callback == null) {
return;
}
// Avoid searching if no directories are specified
if(searchDirs == null || searchDirs.isEmpty()) {
return;
}
new TaskExecutor().executeAsync(new Searcher(text, exts, searchDirs), result -> callback.onResult(result));
}
private static class Searcher implements Callable<Map<File, List<SearchResult>>> {
private final String query;
private final List<String> exts;
private final List<File> dirs;
public Searcher(String query, List<String> exts, List<File> dirs) {
this.query = query;
this.exts = exts;
this.dirs = dirs;
}
@Override
public Map<File, List<SearchResult>> call() throws Exception {
final Map<File, List<SearchResult>> result = new HashMap<>();
for(int i=0;i<dirs.size();i++) {
final File dir = dirs.get(i);
final List<File> files = FileUtils.listFilesInDirWithFilter(dir, new MultiFileFilter(exts), true);
for(int j=0;files != null && j < files.size();j++) {
final File file = files.get(j);
if(file.isDirectory()) continue;
final String text = FileIOUtils.readFile2String(file);
if(text == null || text.trim().isEmpty()) continue;
final Content content = new Content(null, text);
final List<SearchResult> ranges = new ArrayList<>();
Matcher matcher = Pattern.compile(Pattern.quote(this.query)).matcher(text);
while(matcher.find()) {
final Range range = new Range();
final CharPosition start = content.getIndexer().getCharPosition(matcher.start());
final CharPosition end = content.getIndexer().getCharPosition(matcher.end());
range.start = new Position(start.line, start.column);
range.end = new Position(end.line, end.column);
String sub = "...".concat(text.substring(Math.max(0, matcher.start() - 30), Math.min(matcher.end() + 31, text.length()))).trim().concat("...");
String match = content.subContent(start.line, start.column, end.line, end.column).toString();
ranges.add(new SearchResult(range, file, sub.replaceAll("\\s+", " "), match));
}
if(ranges.size() > 0) {
result.put(file, ranges);
}
}
}
return result;
}
}
private static class MultiFileFilter implements FileFilter {
private final List<String> exts;
public MultiFileFilter(List<String> exts) {
this.exts = exts;
}
@Override
public boolean accept(File file) {
boolean accept = false;
if(exts == null || exts.isEmpty() || file.isDirectory()) {
accept = true;
} else {
for(String ext : exts) {
if(file.getName().endsWith(ext)) {
accept = true;
break;
}
}
}
return accept && FileUtils.isUtf8(file);
}
}
public static interface Callback {
void onResult(Map<File, List<SearchResult>> results);
}
}
@@ -102,6 +102,9 @@ import com.itsaky.lsp.TextDocumentPositionParams;
import com.itsaky.lsp.TextDocumentIdentifier;
import com.itsaky.lsp.ReferenceParams;
import com.itsaky.lsp.ReferenceContext;
import com.itsaky.androidide.databinding.LayoutDialogTextInputBinding;
import android.view.LayoutInflater;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
/**
* CodeEditor is a editor that can highlight text regions by doing basic syntax analyzing
@@ -554,9 +557,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
Range found = null;
for(Range r : keys) {
if(r.start.line <= range.start.line
&& r.start.character <= range.start.character
&& r.start.column <= range.start.column
&& r.end.line >= range.end.line
&& r.end.character >= range.end.character) {
&& r.end.column >= range.end.column) {
found = r;
break;
}
@@ -1364,8 +1367,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
for(int i=0;i<diags.size();i++) {
Diagnostic d = diags.get(i);
if(d == null) continue;
int startCol = d.range.start.character;
int endCol = d.range.end.character;
int startCol = d.range.start.column;
int endCol = d.range.end.column;
if(line > d.range.start.line) startCol = 0;
if(line > d.range.end.line) endCol = 0;
final int paintStart = Math.max(firstVisibleChar, startCol);
@@ -2967,10 +2970,15 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
@Override
public boolean onCreateActionMode(ActionMode p1, Menu p2) {
mStartedActionMode = ACTION_MODE_SEARCH_TEXT;
p2.add(0, 0, 0, R.string.next).setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
p2.add(0, 1, 0, R.string.last).setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_NEVER);
p2.add(0, 2, 0, R.string.replace).setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_NEVER);
p2.add(0, 3, 0, R.string.replaceAll).setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_NEVER);
p2.add(0, 1, 0, R.string.last)
.setIcon(R.drawable.ic_search_last)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
p2.add(0, 0, 0, R.string.next)
.setIcon(R.drawable.ic_search_next)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
p2.add(0, 2, 0, R.string.replace)
.setIcon(R.drawable.ic_search_replace)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
SearchView sv = new SearchView(getContext());
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@@ -3010,20 +3018,21 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
getSearcher().gotoNext();
break;
case 2:
case 3:
final boolean replaceAll = p2.getItemId() == 3;
final EditText et = new EditText(getContext());
et.setHint(R.string.replacement);
new AlertDialog.Builder(getContext())
.setTitle(replaceAll ? R.string.replaceAll : R.string.replace)
.setView(et)
final LayoutDialogTextInputBinding binding = LayoutDialogTextInputBinding.inflate(LayoutInflater.from(getContext()));
binding.name.setHint(R.string.replacement);
binding.name.setStartIconDrawable(R.drawable.ic_replace);
binding.name.setCounterEnabled(false);
new MaterialAlertDialogBuilder(getContext(), R.style.AppTheme_MaterialAlertDialog)
.setTitle(R.string.replace)
.setView(binding.getRoot())
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.replace, (dialog, which) -> {
if (replaceAll) {
getSearcher().replaceAll(et.getText().toString());
} else {
getSearcher().replaceThis(et.getText().toString());
}
getSearcher().replaceThis(binding.name.getEditText().getText().toString());
am.finish();
dialog.dismiss();
})
.setNeutralButton(R.string.replaceAll, (dialog, which) -> {
getSearcher().replaceAll(binding.name.getEditText().getText().toString());
am.finish();
dialog.dismiss();
})
@@ -4413,12 +4422,62 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
this.jlsRequestor = requestor;
}
public void commentSelected() {
public void commentLine() {
if(getFile() == null) return;
final Content text = getText();
final String name = getFile().getName();
int line = getCursor().getLeftLine();
if(name.endsWith(".java") || name.endsWith(".gradle")){
while(line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) {
if(!text.getLineString(line).trim().startsWith("//"))
text.insert(line, 0, "//");
line++;
}
} else if(name.endsWith(".xml")) {
while(line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) {
final String lineString = text.getLineString(line);
if(!lineString.trim().startsWith("<!--")
&& !lineString.trim().endsWith("-->")) {
text.replace(line, 0, line, text.getColumnCount(line), "<!--".concat(lineString).concat("-->"));
}
line++;
}
}
}
public void uncommentSelected() {
public void uncommentLine() {
if(getFile() == null) return;
final Content text = getText();
final String name = getFile().getName();
int line = getCursor().getLeftLine();
if(name.endsWith(".java") || name.endsWith(".gradle")) {
while(line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) {
String l = text.getLineString(line);
if(l.trim().startsWith("//")) {
int i = l.indexOf("//");
text.delete(line, i, line, i + 2);
}
line++;
}
} else if(name.endsWith(".xml")) {
final String commentStart = "<!--";
final String commentEnd = "-->";
while(line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) {
String l = text.getLineString(line);
if(l.trim().startsWith(commentStart)) {
int i = l.indexOf(commentStart);
text.delete(line, i, line, i + commentStart.length());
}
if(l.trim().endsWith(commentEnd)) {
int i = l.lastIndexOf(commentEnd);
int count = text.getColumnCount(line);
text.delete(line, count - commentEnd.length(), line, count);
}
line++;
}
}
}
public void findDefinition() {
@@ -4521,7 +4580,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
if(strings == null || strings.isEmpty()) return false;
for(int i=0;i<strings.size();i++) {
Range r = strings.get(i);
if(r.start.character <= column && column <= r.end.character)
if(r.start.column <= column && column <= r.end.column)
return true;
}
return false;
@@ -47,9 +47,9 @@ public class DiagnosticWindow extends EditorBasePopupWindow {
editor.getContext().getString(
com.itsaky.androidide.R.string.diagnostic_position,
""+ (diagnostic.range.start.line + 1),
""+ (diagnostic.range.start.character + 1),
""+ (diagnostic.range.start.column + 1),
""+ (diagnostic.range.end.line + 1),
""+ (diagnostic.range.end.character + 1)
""+ (diagnostic.range.end.column + 1)
)
);
@@ -53,6 +53,7 @@ class EditorTextActionModeStarter implements CodeEditor.EditorTextActionPresente
android.R.attr.actionModeCopyDrawable,
android.R.attr.actionModePasteDrawable,
});
menu.add(0, 0, 0, mEditor.getContext().getString(android.R.string.selectAll))
.setShowAsActionFlags(2)
.setIcon(array.getDrawable(0));
@@ -68,8 +69,15 @@ class EditorTextActionModeStarter implements CodeEditor.EditorTextActionPresente
menu.add(0, 3, 0, mEditor.getContext().getString(android.R.string.paste))
.setShowAsActionFlags(2)
.setIcon(array.getDrawable(3));
menu.add(0, 4, 0, "Go to Definition");
menu.add(0, 5, 0, "Find all references");
menu.add(0, 4, 0, mEditor.getContext().getString(com.itsaky.androidide.R.string.menu_navigate_definition))
.setIcon(com.itsaky.androidide.R.drawable.ic_goto_definition);
menu.add(0, 5, 0, mEditor.getContext().getString(com.itsaky.androidide.R.string.menu_navigate_references))
.setIcon(com.itsaky.androidide.R.drawable.ic_find_references);
menu.add(0, 6, 0, mEditor.getContext().getString(com.itsaky.androidide.R.string.menu_comment_line))
.setIcon(com.itsaky.androidide.R.drawable.ic_comment_line);
menu.add(0, 7, 0, mEditor.getContext().getString(com.itsaky.androidide.R.string.menu_uncomment_line))
.setIcon(com.itsaky.androidide.R.drawable.ic_uncomment_line);
array.recycle();
return true;
}
@@ -100,6 +108,12 @@ class EditorTextActionModeStarter implements CodeEditor.EditorTextActionPresente
case 5:
mEditor.findReferences();
break;
case 6:
mEditor.commentLine();
break;
case 7:
mEditor.uncommentLine();
break;
}
if(menuItem.getItemId() != 0)
onExit();
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M8,3A2,2 0,0 0,6 5V9A2,2 0,0 1,4 11H3V13H4A2,2 0,0 1,6 15V19A2,2 0,0 0,8 21H10V19H8V14A2,2 0,0 0,6 12A2,2 0,0 0,8 10V5H10V3M16,3A2,2 0,0 1,18 5V9A2,2 0,0 0,20 11H21V13H20A2,2 0,0 0,18 15V19A2,2 0,0 1,16 21H14V19H16V14A2,2 0,0 1,18 12A2,2 0,0 1,16 10V5H14V3H16Z"/>
</vector>
@@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#5DFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="1.3401308"
android:strokeLineJoin="miter"
android:fillType="nonZero"
android:strokeLineCap="square"
android:pathData="M17.16,6.00L16.93,6.00L12.59,18.00L12.89,18.00z"/>
<path
android:fillColor="#5DFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="1.3401308"
android:strokeLineJoin="miter"
android:fillType="nonZero"
android:strokeLineCap="square"
android:pathData="M11.41,6.00L11.18,6.00L6.84,18.00L7.14,18.00z"/>
</vector>
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M15,19.88C15.04,20.18 14.94,20.5 14.71,20.71C14.32,21.1 13.69,21.1 13.3,20.71L9.29,16.7C9.06,16.47 8.96,16.16 9,15.87V10.75L4.21,4.62C3.87,4.19 3.95,3.56 4.38,3.22C4.57,3.08 4.78,3 5,3V3H19V3C19.22,3 19.43,3.08 19.62,3.22C20.05,3.56 20.13,4.19 19.79,4.62L15,10.75V19.88M7.04,5L11,10.06V15.58L13,17.58V10.05L16.96,5H7.04Z"/>
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M14,2H6A2,2 0,0 0,4 4V20A2,2 0,0 0,6 22H18A2,2 0,0 0,20 20V8L14,2M6,4H13L18,9V17.58L16.16,15.74C17.44,13.8 17.23,11.17 15.5,9.46C14.55,8.5 13.28,8 12,8C10.72,8 9.45,8.5 8.47,9.46C6.5,11.41 6.5,14.57 8.47,16.5C9.44,17.5 10.72,17.97 12,17.97C12.96,17.97 13.92,17.69 14.75,17.14L17.6,20H6V4M14.11,15.1C13.55,15.66 12.8,16 12,16C11.2,16 10.45,15.67 9.89,15.1C9.33,14.54 9,13.79 9,13C9,12.19 9.32,11.44 9.89,10.88C10.45,10.31 11.2,10 12,10C12.8,10 13.55,10.31 14.11,10.88C14.67,11.44 15,12.19 15,13C15,13.79 14.68,14.54 14.11,15.1Z"/>
</vector>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M7,21H5V3H7V21M14,3H12V9H14V3M14,15H12V21H14V15M19,12L16,9V11H9V13H16V15L19,12Z"/>
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M9.64,13.4C8.63,12.5 7.34,12.03 6,12V15L2,11L6,7V10C7.67,10 9.3,10.57 10.63,11.59C10.22,12.15 9.89,12.76 9.64,13.4M18,15V12C17.5,12 13.5,12.16 13.05,16.2C14.61,16.75 15.43,18.47 14.88,20.03C14.33,21.59 12.61,22.41 11.05,21.86C9.5,21.3 8.67,19.59 9.22,18.03C9.5,17.17 10.2,16.5 11.05,16.2C11.34,12.61 14.4,9.88 18,10V7L22,11L18,15M13,19A1,1 0,0 0,12 18A1,1 0,0 0,11 19A1,1 0,0 0,12 20A1,1 0,0 0,13 19M11,11.12C11.58,10.46 12.25,9.89 13,9.43V5H16L12,1L8,5H11V11.12Z"/>
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M12,7.3L16.3,17.7L12.8,16.2L12,15.8L11.2,16.2L7.7,17.7L12,7.3M12,2L4.5,20.3L5.2,21L12,18L18.8,21L19.5,20.3L12,2Z"/>
</vector>
+38
View File
@@ -0,0 +1,38 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="0.43331328"
android:strokeLineJoin="miter"
android:fillType="nonZero"
android:strokeLineCap="square"
android:pathData="M6.37,10.70C8.50,3.94,14.82,6.73,15.82,8.07L17.22,6.47C10.41,1.26,4.83,7.22,4.58,10.71L6.37,10.70z"/>
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="0.43331328"
android:strokeLineJoin="miter"
android:fillType="nonZero"
android:strokeLineCap="square"
android:pathData="M13.68,10.63L19.57,4.57L19.57,10.72z"/>
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="0.43331328"
android:strokeLineJoin="miter"
android:fillType="nonZero"
android:strokeLineCap="square"
android:pathData="M17.63,13.30C15.50,20.06,9.18,17.27,8.18,15.93L6.78,17.53C13.59,22.74,19.17,16.78,19.42,13.29L17.63,13.30z"/>
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="0.43331328"
android:strokeLineJoin="miter"
android:fillType="nonZero"
android:strokeLineCap="square"
android:pathData="M10.32,13.37L4.43,19.43L4.43,13.28z"/>
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M14,2H6A2,2 0,0 0,4 4V20A2,2 0,0 0,6 22H13C12.59,21.75 12.2,21.44 11.86,21.1C11.53,20.77 11.25,20.4 11,20H6V4H13V9H18V10.18C18.71,10.34 19.39,10.61 20,11V8L14,2M20.31,18.9C21.64,16.79 21,14 18.91,12.68C16.8,11.35 14,12 12.69,14.08C11.35,16.19 12,18.97 14.09,20.3C15.55,21.23 17.41,21.23 18.88,20.32L22,23.39L23.39,22L20.31,18.9M16.5,19A2.5,2.5 0,0 1,14 16.5A2.5,2.5 0,0 1,16.5 14A2.5,2.5 0,0 1,19 16.5A2.5,2.5 0,0 1,16.5 19Z"/>
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z"/>
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z"/>
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M16.5,12C19,12 21,14 21,16.5C21,17.38 20.75,18.21 20.31,18.9L23.39,22L22,23.39L18.88,20.32C18.19,20.75 17.37,21 16.5,21C14,21 12,19 12,16.5C12,14 14,12 16.5,12M16.5,14A2.5,2.5 0,0 0,14 16.5A2.5,2.5 0,0 0,16.5 19A2.5,2.5 0,0 0,19 16.5A2.5,2.5 0,0 0,16.5 14M19,8H3V18H10.17C10.34,18.72 10.63,19.39 11,20H3C1.89,20 1,19.1 1,18V6C1,4.89 1.89,4 3,4H9L11,6H19A2,2 0,0 1,21 8V11.81C20.42,11.26 19.75,10.81 19,10.5V8Z"/>
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M11,6C12.38,6 13.63,6.56 14.54,7.46L12,10H18V4L15.95,6.05C14.68,4.78 12.93,4 11,4C7.47,4 4.57,6.61 4.08,10H6.1C6.56,7.72 8.58,6 11,6M16.64,15.14C17.3,14.24 17.76,13.17 17.92,12H15.9C15.44,14.28 13.42,16 11,16C9.62,16 8.37,15.44 7.46,14.54L10,12H4V18L6.05,15.95C7.32,17.22 9.07,18 11,18C12.55,18 14,17.5 15.14,16.64L20,21.5L21.5,20L16.64,15.14Z"/>
</vector>
@@ -0,0 +1,38 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#5DFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="1.3401308"
android:strokeLineJoin="miter"
android:fillType="nonZero"
android:strokeLineCap="square"
android:pathData="M11.41,6.00L11.18,6.00L6.84,18.00L7.14,18.00z"/>
<path
android:fillColor="#5DFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="0.8170607"
android:strokeLineJoin="miter"
android:fillType="nonZero"
android:strokeLineCap="square"
android:pathData="M13.66,15.37L13.55,15.37L11.38,18.05L11.53,18.05z"/>
<path
android:fillColor="#5DFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="0.8170607"
android:strokeLineJoin="miter"
android:fillType="nonZero"
android:strokeLineCap="square"
android:pathData="M11.38,15.37L11.49,15.37L13.66,18.05L13.51,18.05z"/>
<path
android:fillColor="#5DFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="1.3401308"
android:strokeLineJoin="miter"
android:fillType="nonZero"
android:strokeLineCap="square"
android:pathData="M17.16,6.00L16.93,6.00L14.34,12.81Q14.48,12.80,14.66,12.81z"/>
</vector>
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fillViewport="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/msg_search_modules"
android:layout_marginBottom="8dp"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/modules_container"
android:layout_marginBottom="16dp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:id="@+id/input"
android:hint="@string/text_to_search"
android:layout_marginBottom="8dp"
app:startIconDrawable="@drawable/ic_search"
app:startIconTint="@color/primaryIconColor">
<com.google.android.material.textfield.TextInputEditText
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:id="@+id/filter"
android:hint="@string/hint_find_project_filter"
app:helperTextEnabled="true"
app:helperText="@string/msg_find_project_filter"
app:startIconDrawable="@drawable/ic_filter"
app:startIconTint="@color/primaryIconColor">
<com.google.android.material.textfield.TextInputEditText
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="Overdraw"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/content_background">
<LinearLayout
android:id="@+id/item"
tools:ignore="UnusedAttribute"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:gravity="center_vertical"
android:clickable="true"
android:focusable="true"
android:descendantFocusability="blocksDescendants"
android:background="@color/content_background"
android:foreground="?attr/selectableItemBackground">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:id="@+id/icon"
android:layout_marginEnd="16dp"
android:scaleType="center"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:id="@+id/title"
android:textColor="@color/primaryTextColor"/>
</LinearLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
android:layout_below="@id/item"
android:paddingStart="16dp"
android:clipChildren="false"
android:clipToPadding="false">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/items"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:paddingStart="8dp"
android:paddingEnd="8dp"/>
</HorizontalScrollView>
</RelativeLayout>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="UnusedAttribute"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:gravity="top|start"
android:clickable="true"
android:focusable="true"
android:descendantFocusability="blocksDescendants"
android:background="@color/content_background"
android:foreground="?attr/selectableItemBackground">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/text"
android:textSize="16sp"
android:textColor="@color/primaryTextColor"/>
</RelativeLayout>
+46
View File
@@ -105,4 +105,50 @@
android:id="@+id/menuEditor_save"
android:title="@string/save"
app:showAsAction="ifRoom"/>
<item
android:icon="@drawable/ic_navigation"
android:id="@+id/menuEditor_navigate"
android:title="@string/menu_navigate"
app:showAsAction="ifRoom">
<menu>
<item
android:icon="@drawable/ic_goto_definition"
android:id="@+id/menuEditor_gotoDefinition"
android:title="@string/menu_navigate_definition"
app:showAsAction="never"/>
<item
android:icon="@drawable/ic_find_references"
android:id="@+id/menuEditor_findReferences"
android:title="@string/menu_navigate_references"
app:showAsAction="never"/>
<item
android:icon="@drawable/ic_comment_line"
android:id="@+id/menuEditor_commentLine"
android:title="@string/menu_comment_line"
app:showAsAction="never"/>
<item
android:icon="@drawable/ic_uncomment_line"
android:id="@+id/menuEditor_uncommentLine"
android:title="@string/menu_uncomment_line"
app:showAsAction="never"/>
</menu>
</item>
<item
android:icon="@drawable/ic_search"
android:id="@+id/menuEditor_find"
android:title="@string/menu_find"
app:showAsAction="ifRoom">
<menu>
<item
android:icon="@drawable/ic_search_file"
android:id="@+id/menuEditor_findFile"
android:title="@string/menu_find_file"
app:showAsAction="never"/>
<item
android:icon="@drawable/ic_search_project"
android:id="@+id/menuEditor_findProject"
android:title="@string/menu_find_project"
app:showAsAction="never"/>
</menu>
</item>
</menu>
+18
View File
@@ -1,4 +1,22 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="menu_navigate_definition">Go to definition</string>
<string name="menu_navigate_references">Find all references</string>
<string name="menu_navigate">Code</string>
<string name="menu_code_action">Code Action</string>
<string name="menu_comment_line">Comment line</string>
<string name="menu_uncomment_line">Uncomment line</string>
<string name="menu_find">Find</string>
<string name="menu_find_file">Find in file</string>
<string name="menu_find_project">Find in project</string>
<string name="msg_search_modules">Search in modules</string>
<string name="msg_finding_references">Finding references&#8230;</string>
<string name="msg_finding_definition">Finding definition&#8230;</string>
<string name="msg_no_modules">No modules found in project</string>
<string name="hint_find_project_filter">Filter file extensions (optional)</string>
<string name="msg_find_project_filter">Separated by \'|\'</string>
<string name="msg_empty_search_query">Please enter text</string>
<string name="msg_select_search_modules">Select modules to search</string>
<string name="msg_searching_project">Finding in project, please wait&#8230;</string>
<string name="view_search_results">Search results</string>
<string name="msg_no_definition">Definition not found</string>
<string name="msg_no_references">No references found</string>
@@ -9,13 +9,13 @@ public class Position {
public int line;
@SerializedName("character")
public int character;
public int column;
public Position() {}
public Position(int line, int character) {
this.line = line;
this.character = character;
this.column = character;
}
@Override
@@ -28,7 +28,7 @@ public class Position {
if(obj instanceof Position) {
Position that = (Position) obj;
return this.line == that.line
&& this.character == that.character;
&& this.column == that.column;
}
return false;
}