Fixed: File does not close and crashes if reopened and closed again.

This commit is contained in:
Akash Yadav
2022-01-05 21:41:09 +05:30
parent b4f6606db3
commit 76f5f6908d
7 changed files with 657 additions and 622 deletions
@@ -134,7 +134,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
NavigationView.OnNavigationItemSelectedListener,
DiagnosticClickListener,
EditorFragment.FileOpenListener,
EditorPagerAdapter.EditorStateListener,
IDEHandler.Provider,
EditorActivityProvider {
@@ -202,59 +201,29 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
setSupportActionBar (mBinding.editorToolbar);
getProjectFromIntent ();
mPagerAdapter = new EditorPagerAdapter (getSupportFragmentManager (), this.mProject);
mPagerAdapter = new EditorPagerAdapter (this, this.mProject);
mFileTreeFragment = FileTreeFragment.newInstance (this.mProject).setFileActionListener (this);
mDaemonStatusFragment = new TextSheetFragment ().setTextSelectable (true);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle (this, mBinding.editorDrawerLayout, mBinding.editorToolbar, R.string.app_name, R.string.app_name);
mBinding.editorDrawerLayout.addDrawerListener (toggle);
mBinding.startNav.setNavigationItemSelectedListener (this);
toggle.syncState ();
setupDrawerToggle ();
loadFragment (mFileTreeFragment);
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.setupWithViewPager (mBinding.editorViewPager);
mBinding.tabs.addOnTabSelectedListener (this);
bottomSheetTabAdapter = new EditorBottomSheetTabAdapter (this);
mBinding.bottomSheet.pager.setAdapter (bottomSheetTabAdapter);
final var mediator = new TabLayoutMediator (mBinding.bottomSheet.tabs,
mBinding.bottomSheet.pager,
true,
true,
(tab, position) -> tab.setText (bottomSheetTabAdapter.getTitle (position)));
mediator.attach ();
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.setBinding (mBinding.bottomSheet);
mEditorBottomSheet.addBottomSheetCallback (new BottomSheetBehavior.BottomSheetCallback () {
@Override
public void onStateChanged (@NonNull View bottomSheet, int newState) {
mBinding.bottomSheet.textContainer.setVisibility (newState == BottomSheetBehavior.STATE_EXPANDED ? View.INVISIBLE : View.VISIBLE);
}
@Override
public void onSlide (@NonNull View bottomSheet, float slideOffset) {
mBinding.bottomSheet.textContainer.setAlpha (1f - slideOffset);
}
});
if (!getApp ().getPrefManager ().getBoolean (KEY_BOTTOM_SHEET_SHOWN) && mEditorBottomSheet.getState ()!= BottomSheetBehavior.STATE_EXPANDED) {
mEditorBottomSheet.setState (BottomSheetBehavior.STATE_EXPANDED);
new Handler (Looper.getMainLooper ()).postDelayed (() -> {
mEditorBottomSheet.setState (BottomSheetBehavior.STATE_COLLAPSED);
getApp ().getPrefManager ().putBoolean (KEY_BOTTOM_SHEET_SHOWN, true);
}, 1500);
}
setupEditorBottomSheet ();
createQuickActions ();
@@ -274,6 +243,52 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
mUIDesignerLauncher = registerForActivityResult (new ActivityResultContracts.StartActivityForResult (), this::onGetUIDesignerResult);
}
private void setupDrawerToggle () {
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle (this, mBinding.editorDrawerLayout, mBinding.editorToolbar, R.string.app_name, R.string.app_name);
mBinding.editorDrawerLayout.addDrawerListener (toggle);
mBinding.startNav.setNavigationItemSelectedListener (this);
toggle.syncState ();
}
private void setupEditorBottomSheet () {
bottomSheetTabAdapter = new EditorBottomSheetTabAdapter (this);
mBinding.bottomSheet.pager.setAdapter (bottomSheetTabAdapter);
final var mediator = new TabLayoutMediator (mBinding.bottomSheet.tabs,
mBinding.bottomSheet.pager,
true,
true,
(tab, position) -> tab.setText (bottomSheetTabAdapter.getTitle (position)));
mediator.attach ();
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.setBinding (mBinding.bottomSheet);
mEditorBottomSheet.addBottomSheetCallback (new BottomSheetBehavior.BottomSheetCallback () {
@Override
public void onStateChanged (@NonNull View bottomSheet, int newState) {
mBinding.bottomSheet.textContainer.setVisibility (newState == BottomSheetBehavior.STATE_EXPANDED ? View.INVISIBLE : View.VISIBLE);
}
@Override
public void onSlide (@NonNull View bottomSheet, float slideOffset) {
mBinding.bottomSheet.textContainer.setAlpha (1f - slideOffset);
}
});
if (!getApp ().getPrefManager ().getBoolean (KEY_BOTTOM_SHEET_SHOWN) && mEditorBottomSheet.getState () != BottomSheetBehavior.STATE_EXPANDED) {
mEditorBottomSheet.setState (BottomSheetBehavior.STATE_EXPANDED);
new Handler (Looper.getMainLooper ()).postDelayed (() -> {
mEditorBottomSheet.setState (BottomSheetBehavior.STATE_COLLAPSED);
getApp ().getPrefManager ().putBoolean (KEY_BOTTOM_SHEET_SHOWN, true);
}, 1500);
}
}
@Override
public void onBackPressed () {
if (mBinding.getRoot ().isDrawerOpen (GravityCompat.END)) {
@@ -495,20 +510,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
return mIDEProject;
}
@Override
public void editorStateChanged () {
for (int i = 0; i < mBinding.tabs.getTabCount (); i++) {
try {
final var tab = mBinding.tabs.getTabAt (i);
if (tab != null) {
tab.setText (mPagerAdapter.getPageTitle (i));
}
} catch (Throwable th) {
th.printStackTrace ();
}
}
}
public void handleSearchResults (Map<File, List<SearchResult>> results) {
setSearchResultAdapter (new com.itsaky.androidide.adapters.SearchListAdapter (results, file -> {
openFile (file);
@@ -519,7 +520,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
}));
showSearchResults ();
if (mSearchingProgress != null && mSearchingProgress.isShowing ()) {
mSearchingProgress.dismiss ();
}
@@ -538,7 +539,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
}
public void showDaemonStatus () {
ShellServer shell = getApp ().newShell (t -> getDaemonStatusFragment ().append (t.toString ()));
ShellServer shell = getApp ().newShell (t -> getDaemonStatusFragment ().append (t));
shell.bgAppend (String.format ("echo '%s'", getString (R.string.msg_getting_daemom_status)));
shell.bgAppend (String.format ("cd '%s' && sh gradlew --status", mProject.getProjectPath ()));
if (!getDaemonStatusFragment ().isShowing ()) {
@@ -635,12 +636,12 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
final int width = view.getWidth ();
view.getWindowVisibleDisplayFrame (r);
if (r.width () != width) {
// Will be true when the view is going out of screen to left
if (initialX < r.left) {
view.setX (SizeUtils.dp2px (8)); // an offset of 8dp from the left edge of screen
}
// Will be true when the view is going out of screen to right
if (initialX + width > r.right) {
view.setX (r.right - SizeUtils.dp2px (8) - width); // position to the right but leaving 8dp space from the right edge of screen
@@ -666,11 +667,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
@Override
public void
onTabSelected (@NonNull TabLayout.Tab tab) {
EditorFragment current = mPagerAdapter.getFrag (tab.getPosition ());
if (current != null && current.getFile () != null) {
mCurrentFragment = current;
mCurrentFile = current.getFile ();
refreshSymbolInput (current);
final var opened = mPagerAdapter.getOpenedFile (tab.getPosition ());
if (opened != null && opened.file != null && opened.fragment != null) {
mCurrentFragment = opened.fragment;
mCurrentFile = opened.file;
refreshSymbolInput (mCurrentFragment);
}
invalidateOptionsMenu ();
@@ -678,11 +679,13 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
@Override
public void onTabUnselected (@NonNull TabLayout.Tab tab) {
EditorFragment frag = mPagerAdapter.getFrag (tab.getPosition ());
if (frag == null) {
final var opened = mPagerAdapter.getOpenedFile (tab.getPosition ());
if (opened == null) {
return;
}
boolean isGradle = frag.isModified () && frag.getFile ().getName ().endsWith (EditorFragment.EXT_GRADLE);
final var frag = opened.fragment;
boolean isGradle = frag.isModified () && opened.file.getName ().endsWith (EditorFragment.EXT_GRADLE);
frag.save ();
if (isGradle) {
notifySyncNeeded ();
@@ -875,47 +878,34 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
}
public void closeFile (int index, boolean selectOther) {
mBinding.tabs.removeOnTabSelectedListener (this);
// mBinding.tabs.removeOnTabSelectedListener (this);
mPagerAdapter.closeFileAt (index);
// mBinding.tabs.addOnTabSelectedListener (this);
int pos = index;
final List<Fragment> frags = mPagerAdapter.getFragments ();
final List<File> files = mPagerAdapter.getOpenedFiles ();
final EditorFragment editorFragment = mPagerAdapter.getFrag (index);
frags.remove (index);
files.remove (index);
if (editorFragment != null && editorFragment.getEditor () != null) {
editorFragment.getEditor ().close ();
}
mPagerAdapter = new EditorPagerAdapter (getSupportFragmentManager (), mProject, frags, files);
mBinding.editorViewPager.setAdapter (mPagerAdapter);
mBinding.tabs.setupWithViewPager (mBinding.editorViewPager);
mBinding.tabs.addOnTabSelectedListener (this);
if (selectOther) {
if (pos < 0 || pos >= frags.size ()) {
int i = pos - 1;
if (i >= 0 && i < frags.size ()) {
pos = i;
} else {
i = pos + 1;
if (i >= 0 && i < frags.size ()) {
pos = i;
}
}
}
mBinding.editorViewPager.setCurrentItem (pos, false);
}
if (mPagerAdapter.getCount () <= 0) {
if (mPagerAdapter.getItemCount () <= 0) {
mCurrentFragment = null;
mCurrentFile = null;
}
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);
mPagerAdapter.closeAllFiles ();
mBinding.tabs.addOnTabSelectedListener (this);
}
public void closeOthers () {
mPagerAdapter.closeOthers (mBinding.tabs.getSelectedTabPosition ());
}
/////////////////////////////////////////////////
@@ -926,14 +916,14 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
private void onGetUIDesignerResult (@NonNull ActivityResult result) {
if (mCurrentFragment != null && mCurrentFragment.getEditor () != null && result.getResultCode () == RESULT_OK) {
final var data = result.getData ();
if (data != null) {
if (data != null && data.hasExtra (DesignerActivity.KEY_GENERATED_CODE)) {
final var code = data.getStringExtra (DesignerActivity.KEY_GENERATED_CODE);
mCurrentFragment.getEditor ().setText (code);
saveAll ();
} else {
final var msg = getString (R.string.msg_invalid_designer_result);
getApp ().toast (msg, Toaster.Type.ERROR);
LOG.error (msg, "Data returned by UI Designer is null");
LOG.error (msg, "Data returned by UI Designer is null or is invalid.");
}
} else {
LOG.error ("UI Designer returned an invalid result code.", "Result code: " + result.getResultCode ());
@@ -1057,6 +1047,10 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
}
private void refreshSymbolInput (@NonNull EditorFragment frag) {
if (frag.getEditor () == null || frag.getFile () == null) {
return;
}
symbolInput.bindEditor (frag.getEditor ());
symbolInput.setSymbols (Symbols.forFile (frag.getFile ()));
}
@@ -1085,7 +1079,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
LSP.shutdownAll ();
getApp ().stopAllDaemons ();
if (manualFinish) {
finish ();
}
@@ -1163,7 +1157,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
if (str == null || str.trim ().isEmpty ()) {
continue;
}
extensionList.add (str);
}
} else {
@@ -1233,29 +1227,5 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
});
}
private void closeAll () {
mBinding.tabs.removeOnTabSelectedListener (this);
mPagerAdapter = new EditorPagerAdapter (getSupportFragmentManager (), mProject);
mBinding.editorViewPager.setAdapter (mPagerAdapter);
mBinding.tabs.setupWithViewPager (mBinding.editorViewPager);
mBinding.tabs.addOnTabSelectedListener (this);
}
private void closeOthers () {
final EditorFragment frag = mPagerAdapter.getFrag (mBinding.tabs.getSelectedTabPosition ());
final File file = mPagerAdapter.getOpenedFiles ().get (mBinding.tabs.getSelectedTabPosition ());
try {
frag.save ();
} catch (Throwable th) {
LOG.error ("Failed to save file: " + file, th);
}
mPagerAdapter = new EditorPagerAdapter (getSupportFragmentManager (), mProject);
mBinding.editorViewPager.setAdapter (mPagerAdapter);
mBinding.tabs.setupWithViewPager (mBinding.editorViewPager);
mBinding.tabs.addOnTabSelectedListener (this);
openFile (file);
}
private static final Logger LOG = Logger.instance ("EditorActivity");
}
@@ -1,186 +1,274 @@
/************************************************************************************
/*
* 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.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.itsaky.androidide.fragments.EditorFragment;
import com.itsaky.androidide.project.AndroidProject;
import com.itsaky.androidide.models.SaveResult;
import com.itsaky.androidide.utils.Logger;
import org.eclipse.lsp4j.Range;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class EditorPagerAdapter extends FragmentStatePagerAdapter implements EditorFragment.ModificationStateListener {
public class EditorPagerAdapter extends FragmentStateAdapter implements EditorFragment.ModificationStateListener {
private ArrayList<File> mOpenedFiles;
private ArrayList<Fragment> mFragments;
private AndroidProject project;
private final AndroidProject project;
private final List<OpenedFile> mFiles;
private EditorStateListener mEditorStateListener;
private static final Logger LOG = Logger.instance ("EditorPagerAdapter");
private static final Logger LOG = Logger.instance("EditorPagerAdapter");
public EditorPagerAdapter(FragmentManager manager, AndroidProject project) {
super(manager);
this.project = project;
this.mOpenedFiles = new ArrayList<>();
this.mFragments = new ArrayList<>();
}
public EditorPagerAdapter(FragmentManager manager, AndroidProject project, List<Fragment> fragments, List<File> files) {
super(manager);
this.project = project;
this.mOpenedFiles = new ArrayList<>();
this.mFragments = new ArrayList<>();
mOpenedFiles.addAll(files);
mFragments.addAll(fragments);
}
public EditorPagerAdapter setEditorStateListener (EditorStateListener listener) {
this.mEditorStateListener = listener;
return this;
public EditorPagerAdapter (FragmentActivity activity, AndroidProject project) {
super (activity);
this.project = project;
this.mFiles = new ArrayList<> ();
}
public int findIndexOfEditorByFile(File file) {
for (int i=0;i < mFragments.size();i++) {
Fragment frag = mFragments.get(i);
if (!(frag instanceof EditorFragment))
continue;
EditorFragment editor = (EditorFragment) frag;
if (editor.getFile() != null && editor.getFile().getAbsolutePath().equals(file.getAbsolutePath()))
return i;
public int findIndexOfEditorByFile (File file) {
if (file == null) {
return -1;
}
for (int i = 0; i < this.mFiles.size (); i++) {
final var opened = mFiles.get (i);
if (opened.file.equals (file)) {
return i;
}
}
return -1;
}
public EditorFragment findEditorByFile(File file) {
int index = findIndexOfEditorByFile(file);
if (index != -1)
return getFrag(index);
public EditorFragment findEditorByFile (File file) {
int index = findIndexOfEditorByFile (file);
if (index != -1) {
return getFrag (index);
}
return null;
}
public int openFile(File file, org.eclipse.lsp4j.Range selection, EditorFragment.FileOpenListener listener) {
int openedFileIndex = -1;
for (int i=0;i < mOpenedFiles.size();i++) {
File f = mOpenedFiles.get(i);
if (f.getAbsolutePath().equals(file.getAbsolutePath())) {
openedFileIndex = i;
break;
}
}
public int openFile (File file, Range selection, EditorFragment.FileOpenListener listener) {
int openedFileIndex = -1;
for (int i = 0; i < mFiles.size (); i++) {
final var f = mFiles.get (i);
if (f.file.getAbsolutePath ().equals (file.getAbsolutePath ())) {
openedFileIndex = i;
break;
}
}
if (openedFileIndex == -1) {
mFragments.add(EditorFragment
.newInstance(file, project, selection)
.setFileOpenListener(listener)
.setModificationStateListener(this));
mOpenedFiles.add(file);
notifyDataSetChanged();
return mFragments.size() - 1;
} else return openedFileIndex;
}
public SaveResult saveAll() {
SaveResult result = new SaveResult();
for (int i=0;i < mFragments.size();i++) {
save(i, result);
final var position = mFiles.size ();
final var fragment = EditorFragment
.newInstance (file, project, selection)
.setFileOpenListener (listener)
.setModificationStateListener (this);
mFiles.add (new OpenedFile (file, fragment));
notifyDataSetChanged ();
return position;
} else {
return openedFileIndex;
}
return result;
}
/**
* Close the editor that have opened the given file.
* Does nothing if the file is not opened.
*
* @param file The file to close.
* @return The index of the editor that was closed. -1 if no files were closed.
*/
public int closeFile (File file) {
final var index = findIndexOfEditorByFile (file);
if (index == -1) {
LOG.error ("File is not opened. File: " + file);
return -1;
}
closeFileAt (index);
return index;
}
public void save(int index, SaveResult result) {
if (index >= 0 && index < mFragments.size()) {
EditorFragment frag = getFrag(index);
if (frag == null || frag.getFile() == null) {
/**
* Close the file at the given index. Does nothing if the index is invalid.
* @param index The index of file to close.
*/
public void closeFileAt (int index) {
if (index < 0 || index >= getItemCount ()) {
LOG.error ("Invalid file index. Cannot close any files");
return;
}
// Save the file before closing
var opened = mFiles.get (index);
opened.fragment.save ();
opened.fragment.getEditor ().close (); // Send 'textDocument/didClose' to language servers.
opened = null;
mFiles.remove (index);
notifyDataSetChanged ();
}
/**
* Close all the opened files.
* @see #closeFileAt(int)
*/
public void closeAllFiles () {
// 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 < getItemCount (); i++) {
closeFileAt (i);
}
}
/**
* Close all the files except the file at the given index.
* @param index The index of the file that should not be closed.
*/
public void closeOthers (int index) {
for (int i = 0; i < getItemCount (); i++) {
if (i != index) {
closeFileAt (i);
}
}
}
public SaveResult saveAll () {
SaveResult result = new SaveResult ();
for (int i = 0; i < mFiles.size (); i++) {
save (i, result);
}
return result;
}
public void save (int index, SaveResult result) {
if (index >= 0 && index < mFiles.size ()) {
EditorFragment frag = getFrag (index);
if (frag == null || frag.getFile () == null) {
return;
}
/**
* Must be called before frag.save()
* Otherwise, it'll always return false
*/
final boolean modified = frag.isModified();
// Must be called before frag.save()
// Otherwise, it'll always return false
final boolean modified = frag.isModified ();
frag.save();
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 (EditorFragment.EXT_GRADLE);
final boolean isXml = frag.getFile ().getName ().endsWith (EditorFragment.EXT_XML);
if (!result.gradleSaved) {
result.gradleSaved = modified && isGradle;
}
if (!result.xmlSaved) {
result.xmlSaved = modified && isXml;
}
}
}
public EditorFragment getFrag(int index) {
return (EditorFragment) getItem(index);
}
public List<Fragment> getFragments() {
return mFragments;
}
@Override
public void onModified(EditorFragment editor) {
if(mEditorStateListener != null)
mEditorStateListener.editorStateChanged();
}
@Override
public void onSaved(EditorFragment editor) {
if(mEditorStateListener != null)
mEditorStateListener.editorStateChanged();
}
}
public List<File> getOpenedFiles() {
return mOpenedFiles;
}
@Override
public Fragment getItem(int p1) {
return p1 < 0 || p1 >= mFragments.size() ? null : mFragments.get(p1);
}
@Override
public int getCount() {
return mFragments.size();
}
@Override
public CharSequence getPageTitle(int position) {
return getFrag(position).getTabTitle();
}
public EditorFragment getFrag (int index) {
return mFiles.get (index).fragment;
}
public static interface EditorStateListener {
void editorStateChanged();
public String getEditorTitle (int index) {
return mFiles.get (index).file.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 getFrag (position);
}
@Override
public int getItemCount () {
return mFiles.size ();
}
@Override
public long getItemId (int position) {
return mFiles.get (position).hashCode ();
}
@Override
public boolean containsItem (long itemId) {
for (final var file : mFiles) {
if (itemId == file.hashCode ()) { // see getItemId(int)
return true;
}
}
return false;
}
public static class OpenedFile {
public final File file;
public final EditorFragment fragment;
private OpenedFile (File file, EditorFragment fragment) {
this.file = file;
this.fragment = fragment;
}
@Override
public boolean equals (Object o) {
if (this == o) {
return true;
}
if (o == null || getClass () != o.getClass ()) {
return false;
}
OpenedFile that = (OpenedFile) o;
return Objects.equals (file, that.file) && Objects.equals (fragment, that.fragment);
}
@Override
public int hashCode () {
return Objects.hash (file, fragment);
}
}
}
@@ -81,6 +81,7 @@ public class StudioApp extends BaseApplication {
if(this.uncaughtExceptionHandler != null) {
this.uncaughtExceptionHandler.uncaughtException(thread, th);
}
System.exit (1);
} catch (Throwable error) {
LOG.error ("Unable to show crash handler activity", error);
}
@@ -5,22 +5,23 @@
* 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 com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.SizeUtils;
import com.itsaky.androidide.adapters.CompletionListAdapter;
@@ -38,15 +39,18 @@ import com.itsaky.androidide.tasks.TaskExecutor;
import com.itsaky.androidide.tasks.callables.ReadFileTask;
import com.itsaky.androidide.utils.LSPUtils;
import com.itsaky.androidide.utils.TypefaceUtils;
import io.github.rosemoe.editor.interfaces.EditorEventListener;
import io.github.rosemoe.editor.langs.EmptyLanguage;
import io.github.rosemoe.editor.widget.CodeEditor;
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 org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token;
import org.eclipse.lsp4j.Diagnostic;
@@ -54,16 +58,16 @@ import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
public class EditorFragment extends BaseFragment implements EditorEventListener {
public FragmentEditorBinding mBinding;
private File mFile;
private boolean isRead = false;
private boolean isModified = false;
private boolean isFirstCreate = false;
public FragmentEditorBinding mBinding;
private File mFile;
private boolean isRead = false;
private boolean isModified = false;
private boolean isFirstCreate = false;
private ModificationStateListener mModificationStateListener;
private FileOpenListener mOpenListener;
private static AndroidProject mProject;
private static AndroidProject mProject;
public static final String KEY_FILE_PATH = "file_path";
public static final String KEY_PROJECT = "project";
@@ -72,371 +76,401 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
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 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) {
public EditorFragment setModificationStateListener (ModificationStateListener listener) {
this.mModificationStateListener = listener;
return this;
}
public String getTabTitle() {
if(getFile() != null) {
String title = getFile().getName();
if(isModified())
public String getTabTitle () {
if (getFile () != null) {
String title = getFile ().getName ();
if (isModified ()) {
title += "*";
}
return title;
}
return "";
}
public FragmentEditorBinding getBinding() {
public FragmentEditorBinding getBinding () {
return mBinding;
}
public static EditorFragment newInstance(File file, AndroidProject project, 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());
bundle.putParcelable(KEY_PROJECT, project);
EditorFragment frag = new EditorFragment();
frag.setArguments(bundle);
return frag;
}
public static EditorFragment newInstance (File file, AndroidProject project, 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 ());
bundle.putParcelable (KEY_PROJECT, project);
EditorFragment frag = new EditorFragment ();
frag.setArguments (bundle);
return frag;
}
public EditorFragment setFileOpenListener(FileOpenListener openListener) {
public EditorFragment setFileOpenListener (FileOpenListener openListener) {
this.mOpenListener = openListener;
return this;
}
public CodeEditor getEditor() {
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(Bundle outState) {
save();
super.onSaveInstanceState(outState);
public void onCreate (Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
isFirstCreate = true;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mBinding = FragmentEditorBinding.inflate(inflater, container, false);
return mBinding.getRoot();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (getActivity() == null || getArguments() == null || !getArguments().containsKey(KEY_FILE_PATH) || !getArguments().containsKey(KEY_PROJECT)) return;
mFile = new File(getArguments().getString(KEY_FILE_PATH));
mProject = getArguments().getParcelable(KEY_PROJECT);
@Override
public void onSaveInstanceState (Bundle outState) {
save ();
super.onSaveInstanceState (outState);
}
@Override
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mBinding = FragmentEditorBinding.inflate (inflater, container, false);
return mBinding.getRoot ();
}
@Override
public void onViewCreated (View view, Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
if (getActivity () == null || getArguments () == null || !getArguments ().containsKey (KEY_FILE_PATH) || !getArguments ().containsKey (KEY_PROJECT)) {
return;
}
mFile = new File (getArguments ().getString (KEY_FILE_PATH));
mProject = getArguments ().getParcelable (KEY_PROJECT);
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.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);
mBinding.diagnosticTextContainer.setVisibility (View.GONE);
configureEditorIfNeeded();
final Range range = fromArgs(getArguments());
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());
configureEditorIfNeeded ();
final Range range = fromArgs (getArguments ());
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());
getEditor ().setSelectionRegion (range.getStart ().getLine (), range.getStart ().getCharacter (), range.getEnd ().getLine (), range.getEnd ().getCharacter ());
}
});
});
}
private Range fromArgs(Bundle args) {
if(!(args.containsKey(KEY_LINE_START)
&& args.containsKey(KEY_COLUMN_START)
&& args.containsKey(KEY_LINE_END)
&& args.containsKey(KEY_COLUMN_END)))
});
}
private Range fromArgs (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)
}
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 != null) {
mBinding.editor.getEditorLanguage().getAnalyzer().updateDiagnostics(new HashMap<Integer, Map<Integer, Diagnostic>>());
public void setDiagnostics (List<Diagnostic> diags) {
if (diags == null && mBinding.editor != null) {
mBinding.editor.getEditorLanguage ().getAnalyzer ().updateDiagnostics (new HashMap<Integer, Map<Integer, Diagnostic>> ());
return;
}
if(mBinding.editor != null) {
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<>();
if (mBinding.editor != null) {
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;
}
mappedByColumn.put(column, d);
map.put(line, mappedByColumn);
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);
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();
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 void commentLine() {
mBinding.editor.commentLine();
public String getText () {
return mBinding.editor.getText ().toString ();
}
public void uncommentLine() {
mBinding.editor.uncommentLine();
public boolean isModified () {
return isModified;
}
public void beginSearch() {
mBinding.editor.beginSearchMode();
public void undo () {
if (mBinding.editor.canUndo ()) {
mBinding.editor.undo ();
}
}
public void save() {
if(mFile == null || mBinding == null || mBinding.editor == null || mBinding.editor.getText() == null) return;
final String text = mBinding.editor.getText().toString();
final boolean wrote = FileIOUtils.writeFileFromString(mFile, text);
notifySaved(wrote, text);
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());
}
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 ();
final boolean wrote = FileIOUtils.writeFileFromString (mFile, text);
notifySaved (wrote, text);
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.setFile (getFile ());
mBinding.editor.setColorScheme(new SchemeAndroidIDE());
isRead = true;
if(mOpenListener != null)
mOpenListener.onOpenSuccessful(getFile(), getText());
}
private void closeCurrentTag(String text, int line, int col) {
mBinding.editor.setColorScheme (new SchemeAndroidIDE ());
isRead = true;
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)));
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) {
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());
} 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) {
} 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) {
} 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;
} else {
wasOpen = wasSlash = false;
}
}
if(currentNames.size() > 0) {
mBinding.editor.getText().insert(line, col + 2, currentNames.get(0));
if (currentNames.size () > 0) {
mBinding.editor.getText ().insert (line, col + 2, currentNames.get (0));
}
} catch (Throwable 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) {
} catch (Throwable th) {
}
}
@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;
@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)) {
if (getFile () != null && getFile ().getName ().endsWith (EXT_XML)) {
boolean isOpen = false;
try {
isOpen = editor.getText().charAt(editor.getCursor().getLeft() - 2) == '<';
} catch (Throwable th) {}
if(isOpen && insertedContent.toString().equals("/")) {
closeCurrentTag(editor.getText().toString(), endLine, endColumn);
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) {
}
notifyModified ();
}
private void notifySaved(boolean wrote, String text) {
mBinding.editor.didSave();
@Override
public void beforeReplace (CodeEditor editor, CharSequence content) {
}
private void notifySaved (boolean wrote, String text) {
mBinding.editor.didSave ();
if(mModificationStateListener != null)
mModificationStateListener.onSaved(this);
if (mModificationStateListener != null) {
mModificationStateListener.onSaved (this);
}
}
private void notifyModified() {
if(mModificationStateListener != null)
mModificationStateListener.onModified(this);
private void notifyModified () {
if (mModificationStateListener != null) {
mModificationStateListener.onModified (this);
}
}
public static interface FileOpenListener {
void onOpenSuccessful(File file, String text);
public interface FileOpenListener {
void onOpenSuccessful (File file, String text);
}
public static interface ModificationStateListener {
void onModified(EditorFragment editor);
void onSaved(EditorFragment editor);
public interface ModificationStateListener {
void onModified (EditorFragment editor);
void onSaved (EditorFragment editor);
}
}
@@ -22,7 +22,6 @@ import com.blankj.utilcode.util.ClipboardUtils;
import com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.FileUtils;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.itsaky.androidide.EditorActivity;
import com.itsaky.androidide.R;
import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder;
import com.itsaky.androidide.databinding.LayoutCreateFileJavaBinding;
@@ -299,7 +298,7 @@ public class FileOptionsHandler extends IDEHandler implements OptionsListFragmen
}
EditorFragment frag = activity().getPagerAdapter().findEditorByFile(f);
if(frag != null) {
activity().closeFile(activity().getPagerAdapter().getFragments().indexOf(frag));
activity().closeFile(activity().getPagerAdapter().getFiles ().indexOf(frag));
}
}
})
@@ -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.views;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.viewpager.widget.ViewPager;
/**
Copied from StackOverflow
@see https://stackoverflow.com/a/47143096
*/
public class NonSwipableViewPager extends ViewPager
{
public NonSwipableViewPager(Context ctx)
{
super(ctx);
}
public NonSwipableViewPager(Context context, AttributeSet attrs)
{
super(context, attrs);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev)
{
return false;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
return false;
}
}
+2 -3
View File
@@ -50,12 +50,11 @@
app:tabIndicatorHeight="@dimen/file_tab_height"
app:tabTextAppearance="@style/AppTheme.TabLayout.Title"
app:tabSelectedTextColor="@color/primaryTextColor"
app:tabMode="scrollable"
app:tabGravity="fill"/>
app:tabMode="scrollable" />
</com.google.android.material.appbar.AppBarLayout>
<com.itsaky.androidide.views.NonSwipableViewPager
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/editor_viewPager"
android:layout_height="match_parent"
android:layout_width="match_parent"