From fb2c6313ec39f828329246a267cccfa829ee05e2 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Fri, 13 Aug 2021 13:54:12 +0530 Subject: [PATCH] =?UTF-8?q?v1.0.3-alpha=20-=20Implemented=20=20=20=20=20?= =?UTF-8?q?=E2=80=A2=20Find=20all=20references=20=20=20=20=20=E2=80=A2=20C?= =?UTF-8?q?omment/Uncomment=20line=20=20=20=20=20=E2=80=A2=20Find=20in=20f?= =?UTF-8?q?ile=20=20=20=20=20=E2=80=A2=20Find=20in=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/data/bin.zip | Bin 0 -> 4799 bytes .../itsaky/androidide/DownloadActivity.java | 9 +- .../com/itsaky/androidide/EditorActivity.java | 309 ++++++++++++++---- .../adapters/DiagnosticItemAdpater.java | 7 +- .../adapters/DiagnosticsAdapter.java | 2 +- .../adapters/SearchListAdapter.java | 134 +++++++- .../com/itsaky/androidide/app/StudioApp.java | 18 +- .../androidide/fragments/EditorFragment.java | 28 +- .../fragments/ProjectsFragment.java | 58 ++-- .../interfaces/DiagnosticClickListener.java | 3 +- .../models/CompletionItemWrapper.java | 4 +- .../androidide/models/SearchResult.java | 18 + .../itsaky/androidide/shell/ShellServer.java | 10 +- .../syntax/colorschemes/SchemeAndroidIDE.java | 2 +- .../lexer/highlighters/Highlighter.java | 13 + .../lexer/highlighters/JavaHighlighter.java | 166 ++++++++++ .../lexer/impls/java/JavaLexerImpl.java | 14 +- .../itsaky/androidide/tasks/TaskExecutor.java | 4 +- .../itsaky/androidide/utils/Environment.java | 2 + .../utils/RecursiveFileSearcher.java | 131 ++++++++ .../rosemoe/editor/widget/CodeEditor.java | 105 ++++-- .../editor/widget/DiagnosticWindow.java | 4 +- .../widget/EditorTextActionModeStarter.java | 18 +- app/src/main/res/drawable/ic_code_action.xml | 11 + app/src/main/res/drawable/ic_comment_line.xml | 22 ++ app/src/main/res/drawable/ic_filter.xml | 11 + .../main/res/drawable/ic_find_references.xml | 11 + app/src/main/res/drawable/ic_format_code.xml | 10 + .../main/res/drawable/ic_goto_definition.xml | 11 + app/src/main/res/drawable/ic_navigation.xml | 11 + app/src/main/res/drawable/ic_replace.xml | 38 +++ app/src/main/res/drawable/ic_search_file.xml | 11 + app/src/main/res/drawable/ic_search_last.xml | 11 + app/src/main/res/drawable/ic_search_next.xml | 11 + .../main/res/drawable/ic_search_project.xml | 11 + .../main/res/drawable/ic_search_replace.xml | 11 + .../main/res/drawable/ic_uncomment_line.xml | 38 +++ .../main/res/layout/layout_search_project.xml | 64 ++++ .../res/layout/layout_search_result_group.xml | 62 ++++ .../res/layout/layout_search_result_item.xml | 26 ++ app/src/main/res/menu/menu_editor.xml | 46 +++ app/src/main/res/values/strings.xml | 18 + .../main/java/com/itsaky/lsp/Position.java | 6 +- 43 files changed, 1343 insertions(+), 156 deletions(-) create mode 100644 app/src/main/assets/data/bin.zip create mode 100644 app/src/main/java/com/itsaky/androidide/models/SearchResult.java create mode 100644 app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/Highlighter.java create mode 100644 app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/JavaHighlighter.java create mode 100644 app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java create mode 100644 app/src/main/res/drawable/ic_code_action.xml create mode 100644 app/src/main/res/drawable/ic_comment_line.xml create mode 100644 app/src/main/res/drawable/ic_filter.xml create mode 100644 app/src/main/res/drawable/ic_find_references.xml create mode 100644 app/src/main/res/drawable/ic_format_code.xml create mode 100644 app/src/main/res/drawable/ic_goto_definition.xml create mode 100644 app/src/main/res/drawable/ic_navigation.xml create mode 100644 app/src/main/res/drawable/ic_replace.xml create mode 100644 app/src/main/res/drawable/ic_search_file.xml create mode 100644 app/src/main/res/drawable/ic_search_last.xml create mode 100644 app/src/main/res/drawable/ic_search_next.xml create mode 100644 app/src/main/res/drawable/ic_search_project.xml create mode 100644 app/src/main/res/drawable/ic_search_replace.xml create mode 100644 app/src/main/res/drawable/ic_uncomment_line.xml create mode 100644 app/src/main/res/layout/layout_search_project.xml create mode 100644 app/src/main/res/layout/layout_search_result_group.xml create mode 100644 app/src/main/res/layout/layout_search_result_item.xml diff --git a/app/src/main/assets/data/bin.zip b/app/src/main/assets/data/bin.zip new file mode 100644 index 0000000000000000000000000000000000000000..3b34afa97a9ddaddfe7079a4c9d1056410641825 GIT binary patch literal 4799 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

+>tku>Z%t z%m6XO_e|DzH@~nE$@zP8G{E(T)$RDpqnQsaTAlK1qZ6~Ks27K>4q%a`r73Fq4ER@n z#0tO8gdCW~qulST`OovS$}Iy49F2{I24CnA*xLuis&x@1Vn->H)55+AYv*q$DLiVM|)&lB{_j?|D$y3r+#E((hl@ub2lsA4*8Qp*7gA5H`Jism(3cAd$jO!G1 zloxdE`}^y5_@6Snm|egj=N>Mov4U&m>oWK`1ZxPSTu^Ccmxe%e_C|8$sQFk;UTl9A z6HdGJ{rtqapHES&XRI~n>CA}0`)|30q39u>%9*WsrT7ag)gtHJ)<@=g(q=_5Hw3}u zk*xGL;4Cl;sA8<=>k#y!7xwxzLN&6X5XH#Vv1jctYrEBSzOr(DJwY34Y_cw`KI_a= z4g+-#7p$Wn1_dd*zZG;5p1AAc2J^8+&TFiQ2i&AIu)gI;?3kAf*xa{Y>t2lFWijXY zeL;Suz4Pr;wj@>Yl!+z7vGA7D6NA);`L;VaalwL*JCwhBeIpwcw0FJ>co}Qx*_6wN zTs1N?PgU!_(9$|jPmH*`CEi*K6yV=U*QmCO%F&NdSQmMg^@5F#MaR@6klbci^@6Mi zI?t}cY53t8Cr3nmo6P%5aM)THBfR!-1F;ww5EjUNYWeuWd?b`B>tyrB8ymTLJqmV& z0VT&#Mgd&!29)d+7lF%~zmIS5*;yBs=jG%HDt0`6GWo+N7jJBXK=gjiMA%0xiB}*2 zJ~~VpL$rN*2`CQM&EGuO=w#Iy12YbKf)lql|J)a3g+Pbr!JYUR3mdA^%V4-gkoU13 z?SQ(zxAWqWHqf%eJZ7UIRK z6VW9`1#GmwvC9yK!G$JFsI4tN+$2W6lp-G^94ifFQLAExHZFq#&nY+T1WvhSvJ_{b zCW?2H4>}Yr)<=nz-`5o_j>pB2X9SjFJZ1^AY-2F&!|Oog*qdKsNALRm9K)nzX60be zmM^{Q;METWM-HIiUEfcGl{p$qS@48)j1jN_I=eaBW@#Dri|O6iW#z~d!XuCb%4gN^ zl1OQ+M?%U&_WvOz&OfN>zttW7um4|wiQ>P2>3_i`V?7GWe=kV>$o!B0^9NG_0RICW CVx&F* literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/itsaky/androidide/DownloadActivity.java b/app/src/main/java/com/itsaky/androidide/DownloadActivity.java index 430784ad2..43a9477bf 100644 --- a/app/src/main/java/com/itsaky/androidide/DownloadActivity.java +++ b/app/src/main/java/com/itsaky/androidide/DownloadActivity.java @@ -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()); } diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index a6e1c6246..52cf2ca85 100644 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -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 modules = mProject.getModulePaths(); + final List srcDirs = new ArrayList<>(); + final LayoutSearchProjectBinding binding = LayoutSearchProjectBinding.inflate(getLayoutInflater()); + binding.modulesContainer.removeAllViews(); + for(int i=0;i { + 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 searchDirs = new ArrayList<>(); + for(int i=0;i 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> 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 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 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> results = new HashMap<>(); + for(int i=0;i 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) { diff --git a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdpater.java b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdpater.java index 7c5e14982..97942314c 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdpater.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdpater.java @@ -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 { private List diags; + private final File file; private DiagnosticClickListener listener; - public DiagnosticItemAdpater(List diags, DiagnosticClickListener listener) { + public DiagnosticItemAdpater(List diags, File file, DiagnosticClickListener listener) { this.diags = diags; + this.file = file; this.listener = listener; } @@ -38,7 +41,7 @@ public class DiagnosticItemAdpater extends RecyclerView.Adapter { if(listener != null) { - listener.onDiagnosticClick(diagnostic); + listener.onDiagnosticClick(file, diagnostic); } }); } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java index d5d912e17..b5d920555 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java @@ -38,7 +38,7 @@ public class DiagnosticsAdapter extends RecyclerView.Adapter { if(listener != null) { diff --git a/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java index a8e628757..ad896cd4d 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java @@ -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 { + + private final Map> results; + private final List keys; + + private final OnFileClickListener fileListener; + private final OnMatchClickListener matchListener; + + public SearchListAdapter(Map> results, OnFileClickListener fileListener, OnMatchClickListener matchListener) { + if (results == null) + results = new HashMap<>(); + this.results = results; + this.keys = new ArrayList(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 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 { + + private final List matches; + + public ChildAdapter(List 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; + } + } - - -} \ No newline at end of file + 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); + } +} diff --git a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java index f8d002344..14d589eed 100644 --- a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java @@ -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) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java index 70b6ba732..7013ae075 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java @@ -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; diff --git a/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java index d3e20614c..fb96dc535 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java @@ -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) { diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java b/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java index 19924d539..3e6674fd1 100644 --- a/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java +++ b/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java @@ -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); } diff --git a/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java b/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java index d72298276..1e693baff 100644 --- a/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java +++ b/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java @@ -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); } } } diff --git a/app/src/main/java/com/itsaky/androidide/models/SearchResult.java b/app/src/main/java/com/itsaky/androidide/models/SearchResult.java new file mode 100644 index 000000000..16fbb5680 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/models/SearchResult.java @@ -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; + } +} diff --git a/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java b/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java index 75713846b..06e4b4951 100644 --- a/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java +++ b/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java @@ -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"); diff --git a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java index b9da1e09b..186b28b31 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java @@ -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); diff --git a/app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/Highlighter.java b/app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/Highlighter.java new file mode 100644 index 000000000..a8bd811bc --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/Highlighter.java @@ -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; +} diff --git a/app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/JavaHighlighter.java b/app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/JavaHighlighter.java new file mode 100644 index 000000000..960aa947f --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/syntax/lexer/highlighters/JavaHighlighter.java @@ -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; + } +} diff --git a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java index cb8da03ce..047a1ce46 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java @@ -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 { callback.onComplete(result); }); - } catch (Throwable th) { - } + } catch (Throwable th) {} }); } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/Environment.java b/app/src/main/java/com/itsaky/androidide/utils/Environment.java index 882674b73..3a168d179 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/Environment.java +++ b/app/src/main/java/com/itsaky/androidide/utils/Environment.java @@ -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"); diff --git a/app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java b/app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java new file mode 100644 index 000000000..c0133959e --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java @@ -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 exts, List 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>> { + + private final String query; + private final List exts; + private final List dirs; + + public Searcher(String query, List exts, List dirs) { + this.query = query; + this.exts = exts; + this.dirs = dirs; + } + + @Override + public Map> call() throws Exception { + final Map> result = new HashMap<>(); + for(int i=0;i 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 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 exts; + + public MultiFileFilter(List 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> results); + } +} diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java index 57ca79dd3..918987c69 100644 --- a/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java @@ -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 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("")) { + text.replace(line, 0, line, text.getColumnCount(line), "")); + } + 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 = ""; + 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 + + + + diff --git a/app/src/main/res/drawable/ic_comment_line.xml b/app/src/main/res/drawable/ic_comment_line.xml new file mode 100644 index 000000000..d2b207bd8 --- /dev/null +++ b/app/src/main/res/drawable/ic_comment_line.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml new file mode 100644 index 000000000..dba631d07 --- /dev/null +++ b/app/src/main/res/drawable/ic_filter.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_find_references.xml b/app/src/main/res/drawable/ic_find_references.xml new file mode 100644 index 000000000..dccbadf6b --- /dev/null +++ b/app/src/main/res/drawable/ic_find_references.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_format_code.xml b/app/src/main/res/drawable/ic_format_code.xml new file mode 100644 index 000000000..b585cc996 --- /dev/null +++ b/app/src/main/res/drawable/ic_format_code.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_goto_definition.xml b/app/src/main/res/drawable/ic_goto_definition.xml new file mode 100644 index 000000000..48a9a60b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_goto_definition.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_navigation.xml b/app/src/main/res/drawable/ic_navigation.xml new file mode 100644 index 000000000..a521a7b9a --- /dev/null +++ b/app/src/main/res/drawable/ic_navigation.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_replace.xml b/app/src/main/res/drawable/ic_replace.xml new file mode 100644 index 000000000..3062e2fce --- /dev/null +++ b/app/src/main/res/drawable/ic_replace.xml @@ -0,0 +1,38 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_search_file.xml b/app/src/main/res/drawable/ic_search_file.xml new file mode 100644 index 000000000..f1b43f9d0 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_file.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_search_last.xml b/app/src/main/res/drawable/ic_search_last.xml new file mode 100644 index 000000000..f9fc500f3 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_last.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_search_next.xml b/app/src/main/res/drawable/ic_search_next.xml new file mode 100644 index 000000000..10dd7112e --- /dev/null +++ b/app/src/main/res/drawable/ic_search_next.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_search_project.xml b/app/src/main/res/drawable/ic_search_project.xml new file mode 100644 index 000000000..fd9218f3b --- /dev/null +++ b/app/src/main/res/drawable/ic_search_project.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_search_replace.xml b/app/src/main/res/drawable/ic_search_replace.xml new file mode 100644 index 000000000..a3af73fa0 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_replace.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_uncomment_line.xml b/app/src/main/res/drawable/ic_uncomment_line.xml new file mode 100644 index 000000000..4da85cdf7 --- /dev/null +++ b/app/src/main/res/drawable/ic_uncomment_line.xml @@ -0,0 +1,38 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_search_project.xml b/app/src/main/res/layout/layout_search_project.xml new file mode 100644 index 000000000..bee069eda --- /dev/null +++ b/app/src/main/res/layout/layout_search_project.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_search_result_group.xml b/app/src/main/res/layout/layout_search_result_group.xml new file mode 100644 index 000000000..ba577231f --- /dev/null +++ b/app/src/main/res/layout/layout_search_result_group.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_search_result_item.xml b/app/src/main/res/layout/layout_search_result_item.xml new file mode 100644 index 000000000..52cc2bc0c --- /dev/null +++ b/app/src/main/res/layout/layout_search_result_item.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/app/src/main/res/menu/menu_editor.xml b/app/src/main/res/menu/menu_editor.xml index bd7f14e76..80079da49 100644 --- a/app/src/main/res/menu/menu_editor.xml +++ b/app/src/main/res/menu/menu_editor.xml @@ -105,4 +105,50 @@ android:id="@+id/menuEditor_save" android:title="@string/save" app:showAsAction="ifRoom"/> + +

+ + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 42b943b34..83e0313b0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,22 @@ + Go to definition + Find all references + Code + Code Action + Comment line + Uncomment line + Find + Find in file + Find in project + Search in modules + Finding references… + Finding definition… + No modules found in project + Filter file extensions (optional) + Separated by \'|\' + Please enter text + Select modules to search + Finding in project, please wait… Search results Definition not found No references found diff --git a/lsp/src/main/java/com/itsaky/lsp/Position.java b/lsp/src/main/java/com/itsaky/lsp/Position.java index 196daec95..6aef16b78 100644 --- a/lsp/src/main/java/com/itsaky/lsp/Position.java +++ b/lsp/src/main/java/com/itsaky/lsp/Position.java @@ -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; }