mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Use ViewFlipper instead of ViewPager2 for editor fragments
This commit is contained in:
Generated
+1
@@ -13,6 +13,7 @@
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/activity_designer.xml" value="0.264" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/activity_download.xml" value="0.1188034188034188" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/activity_editor.xml" value="0.264" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/fragment_editor.xml" value="0.20512820512820512" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/fragment_non_editable_editor.xml" value="0.20512820512820512" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_about_items.xml" value="0.20512820512820512" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_diagnostic_info.xml" value="0.20512820512820512" />
|
||||
|
||||
@@ -64,13 +64,11 @@ import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.itsaky.androidide.adapters.DiagnosticsAdapter;
|
||||
import com.itsaky.androidide.adapters.EditorBottomSheetTabAdapter;
|
||||
import com.itsaky.androidide.adapters.EditorPagerAdapter;
|
||||
import com.itsaky.androidide.adapters.SearchListAdapter;
|
||||
import com.itsaky.androidide.app.StudioActivity;
|
||||
import com.itsaky.androidide.databinding.ActivityEditorBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutDiagnosticInfoBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutSearchProjectBinding;
|
||||
import com.itsaky.androidide.fragments.EditorFragment;
|
||||
import com.itsaky.androidide.fragments.FileTreeFragment;
|
||||
import com.itsaky.androidide.fragments.SearchResultFragment;
|
||||
import com.itsaky.androidide.fragments.sheets.OptionsListFragment;
|
||||
@@ -87,7 +85,6 @@ import com.itsaky.androidide.lsp.LSPProvider;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.models.DiagnosticGroup;
|
||||
import com.itsaky.androidide.models.LogLine;
|
||||
import com.itsaky.androidide.models.OpenedFile;
|
||||
import com.itsaky.androidide.models.SaveResult;
|
||||
import com.itsaky.androidide.models.SearchResult;
|
||||
import com.itsaky.androidide.models.SheetOption;
|
||||
@@ -104,6 +101,7 @@ import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.utils.RecursiveFileSearcher;
|
||||
import com.itsaky.androidide.utils.Symbols;
|
||||
import com.itsaky.androidide.viewmodel.EditorViewModel;
|
||||
import com.itsaky.androidide.views.CodeEditorView;
|
||||
import com.itsaky.androidide.views.MaterialBanner;
|
||||
import com.itsaky.androidide.views.SymbolInputView;
|
||||
import com.itsaky.inflater.ILayoutInflater;
|
||||
@@ -126,6 +124,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
@@ -136,13 +135,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
TabLayout.OnTabSelectedListener,
|
||||
NavigationView.OnNavigationItemSelectedListener,
|
||||
DiagnosticClickListener,
|
||||
EditorFragment.FileOpenListener,
|
||||
IDEHandler.Provider,
|
||||
EditorActivityProvider {
|
||||
|
||||
private ActivityEditorBinding mBinding;
|
||||
private LayoutDiagnosticInfoBinding mDiagnosticInfoBinding;
|
||||
private EditorPagerAdapter mPagerAdapter;
|
||||
private EditorBottomSheetTabAdapter bottomSheetTabAdapter;
|
||||
private FileTreeFragment mFileTreeFragment;
|
||||
private TreeNode mLastHeld;
|
||||
@@ -155,10 +152,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
private ProgressSheet mSearchingProgress;
|
||||
private AlertDialog mFindInProjectDialog;
|
||||
private ActivityResultLauncher<Intent> mUIDesignerLauncher;
|
||||
private EditorFragment mCurrentFragment;
|
||||
public static File mCurrentFile;
|
||||
@SuppressWarnings("rawtypes")
|
||||
private EditorBottomSheetBehavior mEditorBottomSheet;
|
||||
private EditorBottomSheetBehavior<? extends View> mEditorBottomSheet;
|
||||
|
||||
private EditorViewModel mViewModel;
|
||||
|
||||
@@ -206,33 +200,17 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
mViewModel = new ViewModelProvider (this).get (EditorViewModel.class);
|
||||
getProjectFromIntent ();
|
||||
|
||||
mPagerAdapter = new EditorPagerAdapter (this, this);
|
||||
mFileTreeFragment = FileTreeFragment.newInstance (this.getAndroidProject ()).setFileActionListener (this);
|
||||
mDaemonStatusFragment = new TextSheetFragment ().setTextSelectable (true);
|
||||
|
||||
setupDrawerToggle ();
|
||||
loadFragment (mFileTreeFragment);
|
||||
|
||||
mViewModel.observeFiles (this, files -> {
|
||||
mPagerAdapter.setFiles (files);
|
||||
});
|
||||
|
||||
symbolInput = new SymbolInputView (this);
|
||||
mBinding.bottomSheet.textContainer.addView (symbolInput, 0, new ViewGroup.LayoutParams (-1, -2));
|
||||
|
||||
final var mediator = new TabLayoutMediator (mBinding.tabs,
|
||||
mBinding.editorViewPager,
|
||||
true,
|
||||
false, // Do NOT enable smooth scrolls. Doing so results in error. Any workaround or fix will be appreciated.
|
||||
(tab, position) -> tab.setText (mPagerAdapter.getEditorTitle (position)));
|
||||
mBinding.editorViewPager.setUserInputEnabled (false);
|
||||
mBinding.editorViewPager.setOffscreenPageLimit (9);
|
||||
mBinding.editorViewPager.setAdapter (mPagerAdapter);
|
||||
mBinding.tabs.addOnTabSelectedListener (this);
|
||||
mediator.attach ();
|
||||
|
||||
setupEditorBottomSheet ();
|
||||
|
||||
createQuickActions ();
|
||||
|
||||
mBuildServiceHandler = new BuildServiceHandler (this);
|
||||
@@ -245,7 +223,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
setupContainers ();
|
||||
setupSignatureText ();
|
||||
setupDiagnosticInfo ();
|
||||
|
||||
startLanguageServers ();
|
||||
|
||||
mUIDesignerLauncher = registerForActivityResult (new ActivityResultContracts.StartActivityForResult (), this::onGetUIDesignerResult);
|
||||
@@ -272,8 +249,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
mBinding.bottomSheet.pager.setUserInputEnabled (false);
|
||||
mBinding.bottomSheet.pager.setOffscreenPageLimit (bottomSheetTabAdapter.getItemCount () - 1); // DO not remove any views
|
||||
|
||||
//noinspection rawtypes
|
||||
mEditorBottomSheet = (EditorBottomSheetBehavior) EditorBottomSheetBehavior.from (mBinding.bottomSheet.getRoot ());
|
||||
mEditorBottomSheet = (EditorBottomSheetBehavior<? extends View>) EditorBottomSheetBehavior.from (mBinding.bottomSheet.getRoot ());
|
||||
mEditorBottomSheet.setBinding (mBinding.bottomSheet);
|
||||
mEditorBottomSheet.addBottomSheetCallback (new BottomSheetBehavior.BottomSheetCallback () {
|
||||
@Override
|
||||
@@ -316,30 +292,19 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
|
||||
@Override
|
||||
protected void onPause () {
|
||||
new Thread (() -> {
|
||||
|
||||
boolean saved;
|
||||
try {
|
||||
saveAll (false /* No notification */);
|
||||
saved = true;
|
||||
} catch (Throwable th) {
|
||||
LOG.error (getString (R.string.err_cannot_save_files), th);
|
||||
saved = false;
|
||||
}
|
||||
|
||||
if (!saved) {
|
||||
ThreadUtils.runOnUiThread (() -> getApp ().toast (R.string.msg_failed_save, Toaster.Type.ERROR));
|
||||
}
|
||||
}, "AndroidIDE FileSaver").start ();
|
||||
dispatchOnPauseToEditors ();
|
||||
super.onPause ();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume () {
|
||||
super.onResume ();
|
||||
|
||||
try {
|
||||
dispatchOnResumeToEditors ();
|
||||
mFileTreeFragment.listProjectFiles ();
|
||||
} catch (Throwable th) {
|
||||
LOG.error ("Failed to update files list", th);
|
||||
getApp ().toast (R.string.msg_failed_list_files, Toaster.Type.ERROR);
|
||||
}
|
||||
}
|
||||
@@ -395,10 +360,15 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
viewLayout.setShowAsAction (MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
}
|
||||
|
||||
final boolean notNull = mCurrentFile != null;
|
||||
final boolean isJava = notNull && mCurrentFile.getName ().endsWith (".java");
|
||||
final boolean isXml = notNull && mCurrentFile.getName ().endsWith (".xml");
|
||||
final boolean isLayout = isXml && mCurrentFile.getParentFile () != null && Pattern.compile (FileOptionsHandler.LAYOUTRES_PATH_REGEX).matcher (mCurrentFile.getParentFile ().getAbsolutePath ()).matches ();
|
||||
final var editor = getCurrentEditor ();
|
||||
final var file = editor != null ? editor.getFile () : null;
|
||||
final var notNull = editor != null && file != null;
|
||||
final boolean isJava = notNull && file.getName ().endsWith (".java");
|
||||
final boolean isXml = notNull && file.getName ().endsWith (".xml");
|
||||
final boolean isLayout = isXml && file.getParentFile () != null &&
|
||||
Pattern.compile (FileOptionsHandler.LAYOUTRES_PATH_REGEX)
|
||||
.matcher (file.getParentFile ().getAbsolutePath ())
|
||||
.matches ();
|
||||
final int nullableAlpha = notNull ? 255 : 76;
|
||||
final int javaFileAlpha = isJava ? 255 : 76;
|
||||
final int layoutFileAlpha = isLayout ? 255 : 76;
|
||||
@@ -435,6 +405,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
MenuBuilder builder = (MenuBuilder) menu;
|
||||
builder.setOptionalIconsVisible (true);
|
||||
}
|
||||
|
||||
getMenuInflater ().inflate (R.menu.menu_editor, menu);
|
||||
return true;
|
||||
}
|
||||
@@ -442,8 +413,10 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
@Override
|
||||
public boolean onOptionsItemSelected (MenuItem item) {
|
||||
if (getBuildService () == null) {
|
||||
LOG.error ("Build service is null. Cannot perform option menu actions.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id = item.getItemId ();
|
||||
if (id == R.id.menuEditor_runDebug || id == R.id.menuEditor_quickRun) {
|
||||
getBuildServiceHandler ().assembleDebug (true);
|
||||
@@ -466,34 +439,32 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
} else if (id == R.id.menuEditor_lintRelease) {
|
||||
getBuildService ().lintRelease ();
|
||||
} else if (id == R.id.menuEditor_save) {
|
||||
// * 1. Notify that all files are saved
|
||||
// * 2. If there were any XML files modified, call ':app:processDebugResources' task
|
||||
// *
|
||||
// * This will make sure that we generate view bindings and R.jar at proper time
|
||||
// * This will further result in updated code completion
|
||||
saveAll (true, true);
|
||||
} else if (id == R.id.menuEditor_undo && this.mCurrentFragment != null && this.mCurrentFragment.isVisible ()) {
|
||||
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 ();
|
||||
}
|
||||
} else if (id == R.id.menuEditor_viewLayout && mCurrentFile != null) {
|
||||
} else if (id == R.id.menuEditor_viewLayout) {
|
||||
previewLayout ();
|
||||
} else if (getCurrentEditor () != null) { // Should be checked at last
|
||||
if (id == R.id.menuEditor_undo) {
|
||||
this.getCurrentEditor ().undo ();
|
||||
} else if (id == R.id.menuEditor_redo) {
|
||||
this.getCurrentEditor ().redo ();
|
||||
} else if (id == R.id.menuEditor_gotoDefinition) {
|
||||
this.getCurrentEditor ().findDefinition ();
|
||||
} else if (id == R.id.menuEditor_findReferences) {
|
||||
this.getCurrentEditor ().findReferences ();
|
||||
} else if (id == R.id.menuEditor_commentLine) {
|
||||
this.getCurrentEditor ().commentLine ();
|
||||
} else if (id == R.id.menuEditor_uncommentLine) {
|
||||
this.getCurrentEditor ().uncommentLine ();
|
||||
} else if (id == R.id.menuEditor_findFile) {
|
||||
this.getCurrentEditor ().beginSearch ();
|
||||
}
|
||||
}
|
||||
|
||||
invalidateOptionsMenu ();
|
||||
return true;
|
||||
}
|
||||
@@ -578,11 +549,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
}
|
||||
|
||||
public void handleDiagnosticsResultVisibility (boolean errorVisible) {
|
||||
bottomSheetTabAdapter.getDiagnosticsFragment ().handleResultVisibility (errorVisible);
|
||||
runOnUiThread (() -> bottomSheetTabAdapter.getDiagnosticsFragment ().handleResultVisibility (errorVisible));
|
||||
}
|
||||
|
||||
public void handleSearchResultVisibility (boolean errorVisible) {
|
||||
bottomSheetTabAdapter.getSearchResultFragment ().handleResultVisibility (errorVisible);
|
||||
runOnUiThread (() -> bottomSheetTabAdapter.getSearchResultFragment ().handleResultVisibility (errorVisible));
|
||||
}
|
||||
|
||||
public void setStatus (final CharSequence text) {
|
||||
@@ -619,10 +590,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
hideViewOptions ();
|
||||
}
|
||||
|
||||
public EditorPagerAdapter getPagerAdapter () {
|
||||
return mPagerAdapter;
|
||||
}
|
||||
|
||||
public ActivityEditorBinding getBinding () {
|
||||
return mBinding;
|
||||
}
|
||||
@@ -661,7 +628,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
|
||||
public void openFileAndSelect (File file, org.eclipse.lsp4j.Range range) {
|
||||
openFile (file, range);
|
||||
EditorFragment opened = getEditorForFile (file);
|
||||
final var opened = getEditorForFile (file);
|
||||
if (opened != null && opened.getEditor () != null) {
|
||||
CodeEditor editor = opened.getEditor ();
|
||||
editor.post (() -> {
|
||||
@@ -676,39 +643,49 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
|
||||
@Override
|
||||
public void onTabSelected (@NonNull TabLayout.Tab tab) {
|
||||
final var opened = mPagerAdapter.getOpenedFile (tab.getPosition ());
|
||||
final var fragment = getEditorAtIndex (tab.getPosition ());
|
||||
if (opened != null && opened.getFile () != null && fragment != null) {
|
||||
mCurrentFragment = fragment;
|
||||
mCurrentFile = opened.getFile ();
|
||||
refreshSymbolInput (mCurrentFragment);
|
||||
}
|
||||
final var position = tab.getPosition ();
|
||||
mBinding.editorContainer.setDisplayedChild (position);
|
||||
|
||||
final var editorView = getEditorAtIndex (position);
|
||||
mViewModel.setCurrentFile (position, Objects.requireNonNull (editorView).getFile ());
|
||||
refreshSymbolInput (editorView);
|
||||
invalidateOptionsMenu ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected (@NonNull TabLayout.Tab tab) {
|
||||
final var opened = mPagerAdapter.getOpenedFile (tab.getPosition ());
|
||||
final var position = mViewModel.getCurrentFileIndex ();
|
||||
if (position < 0 || position >= mViewModel.getOpenedFileCount ()) {
|
||||
// This might happen when the file is being closed.
|
||||
// In this case, the file has already been saved, so we don't need to save it again.
|
||||
// see #close(int)
|
||||
return;
|
||||
}
|
||||
|
||||
final var opened = mViewModel.getOpenedFile (position);
|
||||
if (opened == null) {
|
||||
LOG.error ("No opened file at index", position);
|
||||
return;
|
||||
}
|
||||
|
||||
final var frag = getEditorForFile (opened.getFile ());
|
||||
final var frag = getEditorAtIndex (position);
|
||||
if (frag == null) {
|
||||
LOG.error ("Cannot save unselected editor. Editor is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isGradle = frag.isModified () && opened.getFile ().getName ().endsWith (EditorFragment.EXT_GRADLE);
|
||||
boolean isGradle = frag.isModified () && opened.getName ().endsWith (".gradle");
|
||||
frag.save ();
|
||||
|
||||
if (isGradle) {
|
||||
LOG.info ("Gradle files have been modified. Sync needed.");
|
||||
notifySyncNeeded ();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected (TabLayout.Tab p1) {
|
||||
mTabCloseAction.show (mBinding.tabs);
|
||||
public void onTabReselected (@NonNull TabLayout.Tab tab) {
|
||||
mTabCloseAction.show (tab.view);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -783,16 +760,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
}
|
||||
|
||||
public void setDiagnosticsAdapter (@NonNull final DiagnosticsAdapter adapter) {
|
||||
bottomSheetTabAdapter.getDiagnosticsFragment ().setAdapter (adapter);
|
||||
runOnUiThread (() -> bottomSheetTabAdapter.getDiagnosticsFragment ().setAdapter (adapter));
|
||||
}
|
||||
|
||||
public void setSearchResultAdapter (@NonNull final SearchListAdapter adapter) {
|
||||
bottomSheetTabAdapter.getSearchResultFragment ().setAdapter (adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpenSuccessful (File file, String text) {
|
||||
// textDocument/didOpen is now handled by CodeEditor
|
||||
runOnUiThread (() -> bottomSheetTabAdapter.getSearchResultFragment ().setAdapter (adapter));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -838,8 +810,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
|
||||
public void saveResult (int index, SaveResult result) {
|
||||
if (index >= 0 && index < mViewModel.getOpenedFileCount ()) {
|
||||
EditorFragment frag = getEditorAtIndex (index);
|
||||
if (frag == null || frag.getFile () == null) {
|
||||
var frag = getEditorAtIndex (index);
|
||||
if (frag == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -849,8 +821,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
|
||||
frag.save ();
|
||||
|
||||
final boolean isGradle = frag.getFile ().getName ().endsWith (EditorFragment.EXT_GRADLE);
|
||||
final boolean isXml = frag.getFile ().getName ().endsWith (EditorFragment.EXT_XML);
|
||||
final boolean isGradle = frag.getFile ().getName ().endsWith (".gradle");
|
||||
final boolean isXml = frag.getFile ().getName ().endsWith (".xml");
|
||||
|
||||
if (!result.gradleSaved) {
|
||||
result.gradleSaved = modified && isGradle;
|
||||
@@ -875,6 +847,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
new TaskExecutor ().executeAsync (() -> {
|
||||
IDELanguageServer javaServer = LSPProvider.getServerForLanguage (LSPProvider.LANGUAGE_JAVA);
|
||||
if (javaServer == null) {
|
||||
LOG.error ("Cannot create services as java language server instance is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -900,11 +873,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
}
|
||||
|
||||
@Override
|
||||
public EditorFragment openFile (File file) {
|
||||
public CodeEditorView openFile (File file) {
|
||||
return openFile (file, null);
|
||||
}
|
||||
|
||||
public EditorFragment openFile (File file, Range selection) {
|
||||
public CodeEditorView openFile (File file, Range selection) {
|
||||
if (selection == null) {
|
||||
selection = Range_ofZero;
|
||||
}
|
||||
@@ -919,13 +892,14 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
mBinding.editorDrawerLayout.closeDrawer (GravityCompat.END);
|
||||
}
|
||||
|
||||
mBinding.editorViewPager.setCurrentItem (index);
|
||||
mBinding.editorContainer.setDisplayedChild (index);
|
||||
|
||||
invalidateOptionsMenu ();
|
||||
try {
|
||||
return getEditorAtIndex (index);
|
||||
} catch (Throwable th) {
|
||||
LOG.error ("Unable to get editor fragment at opened file index (" + index + ").", th);
|
||||
LOG.error ("Unable to get editor fragment at opened file index", index);
|
||||
LOG.error (th);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -942,86 +916,112 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
final var position = mViewModel.getOpenedFileCount ();
|
||||
|
||||
LOG.info ("Opening file at index:", position, "file: ", file);
|
||||
mViewModel.addFile (new OpenedFile (file, selection));
|
||||
|
||||
final var editor = new CodeEditorView (this, file, selection);
|
||||
editor.setLayoutParams (new ViewGroup.LayoutParams (
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
));
|
||||
|
||||
mBinding.editorContainer.addView (editor);
|
||||
mBinding.editorContainer.setDisplayedChild (position);
|
||||
mBinding.tabs.addTab (mBinding.tabs.newTab ().setText (file.getName ()));
|
||||
|
||||
mViewModel.addFile (file);
|
||||
mViewModel.setCurrentFile (position, file);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
public void closeFile (int index) {
|
||||
mBinding.tabs.removeOnTabSelectedListener (this);
|
||||
if (index >= 0 && index < mPagerAdapter.getItemCount ()) {// Save the file before closing
|
||||
if (index >= 0 && index < mViewModel.getOpenedFileCount ()) {
|
||||
var opened = mViewModel.getOpenedFile (index);
|
||||
LOG.info ("Saving file before close. File: " + opened.getFile ());
|
||||
final var fragment = getEditorAtIndex (index);
|
||||
if (fragment != null) {
|
||||
fragment.save ();
|
||||
LOG.info ("Language server will be notified if available");
|
||||
fragment.getEditor ().close (); // Send 'textDocument/didClose' to language servers.
|
||||
LOG.info ("Closing file:", opened);
|
||||
final var editor = getEditorAtIndex (index);
|
||||
if (editor != null) {
|
||||
editor.save ();
|
||||
editor.getEditor ().close ();
|
||||
} else {
|
||||
LOG.error ("Cannot save file before close. Editor instance is null");
|
||||
}
|
||||
|
||||
opened = null;
|
||||
|
||||
LOG.debug ("Removing file from file list...");
|
||||
mViewModel.removeFile (index);
|
||||
mBinding.tabs.removeTabAt (index);
|
||||
mBinding.editorContainer.removeViewAt (index);
|
||||
} else {
|
||||
LOG.error ("Invalid file index. Cannot close any files");
|
||||
mBinding.tabs.addOnTabSelectedListener (this);
|
||||
LOG.error ("Invalid file index. Cannot close.");
|
||||
return;
|
||||
}
|
||||
|
||||
mBinding.tabs.addOnTabSelectedListener (this);
|
||||
|
||||
if (mPagerAdapter.getItemCount () <= 0) {
|
||||
mCurrentFragment = null;
|
||||
mCurrentFile = null;
|
||||
}
|
||||
|
||||
mBinding.tabs.requestLayout ();
|
||||
invalidateOptionsMenu ();
|
||||
|
||||
if (mPagerAdapter.getItemCount () == 0 &&
|
||||
(mBinding.editorViewPager.getChildCount () != 0 || mBinding.tabs.getChildCount () != 0)) {
|
||||
// TODO Find out why this happens
|
||||
// Mostly for java files...
|
||||
mBinding.editorViewPager.removeAllViews ();
|
||||
mBinding.tabs.removeAllViews ();
|
||||
}
|
||||
}
|
||||
|
||||
public void closeAll () {
|
||||
mBinding.tabs.removeOnTabSelectedListener (this);
|
||||
// Close all files one by one
|
||||
// This will make sure that we save all the files and send 'textDocument/didClose' to language servers.
|
||||
for (int i = 0; i < mPagerAdapter. getItemCount (); i++) {
|
||||
closeFile (i);
|
||||
final var count = mViewModel.getOpenedFileCount ();
|
||||
|
||||
// Save and close all files one by one
|
||||
for (int i = 0; i < count; i++) {
|
||||
final var editor = getEditorAtIndex (i);
|
||||
if (editor != null) {
|
||||
editor.save ();
|
||||
editor.getEditor ().close ();
|
||||
} else {
|
||||
LOG.error ("Unable to save file at index:", i);
|
||||
}
|
||||
}
|
||||
mBinding.tabs.addOnTabSelectedListener (this);
|
||||
|
||||
mViewModel.removeAllFiles ();
|
||||
mBinding.tabs.removeAllTabs ();
|
||||
mBinding.tabs.requestLayout ();
|
||||
mBinding.editorContainer.removeAllViews ();
|
||||
|
||||
invalidateOptionsMenu ();
|
||||
}
|
||||
|
||||
public void closeOthers () {
|
||||
final var index = mBinding.tabs.getSelectedTabPosition ();
|
||||
for (int i = 0; i < mPagerAdapter. getItemCount (); i++) {
|
||||
if (i != index) {
|
||||
closeFile (i);
|
||||
final var file = mViewModel.getCurrentFile ();
|
||||
for (int i = 0; i < mViewModel.getOpenedFileCount (); i++) {
|
||||
final var editor = getEditorAtIndex (i);
|
||||
|
||||
// Index of files changes as we keep close files
|
||||
// So we compare the files instead of index
|
||||
if (editor != null) {
|
||||
if (!file.equals (editor.getFile ())) {
|
||||
closeFile (i);
|
||||
}
|
||||
} else {
|
||||
LOG.error ("Unable to save file at index:", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EditorFragment getEditorAtIndex (final int index) {
|
||||
return getEditorForFile (mViewModel.getOpenedFile (index).getFile ());
|
||||
@Nullable
|
||||
public CodeEditorView getCurrentEditor () {
|
||||
if (mViewModel.getCurrentFileIndex () != -1) {
|
||||
return getEditorAtIndex (mViewModel.getCurrentFileIndex ());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public EditorFragment getEditorForFile (@NonNull final File file) {
|
||||
final var manager = getSupportFragmentManager ();
|
||||
public CodeEditorView getEditorAtIndex (final int index) {
|
||||
return (CodeEditorView) mBinding.editorContainer.getChildAt (index);
|
||||
}
|
||||
|
||||
final var tag = "f" + file.getAbsolutePath ().hashCode ();
|
||||
final var fragment = manager.findFragmentByTag (tag);
|
||||
|
||||
if (fragment == null) {
|
||||
LOG.error ("No editor found for file: " + file);
|
||||
return null;
|
||||
@Nullable
|
||||
public CodeEditorView getEditorForFile (@NonNull final File file) {
|
||||
for (int i = 0; i < mViewModel.getOpenedFileCount (); i++) {
|
||||
final CodeEditorView editor = (CodeEditorView) mBinding.editorContainer.getChildAt (i);
|
||||
if (editor.getFile ().equals (file)) {
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
|
||||
return (EditorFragment) fragment;
|
||||
return null;
|
||||
}
|
||||
|
||||
public int findIndexOfEditorByFile (File file) {
|
||||
@@ -1032,7 +1032,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
|
||||
for (int i = 0; i < mViewModel.getOpenedFileCount (); i++) {
|
||||
final var opened = mViewModel.getOpenedFile (i);
|
||||
if (opened.getFile ().equals (file)) {
|
||||
if (opened.equals (file)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -1044,13 +1044,37 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
////////////// PRIVATE APIS /////////////////////
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
private void dispatchOnPauseToEditors () {
|
||||
CompletableFuture.runAsync (() -> {
|
||||
for (int i = 0; i < mViewModel.getOpenedFileCount (); i++) {
|
||||
final var editor = getEditorAtIndex (i);
|
||||
if (editor != null) {
|
||||
editor.onPause ();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void dispatchOnResumeToEditors () {
|
||||
CompletableFuture.runAsync (() -> {
|
||||
for (int i = 0; i < mViewModel.getOpenedFileCount (); i++) {
|
||||
final var editor = getEditorAtIndex (i);
|
||||
if (editor != null) {
|
||||
editor.onResume ();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private void onGetUIDesignerResult (@NonNull ActivityResult result) {
|
||||
if (mCurrentFragment != null && mCurrentFragment.getEditor () != null && result.getResultCode () == RESULT_OK) {
|
||||
final var index = mBinding.editorContainer.getDisplayedChild ();
|
||||
final var editor = getEditorAtIndex (index);
|
||||
if (editor != null && result.getResultCode () == RESULT_OK) {
|
||||
final var data = result.getData ();
|
||||
if (data != null && data.hasExtra (DesignerActivity.KEY_GENERATED_CODE)) {
|
||||
final var code = data.getStringExtra (DesignerActivity.KEY_GENERATED_CODE);
|
||||
mCurrentFragment.getEditor ().setText (code);
|
||||
editor.getEditor ().setText (code);
|
||||
saveAll ();
|
||||
} else {
|
||||
final var msg = getString (R.string.msg_invalid_designer_result);
|
||||
@@ -1065,6 +1089,12 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
|
||||
private void previewLayout () {
|
||||
try {
|
||||
|
||||
if (getCurrentEditor () == null) {
|
||||
LOG.error ("No file is opened. Cannot preview layout.");
|
||||
return;
|
||||
}
|
||||
|
||||
saveAll (false);
|
||||
if (getApp ().getLayoutInflater () == null) {
|
||||
LOG.info ("Creating layout inflater instance...");
|
||||
@@ -1072,7 +1102,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
}
|
||||
|
||||
final Intent intent = new Intent (this, DesignerActivity.class);
|
||||
intent.putExtra (DesignerActivity.KEY_LAYOUT_PATH, mCurrentFile.getAbsolutePath ());
|
||||
intent.putExtra (DesignerActivity.KEY_LAYOUT_PATH, getCurrentEditor ().getFile ().getAbsolutePath ());
|
||||
|
||||
LOG.info ("Launching UI Designer...");
|
||||
mUIDesignerLauncher.launch (intent);
|
||||
@@ -1204,11 +1234,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshSymbolInput (@NonNull EditorFragment frag) {
|
||||
if (frag.getEditor () == null || frag.getFile () == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void refreshSymbolInput (@NonNull CodeEditorView frag) {
|
||||
symbolInput.bindEditor (frag.getEditor ());
|
||||
symbolInput.setSymbols (Symbols.forFile (frag.getFile ()));
|
||||
}
|
||||
@@ -1225,7 +1251,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
private void closeProject (boolean manualFinish) {
|
||||
if (getBuildService () != null) {
|
||||
getBuildService ().setListener (null);
|
||||
getBuildService ().exit ();
|
||||
if (mBuildServiceHandler != null) {
|
||||
mBuildServiceHandler.stop ();
|
||||
} else {
|
||||
getBuildService ().exit ();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we close files
|
||||
@@ -1300,7 +1330,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
}
|
||||
|
||||
final List<File> searchDirs = new ArrayList<> ();
|
||||
for (int i = 0; i < binding.modulesContainer.getChildCount (); i++) {
|
||||
for (int i = 0; i < mViewModel.getOpenedFileCount (); i++) {
|
||||
CheckBox check = (CheckBox) binding.modulesContainer.getChildAt (i);
|
||||
if (check.isChecked ()) {
|
||||
searchDirs.add (srcDirs.get (i));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/************************************************************************************
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
@@ -16,19 +16,15 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide;
|
||||
|
||||
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.transition.Transition;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
@@ -56,6 +52,8 @@ import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
|
||||
|
||||
public class MainActivity extends StudioActivity implements View.OnClickListener, ProjectWriterCallback {
|
||||
|
||||
private ActivityMainBinding binding;
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.adapters;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
|
||||
import com.itsaky.androidide.fragments.EditorFragment;
|
||||
import com.itsaky.androidide.models.OpenedFile;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class EditorPagerAdapter extends FragmentStateAdapter implements EditorFragment.ModificationStateListener {
|
||||
|
||||
private final List<OpenedFile> mFiles;
|
||||
private final EditorFragment.FileOpenListener mFileOpenListener;
|
||||
|
||||
private static final Logger LOG = Logger.instance ("EditorPagerAdapter");
|
||||
|
||||
public EditorPagerAdapter (final FragmentActivity activity, EditorFragment.FileOpenListener fileOpenListener) {
|
||||
super (activity);
|
||||
this.mFileOpenListener = fileOpenListener;
|
||||
this.mFiles = new ArrayList<> ();
|
||||
}
|
||||
|
||||
public void setFiles (List<OpenedFile> files) {
|
||||
final var result = DiffUtil.calculateDiff (new DiffUtil.Callback () {
|
||||
@Override
|
||||
public int getOldListSize () {
|
||||
return mFiles.size ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize () {
|
||||
return files.size ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame (int oldItemPosition, int newItemPosition) {
|
||||
return Objects.equals (mFiles.get (oldItemPosition), files.get (newItemPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame (int oldItemPosition, int newItemPosition) {
|
||||
return areItemsTheSame (oldItemPosition, newItemPosition);
|
||||
}
|
||||
});
|
||||
mFiles.clear ();
|
||||
mFiles.addAll (files);
|
||||
result.dispatchUpdatesTo (this);
|
||||
}
|
||||
|
||||
|
||||
public String getEditorTitle (int index) {
|
||||
return mFiles.get (index).getFile ().getName ();
|
||||
}
|
||||
|
||||
public List<OpenedFile> getFiles () {
|
||||
return mFiles;
|
||||
}
|
||||
|
||||
public OpenedFile getOpenedFile (int index) {
|
||||
return mFiles.get (index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified (EditorFragment editor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaved (EditorFragment editor) {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment (int position) {
|
||||
return mFiles.get (position)
|
||||
.createFragment (this.mFileOpenListener, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount () {
|
||||
return mFiles.size ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId (int position) {
|
||||
if (mFiles.isEmpty ()) {
|
||||
LOG.warn ("File list is empty but getItemId(int) is being called. Returning -1");
|
||||
return -1;
|
||||
}
|
||||
|
||||
final var id = mFiles.get (position).hashCode ();
|
||||
LOG.debug ("Item ID for editor fragment at position " + position + " is " + id);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsItem (long itemId) {
|
||||
for (final var file : mFiles) {
|
||||
if (itemId == file.hashCode ()) { // see getItemId(int)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.warn ("EditorPagerAdapter does not contain any file with item ID : " + itemId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
|
||||
package com.itsaky.androidide.adapters;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MainPagerAdapter extends FragmentPagerAdapter
|
||||
{
|
||||
private ArrayList<Fragment> mFragmentList;
|
||||
|
||||
public MainPagerAdapter(FragmentManager manager)
|
||||
{
|
||||
super(manager);
|
||||
mFragmentList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int p1)
|
||||
{
|
||||
return mFragmentList != null && p1 < mFragmentList.size() ? mFragmentList.get(p1) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount()
|
||||
{
|
||||
return mFragmentList != null ? mFragmentList.size() : 0;
|
||||
}
|
||||
|
||||
public MainPagerAdapter addFragment(Fragment frag)
|
||||
{
|
||||
getFragments().add(frag);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArrayList<Fragment> getFragments()
|
||||
{
|
||||
return mFragmentList == null ? mFragmentList = new ArrayList<>() : mFragmentList;
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
|
||||
package com.itsaky.androidide.adapters;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.itsaky.androidide.databinding.LayoutModuleInfoItemBinding;
|
||||
import com.itsaky.androidide.project.IDEModule;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ModuleInfoAdapter extends RecyclerView.Adapter<ModuleInfoAdapter.VH> {
|
||||
|
||||
private List<IDEModule> modules;
|
||||
|
||||
public ModuleInfoAdapter(List<IDEModule> modules) {
|
||||
this.modules = modules;
|
||||
if(this.modules == null)
|
||||
this.modules = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleInfoAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) {
|
||||
return new VH(LayoutModuleInfoItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("SetTextI18n")
|
||||
public void onBindViewHolder(ModuleInfoAdapter.VH p1, int p2) {
|
||||
final IDEModule module = modules.get(p2);
|
||||
final LayoutModuleInfoItemBinding binding = p1.binding;
|
||||
final Context ctx = binding.getRoot().getContext();
|
||||
binding.moduleName.setText(module.displayName.substring(0, 1).toUpperCase(Locale.US) + module.displayName.substring(1));
|
||||
|
||||
SpannableStringBuilder summaryBuilder = new SpannableStringBuilder();
|
||||
if(!module.isLibrary) {
|
||||
summaryBuilder.append(ctx.getString(com.itsaky.androidide.R.string.msg_package_name, module.applicationId));
|
||||
summaryBuilder.append("\n");
|
||||
}
|
||||
summaryBuilder.append(ctx.getString(com.itsaky.androidide.R.string.msg_min_sdk, module.minSdk.apiString));
|
||||
summaryBuilder.append("\n");
|
||||
summaryBuilder.append(ctx.getString(com.itsaky.androidide.R.string.msg_target_sdk, module.targetSdk.apiString));
|
||||
summaryBuilder.append("\n");
|
||||
summaryBuilder.append(ctx.getString(com.itsaky.androidide.R.string.msg_dependency_count, module.dependencies.size()));
|
||||
summaryBuilder.append("\n");
|
||||
summaryBuilder.append(ctx.getString(com.itsaky.androidide.R.string.msg_task_count, module.tasks.size()));
|
||||
|
||||
binding.moduleSummary.setText(summaryBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return modules.size();
|
||||
}
|
||||
|
||||
public class VH extends RecyclerView.ViewHolder {
|
||||
LayoutModuleInfoItemBinding binding;
|
||||
|
||||
public VH(LayoutModuleInfoItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,19 +24,16 @@ import com.google.firebase.messaging.FirebaseMessaging;
|
||||
import com.itsaky.androidide.CrashHandlerActivity;
|
||||
import com.itsaky.androidide.language.xml.completion.XMLCompletionService;
|
||||
import com.itsaky.androidide.project.ProjectResourceFinder;
|
||||
import com.itsaky.androidide.services.IDELogService;
|
||||
import com.itsaky.androidide.services.MessagingService;
|
||||
import com.itsaky.inflater.ILayoutInflater;
|
||||
import com.itsaky.inflater.IResourceFinder;
|
||||
import com.itsaky.inflater.LayoutInflaterConfiguration;
|
||||
import com.itsaky.androidide.utils.FileUtil;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.apiinfo.ApiInfo;
|
||||
import com.itsaky.attrinfo.AttrInfo;
|
||||
import com.itsaky.inflater.ILayoutInflater;
|
||||
import com.itsaky.inflater.IResourceFinder;
|
||||
import com.itsaky.inflater.LayoutInflaterConfiguration;
|
||||
import com.itsaky.widgets.WidgetInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
public class StudioApp extends BaseApplication {
|
||||
|
||||
@@ -1,463 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.androidide.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.itsaky.androidide.adapters.CompletionListAdapter;
|
||||
import com.itsaky.androidide.databinding.FragmentEditorBinding;
|
||||
import com.itsaky.androidide.language.groovy.GroovyLanguage;
|
||||
import com.itsaky.androidide.language.java.JavaLanguage;
|
||||
import com.itsaky.androidide.language.xml.XMLLanguage;
|
||||
import com.itsaky.androidide.lexers.xml.XMLLexer;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.models.ConstantsBridge;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.tasks.callables.ReadFileTask;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.github.rosemoe.editor.interfaces.EditorEventListener;
|
||||
import io.github.rosemoe.editor.langs.EmptyLanguage;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
|
||||
public class EditorFragment extends BaseFragment implements EditorEventListener {
|
||||
|
||||
public FragmentEditorBinding mBinding;
|
||||
private File mFile;
|
||||
private boolean isModified = false;
|
||||
private boolean isFirstCreate = false;
|
||||
|
||||
private ModificationStateListener mModificationStateListener;
|
||||
private FileOpenListener mOpenListener;
|
||||
|
||||
private static final Logger LOG = Logger.instance ("EditorFragment");
|
||||
|
||||
public static final String KEY_FILE_PATH = "file_path";
|
||||
public static final String KEY_LINE_START = "line_start";
|
||||
public static final String KEY_LINE_END = "line_end";
|
||||
public static final String KEY_COLUMN_START = "col_start";
|
||||
public static final String KEY_COLUMN_END = "col_end";
|
||||
|
||||
public static final String EXT_JAVA = ".java";
|
||||
public static final String EXT_XML = ".xml";
|
||||
public static final String EXT_HTML = ".html";
|
||||
public static final String EXT_GRADLE = ".gradle";
|
||||
public static final String EXT_GROOVY = ".groovy";
|
||||
public static final String EXT_KOTLIN = ".kt";
|
||||
public static final String EXT_JSON = ".json";
|
||||
|
||||
public EditorFragment setModificationStateListener (ModificationStateListener listener) {
|
||||
this.mModificationStateListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FragmentEditorBinding getBinding () {
|
||||
return mBinding;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static EditorFragment newInstance (@NonNull File file, @NonNull Range selection) {
|
||||
Bundle bundle = new Bundle ();
|
||||
bundle.putString (KEY_FILE_PATH, file.getAbsolutePath ());
|
||||
bundle.putInt (KEY_LINE_START, selection.getStart ().getLine ());
|
||||
bundle.putInt (KEY_LINE_END, selection.getEnd ().getLine ());
|
||||
bundle.putInt (KEY_COLUMN_START, selection.getStart ().getCharacter ());
|
||||
bundle.putInt (KEY_COLUMN_END, selection.getEnd ().getCharacter ());
|
||||
|
||||
EditorFragment frag = new EditorFragment ();
|
||||
frag.setArguments (bundle);
|
||||
return frag;
|
||||
}
|
||||
|
||||
public EditorFragment setFileOpenListener (FileOpenListener openListener) {
|
||||
this.mOpenListener = openListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeEditor getEditor () {
|
||||
if (mBinding == null) {
|
||||
return null;
|
||||
}
|
||||
return mBinding.editor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate (Bundle savedInstanceState) {
|
||||
super.onCreate (savedInstanceState);
|
||||
isFirstCreate = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState (@NonNull Bundle outState) {
|
||||
save ();
|
||||
super.onSaveInstanceState (outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView (@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
mBinding = FragmentEditorBinding.inflate (inflater, container, false);
|
||||
return mBinding.getRoot ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated (@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated (view, savedInstanceState);
|
||||
|
||||
LOG.debug ("onViewCreated() called");
|
||||
mFile = new File (requireArguments ().getString (KEY_FILE_PATH));
|
||||
|
||||
mBinding.editor.setOverScrollEnabled (false);
|
||||
mBinding.editor.setTypefaceText (TypefaceUtils.jetbrainsMono ());
|
||||
mBinding.editor.setTextActionMode (CodeEditor.TextActionMode.ACTION_MODE);
|
||||
mBinding.editor.setHighlightCurrentBlock (true);
|
||||
mBinding.editor.setEventListener (this);
|
||||
mBinding.editor.setAutoCompletionOnComposing (true);
|
||||
mBinding.editor.setAutoCompletionItemAdapter (new CompletionListAdapter ());
|
||||
mBinding.editor.setLineColorsEnabled (true);
|
||||
mBinding.editor.setDividerWidth (SizeUtils.dp2px (1));
|
||||
|
||||
mBinding.diagnosticTextContainer.setVisibility (View.GONE);
|
||||
|
||||
LOG.debug ("Configuring editor from preferences...");
|
||||
configureEditorIfNeeded ();
|
||||
|
||||
LOG.debug ("Reading initial selection range");
|
||||
final Range range = fromArgs (requireArguments ());
|
||||
new TaskExecutor ().executeAsync (new ReadFileTask (mFile), result -> {
|
||||
mBinding.editor.setText (result);
|
||||
postRead ();
|
||||
mBinding.editor.post (() -> {
|
||||
if (LSPUtils.isEqual (range.getStart (), range.getEnd ())) {
|
||||
getEditor ().setSelection (range.getStart ().getLine (), range.getStart ().getCharacter ());
|
||||
} else {
|
||||
getEditor ().setSelectionRegion (range.getStart ().getLine (), range.getStart ().getCharacter (), range.getEnd ().getLine (), range.getEnd ().getCharacter ());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Range fromArgs (@NonNull Bundle args) {
|
||||
if (!(args.containsKey (KEY_LINE_START)
|
||||
&& args.containsKey (KEY_COLUMN_START)
|
||||
&& args.containsKey (KEY_LINE_END)
|
||||
&& args.containsKey (KEY_COLUMN_END))) {
|
||||
return LSPUtils.Range_ofZero;
|
||||
}
|
||||
|
||||
return new Range (
|
||||
new Position (
|
||||
args.getInt (KEY_LINE_START),
|
||||
args.getInt (KEY_COLUMN_START)),
|
||||
new Position (
|
||||
args.getInt (KEY_LINE_END),
|
||||
args.getInt (KEY_COLUMN_END)
|
||||
));
|
||||
}
|
||||
|
||||
public void setDiagnostics (List<Diagnostic> diags) {
|
||||
if (diags == null) {
|
||||
mBinding.editor.getEditorLanguage ().getAnalyzer ().updateDiagnostics (new HashMap<> ());
|
||||
return;
|
||||
}
|
||||
Map<Integer, Map<Integer, Diagnostic>> map = new HashMap<> ();
|
||||
for (int i = 0; i < diags.size (); i++) {
|
||||
final Diagnostic d = diags.get (i);
|
||||
if (d == null) {
|
||||
continue;
|
||||
}
|
||||
final Range range = d.getRange ();
|
||||
final int line = range.getStart ().getLine ();
|
||||
final int column = range.getStart ().getCharacter ();
|
||||
Map<Integer, Diagnostic> mappedByColumn = map.get (line);
|
||||
if (mappedByColumn == null) {
|
||||
mappedByColumn = new HashMap<> ();
|
||||
}
|
||||
mappedByColumn.put (column, d);
|
||||
map.put (line, mappedByColumn);
|
||||
}
|
||||
mBinding.editor.getEditorLanguage ().getAnalyzer ().updateDiagnostics (map);
|
||||
}
|
||||
|
||||
private void configureEditorIfNeeded () {
|
||||
boolean sizeChanged = isFirstCreate || ConstantsBridge.EDITORPREF_SIZE_CHANGED;
|
||||
boolean flagsChanged = isFirstCreate || ConstantsBridge.EDITORPREF_FLAGS_CHANGED;
|
||||
boolean drawHexChanged = isFirstCreate || ConstantsBridge.EDITORPREF_DRAW_HEX_CHANGED;
|
||||
final PreferenceManager prefs = getStudioActivity ().getApp ().getPrefManager ();
|
||||
if (sizeChanged) {
|
||||
float textSize = prefs.getFloat (PreferenceManager.KEY_EDITOR_FONT_SIZE);
|
||||
if (textSize < 6 || textSize > 32) {
|
||||
textSize = 14;
|
||||
}
|
||||
mBinding.editor.setTextSize (textSize);
|
||||
ConstantsBridge.EDITORPREF_SIZE_CHANGED = false;
|
||||
}
|
||||
|
||||
if (flagsChanged) {
|
||||
int flags = 0;
|
||||
if (prefs.getBoolean (PreferenceManager.KEY_EDITORFLAG_WS_LEADING, true)) {
|
||||
flags |= CodeEditor.FLAG_DRAW_WHITESPACE_LEADING;
|
||||
}
|
||||
if (prefs.getBoolean (PreferenceManager.KEY_EDITORFLAG_WS_TRAILING, false)) {
|
||||
flags |= CodeEditor.FLAG_DRAW_WHITESPACE_TRAILING;
|
||||
}
|
||||
if (prefs.getBoolean (PreferenceManager.KEY_EDITORFLAG_WS_INNER, true)) {
|
||||
flags |= CodeEditor.FLAG_DRAW_WHITESPACE_INNER;
|
||||
}
|
||||
if (prefs.getBoolean (PreferenceManager.KEY_EDITORFLAG_WS_EMPTY_LINE, true)) {
|
||||
flags |= CodeEditor.FLAG_DRAW_WHITESPACE_FOR_EMPTY_LINE;
|
||||
}
|
||||
if (prefs.getBoolean (PreferenceManager.KEY_EDITORFLAG_LINE_BREAK, true)) {
|
||||
flags |= CodeEditor.FLAG_DRAW_LINE_SEPARATOR;
|
||||
}
|
||||
|
||||
mBinding.editor.setNonPrintablePaintingFlags (flags);
|
||||
ConstantsBridge.EDITORPREF_FLAGS_CHANGED = false;
|
||||
}
|
||||
|
||||
if (drawHexChanged) {
|
||||
mBinding.editor.setLineColorsEnabled (prefs.getBoolean (PreferenceManager.KEY_EDITOR_DRAW_HEX, true));
|
||||
ConstantsBridge.EDITORPREF_DRAW_HEX_CHANGED = false;
|
||||
}
|
||||
|
||||
isFirstCreate = false;
|
||||
}
|
||||
|
||||
public String getText () {
|
||||
return mBinding.editor.getText ().toString ();
|
||||
}
|
||||
|
||||
public boolean isModified () {
|
||||
return isModified;
|
||||
}
|
||||
|
||||
public void undo () {
|
||||
if (mBinding.editor.canUndo ()) {
|
||||
mBinding.editor.undo ();
|
||||
}
|
||||
}
|
||||
|
||||
public void redo () {
|
||||
if (mBinding.editor.canRedo ()) {
|
||||
mBinding.editor.redo ();
|
||||
}
|
||||
}
|
||||
|
||||
public void findDefinition () {
|
||||
mBinding.editor.findDefinition ();
|
||||
}
|
||||
|
||||
public void findReferences () {
|
||||
mBinding.editor.findReferences ();
|
||||
}
|
||||
|
||||
public void commentLine () {
|
||||
mBinding.editor.commentLine ();
|
||||
}
|
||||
|
||||
public void uncommentLine () {
|
||||
mBinding.editor.uncommentLine ();
|
||||
}
|
||||
|
||||
public void beginSearch () {
|
||||
mBinding.editor.beginSearchMode ();
|
||||
}
|
||||
|
||||
public void save () {
|
||||
if (mFile == null || mBinding == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String text = mBinding.editor.getText ().toString ();
|
||||
FileIOUtils.writeFileFromString (mFile, text);
|
||||
notifySaved ();
|
||||
isModified = false;
|
||||
}
|
||||
|
||||
public File getFile () {
|
||||
return mFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume () {
|
||||
super.onResume ();
|
||||
configureEditorIfNeeded ();
|
||||
}
|
||||
|
||||
private void postRead () {
|
||||
if (mFile.isFile () && mFile.getName ().endsWith (EXT_JAVA)) {
|
||||
mBinding.editor.setEditorLanguage (new JavaLanguage (getFile ()));
|
||||
} else if (mFile.isFile () && mFile.getName ().endsWith (EXT_XML)) {
|
||||
mBinding.editor.setEditorLanguage (new XMLLanguage (getFile ()));
|
||||
} else if (mFile.isFile () && mFile.getName ().endsWith (EXT_GRADLE)) {
|
||||
mBinding.editor.setEditorLanguage (new GroovyLanguage (getFile ()));
|
||||
} else {
|
||||
mBinding.editor.setEditorLanguage (new EmptyLanguage ());
|
||||
}
|
||||
|
||||
// File must be set only after setting the language
|
||||
// This will make sure that textDocument/didOpen is sent
|
||||
mBinding.editor.setFile (getFile ());
|
||||
|
||||
mBinding.editor.setColorScheme (new SchemeAndroidIDE ());
|
||||
if (mOpenListener != null) {
|
||||
mOpenListener.onOpenSuccessful (getFile (), getText ());
|
||||
}
|
||||
}
|
||||
|
||||
private void closeCurrentTag (String text, int line, int col) {
|
||||
try {
|
||||
XMLLexer lexer = new XMLLexer (CharStreams.fromReader (new StringReader (text)));
|
||||
Token token;
|
||||
boolean wasSlash = false, wasOpen = false;
|
||||
ArrayList<String> currentNames = new ArrayList<> ();
|
||||
while (((token = lexer.nextToken ()) != null && token.getType () != token.EOF)) {
|
||||
final int type = token.getType ();
|
||||
if (type == XMLLexer.OPEN) {
|
||||
wasOpen = true;
|
||||
} else if (type == XMLLexer.Name) {
|
||||
if (wasOpen && wasSlash && currentNames.size () > 0) {
|
||||
currentNames.remove (0);
|
||||
} else if (wasOpen) {
|
||||
currentNames.add (0, token.getText ());
|
||||
wasOpen = false;
|
||||
}
|
||||
} else if (type == XMLLexer.OPEN_SLASH) {
|
||||
int l = token.getLine () - 1;
|
||||
int c = token.getCharPositionInLine ();
|
||||
if (l == line && c == col) {
|
||||
break;
|
||||
} else if (currentNames.size () > 0) {
|
||||
currentNames.remove (0);
|
||||
}
|
||||
} else if (type == XMLLexer.SLASH_CLOSE
|
||||
|| type == XMLLexer.SPECIAL_CLOSE) {
|
||||
if (currentNames.size () > 0 && token.getText ().trim ().endsWith ("/>")) {
|
||||
currentNames.remove (0);
|
||||
}
|
||||
} else if (type == XMLLexer.SLASH) {
|
||||
wasSlash = true;
|
||||
} else {
|
||||
wasOpen = wasSlash = false;
|
||||
}
|
||||
}
|
||||
if (currentNames.size () > 0) {
|
||||
mBinding.editor.getText ().insert (line, col + 2, currentNames.get (0));
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
LOG.error ("Unable to close current tag", th);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onRequestFormat (CodeEditor editor, boolean async) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFormatFail (CodeEditor editor, Throwable cause) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFormatSucceed (CodeEditor editor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTextSet (CodeEditor editor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetSelection (int startLine, int startCol, int endLine, int endCol) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterDelete (CodeEditor editor, CharSequence content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) {
|
||||
isModified = true;
|
||||
notifyModified ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterInsert (CodeEditor editor, CharSequence content, int startLine, int startColumn, int endLine, int endColumn, CharSequence insertedContent) {
|
||||
isModified = true;
|
||||
|
||||
if (getFile () != null && getFile ().getName ().endsWith (EXT_XML)) {
|
||||
boolean isOpen = false;
|
||||
try {
|
||||
isOpen = editor.getText ().charAt (editor.getCursor ().getLeft () - 2) == '<';
|
||||
} catch (Throwable th) {
|
||||
// ignored
|
||||
}
|
||||
if (isOpen && insertedContent.toString ().equals ("/")) {
|
||||
closeCurrentTag (editor.getText ().toString (), endLine, endColumn);
|
||||
}
|
||||
}
|
||||
|
||||
notifyModified ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeReplace (CodeEditor editor, CharSequence content) {
|
||||
}
|
||||
|
||||
private void notifySaved () {
|
||||
mBinding.editor.didSave ();
|
||||
|
||||
if (mModificationStateListener != null) {
|
||||
mModificationStateListener.onSaved (this);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyModified () {
|
||||
if (mModificationStateListener != null) {
|
||||
mModificationStateListener.onModified (this);
|
||||
}
|
||||
}
|
||||
|
||||
public interface FileOpenListener {
|
||||
void onOpenSuccessful (File file, String text);
|
||||
}
|
||||
|
||||
public interface ModificationStateListener {
|
||||
void onModified (EditorFragment editor);
|
||||
|
||||
void onSaved (EditorFragment editor);
|
||||
}
|
||||
}
|
||||
@@ -28,10 +28,11 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.transition.ChangeBounds;
|
||||
import androidx.transition.TransitionManager;
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder;
|
||||
@@ -41,11 +42,10 @@ import com.itsaky.androidide.project.AndroidProject;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.tasks.callables.FileTreeCallable;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import com.itsaky.androidide.views.CodeEditorView;
|
||||
import com.unnamed.b.atv.model.TreeNode;
|
||||
import com.unnamed.b.atv.view.AndroidTreeView;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
|
||||
@@ -65,6 +65,7 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static FileTreeFragment newInstance(AndroidProject project) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable("project", project);
|
||||
@@ -82,8 +83,7 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (getActivity() == null || getArguments() == null || !getArguments().containsKey("project")) return;
|
||||
mProject = getArguments().getParcelable("project");
|
||||
mProject = requireArguments ().getParcelable("project");
|
||||
|
||||
listProjectFiles();
|
||||
}
|
||||
@@ -102,12 +102,12 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN
|
||||
expandNode(node);
|
||||
} else {
|
||||
setLoading(node);
|
||||
listNode(node, true);
|
||||
listNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void listNode(TreeNode node, boolean wasClicked) {
|
||||
private void listNode (@NonNull TreeNode node) {
|
||||
node.getChildren().clear();
|
||||
node.setExpanded(false);
|
||||
new TaskExecutor().executeAsync(() -> {
|
||||
@@ -136,13 +136,13 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN
|
||||
}
|
||||
}
|
||||
|
||||
private void setLoading(TreeNode node) {
|
||||
private void setLoading(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).setLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChevron(TreeNode node) {
|
||||
private void updateChevron(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded());
|
||||
}
|
||||
@@ -180,9 +180,7 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN
|
||||
|
||||
final File gradleProps = Environment.GRADLE_PROPS;
|
||||
final File gradleHome = Environment.GRADLE_USER_HOME;
|
||||
final File root = StudioApp.getInstance().getRootDir().getParentFile();
|
||||
File projectDir = new File(mProject.getProjectPath());
|
||||
// projectDir = root;
|
||||
mRoot = TreeNode.root(projectDir);
|
||||
if(gradleHome.exists() && gradleHome.isDirectory()) {
|
||||
if(!gradleProps.exists())
|
||||
@@ -235,7 +233,7 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN
|
||||
}
|
||||
|
||||
public interface FileActionListener {
|
||||
public EditorFragment openFile(File file);
|
||||
public void showFileOptions(File file, TreeNode node);
|
||||
CodeEditorView openFile(File file);
|
||||
void showFileOptions(File file, TreeNode node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import com.itsaky.androidide.databinding.LayoutProgressSheetBinding;
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
public class ProgressSheet extends BaseBottomSheetFragment {
|
||||
@@ -39,6 +41,8 @@ public class ProgressSheet extends BaseBottomSheetFragment {
|
||||
private boolean subMessageEnabled = false;
|
||||
private boolean welcomTextEnabled = false;
|
||||
|
||||
private final Logger LOG = Logger.instance ("ProgressSheet");
|
||||
|
||||
@Override
|
||||
protected void bind(LinearLayout container) {
|
||||
binding = LayoutProgressSheetBinding.inflate(LayoutInflater.from(getContext()));
|
||||
@@ -56,14 +60,18 @@ public class ProgressSheet extends BaseBottomSheetFragment {
|
||||
RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) binding.message.getLayoutParams();
|
||||
try {
|
||||
p.removeRule(RelativeLayout.CENTER_VERTICAL);
|
||||
} catch (Throwable th) {}
|
||||
} catch (Throwable th) {
|
||||
LOG.error ("Unable to remove center_vertical rule.", th);
|
||||
}
|
||||
binding.message.setLayoutParams(p);
|
||||
} else {
|
||||
binding.subMessage.setVisibility(View.GONE);
|
||||
RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) binding.message.getLayoutParams();
|
||||
try {
|
||||
p.addRule(RelativeLayout.CENTER_VERTICAL);
|
||||
} catch (Throwable th) {}
|
||||
} catch (Throwable th) {
|
||||
LOG.error ("Unable to remove center_vertical rule.", th);
|
||||
}
|
||||
binding.message.setLayoutParams(p);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder;
|
||||
import com.itsaky.androidide.databinding.LayoutCreateFileJavaBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutDialogTextInputBinding;
|
||||
import com.itsaky.androidide.fragments.EditorFragment;
|
||||
import com.itsaky.androidide.fragments.sheets.OptionsListFragment;
|
||||
import com.itsaky.androidide.lsp.LSP;
|
||||
import com.itsaky.androidide.models.SheetOption;
|
||||
@@ -298,7 +297,7 @@ public class FileOptionsHandler extends IDEHandler implements OptionsListFragmen
|
||||
activity().getFileTreeFragment().listProjectFiles();
|
||||
}
|
||||
|
||||
EditorFragment frag = activity().getEditorForFile (f);
|
||||
final var frag = activity().getEditorForFile (f);
|
||||
if(frag != null) {
|
||||
activity().closeFile(activity().findIndexOfEditorByFile (frag.getFile ()));
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public abstract class IDEHandler {
|
||||
/**
|
||||
* An interface to communicate between a handler and its client
|
||||
*/
|
||||
public static interface Provider {
|
||||
public interface Provider {
|
||||
|
||||
/**
|
||||
* Called by handler to get a reference to {@link EditorActivity}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/************************************************************************************
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
@@ -16,15 +16,14 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
|
||||
*/
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
@@ -39,9 +38,7 @@ import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.DiagnosticsAdapter;
|
||||
import com.itsaky.androidide.adapters.SearchListAdapter;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.FragmentEditorBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutDiagnosticInfoBinding;
|
||||
import com.itsaky.androidide.fragments.EditorFragment;
|
||||
import com.itsaky.androidide.fragments.sheets.ProgressSheet;
|
||||
import com.itsaky.androidide.interfaces.EditorActivityProvider;
|
||||
import com.itsaky.androidide.models.DiagnosticGroup;
|
||||
@@ -50,11 +47,39 @@ import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.views.CodeEditorView;
|
||||
import com.itsaky.lsp.SemanticHighlight;
|
||||
import com.itsaky.lsp.services.IDELanguageClient;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import io.github.rosemoe.editor.text.Content;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditResponse;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.ConfigurationParams;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.LogTraceParams;
|
||||
import org.eclipse.lsp4j.MessageActionItem;
|
||||
import org.eclipse.lsp4j.MessageParams;
|
||||
import org.eclipse.lsp4j.ParameterInformation;
|
||||
import org.eclipse.lsp4j.ProgressParams;
|
||||
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.RegistrationParams;
|
||||
import org.eclipse.lsp4j.SetTraceParams;
|
||||
import org.eclipse.lsp4j.ShowDocumentParams;
|
||||
import org.eclipse.lsp4j.ShowDocumentResult;
|
||||
import org.eclipse.lsp4j.ShowMessageRequestParams;
|
||||
import org.eclipse.lsp4j.SignatureHelp;
|
||||
import org.eclipse.lsp4j.SignatureInformation;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.eclipse.lsp4j.UnregistrationParams;
|
||||
import org.eclipse.lsp4j.WorkDoneProgressCreateParams;
|
||||
import org.eclipse.lsp4j.WorkspaceFolder;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
@@ -62,35 +87,12 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.LogTraceParams;
|
||||
import org.eclipse.lsp4j.MessageParams;
|
||||
import org.eclipse.lsp4j.ParameterInformation;
|
||||
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.ShowDocumentParams;
|
||||
import org.eclipse.lsp4j.ShowDocumentResult;
|
||||
import org.eclipse.lsp4j.SignatureHelp;
|
||||
import org.eclipse.lsp4j.SignatureInformation;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.SetTraceParams;
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditResponse;
|
||||
import org.eclipse.lsp4j.WorkDoneProgressCreateParams;
|
||||
import org.eclipse.lsp4j.ProgressParams;
|
||||
import org.eclipse.lsp4j.ConfigurationParams;
|
||||
import org.eclipse.lsp4j.RegistrationParams;
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
|
||||
import org.eclipse.lsp4j.MessageActionItem;
|
||||
import org.eclipse.lsp4j.WorkspaceFolder;
|
||||
import org.eclipse.lsp4j.ShowMessageRequestParams;
|
||||
import org.eclipse.lsp4j.UnregistrationParams;
|
||||
|
||||
import io.github.rosemoe.editor.text.Content;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
|
||||
/**
|
||||
* AndroidIDE specific implementation of the LanguageClient
|
||||
@@ -99,13 +101,11 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
|
||||
protected static final Gson gson = new Gson();
|
||||
protected static final Logger LOG = Logger.instance("AbstractLanguageClient");
|
||||
public static final int DIAGNOSTIC_TRANSITION_DURATION = 80;
|
||||
|
||||
private final Map<File, List<Diagnostic>> diagnostics = new HashMap<>();
|
||||
private static IDELanguageClientImpl mInstance;
|
||||
|
||||
protected EditorActivityProvider activityProvider;
|
||||
private boolean isConnected;
|
||||
|
||||
private IDELanguageClientImpl (EditorActivityProvider provider) {
|
||||
setActivityProvider(provider);
|
||||
@@ -137,26 +137,19 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
this.activityProvider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we connected to the server?
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
protected EditorActivity activity() {
|
||||
if (activityProvider == null) return null;
|
||||
return activityProvider.provide();
|
||||
}
|
||||
|
||||
private EditorFragment findEditorByFile (File file) {
|
||||
private CodeEditorView findEditorByFile (File file) {
|
||||
return activity ().getEditorForFile (file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void semanticHighlights(SemanticHighlight highlights) {
|
||||
final File file = new File(URI.create(highlights.uri));
|
||||
final EditorFragment editor = findEditorByFile(file);
|
||||
final var editor = findEditorByFile(file);
|
||||
|
||||
if(editor != null) {
|
||||
editor.getEditor().setSemanticHighlights(highlights);
|
||||
@@ -200,13 +193,13 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
return;
|
||||
}
|
||||
|
||||
final EditorFragment frag = findEditorByFile(file);
|
||||
if(frag == null || frag.getBinding() == null) {
|
||||
final var frag = findEditorByFile(file);
|
||||
if(frag == null) {
|
||||
hideBottomDiagnosticView(file);
|
||||
return;
|
||||
}
|
||||
|
||||
final FragmentEditorBinding binding = frag.getBinding();
|
||||
final var binding = frag.getBinding();
|
||||
binding.diagnosticTextContainer.setVisibility(View.VISIBLE);
|
||||
binding.diagnosticText.setVisibility(View.VISIBLE);
|
||||
binding.diagnosticText.setClickable(false);
|
||||
@@ -219,13 +212,12 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
}
|
||||
|
||||
future.whenComplete((a, b) -> {
|
||||
final Throwable error = b;
|
||||
if(a == null || a.isEmpty()) {
|
||||
hideBottomDiagnosticView(file);
|
||||
return;
|
||||
}
|
||||
final List<CodeAction> actions = a.stream().filter(e -> e.isRight()).map (e -> e.getRight()).collect(Collectors.toList());
|
||||
if(actions == null || actions.isEmpty()) {
|
||||
final List<CodeAction> actions = a.stream().filter(Either::isRight).map (Either::getRight).collect(Collectors.toList());
|
||||
if(actions.isEmpty()) {
|
||||
hideBottomDiagnosticView(file);
|
||||
return;
|
||||
}
|
||||
@@ -236,9 +228,7 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
sb.append(diagnostic.getMessage());
|
||||
binding.diagnosticText.setText(sb);
|
||||
binding.diagnosticText.setClickable(true);
|
||||
binding.diagnosticText.setOnClickListener(v -> {
|
||||
showAvailableQuickfixes(editor, actions);
|
||||
});
|
||||
binding.diagnosticText.setOnClickListener(v -> showAvailableQuickfixes(editor, actions));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -256,7 +246,7 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
return;
|
||||
}
|
||||
|
||||
final EditorFragment frag = findEditorByFile(file);
|
||||
final var frag = findEditorByFile(file);
|
||||
if(frag == null || frag.getBinding() == null) {
|
||||
return;
|
||||
}
|
||||
@@ -277,7 +267,7 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
SignatureInformation info = signatureWithMostParams(signature);
|
||||
if(info == null) return;
|
||||
activity().getBinding().symbolText.setText(formatSignature(info, signature.getActiveParameter()));
|
||||
final EditorFragment frag = findEditorByFile(file);
|
||||
final var frag = findEditorByFile(file);
|
||||
if(frag != null) {
|
||||
final CodeEditor editor = frag.getEditor();
|
||||
final float[] cursor = editor.getCursorPosition();
|
||||
@@ -325,7 +315,8 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
* @param signature Signature information
|
||||
* @param paramIndex Currently active parameter index
|
||||
*/
|
||||
private CharSequence formatSignature(SignatureInformation signature, int paramIndex) {
|
||||
@NonNull
|
||||
private CharSequence formatSignature(@NonNull SignatureInformation signature, int paramIndex) {
|
||||
String name = signature.getLabel();
|
||||
name = name.substring(0, name.indexOf("("));
|
||||
|
||||
@@ -362,7 +353,7 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
diagnostics.put(file, params.getDiagnostics());
|
||||
activity().setDiagnosticsAdapter(newDiagnosticsAdapter());
|
||||
|
||||
EditorFragment editor;
|
||||
CodeEditorView editor;
|
||||
if((editor = findEditorByFile(file)) != null) {
|
||||
editor.setDiagnostics(params.getDiagnostics());
|
||||
}
|
||||
@@ -394,13 +385,13 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
if(loc == null || loc.getUri() == null || loc.getRange() == null) continue;
|
||||
final File file = new File(URI.create(loc.getUri()));
|
||||
if(!file.exists() || !file.isFile()) continue;
|
||||
EditorFragment frag = findEditorByFile(file);
|
||||
var frag = 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(
|
||||
Objects.requireNonNull (matches).add(
|
||||
new SearchResult(
|
||||
loc.getRange(),
|
||||
file,
|
||||
@@ -415,7 +406,7 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
);
|
||||
results.put(file, matches);
|
||||
} catch (Throwable th) {
|
||||
|
||||
LOG.error ("Failed to show file location", th);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,9 +425,9 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
|
||||
showLocations(locations
|
||||
.stream()
|
||||
.filter(l -> l != null)
|
||||
.map(l -> asLocation(l))
|
||||
.filter(l -> l != null)
|
||||
.filter(Objects::nonNull)
|
||||
.map(this::asLocation)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
@@ -462,14 +453,9 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
progress.show(activity().getSupportFragmentManager(), "quick_fix_progress");
|
||||
|
||||
new TaskExecutor().executeAsyncProvideError(() -> performCodeActionAsync(editor, action), (a, b) -> {
|
||||
final Boolean complete = a;
|
||||
final Throwable error = b;
|
||||
|
||||
progress.dismiss();
|
||||
|
||||
if(complete == null || error != null || !complete.booleanValue()) {
|
||||
if(a == null || b != null || !a) {
|
||||
StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -491,7 +477,7 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
File file = new File(URI.create(params.getUri()));
|
||||
if(file.exists() && file.isFile() && FileUtils.isUtf8(file)) {
|
||||
final Range range = params.getSelection();
|
||||
EditorFragment frag = activity().getEditorAtIndex (
|
||||
var frag = activity().getEditorAtIndex (
|
||||
activity().getBinding ().tabs.getSelectedTabPosition ());
|
||||
if(frag != null
|
||||
&& frag.getFile() != null
|
||||
@@ -554,13 +540,10 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
|
||||
builder.setTitle(R.string.msg_code_actions);
|
||||
builder.setItems(asArray(actions), (d, w) -> {
|
||||
final DialogInterface dialog = d;
|
||||
final int which = w;
|
||||
|
||||
dialog.dismiss();
|
||||
d.dismiss();
|
||||
hideDiagnostics();
|
||||
hideBottomDiagnosticView(editor.getFile());
|
||||
performCodeAction(editor, actions.get(which));
|
||||
performCodeAction(editor, actions.get(w));
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
@@ -589,7 +572,7 @@ public class IDELanguageClientImpl implements IDELanguageClient {
|
||||
// Edit is in the same editor which requested the code action
|
||||
editInEditor(editor, edit);
|
||||
} else {
|
||||
EditorFragment openedFrag = findEditorByFile(file);
|
||||
var openedFrag = findEditorByFile(file);
|
||||
|
||||
if(openedFrag != null && openedFrag.getEditor() != null) {
|
||||
// Edit is in another 'opened' file
|
||||
|
||||
@@ -67,13 +67,6 @@ public abstract class LSPClientLauncher extends Thread {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we connected to the server?
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return languageClient != null && languageClient.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual logic to start the server
|
||||
*/
|
||||
|
||||
@@ -1,467 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import com.itsaky.lsp.SemanticHighlight;
|
||||
import com.itsaky.lsp.SemanticHighlightsParams;
|
||||
import com.itsaky.lsp.services.IDELanguageServer;
|
||||
import com.itsaky.lsp.services.IDETextDocumentService;
|
||||
import com.itsaky.lsp.services.IDEWorkspaceService;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.CallHierarchyIncomingCall;
|
||||
import org.eclipse.lsp4j.CallHierarchyIncomingCallsParams;
|
||||
import org.eclipse.lsp4j.CallHierarchyItem;
|
||||
import org.eclipse.lsp4j.CallHierarchyOutgoingCall;
|
||||
import org.eclipse.lsp4j.CallHierarchyOutgoingCallsParams;
|
||||
import org.eclipse.lsp4j.CallHierarchyPrepareParams;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionParams;
|
||||
import org.eclipse.lsp4j.CodeLens;
|
||||
import org.eclipse.lsp4j.CodeLensParams;
|
||||
import org.eclipse.lsp4j.ColorInformation;
|
||||
import org.eclipse.lsp4j.ColorPresentation;
|
||||
import org.eclipse.lsp4j.ColorPresentationParams;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.CompletionParams;
|
||||
import org.eclipse.lsp4j.CreateFilesParams;
|
||||
import org.eclipse.lsp4j.DeclarationParams;
|
||||
import org.eclipse.lsp4j.DefinitionParams;
|
||||
import org.eclipse.lsp4j.DeleteFilesParams;
|
||||
import org.eclipse.lsp4j.DidChangeConfigurationParams;
|
||||
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
|
||||
import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams;
|
||||
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DocumentColorParams;
|
||||
import org.eclipse.lsp4j.DocumentFormattingParams;
|
||||
import org.eclipse.lsp4j.DocumentHighlight;
|
||||
import org.eclipse.lsp4j.DocumentHighlightParams;
|
||||
import org.eclipse.lsp4j.DocumentLink;
|
||||
import org.eclipse.lsp4j.DocumentLinkParams;
|
||||
import org.eclipse.lsp4j.DocumentOnTypeFormattingParams;
|
||||
import org.eclipse.lsp4j.DocumentRangeFormattingParams;
|
||||
import org.eclipse.lsp4j.DocumentSymbol;
|
||||
import org.eclipse.lsp4j.DocumentSymbolParams;
|
||||
import org.eclipse.lsp4j.ExecuteCommandParams;
|
||||
import org.eclipse.lsp4j.FoldingRange;
|
||||
import org.eclipse.lsp4j.FoldingRangeRequestParams;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.HoverParams;
|
||||
import org.eclipse.lsp4j.ImplementationParams;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.InitializedParams;
|
||||
import org.eclipse.lsp4j.LinkedEditingRangeParams;
|
||||
import org.eclipse.lsp4j.LinkedEditingRanges;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.Moniker;
|
||||
import org.eclipse.lsp4j.MonikerParams;
|
||||
import org.eclipse.lsp4j.PrepareRenameParams;
|
||||
import org.eclipse.lsp4j.PrepareRenameResult;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.ReferenceParams;
|
||||
import org.eclipse.lsp4j.RenameFilesParams;
|
||||
import org.eclipse.lsp4j.RenameParams;
|
||||
import org.eclipse.lsp4j.ResolveTypeHierarchyItemParams;
|
||||
import org.eclipse.lsp4j.SelectionRange;
|
||||
import org.eclipse.lsp4j.SelectionRangeParams;
|
||||
import org.eclipse.lsp4j.SemanticTokens;
|
||||
import org.eclipse.lsp4j.SemanticTokensDelta;
|
||||
import org.eclipse.lsp4j.SemanticTokensDeltaParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensRangeParams;
|
||||
import org.eclipse.lsp4j.SetTraceParams;
|
||||
import org.eclipse.lsp4j.SignatureHelp;
|
||||
import org.eclipse.lsp4j.SignatureHelpParams;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.eclipse.lsp4j.TypeDefinitionParams;
|
||||
import org.eclipse.lsp4j.TypeHierarchyItem;
|
||||
import org.eclipse.lsp4j.TypeHierarchyParams;
|
||||
import org.eclipse.lsp4j.WillSaveTextDocumentParams;
|
||||
import org.eclipse.lsp4j.WorkDoneProgressCancelParams;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.WorkspaceSymbolParams;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
import org.eclipse.lsp4j.services.TextDocumentService;
|
||||
import org.eclipse.lsp4j.services.WorkspaceService;
|
||||
|
||||
/**
|
||||
* Wraps a {@link LanguageServer} to an {@link IDELanguageServer }
|
||||
*/
|
||||
public class LanguageServerWrapper implements IDELanguageServer {
|
||||
|
||||
private final LanguageServer server;
|
||||
private final IDETextDocumentService textDocumentServiceWrapper;
|
||||
private final IDEWorkspaceService workspaceService;
|
||||
|
||||
public LanguageServerWrapper(LanguageServer server) {
|
||||
this.server = server;
|
||||
this.textDocumentServiceWrapper = new TextDocumentServiceWrapper(server.getTextDocumentService());
|
||||
this.workspaceService = new WorkspaceServiceWrapper(server.getWorkspaceService());
|
||||
}
|
||||
|
||||
public LanguageServer getServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
|
||||
return server.initialize(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialized(InitializedParams params) {
|
||||
server.initialized(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialized() {
|
||||
server.initialized();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Object> shutdown() {
|
||||
return server.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
server.exit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDETextDocumentService getTextDocumentService() {
|
||||
return textDocumentServiceWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDEWorkspaceService getWorkspaceService() {
|
||||
return workspaceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelProgress(WorkDoneProgressCancelParams params) {
|
||||
server.cancelProgress(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrace(SetTraceParams params) {
|
||||
}
|
||||
|
||||
class TextDocumentServiceWrapper implements IDETextDocumentService {
|
||||
|
||||
private final TextDocumentService service;
|
||||
|
||||
public TextDocumentServiceWrapper(TextDocumentService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<CallHierarchyIncomingCall>> callHierarchyIncomingCalls(CallHierarchyIncomingCallsParams p1) {
|
||||
return service.callHierarchyIncomingCalls(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<CallHierarchyOutgoingCall>> callHierarchyOutgoingCalls(CallHierarchyOutgoingCallsParams p1) {
|
||||
return service.callHierarchyOutgoingCalls(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams p1) {
|
||||
return service.codeAction(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams p1) {
|
||||
return service.codeLens(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<ColorPresentation>> colorPresentation(ColorPresentationParams p1) {
|
||||
return service.colorPresentation(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams p1) {
|
||||
return service.completion(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> declaration(DeclarationParams p1) {
|
||||
return service.declaration(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(DefinitionParams p1) {
|
||||
return service.definition(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didChange(DidChangeTextDocumentParams p1) {
|
||||
service.didChange(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didClose(DidCloseTextDocumentParams p1) {
|
||||
service.didClose(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didOpen(DidOpenTextDocumentParams p1) {
|
||||
service.didOpen(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didSave(DidSaveTextDocumentParams p1) {
|
||||
service.didSave(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<ColorInformation>> documentColor(DocumentColorParams p1) {
|
||||
return service.documentColor(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends DocumentHighlight>> documentHighlight(DocumentHighlightParams p1) {
|
||||
return service.documentHighlight(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<DocumentLink>> documentLink(DocumentLinkParams p1) {
|
||||
return service.documentLink(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DocumentLink> documentLinkResolve(DocumentLink p1) {
|
||||
return service.documentLinkResolve(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(DocumentSymbolParams p1) {
|
||||
return service.documentSymbol(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<FoldingRange>> foldingRange(FoldingRangeRequestParams p1) {
|
||||
return service.foldingRange(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams p1) {
|
||||
return service.formatting(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Hover> hover(HoverParams p1) {
|
||||
return service.hover(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> implementation(ImplementationParams p1) {
|
||||
return service.implementation(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LinkedEditingRanges> linkedEditingRange(LinkedEditingRangeParams p1) {
|
||||
return service.linkedEditingRange(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<Moniker>> moniker(MonikerParams p1) {
|
||||
return service.moniker(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends TextEdit>> onTypeFormatting(DocumentOnTypeFormattingParams p1) {
|
||||
return service.onTypeFormatting(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<CallHierarchyItem>> prepareCallHierarchy(CallHierarchyPrepareParams p1) {
|
||||
return service.prepareCallHierarchy(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Either<Range, PrepareRenameResult>> prepareRename(PrepareRenameParams p1) {
|
||||
return service.prepareRename(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams p1) {
|
||||
return service.rangeFormatting(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends Location>> references(ReferenceParams p1) {
|
||||
return service.references(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<WorkspaceEdit> rename(RenameParams p1) {
|
||||
return service.rename(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CodeAction> resolveCodeAction(CodeAction p1) {
|
||||
return service.resolveCodeAction(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CodeLens> resolveCodeLens(CodeLens p1) {
|
||||
return service.resolveCodeLens(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem p1) {
|
||||
return service.resolveCompletionItem(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TypeHierarchyItem> resolveTypeHierarchy(ResolveTypeHierarchyItemParams p1) {
|
||||
return service.resolveTypeHierarchy(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<SelectionRange>> selectionRange(SelectionRangeParams p1) {
|
||||
return service.selectionRange(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams p1) {
|
||||
return service.semanticTokensFull(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Either<SemanticTokens, SemanticTokensDelta>> semanticTokensFullDelta(SemanticTokensDeltaParams p1) {
|
||||
return service.semanticTokensFullDelta(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<SemanticTokens> semanticTokensRange(SemanticTokensRangeParams p1) {
|
||||
return service.semanticTokensRange(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<SignatureHelp> signatureHelp(SignatureHelpParams p1) {
|
||||
return service.signatureHelp(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> typeDefinition(TypeDefinitionParams p1) {
|
||||
return service.typeDefinition(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TypeHierarchyItem> typeHierarchy(TypeHierarchyParams p1) {
|
||||
return service.typeHierarchy(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void willSave(WillSaveTextDocumentParams p1) {
|
||||
service.willSave(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<TextEdit>> willSaveWaitUntil(WillSaveTextDocumentParams p1) {
|
||||
return service.willSaveWaitUntil(p1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<SemanticHighlight>> semanticHighlights(SemanticHighlightsParams params) {
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspaceServiceWrapper implements IDEWorkspaceService {
|
||||
|
||||
private final WorkspaceService service;
|
||||
|
||||
public WorkspaceServiceWrapper(WorkspaceService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
|
||||
return service.executeCommand(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends SymbolInformation>> symbol(WorkspaceSymbolParams params) {
|
||||
return service.symbol(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didChangeConfiguration(DidChangeConfigurationParams params) {
|
||||
service.didChangeConfiguration(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
|
||||
service.didChangeWatchedFiles(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) {
|
||||
service.didChangeWorkspaceFolders(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<WorkspaceEdit> willCreateFiles(CreateFilesParams params) {
|
||||
return service.willCreateFiles(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didCreateFiles(CreateFilesParams params) {
|
||||
service.didCreateFiles(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<WorkspaceEdit> willRenameFiles(RenameFilesParams params) {
|
||||
return service.willRenameFiles(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didRenameFiles(RenameFilesParams params) {
|
||||
service.didRenameFiles(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<WorkspaceEdit> willDeleteFiles(DeleteFilesParams params) {
|
||||
return service.willDeleteFiles(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didDeleteFiles(DeleteFilesParams params) {
|
||||
service.didDeleteFiles(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import org.eclipse.lsp4j.*;
|
||||
|
||||
public enum TokenModifiers {
|
||||
|
||||
PUBLIC("public"),
|
||||
PRIVATE("private"),
|
||||
PROTECTED("protected"),
|
||||
NATIVE("native"),
|
||||
DEFAULT("default"),
|
||||
STRICTFP("strictp"),
|
||||
SYNCHRONIZED("synchronized"),
|
||||
TRANSIENT("transient"),
|
||||
VOLATILE("volatile"),
|
||||
ABSTRACT(SemanticTokenModifiers.Abstract),
|
||||
STATIC(SemanticTokenModifiers.Static),
|
||||
FINAL(SemanticTokenModifiers.Readonly),
|
||||
|
||||
DEPRECATED(SemanticTokenModifiers.Deprecated);
|
||||
|
||||
public final int bitmask = 1 << ordinal();
|
||||
public final int invertBitmask = ~bitmask;
|
||||
|
||||
private String name;
|
||||
|
||||
private TokenModifiers (String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import org.eclipse.lsp4j.SemanticTokenTypes;
|
||||
import io.github.rosemoe.editor.widget.EditorColorScheme;
|
||||
|
||||
public enum TokenType {
|
||||
/**
|
||||
* Order of the member declaration must be preseved!
|
||||
*/
|
||||
NAMESPACE(SemanticTokenTypes.Namespace, EditorColorScheme.PACKAGE_NAME),
|
||||
CLASS(SemanticTokenTypes.Class, EditorColorScheme.TYPE_NAME),
|
||||
INTERFACE(SemanticTokenTypes.Interface, EditorColorScheme.INTERFACE),
|
||||
ENUM(SemanticTokenTypes.Enum, EditorColorScheme.ENUM),
|
||||
ENUM_MEMBER(SemanticTokenTypes.EnumMember, EditorColorScheme.STATIC_FIELD),
|
||||
TYPE(SemanticTokenTypes.Type, EditorColorScheme.TYPE_NAME),
|
||||
TYPE_PARAMETER(SemanticTokenTypes.TypeParameter, EditorColorScheme.TYPE_PARAM),
|
||||
METHOD(SemanticTokenTypes.Method, EditorColorScheme.METHOD_DECLARATION),
|
||||
PROPERTY(SemanticTokenTypes.Property, EditorColorScheme.FIELD),
|
||||
VARIABLE(SemanticTokenTypes.Variable, EditorColorScheme.LOCAL_VARIABLE),
|
||||
PARAMETER(SemanticTokenTypes.Parameter, EditorColorScheme.PARAMETER),
|
||||
MODIFIER(SemanticTokenTypes.Modifier, EditorColorScheme.KEYWORD),
|
||||
KEYWORD(SemanticTokenTypes.Keyword, EditorColorScheme.KEYWORD),
|
||||
|
||||
ANNOTATION("annotation", EditorColorScheme.ANNOTATION),
|
||||
ANNOTATION_TYPE("annotationType", EditorColorScheme.TYPE_NAME),
|
||||
|
||||
UNKNOWN("unknown", EditorColorScheme.TEXT_NORMAL);
|
||||
|
||||
private String name;
|
||||
private int editorType;
|
||||
|
||||
private TokenType (String name, int editorType) {
|
||||
this.name = name;
|
||||
this.editorType = editorType;
|
||||
}
|
||||
|
||||
public int getEditorType() {
|
||||
return editorType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.models;
|
||||
|
||||
import com.itsaky.androidide.fragments.EditorFragment;
|
||||
import com.itsaky.androidide.project.AndroidProject;
|
||||
|
||||
import org.eclipse.lsp4j.Range;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Represents a file that has been opened in the editor,
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class OpenedFile {
|
||||
|
||||
private final File file;
|
||||
private final Range selection;
|
||||
|
||||
public OpenedFile (File file, Range selection) {
|
||||
this.file = file;
|
||||
this.selection = selection;
|
||||
}
|
||||
|
||||
public File getFile () {
|
||||
return file;
|
||||
}
|
||||
|
||||
public Range getSelection () {
|
||||
return selection;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
return this.file.getAbsolutePath ().hashCode ();
|
||||
}
|
||||
|
||||
public EditorFragment createFragment (final EditorFragment.FileOpenListener fileOpenListener,
|
||||
EditorFragment.ModificationStateListener modificationStateListener) {
|
||||
return EditorFragment.newInstance (this.file, this.selection)
|
||||
.setModificationStateListener (modificationStateListener)
|
||||
.setFileOpenListener (fileOpenListener);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.services;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.models.LogLine;
|
||||
import com.itsaky.androidide.shell.ShellServer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class IDELogService {
|
||||
|
||||
private final ShellServer shell;
|
||||
private final List<LogReceiver.LogListener> logListeners;
|
||||
|
||||
private boolean started;
|
||||
|
||||
public IDELogService () {
|
||||
this.shell = StudioApp.getInstance ().newShell (this::output);
|
||||
this.logListeners = new ArrayList<> ();
|
||||
}
|
||||
|
||||
public void start () {
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.shell.bgAppend ("logcat -v threadtime");
|
||||
started = true;
|
||||
}
|
||||
|
||||
public boolean isStarted () {
|
||||
return started;
|
||||
}
|
||||
|
||||
public void addLogListener (LogReceiver.LogListener listener) {
|
||||
this.logListeners.add (Objects.requireNonNull (listener));
|
||||
}
|
||||
|
||||
public void removeLogListener (LogReceiver.LogListener listener) {
|
||||
this.logListeners.remove (Objects.requireNonNull (listener));
|
||||
}
|
||||
|
||||
private void output (@NonNull String log) {
|
||||
final var line = LogLine.forLogString (log);
|
||||
for (final var listener : this.logListeners) {
|
||||
listener.appendLogLine (line);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import com.itsaky.androidide.models.LogLine;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -35,6 +37,8 @@ public class LogReceiver extends BroadcastReceiver {
|
||||
public static final String APPEND_LOG = "com.itsaky.androidide.logs.APPEND_LOG";
|
||||
public static final String EXTRA_LINE = "log_line";
|
||||
|
||||
private final Logger LOG = Logger.instance ("LogReceiver");
|
||||
|
||||
public LogReceiver setLogListener(LogListener listener) {
|
||||
this.listener = listener;
|
||||
return this;
|
||||
@@ -47,7 +51,9 @@ public class LogReceiver extends BroadcastReceiver {
|
||||
if(line == null) return;
|
||||
try {
|
||||
sendLogLine(line);
|
||||
} catch (Throwable th) {}
|
||||
} catch (Throwable th) {
|
||||
LOG.error ("Unable to parse log line from app.", th);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +64,7 @@ public class LogReceiver extends BroadcastReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
public static interface LogListener {
|
||||
public void appendLogLine(LogLine line);
|
||||
public interface LogListener {
|
||||
void appendLogLine (LogLine line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ public class TaskExecutor {
|
||||
private final Executor executor = Executors.newSingleThreadExecutor();
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private final Logger LOG = Logger.instance ("TaskExecutor");
|
||||
|
||||
public interface Callback<R> {
|
||||
void complete(R result);
|
||||
}
|
||||
@@ -49,7 +51,9 @@ public class TaskExecutor {
|
||||
handler.post(() -> {
|
||||
callback.complete(result);
|
||||
});
|
||||
} catch (Throwable th) {}
|
||||
} catch (Throwable th) {
|
||||
LOG.error ("Callable task was not able to finish", th);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,6 +65,7 @@ public class TaskExecutor {
|
||||
try {
|
||||
result = callable.call();
|
||||
} catch (Throwable th) {
|
||||
LOG.error ("Callable task was not able to finish", th);
|
||||
error = th;
|
||||
}
|
||||
final R resultCopied = result;
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
|
||||
package com.itsaky.androidide.tasks.callables;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class ReadFileTask implements Callable<String> {
|
||||
|
||||
private final File file;
|
||||
|
||||
public ReadFileTask(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call() throws Exception {
|
||||
return FileIOUtils.readFile2String(file);
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.internal.Streams;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* Adapts values whose runtime type may differ from their declaration type. This
|
||||
* is necessary when a field's type is not the same type that GSON should create
|
||||
* when deserializing that field. For example, consider these types:
|
||||
* <pre> {@code
|
||||
* abstract class Shape {
|
||||
* int x;
|
||||
* int y;
|
||||
* }
|
||||
* class Circle extends Shape {
|
||||
* int radius;
|
||||
* }
|
||||
* class Rectangle extends Shape {
|
||||
* int width;
|
||||
* int height;
|
||||
* }
|
||||
* class Diamond extends Shape {
|
||||
* int width;
|
||||
* int height;
|
||||
* }
|
||||
* class Drawing {
|
||||
* Shape bottomShape;
|
||||
* Shape topShape;
|
||||
* }
|
||||
* }</pre>
|
||||
* <p>Without additional type information, the serialized JSON is ambiguous. Is
|
||||
* the bottom shape in this drawing a rectangle or a diamond? <pre> {@code
|
||||
* {
|
||||
* "bottomShape": {
|
||||
* "width": 10,
|
||||
* "height": 5,
|
||||
* "x": 0,
|
||||
* "y": 0
|
||||
* },
|
||||
* "topShape": {
|
||||
* "radius": 2,
|
||||
* "x": 4,
|
||||
* "y": 1
|
||||
* }
|
||||
* }}</pre>
|
||||
* This class addresses this problem by adding type information to the
|
||||
* serialized JSON and honoring that type information when the JSON is
|
||||
* deserialized: <pre> {@code
|
||||
* {
|
||||
* "bottomShape": {
|
||||
* "type": "Diamond",
|
||||
* "width": 10,
|
||||
* "height": 5,
|
||||
* "x": 0,
|
||||
* "y": 0
|
||||
* },
|
||||
* "topShape": {
|
||||
* "type": "Circle",
|
||||
* "radius": 2,
|
||||
* "x": 4,
|
||||
* "y": 1
|
||||
* }
|
||||
* }}</pre>
|
||||
* Both the type field name ({@code "type"}) and the type labels ({@code
|
||||
* "Rectangle"}) are configurable.
|
||||
*
|
||||
* <h3>Registering Types</h3>
|
||||
* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field
|
||||
* name to the {@link #of} factory method. If you don't supply an explicit type
|
||||
* field name, {@code "type"} will be used. <pre> {@code
|
||||
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory
|
||||
* = RuntimeTypeAdapterFactory.of(Shape.class, "type");
|
||||
* }</pre>
|
||||
* Next register all of your subtypes. Every subtype must be explicitly
|
||||
* registered. This protects your application from injection attacks. If you
|
||||
* don't supply an explicit type label, the type's simple name will be used.
|
||||
* <pre> {@code
|
||||
* shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
|
||||
* shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
|
||||
* shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
|
||||
* }</pre>
|
||||
* Finally, register the type adapter factory in your application's GSON builder:
|
||||
* <pre> {@code
|
||||
* Gson gson = new GsonBuilder()
|
||||
* .registerTypeAdapterFactory(shapeAdapterFactory)
|
||||
* .create();
|
||||
* }</pre>
|
||||
* Like {@code GsonBuilder}, this API supports chaining: <pre> {@code
|
||||
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
|
||||
* .registerSubtype(Rectangle.class)
|
||||
* .registerSubtype(Circle.class)
|
||||
* .registerSubtype(Diamond.class);
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Serialization and deserialization</h3>
|
||||
* In order to serialize and deserialize a polymorphic object,
|
||||
* you must specify the base type explicitly.
|
||||
* <pre> {@code
|
||||
* Diamond diamond = new Diamond();
|
||||
* String json = gson.toJson(diamond, Shape.class);
|
||||
* }</pre>
|
||||
* And then:
|
||||
* <pre> {@code
|
||||
* Shape shape = gson.fromJson(json, Shape.class);
|
||||
* }</pre>
|
||||
*/
|
||||
public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||
private final Class<?> baseType;
|
||||
private final String typeFieldName;
|
||||
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
|
||||
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
|
||||
private final boolean maintainType;
|
||||
|
||||
private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName, boolean maintainType) {
|
||||
if (typeFieldName == null || baseType == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
this.baseType = baseType;
|
||||
this.typeFieldName = typeFieldName;
|
||||
this.maintainType = maintainType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new runtime type adapter using for {@code baseType} using {@code
|
||||
* typeFieldName} as the type field name. Type field names are case sensitive.
|
||||
* {@code maintainType} flag decide if the type will be stored in pojo or not.
|
||||
*/
|
||||
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName, boolean maintainType) {
|
||||
return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName, maintainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new runtime type adapter using for {@code baseType} using {@code
|
||||
* typeFieldName} as the type field name. Type field names are case sensitive.
|
||||
*/
|
||||
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
|
||||
return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
|
||||
* the type field name.
|
||||
*/
|
||||
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
|
||||
return new RuntimeTypeAdapterFactory<T>(baseType, "type", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers {@code type} identified by {@code label}. Labels are case
|
||||
* sensitive.
|
||||
*
|
||||
* @throws IllegalArgumentException if either {@code type} or {@code label}
|
||||
* have already been registered on this type adapter.
|
||||
*/
|
||||
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
|
||||
if (type == null || label == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
|
||||
throw new IllegalArgumentException("types and labels must be unique");
|
||||
}
|
||||
labelToSubtype.put(label, type);
|
||||
subtypeToLabel.put(type, label);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers {@code type} identified by its {@link Class#getSimpleName simple
|
||||
* name}. Labels are case sensitive.
|
||||
*
|
||||
* @throws IllegalArgumentException if either {@code type} or its simple name
|
||||
* have already been registered on this type adapter.
|
||||
*/
|
||||
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
|
||||
return registerSubtype(type, type.getSimpleName());
|
||||
}
|
||||
|
||||
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
|
||||
if (type.getRawType() != baseType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, TypeAdapter<?>> labelToDelegate
|
||||
= new LinkedHashMap<String, TypeAdapter<?>>();
|
||||
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate
|
||||
= new LinkedHashMap<Class<?>, TypeAdapter<?>>();
|
||||
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
|
||||
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
|
||||
labelToDelegate.put(entry.getKey(), delegate);
|
||||
subtypeToDelegate.put(entry.getValue(), delegate);
|
||||
}
|
||||
|
||||
return new TypeAdapter<R>() {
|
||||
@Override public R read(JsonReader in) throws IOException {
|
||||
JsonElement jsonElement = Streams.parse(in);
|
||||
JsonElement labelJsonElement;
|
||||
if (maintainType) {
|
||||
labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);
|
||||
} else {
|
||||
labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
|
||||
}
|
||||
|
||||
if (labelJsonElement == null) {
|
||||
throw new JsonParseException("cannot deserialize " + baseType
|
||||
+ " because it does not define a field named " + typeFieldName);
|
||||
}
|
||||
String label = labelJsonElement.getAsString();
|
||||
@SuppressWarnings("unchecked") // registration requires that subtype extends T
|
||||
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
|
||||
if (delegate == null) {
|
||||
throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
|
||||
+ label + "; did you forget to register a subtype?");
|
||||
}
|
||||
return delegate.fromJsonTree(jsonElement);
|
||||
}
|
||||
|
||||
@Override public void write(JsonWriter out, R value) throws IOException {
|
||||
Class<?> srcType = value.getClass();
|
||||
String label = subtypeToLabel.get(srcType);
|
||||
@SuppressWarnings("unchecked") // registration requires that subtype extends T
|
||||
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
|
||||
if (delegate == null) {
|
||||
throw new JsonParseException("cannot serialize " + srcType.getName()
|
||||
+ "; did you forget to register a subtype?");
|
||||
}
|
||||
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
|
||||
|
||||
if (maintainType) {
|
||||
Streams.write(jsonObject, out);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject clone = new JsonObject();
|
||||
|
||||
if (jsonObject.has(typeFieldName)) {
|
||||
throw new JsonParseException("cannot serialize " + srcType.getName()
|
||||
+ " because it already defines a field named " + typeFieldName);
|
||||
}
|
||||
clone.add(typeFieldName, new JsonPrimitive(label));
|
||||
|
||||
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
|
||||
clone.add(e.getKey(), e.getValue());
|
||||
}
|
||||
Streams.write(clone, out);
|
||||
}
|
||||
}.nullSafe();
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@
|
||||
package com.itsaky.androidide.utils;
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.fragments.EditorFragment;
|
||||
import com.itsaky.androidide.views.SymbolInputView.Symbol;
|
||||
import java.io.File;
|
||||
|
||||
@@ -30,11 +29,11 @@ public class Symbols {
|
||||
|
||||
public static Symbol[] forFile(File file) {
|
||||
if (file.isFile()) {
|
||||
if (file.getName().endsWith(EditorFragment.EXT_JAVA)
|
||||
|| file.getName().endsWith(EditorFragment.EXT_GRADLE))
|
||||
if (file.getName().endsWith(".java")
|
||||
|| file.getName().endsWith(".gradle"))
|
||||
return javaSymbols();
|
||||
|
||||
if (file.getName().endsWith(EditorFragment.EXT_XML))
|
||||
if (file.getName().endsWith(".xml"))
|
||||
return xmlSymbols();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.itsaky.androidide.viewmodel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
@@ -24,7 +25,6 @@ import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.itsaky.androidide.models.OpenedFile;
|
||||
import com.itsaky.androidide.project.AndroidProject;
|
||||
import com.itsaky.androidide.project.IDEProject;
|
||||
|
||||
@@ -38,7 +38,7 @@ import java.util.Objects;
|
||||
*/
|
||||
public class EditorViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData <List<OpenedFile>> mFiles = new MutableLiveData<> (new ArrayList<> ());
|
||||
private final MutableLiveData <List<File>> mFiles = new MutableLiveData<> (new ArrayList<> ());
|
||||
private final MutableLiveData <AndroidProject> mProject = new MutableLiveData<> (null);
|
||||
private final MutableLiveData <IDEProject> mIDEProject = new MutableLiveData<> (null);
|
||||
|
||||
@@ -87,7 +87,7 @@ public class EditorViewModel extends ViewModel {
|
||||
* Add the given file to the list of opened files.
|
||||
* @param file The file that has been opened.
|
||||
*/
|
||||
public void addFile (final OpenedFile file) {
|
||||
public void addFile (final File file) {
|
||||
final var files = mFiles.getValue ();
|
||||
Objects.requireNonNull (files).add (file);
|
||||
mFiles.setValue (files);
|
||||
@@ -103,12 +103,17 @@ public class EditorViewModel extends ViewModel {
|
||||
mFiles.setValue (files);
|
||||
}
|
||||
|
||||
public void removeAllFiles () {
|
||||
mFiles.setValue (new ArrayList<> ());
|
||||
setCurrentFile (-1, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the opened file at the given index.
|
||||
* @param index The index of the file.
|
||||
* @return The file at the given index.
|
||||
*/
|
||||
public OpenedFile getOpenedFile (final int index) {
|
||||
public File getOpenedFile (final int index) {
|
||||
return Objects.requireNonNull (mFiles.getValue ()).get (index);
|
||||
}
|
||||
|
||||
@@ -125,7 +130,27 @@ public class EditorViewModel extends ViewModel {
|
||||
* @param lifecycleOwner The lifecycle owner.
|
||||
* @param observer The observer.
|
||||
*/
|
||||
public void observeFiles (LifecycleOwner lifecycleOwner, Observer<List<OpenedFile>> observer) {
|
||||
public void observeFiles (LifecycleOwner lifecycleOwner, Observer<List<File>> observer) {
|
||||
this.mFiles.observe (lifecycleOwner, observer);
|
||||
}
|
||||
|
||||
public int getCurrentFileIndex () {
|
||||
if (mCurrentFile.getValue () == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return mCurrentFile.getValue ().first;
|
||||
}
|
||||
|
||||
public File getCurrentFile () {
|
||||
if (mCurrentFile.getValue () == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mCurrentFile.getValue ().second;
|
||||
}
|
||||
|
||||
public void setCurrentFile (final int index, @Nullable final File file) {
|
||||
mCurrentFile.setValue (Pair.create (index, file));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.views;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.itsaky.androidide.adapters.CompletionListAdapter;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.LayoutCodeEditorBinding;
|
||||
import com.itsaky.androidide.language.groovy.GroovyLanguage;
|
||||
import com.itsaky.androidide.language.java.JavaLanguage;
|
||||
import com.itsaky.androidide.language.xml.XMLLanguage;
|
||||
import com.itsaky.androidide.lexers.xml.XMLLexer;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.models.ConstantsBridge;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.github.rosemoe.editor.interfaces.EditorEventListener;
|
||||
import io.github.rosemoe.editor.langs.EmptyLanguage;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
|
||||
/**
|
||||
* A view that handles opened code editors.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@SuppressLint("ViewConstructor") // This view is always dynamically created.
|
||||
public class CodeEditorView extends FrameLayout {
|
||||
|
||||
private final File file;
|
||||
private final LayoutCodeEditorBinding binding;
|
||||
|
||||
private boolean isModified;
|
||||
private boolean isFirstCreate;
|
||||
|
||||
private static final Logger LOG = Logger.instance ("CodeEditorView");
|
||||
|
||||
private final EditorEventListener mEventListener = new EditorEventListener () {
|
||||
@Override
|
||||
public boolean onRequestFormat (CodeEditor editor, boolean async) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFormatFail (CodeEditor editor, Throwable cause) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFormatSucceed (CodeEditor editor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTextSet (CodeEditor editor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeReplace (CodeEditor editor, CharSequence content) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetSelection (int startLine, int startCol, int endLine, int endCol) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterDelete (CodeEditor editor, CharSequence content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) {
|
||||
isModified = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterInsert (CodeEditor editor, CharSequence content, int startLine, int startColumn, int endLine, int endColumn, CharSequence insertedContent) {
|
||||
isModified = true;
|
||||
if (file.getName ().endsWith (".xml")) {
|
||||
boolean isOpen = false;
|
||||
try {
|
||||
isOpen = editor.getText ().charAt (editor.getCursor ().getLeft () - 2) == '<';
|
||||
} catch (Throwable th) {
|
||||
LOG.error (th);
|
||||
}
|
||||
|
||||
if (isOpen && insertedContent.toString ().equals ("/")) {
|
||||
closeCurrentTag (editor.getText ().toString (), endLine, endColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public CodeEditorView (@NonNull Context context, @NonNull File file, final @NonNull Range selection) {
|
||||
super (context);
|
||||
this.file = file;
|
||||
this.isModified = false;
|
||||
this.isFirstCreate = true;
|
||||
|
||||
this.binding = LayoutCodeEditorBinding.inflate (LayoutInflater.from (context));
|
||||
this.binding.editor.setOverScrollEnabled (false);
|
||||
this.binding.editor.setTypefaceText (TypefaceUtils.jetbrainsMono ());
|
||||
this.binding.editor.setTextActionMode (CodeEditor.TextActionMode.ACTION_MODE);
|
||||
this.binding.editor.setHighlightCurrentBlock (true);
|
||||
this.binding.editor.setEventListener (mEventListener);
|
||||
this.binding.editor.setAutoCompletionOnComposing (true);
|
||||
this.binding.editor.setAutoCompletionItemAdapter (new CompletionListAdapter ());
|
||||
this.binding.editor.setLineColorsEnabled (true);
|
||||
this.binding.editor.setDividerWidth (SizeUtils.dp2px (1));
|
||||
this.binding.editor.setColorScheme (new SchemeAndroidIDE ());
|
||||
|
||||
this.binding.diagnosticTextContainer.setVisibility (GONE);
|
||||
|
||||
removeAllViews ();
|
||||
addView (this.binding.getRoot ());
|
||||
|
||||
CompletableFuture.runAsync (() -> {
|
||||
final var contents = FileIOUtils.readFile2String (file);
|
||||
binding.editor.post (() -> {
|
||||
binding.editor.setText (contents);
|
||||
postRead ();
|
||||
if (LSPUtils.isEqual (selection.getStart (), selection.getEnd ())) {
|
||||
getEditor ().setSelection (selection.getStart ().getLine (), selection.getStart ().getCharacter ());
|
||||
} else {
|
||||
getEditor ().setSelectionRegion (
|
||||
selection.getStart ().getLine (),
|
||||
selection.getStart ().getCharacter (),
|
||||
selection.getEnd ().getLine (),
|
||||
selection.getEnd ().getCharacter ()
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public boolean save () {
|
||||
final var text = getText ();
|
||||
final var saved = FileIOUtils.writeFileFromString (getFile (), text);
|
||||
notifySaved ();
|
||||
isModified = false;
|
||||
return saved;
|
||||
}
|
||||
|
||||
public Editor getEditor () {
|
||||
return binding.editor;
|
||||
}
|
||||
|
||||
public LayoutCodeEditorBinding getBinding () {
|
||||
return binding;
|
||||
}
|
||||
|
||||
public File getFile () {
|
||||
return file;
|
||||
}
|
||||
|
||||
public String getText () {
|
||||
return binding.editor.getText ().toString ();
|
||||
}
|
||||
|
||||
public boolean isModified () {
|
||||
return isModified;
|
||||
}
|
||||
|
||||
public void onPause () {
|
||||
CompletableFuture.runAsync (this::save);
|
||||
}
|
||||
|
||||
public void onResume () {
|
||||
configureEditorIfNeeded ();
|
||||
}
|
||||
|
||||
public void undo () {
|
||||
if (binding.editor.canUndo ()) {
|
||||
binding.editor.undo ();
|
||||
}
|
||||
}
|
||||
|
||||
public void redo () {
|
||||
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 setDiagnostics (List<Diagnostic> diagnostics) {
|
||||
if (diagnostics == null) {
|
||||
LOG.info ("Clearing diagnostics of code editor", "file:", file);
|
||||
binding.editor.getEditorLanguage ().getAnalyzer ().updateDiagnostics (new HashMap<> ());
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Integer, Map<Integer, Diagnostic>> map = new HashMap<> ();
|
||||
for (int i = 0; i < diagnostics.size (); i++) {
|
||||
final Diagnostic d = diagnostics.get (i);
|
||||
if (d == null) {
|
||||
continue;
|
||||
}
|
||||
final Range range = d.getRange ();
|
||||
final int line = range.getStart ().getLine ();
|
||||
final int column = range.getStart ().getCharacter ();
|
||||
Map<Integer, Diagnostic> mappedByColumn = map.get (line);
|
||||
if (mappedByColumn == null) {
|
||||
mappedByColumn = new HashMap<> ();
|
||||
}
|
||||
mappedByColumn.put (column, d);
|
||||
map.put (line, mappedByColumn);
|
||||
}
|
||||
|
||||
binding.editor.getEditorLanguage ().getAnalyzer ().updateDiagnostics (map);
|
||||
}
|
||||
|
||||
protected void postRead () {
|
||||
if (file.isFile () && file.getName ().endsWith (".java")) {
|
||||
binding.editor.setEditorLanguage (new JavaLanguage (getFile ()));
|
||||
} else if (file.isFile () && file.getName ().endsWith (".xml")) {
|
||||
binding.editor.setEditorLanguage (new XMLLanguage (getFile ()));
|
||||
} else if (file.isFile () && file.getName ().endsWith (".gradle")) {
|
||||
binding.editor.setEditorLanguage (new GroovyLanguage (getFile ()));
|
||||
} else {
|
||||
binding.editor.setEditorLanguage (new EmptyLanguage ());
|
||||
}
|
||||
|
||||
// File must be set only after setting the language
|
||||
// This will make sure that textDocument/didOpen is sent
|
||||
binding.editor.setFile (getFile ());
|
||||
}
|
||||
|
||||
private void configureEditorIfNeeded () {
|
||||
boolean sizeChanged = isFirstCreate || ConstantsBridge.EDITORPREF_SIZE_CHANGED;
|
||||
boolean flagsChanged = isFirstCreate || ConstantsBridge.EDITORPREF_FLAGS_CHANGED;
|
||||
boolean drawHexChanged = isFirstCreate || ConstantsBridge.EDITORPREF_DRAW_HEX_CHANGED;
|
||||
final PreferenceManager prefs = StudioApp.getInstance ().getPrefManager ();
|
||||
|
||||
if (sizeChanged) {
|
||||
float textSize = prefs.getFloat (PreferenceManager.KEY_EDITOR_FONT_SIZE);
|
||||
if (textSize < 6 || textSize > 32) {
|
||||
textSize = 14;
|
||||
}
|
||||
|
||||
binding.editor.setTextSize (textSize);
|
||||
ConstantsBridge.EDITORPREF_SIZE_CHANGED = false;
|
||||
}
|
||||
|
||||
if (flagsChanged) {
|
||||
int flags = 0;
|
||||
if (prefs.getBoolean (PreferenceManager.KEY_EDITORFLAG_WS_LEADING, true)) {
|
||||
flags |= CodeEditor.FLAG_DRAW_WHITESPACE_LEADING;
|
||||
}
|
||||
|
||||
if (prefs.getBoolean (PreferenceManager.KEY_EDITORFLAG_WS_TRAILING, false)) {
|
||||
flags |= CodeEditor.FLAG_DRAW_WHITESPACE_TRAILING;
|
||||
}
|
||||
|
||||
if (prefs.getBoolean (PreferenceManager.KEY_EDITORFLAG_WS_INNER, true)) {
|
||||
flags |= CodeEditor.FLAG_DRAW_WHITESPACE_INNER;
|
||||
}
|
||||
|
||||
if (prefs.getBoolean (PreferenceManager.KEY_EDITORFLAG_WS_EMPTY_LINE, true)) {
|
||||
flags |= CodeEditor.FLAG_DRAW_WHITESPACE_FOR_EMPTY_LINE;
|
||||
}
|
||||
|
||||
if (prefs.getBoolean (PreferenceManager.KEY_EDITORFLAG_LINE_BREAK, true)) {
|
||||
flags |= CodeEditor.FLAG_DRAW_LINE_SEPARATOR;
|
||||
}
|
||||
|
||||
binding.editor.setNonPrintablePaintingFlags (flags);
|
||||
ConstantsBridge.EDITORPREF_FLAGS_CHANGED = false;
|
||||
}
|
||||
|
||||
if (drawHexChanged) {
|
||||
binding.editor.setLineColorsEnabled (prefs.getBoolean (PreferenceManager.KEY_EDITOR_DRAW_HEX, true));
|
||||
ConstantsBridge.EDITORPREF_DRAW_HEX_CHANGED = false;
|
||||
}
|
||||
|
||||
isFirstCreate = false;
|
||||
}
|
||||
|
||||
private void closeCurrentTag (String text, int line, int col) {
|
||||
try {
|
||||
XMLLexer lexer = new XMLLexer (CharStreams.fromReader (new StringReader (text)));
|
||||
Token token;
|
||||
boolean wasSlash = false, wasOpen = false;
|
||||
ArrayList<String> currentNames = new ArrayList<> ();
|
||||
while (((token = lexer.nextToken ()) != null && token.getType () != token.EOF)) {
|
||||
final int type = token.getType ();
|
||||
if (type == XMLLexer.OPEN) {
|
||||
wasOpen = true;
|
||||
} else if (type == XMLLexer.Name) {
|
||||
if (wasOpen && wasSlash && currentNames.size () > 0) {
|
||||
currentNames.remove (0);
|
||||
} else if (wasOpen) {
|
||||
currentNames.add (0, token.getText ());
|
||||
wasOpen = false;
|
||||
}
|
||||
} else if (type == XMLLexer.OPEN_SLASH) {
|
||||
int l = token.getLine () - 1;
|
||||
int c = token.getCharPositionInLine ();
|
||||
if (l == line && c == col) {
|
||||
break;
|
||||
} else if (currentNames.size () > 0) {
|
||||
currentNames.remove (0);
|
||||
}
|
||||
} else if (type == XMLLexer.SLASH_CLOSE
|
||||
|| type == XMLLexer.SPECIAL_CLOSE) {
|
||||
if (currentNames.size () > 0 && token.getText ().trim ().endsWith ("/>")) {
|
||||
currentNames.remove (0);
|
||||
}
|
||||
} else if (type == XMLLexer.SLASH) {
|
||||
wasSlash = true;
|
||||
} else {
|
||||
wasOpen = wasSlash = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentNames.size () > 0) {
|
||||
binding.editor.getText ().insert (line, col + 2, currentNames.get (0));
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
LOG.error ("Unable to close current tag", th);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifySaved () {
|
||||
binding.editor.didSave ();
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
|
||||
package com.itsaky.androidide.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.LinearLayout;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Color;
|
||||
|
||||
public class TooltipLayout extends LinearLayout {
|
||||
|
||||
private final float dp8;
|
||||
private final Path path;
|
||||
private final Paint paint;
|
||||
|
||||
public TooltipLayout (Context ctx) {
|
||||
this(ctx, null);
|
||||
}
|
||||
|
||||
public TooltipLayout (Context ctx, AttributeSet attrs) {
|
||||
this(ctx, attrs, 0);
|
||||
}
|
||||
|
||||
public TooltipLayout (Context ctx, AttributeSet attrs, int style) {
|
||||
super(ctx, attrs, style);
|
||||
|
||||
dp8 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
|
||||
path = new Path();
|
||||
|
||||
paint = new Paint();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(Color.parseColor("#424242"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
final float midX = width / 2;
|
||||
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(width, 0);
|
||||
path.lineTo(width, height - dp8);
|
||||
path.lineTo(midX + dp8, height - dp8);
|
||||
path.lineTo(midX, height );
|
||||
path.lineTo(midX - dp8, height - dp8);
|
||||
path.lineTo(0, height - dp8);
|
||||
path.close();
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
|
||||
package com.itsaky.androidide.views;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
public class VerticalViewPager extends ViewPager
|
||||
{
|
||||
public static final String TAG = "VerticalViewPager";
|
||||
private boolean scrollEnabled = true;
|
||||
|
||||
public VerticalViewPager(Context context)
|
||||
{
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public VerticalViewPager(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public void setScrollEnabled(boolean scrollEnabled)
|
||||
{
|
||||
this.scrollEnabled = scrollEnabled;
|
||||
}
|
||||
|
||||
public boolean isScrollEnabled()
|
||||
{
|
||||
return scrollEnabled;
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setPageTransformer(true, new VerticalPageTransformer());
|
||||
setOverScrollMode(OVER_SCROLL_NEVER);
|
||||
}
|
||||
|
||||
private class VerticalPageTransformer implements ViewPager.PageTransformer
|
||||
{
|
||||
@Override
|
||||
public void transformPage(View view, float position)
|
||||
{
|
||||
if (position < -1)
|
||||
{
|
||||
view.setAlpha(0);
|
||||
} else if (position <= 1)
|
||||
{
|
||||
view.setAlpha(1);
|
||||
view.setTranslationX(view.getWidth() * -position);
|
||||
float yPosition = position * view.getHeight();
|
||||
view.setTranslationY(yPosition);
|
||||
} else
|
||||
{
|
||||
view.setAlpha(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the X and Y coordinates of your touch event.
|
||||
*/
|
||||
private MotionEvent swapXY(MotionEvent ev)
|
||||
{
|
||||
float width = getWidth();
|
||||
float height = getHeight();
|
||||
float newX = (ev.getY() / height) * width;
|
||||
float newY = (ev.getX() / width) * height;
|
||||
ev.setLocation(newX, newY);
|
||||
return ev;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev)
|
||||
{
|
||||
boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
|
||||
swapXY(ev); // return touch coordinates to original reference frame for any child views
|
||||
return intercepted;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev)
|
||||
{
|
||||
return isScrollEnabled() ? super.onTouchEvent(swapXY(ev)) : true;
|
||||
}
|
||||
}
|
||||
@@ -4618,16 +4618,15 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
params.setPosition (getCursorAsLSPPosition ());
|
||||
final CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> future = mLanguageServer.getTextDocumentService ().definition (params);
|
||||
future.whenComplete ((e, t) -> {
|
||||
final Either<List<? extends Location>, List<? extends LocationLink>> either = e;
|
||||
final Throwable th = t;
|
||||
|
||||
if (either == null || mLanguageClient == null || future.isCancelled () || future.isCompletedExceptionally ()) {
|
||||
if (e == null || mLanguageClient == null || future.isCancelled () || future.isCompletedExceptionally ()) {
|
||||
LOG.error ("An error occurred while finding definition", t);
|
||||
showDefinitionNotFound (pd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (either.isLeft ()) {
|
||||
final List<? extends Location> locations = either.getLeft ();
|
||||
if (e.isLeft ()) {
|
||||
final List<? extends Location> locations = e.getLeft ();
|
||||
if (locations == null || locations.size () <= 0) {
|
||||
showDefinitionNotFound (pd);
|
||||
return;
|
||||
@@ -4643,8 +4642,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
});
|
||||
|
||||
dismissOnUiThread (pd);
|
||||
} else if (either.isRight ()) {
|
||||
final List<? extends LocationLink> locations = either.getRight ();
|
||||
} else if (e.isRight ()) {
|
||||
final List<? extends LocationLink> locations = e.getRight ();
|
||||
if (mLanguageClient == null || locations == null || locations.size () <= 0) {
|
||||
showDefinitionNotFound (pd);
|
||||
return;
|
||||
@@ -4663,6 +4662,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
}
|
||||
});
|
||||
} catch (Throwable th) {
|
||||
LOG.error ("An error occurred while finding definition", th);
|
||||
showDefinitionNotFound (pd);
|
||||
}
|
||||
}
|
||||
@@ -4690,26 +4690,26 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
final CompletableFuture<List<? extends Location>> future = mLanguageServer.getTextDocumentService ().references (params);
|
||||
|
||||
future.whenComplete ((l, t) -> {
|
||||
final List<? extends Location> locations = l;
|
||||
final Throwable th = t;
|
||||
|
||||
if (locations == null || mLanguageClient == null || future.isCancelled () || future.isCompletedExceptionally ()) {
|
||||
if (l == null || mLanguageClient == null || future.isCancelled () || future.isCompletedExceptionally ()) {
|
||||
LOG.error ("An error occurred while finding references", t);
|
||||
showReferencesNotFound (pd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (locations.isEmpty ()) {
|
||||
if (l.isEmpty ()) {
|
||||
showReferencesNotFound (pd);
|
||||
return;
|
||||
} else {
|
||||
ThreadUtils.runOnUiThread (() -> {
|
||||
mLanguageClient.showLocations (locations);
|
||||
mLanguageClient.showLocations (l);
|
||||
});
|
||||
}
|
||||
|
||||
dismissOnUiThread (pd);
|
||||
});
|
||||
} catch (Throwable th) {
|
||||
LOG.error ("An error occurred while finding references", th);
|
||||
showReferencesNotFound (pd);
|
||||
}
|
||||
}
|
||||
@@ -4741,24 +4741,20 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
CompletableFuture<SignatureHelp> future = mLanguageServer.getTextDocumentService ().signatureHelp (params);
|
||||
|
||||
future.whenComplete ((h, t) -> {
|
||||
final SignatureHelp help = h;
|
||||
final Throwable th = t;
|
||||
|
||||
if (help == null || mLanguageClient == null || future.isCancelled () || future.isCompletedExceptionally ()) {
|
||||
if (h == null || mLanguageClient == null || future.isCancelled () || future.isCompletedExceptionally ()) {
|
||||
LOG.error ("An error occurred while finding signature help", t);
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread (() -> {
|
||||
mLanguageClient.showSignatureHelp (help, getFile ());
|
||||
mLanguageClient.showSignatureHelp (h, getFile ());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void dismissOnUiThread (final Dialog dialog) {
|
||||
ThreadUtils.runOnUiThread (() -> {
|
||||
dialog.dismiss ();
|
||||
});
|
||||
ThreadUtils.runOnUiThread (dialog::dismiss);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,21 +45,24 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/file_tab_height"
|
||||
android:background="@color/primaryDarkColor"
|
||||
app:tabIndicatorColor="@color/secondaryColor"
|
||||
app:tabIndicatorGravity="stretch"
|
||||
app:tabIndicatorHeight="@dimen/file_tab_height"
|
||||
app:tabTextAppearance="@style/AppTheme.TabLayout.Title"
|
||||
app:tabSelectedTextColor="@color/primaryTextColor"
|
||||
app:tabMode="scrollable" />
|
||||
app:tabGravity="start"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
app:tabIndicatorColor="@color/secondaryColor"
|
||||
app:tabIndicatorFullWidth="false"
|
||||
app:tabIndicatorGravity="bottom"
|
||||
app:tabIndicatorHeight="4dp"
|
||||
app:tabMode="scrollable"
|
||||
app:tabSelectedTextColor="@color/secondaryColor"
|
||||
app:tabTextAppearance="@style/AppTheme.TabLayout.Title"
|
||||
app:tabTextColor="@color/secondaryTextColor_light" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/editor_viewPager"
|
||||
<ViewFlipper
|
||||
android:id="@+id/editor_container"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@color/content_background"
|
||||
android:layout_gravity="top"
|
||||
android:layout_marginBottom="@dimen/editor_sheet_peek_height"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user