mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Wrote tests for JavaSelectionProvider; Formatted code
This commit is contained in:
Generated
+1
-1
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="1.8">
|
||||
<bytecodeTargetLevel target="11">
|
||||
<module name="AndroidIDE.gradle-tooling" target="1.8" />
|
||||
<module name="AndroidIDE.gradle-tooling.plugin" target="1.8" />
|
||||
</bytecodeTargetLevel>
|
||||
|
||||
Generated
+1
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="GoogleJavaFormatSettings">
|
||||
<option name="enabled" value="true" />
|
||||
<option name="style" value="AOSP" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+1
-1
@@ -130,7 +130,7 @@
|
||||
</option>
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
+1
-2
@@ -12,8 +12,7 @@ This is the changelog of all the releases of AndroidIDE.
|
||||
<!--New features-->
|
||||
- Code formatter for Java source code.
|
||||
- Support for
|
||||
[`GestureOverlayView`](https://developer.android.com/reference/android/gesture/GestureOverlayView)
|
||||
,
|
||||
[`GestureOverlayView`](https://developer.android.com/reference/android/gesture/GestureOverlayView),
|
||||
[`ToggleButton`](https://developer.android.com/reference/android/widget/ToggleButton),
|
||||
[`Switch`](https://developer.android.com/reference/android/widget/Switch),
|
||||
[`GridLayout`](https://developer.android.com/reference/android/widget/GridLayout) in layout
|
||||
|
||||
@@ -21,15 +21,12 @@ import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.widget.TooltipCompat;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import com.itsaky.androidide.app.StudioActivity;
|
||||
import com.itsaky.androidide.databinding.ActivityAboutBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutAboutItemsBinding;
|
||||
import com.itsaky.androidide.models.License;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ import android.widget.RelativeLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.core.content.ContextCompat;
|
||||
@@ -51,7 +50,6 @@ import androidx.transition.Fade;
|
||||
import androidx.transition.Slide;
|
||||
import androidx.transition.TransitionManager;
|
||||
import androidx.transition.TransitionSet;
|
||||
|
||||
import com.blankj.utilcode.util.DeviceUtils;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.blankj.utilcode.util.ThrowableUtils;
|
||||
@@ -76,9 +74,6 @@ import com.itsaky.inflater.IView;
|
||||
import com.itsaky.inflater.IViewGroup;
|
||||
import com.itsaky.inflater.LayoutInflaterConfiguration;
|
||||
import com.itsaky.inflater.impl.BaseView;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -86,418 +81,445 @@ import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
public class DesignerActivity extends StudioActivity
|
||||
implements WidgetItemAdapter.OnDragStartListener,
|
||||
AttrEditorSheet.OnViewDeletionFailedListener,
|
||||
ILayoutInflater.ContextProvider {
|
||||
implements WidgetItemAdapter.OnDragStartListener,
|
||||
AttrEditorSheet.OnViewDeletionFailedListener,
|
||||
ILayoutInflater.ContextProvider {
|
||||
|
||||
public static final String KEY_LAYOUT_PATH = "designer_layoutPath";
|
||||
public static final String KEY_RES_DIRS = "designer_resDirs";
|
||||
public static final String KEY_GENERATED_CODE = "designer_xmlCode";
|
||||
public static final String DRAGGING_WIDGET_TAG = "DRAGGING_WIDGET";
|
||||
public static final String DRAGGING_WIDGET_MIME = "application/ide_widget";
|
||||
private static final Logger LOG = Logger.instance("DesignerActivity");
|
||||
public final String EDITOR_SHEET_DIALOG_TAG = "attribute_editor_dialog";
|
||||
private final boolean isTablet = DeviceUtils.isTablet();
|
||||
private final List<UIWidgetGroup> widgetGroups = new ArrayList<>();
|
||||
private ActivityDesignerBinding mBinding;
|
||||
private UIWidgetGroup checkedWidgetCategory;
|
||||
private ILayoutInflater layoutInflater;
|
||||
private IViewGroup inflatedRoot;
|
||||
private File layout;
|
||||
private boolean inflationFailed = false;
|
||||
private AttrEditorSheet mEditorSheet;
|
||||
// This will make sure to apply listeners, background and data to view that are inflated from
|
||||
// XML.
|
||||
private final IInflateListener mInflateListener =
|
||||
new IInflateListener() {
|
||||
public static final String KEY_LAYOUT_PATH = "designer_layoutPath";
|
||||
public static final String KEY_RES_DIRS = "designer_resDirs";
|
||||
public static final String KEY_GENERATED_CODE = "designer_xmlCode";
|
||||
public static final String DRAGGING_WIDGET_TAG = "DRAGGING_WIDGET";
|
||||
public static final String DRAGGING_WIDGET_MIME = "application/ide_widget";
|
||||
private static final Logger LOG = Logger.instance("DesignerActivity");
|
||||
public final String EDITOR_SHEET_DIALOG_TAG = "attribute_editor_dialog";
|
||||
private final boolean isTablet = DeviceUtils.isTablet();
|
||||
private final List<UIWidgetGroup> widgetGroups = new ArrayList<>();
|
||||
private ActivityDesignerBinding mBinding;
|
||||
private UIWidgetGroup checkedWidgetCategory;
|
||||
private ILayoutInflater layoutInflater;
|
||||
private IViewGroup inflatedRoot;
|
||||
private File layout;
|
||||
private boolean inflationFailed = false;
|
||||
private AttrEditorSheet mEditorSheet;
|
||||
// This will make sure to apply listeners, background and data to view that are inflated from
|
||||
// XML.
|
||||
private final IInflateListener mInflateListener =
|
||||
new IInflateListener() {
|
||||
|
||||
@Override
|
||||
public void onBeginInflate() {}
|
||||
@Override
|
||||
public void onBeginInflate() {}
|
||||
|
||||
@Override
|
||||
public void onInflateView(IView view, IViewGroup parent) {
|
||||
if (view instanceof IViewGroup) {
|
||||
View v = view.asView();
|
||||
v.setBackgroundResource(R.drawable.bg_design_container_background);
|
||||
v.setOnDragListener(getOnDragListener((IViewGroup) view));
|
||||
}
|
||||
@Override
|
||||
public void onInflateView(IView view, IViewGroup parent) {
|
||||
if (view instanceof IViewGroup) {
|
||||
View v = view.asView();
|
||||
v.setBackgroundResource(R.drawable.bg_design_container_background);
|
||||
v.setOnDragListener(getOnDragListener((IViewGroup) view));
|
||||
}
|
||||
|
||||
setupInflatedView(view);
|
||||
setDragDataToInflatedView(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyAttribute(IAttribute attr, IView view) {}
|
||||
|
||||
@Override
|
||||
public void onFinishInflate(IView rootView) {
|
||||
if (rootView instanceof IViewGroup) {
|
||||
inflatedRoot = (IViewGroup) rootView;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
this.mBinding = ActivityDesignerBinding.inflate(getLayoutInflater());
|
||||
return mBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setSupportActionBar(mBinding.toolbar);
|
||||
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
final var toggle =
|
||||
new ActionBarDrawerToggle(
|
||||
this, mBinding.getRoot(), mBinding.toolbar, R.string.app_name, R.string.app_name) {
|
||||
|
||||
@Override
|
||||
public void onDrawerSlide(View drawerView, float slideOffset) {
|
||||
super.onDrawerSlide(drawerView, slideOffset);
|
||||
if (isTablet) {
|
||||
mBinding.mainContainer.setTranslationX(drawerView.getWidth() * slideOffset);
|
||||
}
|
||||
}
|
||||
};
|
||||
mBinding.getRoot().addDrawerListener(toggle);
|
||||
toggle.syncState();
|
||||
|
||||
final var extras = getIntent().getExtras();
|
||||
final var path = extras.getString(KEY_LAYOUT_PATH, null);
|
||||
final var name = path.substring(path.lastIndexOf(File.separator) + 1);
|
||||
final var resDirs = extras.getStringArrayList(KEY_RES_DIRS);
|
||||
getSupportActionBar().setTitle(name);
|
||||
|
||||
try {
|
||||
this.layout = new File(path);
|
||||
LOG.debug("Inflating layout file:", this.layout);
|
||||
LOG.debug("Resource directories:", TextUtils.join(",\n", resDirs));
|
||||
|
||||
//noinspection deprecation
|
||||
final var pd = ProgressDialog.show(this, null, getString(R.string.please_wait), true, false);
|
||||
getApp()
|
||||
.createInflaterConfig(this, resDirs.stream().map(File::new).collect(Collectors.toSet()))
|
||||
.whenComplete(
|
||||
(config, throwable) -> {
|
||||
if (config == null || throwable != null) {
|
||||
LOG.error("Unable to create inflater configuration", throwable);
|
||||
return;
|
||||
setupInflatedView(view);
|
||||
setDragDataToInflatedView(view);
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
pd.dismiss();
|
||||
inflatePath(path, config);
|
||||
});
|
||||
});
|
||||
} catch (Throwable th) {
|
||||
mBinding.layoutContainer.removeAllViews();
|
||||
mBinding.layoutContainer.addView(createErrorText(th));
|
||||
LOG.error(getString(R.string.err_cannot_inflate_layout), th);
|
||||
inflationFailed = true;
|
||||
@Override
|
||||
public void onApplyAttribute(IAttribute attr, IView view) {}
|
||||
|
||||
@Override
|
||||
public void onFinishInflate(IView rootView) {
|
||||
if (rootView instanceof IViewGroup) {
|
||||
inflatedRoot = (IViewGroup) rootView;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
this.mBinding = ActivityDesignerBinding.inflate(getLayoutInflater());
|
||||
return mBinding.getRoot();
|
||||
}
|
||||
|
||||
setupWidgets();
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Always open the drawer if this device is a tablet.
|
||||
if (isTablet) {
|
||||
mBinding.getRoot().setScrimColor(Color.TRANSPARENT);
|
||||
mBinding.getRoot().openDrawer(GravityCompat.START);
|
||||
mBinding.getRoot().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
|
||||
}
|
||||
}
|
||||
setSupportActionBar(mBinding.toolbar);
|
||||
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
private void inflatePath(String path, LayoutInflaterConfiguration config) {
|
||||
this.layoutInflater = ILayoutInflater.newInstance(config);
|
||||
this.layoutInflater.resetContextProvider(this);
|
||||
this.layoutInflater.registerInflateListener(this.mInflateListener);
|
||||
final IView view = this.layoutInflater.inflatePath(path, mBinding.layoutContainer);
|
||||
final var toggle =
|
||||
new ActionBarDrawerToggle(
|
||||
this,
|
||||
mBinding.getRoot(),
|
||||
mBinding.toolbar,
|
||||
R.string.app_name,
|
||||
R.string.app_name) {
|
||||
|
||||
if (this.inflatedRoot != null) {
|
||||
this.inflatedRoot.asView().setOnDragListener(getOnDragListener(this.inflatedRoot));
|
||||
@Override
|
||||
public void onDrawerSlide(View drawerView, float slideOffset) {
|
||||
super.onDrawerSlide(drawerView, slideOffset);
|
||||
if (isTablet) {
|
||||
mBinding.mainContainer.setTranslationX(
|
||||
drawerView.getWidth() * slideOffset);
|
||||
}
|
||||
}
|
||||
};
|
||||
mBinding.getRoot().addDrawerListener(toggle);
|
||||
toggle.syncState();
|
||||
|
||||
final var extras = getIntent().getExtras();
|
||||
final var path = extras.getString(KEY_LAYOUT_PATH, null);
|
||||
final var name = path.substring(path.lastIndexOf(File.separator) + 1);
|
||||
final var resDirs = extras.getStringArrayList(KEY_RES_DIRS);
|
||||
getSupportActionBar().setTitle(name);
|
||||
|
||||
try {
|
||||
this.layout = new File(path);
|
||||
LOG.debug("Inflating layout file:", this.layout);
|
||||
LOG.debug("Resource directories:", TextUtils.join(",\n", resDirs));
|
||||
|
||||
//noinspection deprecation
|
||||
final var pd =
|
||||
ProgressDialog.show(this, null, getString(R.string.please_wait), true, false);
|
||||
getApp().createInflaterConfig(
|
||||
this, resDirs.stream().map(File::new).collect(Collectors.toSet()))
|
||||
.whenComplete(
|
||||
(config, throwable) -> {
|
||||
if (config == null || throwable != null) {
|
||||
LOG.error("Unable to create inflater configuration", throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
pd.dismiss();
|
||||
inflatePath(path, config);
|
||||
});
|
||||
});
|
||||
} catch (Throwable th) {
|
||||
mBinding.layoutContainer.removeAllViews();
|
||||
mBinding.layoutContainer.addView(createErrorText(th));
|
||||
LOG.error(getString(R.string.err_cannot_inflate_layout), th);
|
||||
inflationFailed = true;
|
||||
}
|
||||
|
||||
setupWidgets();
|
||||
|
||||
// Always open the drawer if this device is a tablet.
|
||||
if (isTablet) {
|
||||
mBinding.getRoot().setScrimColor(Color.TRANSPARENT);
|
||||
mBinding.getRoot().openDrawer(GravityCompat.START);
|
||||
mBinding.getRoot().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
|
||||
}
|
||||
}
|
||||
|
||||
mBinding.layoutContainer.addView(view.asView());
|
||||
mBinding.layoutContainer.setOnClickListener(
|
||||
v -> mBinding.getRoot().openDrawer(GravityCompat.START));
|
||||
private void inflatePath(String path, LayoutInflaterConfiguration config) {
|
||||
this.layoutInflater = ILayoutInflater.newInstance(config);
|
||||
this.layoutInflater.resetContextProvider(this);
|
||||
this.layoutInflater.registerInflateListener(this.mInflateListener);
|
||||
final IView view = this.layoutInflater.inflatePath(path, mBinding.layoutContainer);
|
||||
|
||||
tryDismiss();
|
||||
}
|
||||
if (this.inflatedRoot != null) {
|
||||
this.inflatedRoot.asView().setOnDragListener(getOnDragListener(this.inflatedRoot));
|
||||
}
|
||||
|
||||
private void tryDismiss() {
|
||||
final var manager = getSupportFragmentManager();
|
||||
final var frag = manager.findFragmentByTag(EDITOR_SHEET_DIALOG_TAG);
|
||||
if (frag != null) {
|
||||
final var transaction = manager.beginTransaction();
|
||||
transaction.remove(frag);
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
mBinding.layoutContainer.addView(view.asView());
|
||||
mBinding.layoutContainer.setOnClickListener(
|
||||
v -> mBinding.getRoot().openDrawer(GravityCompat.START));
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
|
||||
if (inflationFailed) {
|
||||
super.onBackPressed();
|
||||
return;
|
||||
tryDismiss();
|
||||
}
|
||||
|
||||
// When the user presses the back button, set the activity result and finish this activity
|
||||
private void tryDismiss() {
|
||||
final var manager = getSupportFragmentManager();
|
||||
final var frag = manager.findFragmentByTag(EDITOR_SHEET_DIALOG_TAG);
|
||||
if (frag != null) {
|
||||
final var transaction = manager.beginTransaction();
|
||||
transaction.remove(frag);
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
final var progress = new ProgressSheet();
|
||||
progress.setSubMessageEnabled(true);
|
||||
progress.setShowTitle(false);
|
||||
progress.setMessage(getString(R.string.title_generating_xml));
|
||||
progress.setSubMessage(getString(R.string.please_wait));
|
||||
progress.show(getSupportFragmentManager(), "generate_code_progress");
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
|
||||
final var future =
|
||||
CompletableFuture.supplyAsync(
|
||||
() -> BaseView.XML_HEADER.concat("\n").concat(inflatedRoot.generateCode()));
|
||||
future.whenComplete(
|
||||
(s, throwable) -> {
|
||||
progress.dismiss();
|
||||
|
||||
if (throwable != null) {
|
||||
notifyXmlGenerationFailed(throwable);
|
||||
if (inflationFailed) {
|
||||
super.onBackPressed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
finishWithResult(future.get());
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
notifyXmlGenerationFailed(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
// When the user presses the back button, set the activity result and finish this activity
|
||||
|
||||
private void notifyXmlGenerationFailed(Throwable error) {
|
||||
final var errorMessage = getString(R.string.msg_generate_xml_failed);
|
||||
LOG.error(errorMessage, error);
|
||||
final var progress = new ProgressSheet();
|
||||
progress.setSubMessageEnabled(true);
|
||||
progress.setShowTitle(false);
|
||||
progress.setMessage(getString(R.string.title_generating_xml));
|
||||
progress.setSubMessage(getString(R.string.please_wait));
|
||||
progress.show(getSupportFragmentManager(), "generate_code_progress");
|
||||
|
||||
DialogUtils.newYesNoDialog(
|
||||
this,
|
||||
getString(R.string.title_code_generation_failed),
|
||||
getString(R.string.msg_code_generation_failed),
|
||||
(dialog1, which) -> finishWithError(),
|
||||
(dialog1, which) -> dialog1.dismiss())
|
||||
.show();
|
||||
}
|
||||
final var future =
|
||||
CompletableFuture.supplyAsync(
|
||||
() -> BaseView.XML_HEADER.concat("\n").concat(inflatedRoot.generateCode()));
|
||||
future.whenComplete(
|
||||
(s, throwable) -> {
|
||||
progress.dismiss();
|
||||
|
||||
private void finishWithResult(String result) {
|
||||
final var intent = new Intent();
|
||||
intent.putExtra(KEY_GENERATED_CODE, result);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
if (throwable != null) {
|
||||
notifyXmlGenerationFailed(throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
private void finishWithError() {
|
||||
final var intent = new Intent();
|
||||
setResult(-123, intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LOG.logThis();
|
||||
if (isAttrEditorShowing()) {
|
||||
getAttrEditorSheet().dismiss();
|
||||
try {
|
||||
finishWithResult(future.get());
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
notifyXmlGenerationFailed(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.mEditorSheet = null;
|
||||
private void notifyXmlGenerationFailed(Throwable error) {
|
||||
final var errorMessage = getString(R.string.msg_generate_xml_failed);
|
||||
LOG.error(errorMessage, error);
|
||||
|
||||
// Release the reference to inflate listener and context from the layout inflater
|
||||
// Failing to do so will lead getSupportFragmentManager() to return a destroyed fragment
|
||||
// manager
|
||||
try {
|
||||
if (layoutInflater == null) {
|
||||
LOG.debug("Skipping layout inflater context reference release. Inflater is null");
|
||||
return;
|
||||
}
|
||||
|
||||
this.layoutInflater.unregisterListener(mInflateListener);
|
||||
this.layoutInflater.resetContextProvider(null);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to release activity references from ILayoutInflater", e);
|
||||
DialogUtils.newYesNoDialog(
|
||||
this,
|
||||
getString(R.string.title_code_generation_failed),
|
||||
getString(R.string.msg_code_generation_failed),
|
||||
(dialog1, which) -> finishWithError(),
|
||||
(dialog1, which) -> dialog1.dismiss())
|
||||
.show();
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void setupInflatedView(@NonNull IView view) {
|
||||
final var listener =
|
||||
new WidgetTouchListener(view, this::onLayoutViewClick, this::onLayoutViewLongClick);
|
||||
view.asView().setOnTouchListener(listener);
|
||||
|
||||
setDragDataToInflatedView(view);
|
||||
}
|
||||
|
||||
private boolean onLayoutViewLongClick(@NonNull IView view) {
|
||||
final var shadow = new WidgetDragShadowBuilder(view.asView());
|
||||
final var item = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG);
|
||||
final var clip =
|
||||
new ClipData(
|
||||
DesignerActivity.DRAGGING_WIDGET_TAG,
|
||||
new String[] {DesignerActivity.DRAGGING_WIDGET_MIME},
|
||||
item);
|
||||
final var data = view.getExtraData();
|
||||
|
||||
view.asView().startDragAndDrop(clip, shadow, data, 0);
|
||||
view.removeFromParent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onLayoutViewClick(@NonNull IView view) {
|
||||
if (isAttrEditorShowing()) {
|
||||
getAttrEditorSheet().dismiss();
|
||||
private void finishWithResult(String result) {
|
||||
final var intent = new Intent();
|
||||
intent.putExtra(KEY_GENERATED_CODE, result);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
getAttrEditorSheet().setSelectedView(view);
|
||||
|
||||
if (!getSupportFragmentManager().isDestroyed()) {
|
||||
getAttrEditorSheet().show(getSupportFragmentManager(), EDITOR_SHEET_DIALOG_TAG);
|
||||
private void finishWithError() {
|
||||
final var intent = new Intent();
|
||||
setResult(-123, intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAttrEditorShowing() {
|
||||
return getAttrEditorSheet().getDialog() != null && getAttrEditorSheet().getDialog().isShowing();
|
||||
}
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LOG.logThis();
|
||||
if (isAttrEditorShowing()) {
|
||||
getAttrEditorSheet().dismiss();
|
||||
}
|
||||
|
||||
private void setupWidgets() {
|
||||
final var android = new UIWidgetGroup(getString(R.string.widget_group_android));
|
||||
android.addChild(
|
||||
new UIWidget(getString(R.string.widget_button), R.drawable.ic_widget_button, Button.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_checkbox), R.drawable.ic_widget_checkbox, CheckBox.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_checked_textview),
|
||||
R.drawable.ic_widget_checked_textview,
|
||||
CheckedTextView.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_edittext), R.drawable.ic_widget_edittext, EditText.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_image_button),
|
||||
R.drawable.ic_widget_image,
|
||||
ImageButton.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_image_view), R.drawable.ic_widget_image, ImageView.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_progressbar),
|
||||
R.drawable.ic_widget_progress,
|
||||
ProgressBar.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_radio_button),
|
||||
R.drawable.ic_widget_radio_button,
|
||||
RadioButton.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_seekbar), R.drawable.ic_widget_seekbar, SeekBar.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_spinner), R.drawable.ic_widget_spinner, Spinner.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_textview), R.drawable.ic_widget_textview, TextView.class));
|
||||
this.mEditorSheet = null;
|
||||
|
||||
// IMPORTANT: Do not select any other groups here
|
||||
android.setSelected(true);
|
||||
this.checkedWidgetCategory = android;
|
||||
widgetGroups.add(android);
|
||||
// Release the reference to inflate listener and context from the layout inflater
|
||||
// Failing to do so will lead getSupportFragmentManager() to return a destroyed fragment
|
||||
// manager
|
||||
try {
|
||||
if (layoutInflater == null) {
|
||||
LOG.debug("Skipping layout inflater context reference release. Inflater is null");
|
||||
return;
|
||||
}
|
||||
|
||||
final var layouts = new UIWidgetGroup(getString(R.string.widget_group_layouts));
|
||||
layouts.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.layout_linear), R.drawable.ic_widget_linear, LinearLayout.class));
|
||||
layouts.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.layout_relative),
|
||||
R.drawable.ic_widget_relative,
|
||||
RelativeLayout.class));
|
||||
this.layoutInflater.unregisterListener(mInflateListener);
|
||||
this.layoutInflater.resetContextProvider(null);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to release activity references from ILayoutInflater", e);
|
||||
}
|
||||
|
||||
widgetGroups.add(layouts);
|
||||
|
||||
final var adapter = new WidgetGroupItemAdapter(widgetGroups, this::showWidgetGroup);
|
||||
mBinding.groupItems.setAdapter(adapter);
|
||||
|
||||
showWidgetGroup(android);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private void showWidgetGroup(@NonNull UIWidgetGroup group) {
|
||||
final var children = group.getChildren();
|
||||
this.mBinding.widgetItems.setAdapter(new WidgetItemAdapter(children, this));
|
||||
this.checkedWidgetCategory.setSelected(false);
|
||||
this.checkedWidgetCategory = group;
|
||||
this.checkedWidgetCategory.setSelected(true);
|
||||
|
||||
TransitionManager.beginDelayedTransition(
|
||||
mBinding.navigation,
|
||||
new TransitionSet().addTransition(new Slide(Gravity.END)).addTransition(new Fade()));
|
||||
|
||||
//noinspection ConstantConditions
|
||||
this.mBinding.groupItems.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private TextView createErrorText(Throwable th) {
|
||||
final TextView error = new TextView(this);
|
||||
error.setText(ThrowableUtils.getFullStackTrace(th));
|
||||
error.setLayoutParams(new ViewGroup.LayoutParams(-2, -2));
|
||||
error.setTextColor(ContextCompat.getColor(this, android.R.color.black));
|
||||
error.setBackgroundColor(ContextCompat.getColor(this, android.R.color.white));
|
||||
return error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragStarted(View view) {
|
||||
mBinding.getRoot().closeDrawer(GravityCompat.START);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
private View.OnDragListener getOnDragListener(IViewGroup group) {
|
||||
// WidgetDragListener cannot be reused
|
||||
// This is because they keep a reference to the view group in which dragged view will be
|
||||
// added
|
||||
|
||||
return new WidgetDragListener(this, group, this::setupInflatedView);
|
||||
}
|
||||
|
||||
private void setDragDataToInflatedView(@NonNull IView view) {
|
||||
view.setExtraData(new WidgetDragData(true, view, null));
|
||||
}
|
||||
|
||||
private AttrEditorSheet getAttrEditorSheet() {
|
||||
return this.mEditorSheet == null
|
||||
? mEditorSheet = new AttrEditorSheet().setLayout(this.layout)
|
||||
: mEditorSheet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDeletionFailed(@NonNull IView view) {
|
||||
final var v = view.asView();
|
||||
if (mBinding.layoutContainer.indexOfChild(v) >= 0) {
|
||||
mBinding.layoutContainer.removeView(v);
|
||||
return true;
|
||||
super.onDestroy();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return this;
|
||||
}
|
||||
private void setupInflatedView(@NonNull IView view) {
|
||||
final var listener =
|
||||
new WidgetTouchListener(view, this::onLayoutViewClick, this::onLayoutViewLongClick);
|
||||
view.asView().setOnTouchListener(listener);
|
||||
|
||||
setDragDataToInflatedView(view);
|
||||
}
|
||||
|
||||
private boolean onLayoutViewLongClick(@NonNull IView view) {
|
||||
final var shadow = new WidgetDragShadowBuilder(view.asView());
|
||||
final var item = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG);
|
||||
final var clip =
|
||||
new ClipData(
|
||||
DesignerActivity.DRAGGING_WIDGET_TAG,
|
||||
new String[] {DesignerActivity.DRAGGING_WIDGET_MIME},
|
||||
item);
|
||||
final var data = view.getExtraData();
|
||||
|
||||
view.asView().startDragAndDrop(clip, shadow, data, 0);
|
||||
view.removeFromParent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onLayoutViewClick(@NonNull IView view) {
|
||||
if (isAttrEditorShowing()) {
|
||||
getAttrEditorSheet().dismiss();
|
||||
}
|
||||
|
||||
getAttrEditorSheet().setSelectedView(view);
|
||||
|
||||
if (!getSupportFragmentManager().isDestroyed()) {
|
||||
getAttrEditorSheet().show(getSupportFragmentManager(), EDITOR_SHEET_DIALOG_TAG);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAttrEditorShowing() {
|
||||
return getAttrEditorSheet().getDialog() != null
|
||||
&& getAttrEditorSheet().getDialog().isShowing();
|
||||
}
|
||||
|
||||
private void setupWidgets() {
|
||||
final var android = new UIWidgetGroup(getString(R.string.widget_group_android));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_button),
|
||||
R.drawable.ic_widget_button,
|
||||
Button.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_checkbox),
|
||||
R.drawable.ic_widget_checkbox,
|
||||
CheckBox.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_checked_textview),
|
||||
R.drawable.ic_widget_checked_textview,
|
||||
CheckedTextView.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_edittext),
|
||||
R.drawable.ic_widget_edittext,
|
||||
EditText.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_image_button),
|
||||
R.drawable.ic_widget_image,
|
||||
ImageButton.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_image_view),
|
||||
R.drawable.ic_widget_image,
|
||||
ImageView.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_progressbar),
|
||||
R.drawable.ic_widget_progress,
|
||||
ProgressBar.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_radio_button),
|
||||
R.drawable.ic_widget_radio_button,
|
||||
RadioButton.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_seekbar),
|
||||
R.drawable.ic_widget_seekbar,
|
||||
SeekBar.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_spinner),
|
||||
R.drawable.ic_widget_spinner,
|
||||
Spinner.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_textview),
|
||||
R.drawable.ic_widget_textview,
|
||||
TextView.class));
|
||||
|
||||
// IMPORTANT: Do not select any other groups here
|
||||
android.setSelected(true);
|
||||
this.checkedWidgetCategory = android;
|
||||
widgetGroups.add(android);
|
||||
|
||||
final var layouts = new UIWidgetGroup(getString(R.string.widget_group_layouts));
|
||||
layouts.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.layout_linear),
|
||||
R.drawable.ic_widget_linear,
|
||||
LinearLayout.class));
|
||||
layouts.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.layout_relative),
|
||||
R.drawable.ic_widget_relative,
|
||||
RelativeLayout.class));
|
||||
|
||||
widgetGroups.add(layouts);
|
||||
|
||||
final var adapter = new WidgetGroupItemAdapter(widgetGroups, this::showWidgetGroup);
|
||||
mBinding.groupItems.setAdapter(adapter);
|
||||
|
||||
showWidgetGroup(android);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private void showWidgetGroup(@NonNull UIWidgetGroup group) {
|
||||
final var children = group.getChildren();
|
||||
this.mBinding.widgetItems.setAdapter(new WidgetItemAdapter(children, this));
|
||||
this.checkedWidgetCategory.setSelected(false);
|
||||
this.checkedWidgetCategory = group;
|
||||
this.checkedWidgetCategory.setSelected(true);
|
||||
|
||||
TransitionManager.beginDelayedTransition(
|
||||
mBinding.navigation,
|
||||
new TransitionSet()
|
||||
.addTransition(new Slide(Gravity.END))
|
||||
.addTransition(new Fade()));
|
||||
|
||||
//noinspection ConstantConditions
|
||||
this.mBinding.groupItems.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private TextView createErrorText(Throwable th) {
|
||||
final TextView error = new TextView(this);
|
||||
error.setText(ThrowableUtils.getFullStackTrace(th));
|
||||
error.setLayoutParams(new ViewGroup.LayoutParams(-2, -2));
|
||||
error.setTextColor(ContextCompat.getColor(this, android.R.color.black));
|
||||
error.setBackgroundColor(ContextCompat.getColor(this, android.R.color.white));
|
||||
return error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragStarted(View view) {
|
||||
mBinding.getRoot().closeDrawer(GravityCompat.START);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
private View.OnDragListener getOnDragListener(IViewGroup group) {
|
||||
// WidgetDragListener cannot be reused
|
||||
// This is because they keep a reference to the view group in which dragged view will be
|
||||
// added
|
||||
|
||||
return new WidgetDragListener(this, group, this::setupInflatedView);
|
||||
}
|
||||
|
||||
private void setDragDataToInflatedView(@NonNull IView view) {
|
||||
view.setExtraData(new WidgetDragData(true, view, null));
|
||||
}
|
||||
|
||||
private AttrEditorSheet getAttrEditorSheet() {
|
||||
return this.mEditorSheet == null
|
||||
? mEditorSheet = new AttrEditorSheet().setLayout(this.layout)
|
||||
: mEditorSheet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDeletionFailed(@NonNull IView view) {
|
||||
final var v = view.asView();
|
||||
if (mBinding.layoutContainer.indexOfChild(v) >= 0) {
|
||||
mBinding.layoutContainer.removeView(v);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
@@ -51,7 +50,6 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.transition.Slide;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import com.blankj.utilcode.util.ClipboardUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.blankj.utilcode.util.IntentUtils;
|
||||
@@ -116,9 +114,8 @@ import com.itsaky.lsp.models.InitializeParams;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import com.unnamed.b.atv.model.TreeNode;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
import io.github.rosemoe.sora.event.Unsubscribe;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -132,11 +129,9 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
import io.github.rosemoe.sora.event.Unsubscribe;
|
||||
import me.piruin.quickaction.ActionItem;
|
||||
import me.piruin.quickaction.QuickAction;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
public class EditorActivity extends StudioActivity
|
||||
implements FileTreeFragment.FileActionListener,
|
||||
|
||||
@@ -29,9 +29,7 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.ClipboardUtils;
|
||||
import com.blankj.utilcode.util.KeyboardUtils;
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
@@ -56,12 +54,10 @@ import com.itsaky.terminal.TerminalSessionClient;
|
||||
import com.itsaky.terminal.TextStyle;
|
||||
import com.itsaky.terminal.view.TerminalView;
|
||||
import com.itsaky.terminal.view.TerminalViewClient;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.Map;
|
||||
import org.json.JSONException;
|
||||
|
||||
public class TerminalActivity extends StudioActivity {
|
||||
|
||||
|
||||
@@ -18,17 +18,14 @@ package com.itsaky.androidide.adapters;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
|
||||
import com.itsaky.androidide.fragments.attr.BaseValueEditorFragment;
|
||||
import com.itsaky.androidide.models.XMLAttribute;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -25,7 +25,6 @@ import androidx.collection.LongSparseArray;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.fragments.DiagnosticsListFragment;
|
||||
import com.itsaky.androidide.fragments.IDELogFragment;
|
||||
@@ -33,7 +32,6 @@ import com.itsaky.androidide.fragments.LogViewFragment;
|
||||
import com.itsaky.androidide.fragments.SearchResultFragment;
|
||||
import com.itsaky.androidide.fragments.SimpleOutputFragment;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -19,13 +19,10 @@ package com.itsaky.androidide.adapters;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.itsaky.androidide.databinding.LayoutTextActionItemBinding;
|
||||
import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
package com.itsaky.androidide.app;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.ThrowableUtils;
|
||||
import com.google.firebase.messaging.FirebaseMessaging;
|
||||
import com.itsaky.androidide.CrashHandlerActivity;
|
||||
@@ -38,7 +36,6 @@ import com.itsaky.lsp.java.JavaLanguageServer;
|
||||
import com.itsaky.lsp.xml.XMLLanguageServer;
|
||||
import com.itsaky.sdk.SDKInfo;
|
||||
import com.itsaky.widgets.WidgetInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -46,114 +43,114 @@ import java.util.concurrent.CompletionException;
|
||||
|
||||
public class StudioApp extends BaseApplication {
|
||||
|
||||
private static final Logger LOG = Logger.instance("StudioApp");
|
||||
private static StudioApp instance;
|
||||
private static SDKInfo sdkInfo;
|
||||
private final ILanguageServer mJavaLanguageServer = new JavaLanguageServer();
|
||||
private final ILanguageServer mXMLLanguageServer = new XMLLanguageServer();
|
||||
private IResourceTable mResTable;
|
||||
private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
|
||||
private static final Logger LOG = Logger.instance("StudioApp");
|
||||
private static StudioApp instance;
|
||||
private static SDKInfo sdkInfo;
|
||||
private final ILanguageServer mJavaLanguageServer = new JavaLanguageServer();
|
||||
private final ILanguageServer mXMLLanguageServer = new XMLLanguageServer();
|
||||
private IResourceTable mResTable;
|
||||
private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
|
||||
|
||||
public static StudioApp getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
instance = this;
|
||||
this.uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler(this::handleCrash);
|
||||
super.onCreate();
|
||||
|
||||
FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_UPDATE);
|
||||
FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_DEV_MSGS);
|
||||
|
||||
getApiInformation();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ILanguageServer getJavaLanguageServer() {
|
||||
return mJavaLanguageServer;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ILanguageServer getXMLLanguageServer() {
|
||||
return mXMLLanguageServer;
|
||||
}
|
||||
|
||||
private void handleCrash(Thread thread, Throwable th) {
|
||||
writeException(th);
|
||||
|
||||
try {
|
||||
final var intent = new Intent();
|
||||
intent.setAction(CrashHandlerActivity.REPORT_ACTION);
|
||||
intent.putExtra(CrashHandlerActivity.TRACE_KEY, ThrowableUtils.getFullStackTrace(th));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
|
||||
if (this.uncaughtExceptionHandler != null) {
|
||||
this.uncaughtExceptionHandler.uncaughtException(thread, th);
|
||||
}
|
||||
System.exit(1);
|
||||
} catch (Throwable error) {
|
||||
LOG.error("Unable to show crash handler activity", error);
|
||||
public static StudioApp getInstance() {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<LayoutInflaterConfiguration> createInflaterConfig(
|
||||
ILayoutInflater.ContextProvider contextProvider, Set<File> resDirs) {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
instance = this;
|
||||
this.uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler(this::handleCrash);
|
||||
super.onCreate();
|
||||
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
try {
|
||||
final var sdkInfo = getApiInformation().get();
|
||||
return new LayoutInflaterConfiguration.Builder()
|
||||
.setAttrInfo(sdkInfo.getAttrInfo())
|
||||
.setWidgetInfo(sdkInfo.getWidgetInfo())
|
||||
.setResourceFinder(getResourceTable())
|
||||
.setResourceDirectories(resDirs)
|
||||
.setContextProvider(contextProvider)
|
||||
.create();
|
||||
} catch (Throwable e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_UPDATE);
|
||||
FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_DEV_MSGS);
|
||||
|
||||
public IResourceTable getResourceTable() {
|
||||
return mResTable == null ? mResTable = new ProjectResourceTable() : mResTable;
|
||||
}
|
||||
getApiInformation();
|
||||
}
|
||||
|
||||
/** Reads API version information from api-versions.xml */
|
||||
public CompletableFuture<SDKInfo> getApiInformation() {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
if (sdkInfo == null) {
|
||||
try {
|
||||
sdkInfo = new SDKInfo(StudioApp.this);
|
||||
((XMLLanguageServer) mXMLLanguageServer).setupSDK(sdkInfo);
|
||||
} catch (Throwable th) {
|
||||
LOG.error(getString(R.string.err_init_sdkinfo), th);
|
||||
@NonNull
|
||||
public ILanguageServer getJavaLanguageServer() {
|
||||
return mJavaLanguageServer;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ILanguageServer getXMLLanguageServer() {
|
||||
return mXMLLanguageServer;
|
||||
}
|
||||
|
||||
private void handleCrash(Thread thread, Throwable th) {
|
||||
writeException(th);
|
||||
|
||||
try {
|
||||
final var intent = new Intent();
|
||||
intent.setAction(CrashHandlerActivity.REPORT_ACTION);
|
||||
intent.putExtra(CrashHandlerActivity.TRACE_KEY, ThrowableUtils.getFullStackTrace(th));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
|
||||
if (this.uncaughtExceptionHandler != null) {
|
||||
this.uncaughtExceptionHandler.uncaughtException(thread, th);
|
||||
}
|
||||
}
|
||||
System.exit(1);
|
||||
} catch (Throwable error) {
|
||||
LOG.error("Unable to show crash handler activity", error);
|
||||
}
|
||||
}
|
||||
|
||||
return sdkInfo;
|
||||
});
|
||||
}
|
||||
public CompletableFuture<LayoutInflaterConfiguration> createInflaterConfig(
|
||||
ILayoutInflater.ContextProvider contextProvider, Set<File> resDirs) {
|
||||
|
||||
public void stopAllDaemons() {
|
||||
newShell(null).bgAppend("gradle --stop");
|
||||
}
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
try {
|
||||
final var sdkInfo = getApiInformation().get();
|
||||
return new LayoutInflaterConfiguration.Builder()
|
||||
.setAttrInfo(sdkInfo.getAttrInfo())
|
||||
.setWidgetInfo(sdkInfo.getWidgetInfo())
|
||||
.setResourceFinder(getResourceTable())
|
||||
.setResourceDirectories(resDirs)
|
||||
.setContextProvider(contextProvider)
|
||||
.create();
|
||||
} catch (Throwable e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ApiInfo apiInfo() {
|
||||
return sdkInfo.getApiInfo();
|
||||
}
|
||||
public IResourceTable getResourceTable() {
|
||||
return mResTable == null ? mResTable = new ProjectResourceTable() : mResTable;
|
||||
}
|
||||
|
||||
public AttrInfo attrInfo() {
|
||||
return sdkInfo.getAttrInfo();
|
||||
}
|
||||
/** Reads API version information from api-versions.xml */
|
||||
public CompletableFuture<SDKInfo> getApiInformation() {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
if (sdkInfo == null) {
|
||||
try {
|
||||
sdkInfo = new SDKInfo(StudioApp.this);
|
||||
((XMLLanguageServer) mXMLLanguageServer).setupSDK(sdkInfo);
|
||||
} catch (Throwable th) {
|
||||
LOG.error(getString(R.string.err_init_sdkinfo), th);
|
||||
}
|
||||
}
|
||||
|
||||
public WidgetInfo widgetInfo() {
|
||||
return sdkInfo.getWidgetInfo();
|
||||
}
|
||||
return sdkInfo;
|
||||
});
|
||||
}
|
||||
|
||||
public void stopAllDaemons() {
|
||||
newShell(null).bgAppend("gradle --stop");
|
||||
}
|
||||
|
||||
public ApiInfo apiInfo() {
|
||||
return sdkInfo.getApiInfo();
|
||||
}
|
||||
|
||||
public AttrInfo attrInfo() {
|
||||
return sdkInfo.getAttrInfo();
|
||||
}
|
||||
|
||||
public WidgetInfo widgetInfo() {
|
||||
return sdkInfo.getWidgetInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,9 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.blankj.utilcode.util.ClipboardUtils;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.BaseApplication;
|
||||
|
||||
@@ -26,11 +26,9 @@ 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.blankj.utilcode.util.FileUtils;
|
||||
import com.blankj.utilcode.util.IntentUtils;
|
||||
@@ -47,7 +45,6 @@ import com.itsaky.androidide.views.editor.CodeEditorView;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import com.unnamed.b.atv.model.TreeNode;
|
||||
import com.unnamed.b.atv.view.AndroidTreeView;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
@@ -23,17 +23,14 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.itsaky.androidide.databinding.FragmentNonEditableEditorBinding;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
|
||||
import io.github.rosemoe.sora.lang.EmptyLanguage;
|
||||
|
||||
public abstract class NonEditableEditorFragment extends Fragment {
|
||||
|
||||
@@ -19,12 +19,9 @@ package com.itsaky.androidide.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -20,14 +20,11 @@ package com.itsaky.androidide.fragments.attr;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.itsaky.androidide.models.XMLAttribute;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,11 +21,9 @@
|
||||
package com.itsaky.androidide.fragments.preferences;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.R;
|
||||
@@ -33,7 +31,6 @@ import com.itsaky.androidide.fragments.sheets.ProgressSheet;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class BuildPreferences extends BasePreferenceFragment
|
||||
|
||||
@@ -35,12 +35,10 @@ import static com.itsaky.lsp.java.models.JavaServerSettings.KEY_JAVA_PREF_GOOGLE
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.databinding.LayoutTextSizeSliderBinding;
|
||||
|
||||
@@ -22,11 +22,9 @@ import static com.itsaky.androidide.utils.Logger.instance;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.itsaky.androidide.AboutActivity;
|
||||
import com.itsaky.androidide.BuildConfig;
|
||||
import com.itsaky.androidide.R;
|
||||
|
||||
@@ -22,11 +22,9 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.SimpleIconTextAdapter;
|
||||
@@ -44,9 +42,6 @@ import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IView;
|
||||
import com.itsaky.inflater.impl.UiAttribute;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -56,302 +51,315 @@ import java.util.Objects;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
public class AttrEditorSheet extends BottomSheetDialogFragment
|
||||
implements SimpleIconTextAdapter.OnBindListener<IconTextListItem>,
|
||||
Consumer<Attr>,
|
||||
BaseValueEditorFragment.OnValueChangeListener {
|
||||
implements SimpleIconTextAdapter.OnBindListener<IconTextListItem>,
|
||||
Consumer<Attr>,
|
||||
BaseValueEditorFragment.OnValueChangeListener {
|
||||
|
||||
private static final List<IconTextListItem> VIEW_ACTIONS = new ArrayList<>();
|
||||
private static final Logger LOG = Logger.instance("AttrBottomSheet");
|
||||
private AttributeListSheet mAttrListSheet;
|
||||
private AttrValueEditorSheet mValueEditorSheet;
|
||||
private IView selectedView;
|
||||
private static final List<IconTextListItem> VIEW_ACTIONS = new ArrayList<>();
|
||||
private static final Logger LOG = Logger.instance("AttrBottomSheet");
|
||||
private AttributeListSheet mAttrListSheet;
|
||||
private AttrValueEditorSheet mValueEditorSheet;
|
||||
private IView selectedView;
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private File layout;
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private File layout;
|
||||
|
||||
private LayoutAttrEditorSheetBinding binding;
|
||||
private OnViewDeletionFailedListener mDeletionFailedListener;
|
||||
private LayoutAttrEditorSheetBinding binding;
|
||||
private OnViewDeletionFailedListener mDeletionFailedListener;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
this.binding =
|
||||
LayoutAttrEditorSheetBinding.inflate(LayoutInflater.from(getContext()), container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (VIEW_ACTIONS.isEmpty()) {
|
||||
Collections.addAll(
|
||||
VIEW_ACTIONS,
|
||||
IconTextListItem.create(getString(R.string.msg_viewaction_add_attr), R.drawable.ic_add),
|
||||
IconTextListItem.create(
|
||||
getString(R.string.title_viewaction_delete), R.drawable.ic_delete),
|
||||
IconTextListItem.create(
|
||||
getString(R.string.msg_viewaction_select_parent), R.drawable.ic_view_select_parent));
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
this.binding =
|
||||
LayoutAttrEditorSheetBinding.inflate(
|
||||
LayoutInflater.from(getContext()), container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
setupViewData();
|
||||
}
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mDeletionFailedListener = (OnViewDeletionFailedListener) context;
|
||||
}
|
||||
|
||||
public AttrEditorSheet setLayout(File layout) {
|
||||
Objects.requireNonNull(layout);
|
||||
|
||||
this.layout = layout;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setupViewData() {
|
||||
binding.actionsList.setAdapter(new SimpleIconTextAdapter(VIEW_ACTIONS).setOnBindListener(this));
|
||||
|
||||
if (dismissIfSelectedNull()) {
|
||||
LOG.error("Cannot edit attributes of a null view.");
|
||||
return;
|
||||
}
|
||||
|
||||
binding.widgetName.setText(this.selectedView.getXmlTag());
|
||||
binding.attrList.setAdapter(
|
||||
new XMLAttributeListAdapter(
|
||||
this.selectedView.getAttributes().stream()
|
||||
.map(XMLAttribute::new)
|
||||
.collect(Collectors.toList()),
|
||||
this::onAttrClick));
|
||||
}
|
||||
|
||||
public void setSelectedView(IView view) {
|
||||
this.selectedView = view;
|
||||
}
|
||||
|
||||
private void onAttrClick(
|
||||
LayoutAttrEditorSheetItemBinding binding, @NonNull XMLAttribute attribute) {
|
||||
|
||||
if (dismissIfSelectedNull()) {
|
||||
LOG.error("Cannot edit attributes of a null view.");
|
||||
return;
|
||||
}
|
||||
|
||||
final var format = attribute.findFormat();
|
||||
if (format == -1) {
|
||||
StudioApp.getInstance().toast(getString(R.string.msg_no_attr_format), Toaster.Type.ERROR);
|
||||
LOG.error(getString(R.string.msg_no_attr_format), attribute);
|
||||
return;
|
||||
}
|
||||
|
||||
showValueEditorSheet(attribute);
|
||||
}
|
||||
|
||||
private void showValueEditorSheet(@NonNull XMLAttribute attribute) {
|
||||
getValueEditorSheet(attribute).show(getChildFragmentManager(), "attr_value_editor_sheet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postBind(
|
||||
IconTextListItem item, @NonNull SimpleIconTextAdapter.VH holder, int position) {
|
||||
final var binding = holder.binding;
|
||||
binding.getRoot().setOnClickListener(v -> onViewActionClick(position));
|
||||
}
|
||||
|
||||
private void onViewActionClick(int position) {
|
||||
if (dismissIfSelectedNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (position == 0) { // Add attribute
|
||||
var tag = this.selectedView.getXmlTag();
|
||||
if ("include".equals(tag) || "merge".equals(tag)) {
|
||||
tag = "View";
|
||||
}
|
||||
|
||||
final var attrs = StudioApp.getInstance().attrInfo();
|
||||
final var style = attrs.getStyle(tag);
|
||||
if (style == null) {
|
||||
LOG.error("Unable to retrieve attributes for tag:", tag);
|
||||
return;
|
||||
}
|
||||
|
||||
final var attributes = new TreeSet<Attr>(Comparator.comparing(attr -> attr.name));
|
||||
attributes.addAll(style.attributes);
|
||||
attributes.addAll(attrs.NO_PARENT.attributes);
|
||||
|
||||
final var widgetInfo = StudioApp.getInstance().widgetInfo();
|
||||
final var widget = widgetInfo.getWidgetBySimpleName(tag);
|
||||
if (widget != null) {
|
||||
for (var superclass : widget.superclasses) {
|
||||
if ("java.lang.Object".equals(superclass)) {
|
||||
break;
|
||||
}
|
||||
|
||||
final var simpleName = superclass.substring(superclass.lastIndexOf(".") + 1);
|
||||
final var superStyle = attrs.getStyle(simpleName);
|
||||
if (superStyle != null) {
|
||||
attributes.addAll(superStyle.attributes);
|
||||
}
|
||||
if (VIEW_ACTIONS.isEmpty()) {
|
||||
Collections.addAll(
|
||||
VIEW_ACTIONS,
|
||||
IconTextListItem.create(
|
||||
getString(R.string.msg_viewaction_add_attr), R.drawable.ic_add),
|
||||
IconTextListItem.create(
|
||||
getString(R.string.title_viewaction_delete), R.drawable.ic_delete),
|
||||
IconTextListItem.create(
|
||||
getString(R.string.msg_viewaction_select_parent),
|
||||
R.drawable.ic_view_select_parent));
|
||||
}
|
||||
}
|
||||
|
||||
final var parent = this.selectedView.getParent();
|
||||
if (parent != null) {
|
||||
final var parentTag = parent.getXmlTag();
|
||||
final var parentWidget = widgetInfo.getWidgetBySimpleName(parentTag);
|
||||
if (parentWidget != null) {
|
||||
final var parentLayoutParams = attrs.getStyle(parentTag + "_Layout");
|
||||
if (parentLayoutParams != null) {
|
||||
attributes.addAll(parentLayoutParams.attributes);
|
||||
}
|
||||
setupViewData();
|
||||
}
|
||||
|
||||
final var paramSuperclasses = widgetInfo.getLayoutParamSuperClasses(parentWidget.name);
|
||||
if (paramSuperclasses != null) {
|
||||
for (var superclass : paramSuperclasses) {
|
||||
if ("java.lang.Object".equals(superclass)) {
|
||||
continue;
|
||||
}
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mDeletionFailedListener = (OnViewDeletionFailedListener) context;
|
||||
}
|
||||
|
||||
final var split = superclass.split("\\.");
|
||||
final var simpleClassName = split[split.length - 2];
|
||||
var paramName = split[split.length - 1];
|
||||
paramName = paramName.substring(0, paramName.length() - "Params".length());
|
||||
final var superParamEntry = attrs.getStyle(simpleClassName + "_" + paramName);
|
||||
if (superParamEntry != null) {
|
||||
attributes.addAll(superParamEntry.attributes);
|
||||
}
|
||||
public AttrEditorSheet setLayout(File layout) {
|
||||
Objects.requireNonNull(layout);
|
||||
|
||||
this.layout = layout;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setupViewData() {
|
||||
binding.actionsList.setAdapter(
|
||||
new SimpleIconTextAdapter(VIEW_ACTIONS).setOnBindListener(this));
|
||||
|
||||
if (dismissIfSelectedNull()) {
|
||||
LOG.error("Cannot edit attributes of a null view.");
|
||||
return;
|
||||
}
|
||||
|
||||
binding.widgetName.setText(this.selectedView.getXmlTag());
|
||||
binding.attrList.setAdapter(
|
||||
new XMLAttributeListAdapter(
|
||||
this.selectedView.getAttributes().stream()
|
||||
.map(XMLAttribute::new)
|
||||
.collect(Collectors.toList()),
|
||||
this::onAttrClick));
|
||||
}
|
||||
|
||||
public void setSelectedView(IView view) {
|
||||
this.selectedView = view;
|
||||
}
|
||||
|
||||
private void onAttrClick(
|
||||
LayoutAttrEditorSheetItemBinding binding, @NonNull XMLAttribute attribute) {
|
||||
|
||||
if (dismissIfSelectedNull()) {
|
||||
LOG.error("Cannot edit attributes of a null view.");
|
||||
return;
|
||||
}
|
||||
|
||||
final var format = attribute.findFormat();
|
||||
if (format == -1) {
|
||||
StudioApp.getInstance()
|
||||
.toast(getString(R.string.msg_no_attr_format), Toaster.Type.ERROR);
|
||||
LOG.error(getString(R.string.msg_no_attr_format), attribute);
|
||||
return;
|
||||
}
|
||||
|
||||
showValueEditorSheet(attribute);
|
||||
}
|
||||
|
||||
private void showValueEditorSheet(@NonNull XMLAttribute attribute) {
|
||||
getValueEditorSheet(attribute).show(getChildFragmentManager(), "attr_value_editor_sheet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postBind(
|
||||
IconTextListItem item, @NonNull SimpleIconTextAdapter.VH holder, int position) {
|
||||
final var binding = holder.binding;
|
||||
binding.getRoot().setOnClickListener(v -> onViewActionClick(position));
|
||||
}
|
||||
|
||||
private void onViewActionClick(int position) {
|
||||
if (dismissIfSelectedNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (position == 0) { // Add attribute
|
||||
var tag = this.selectedView.getXmlTag();
|
||||
if ("include".equals(tag) || "merge".equals(tag)) {
|
||||
tag = "View";
|
||||
}
|
||||
}
|
||||
|
||||
final var attrs = StudioApp.getInstance().attrInfo();
|
||||
final var style = attrs.getStyle(tag);
|
||||
if (style == null) {
|
||||
LOG.error("Unable to retrieve attributes for tag:", tag);
|
||||
return;
|
||||
}
|
||||
|
||||
final var attributes = new TreeSet<Attr>(Comparator.comparing(attr -> attr.name));
|
||||
attributes.addAll(style.attributes);
|
||||
attributes.addAll(attrs.NO_PARENT.attributes);
|
||||
|
||||
final var widgetInfo = StudioApp.getInstance().widgetInfo();
|
||||
final var widget = widgetInfo.getWidgetBySimpleName(tag);
|
||||
if (widget != null) {
|
||||
for (var superclass : widget.superclasses) {
|
||||
if ("java.lang.Object".equals(superclass)) {
|
||||
break;
|
||||
}
|
||||
|
||||
final var simpleName = superclass.substring(superclass.lastIndexOf(".") + 1);
|
||||
final var superStyle = attrs.getStyle(simpleName);
|
||||
if (superStyle != null) {
|
||||
attributes.addAll(superStyle.attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final var parent = this.selectedView.getParent();
|
||||
if (parent != null) {
|
||||
final var parentTag = parent.getXmlTag();
|
||||
final var parentWidget = widgetInfo.getWidgetBySimpleName(parentTag);
|
||||
if (parentWidget != null) {
|
||||
final var parentLayoutParams = attrs.getStyle(parentTag + "_Layout");
|
||||
if (parentLayoutParams != null) {
|
||||
attributes.addAll(parentLayoutParams.attributes);
|
||||
}
|
||||
|
||||
final var paramSuperclasses =
|
||||
widgetInfo.getLayoutParamSuperClasses(parentWidget.name);
|
||||
if (paramSuperclasses != null) {
|
||||
for (var superclass : paramSuperclasses) {
|
||||
if ("java.lang.Object".equals(superclass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var split = superclass.split("\\.");
|
||||
final var simpleClassName = split[split.length - 2];
|
||||
var paramName = split[split.length - 1];
|
||||
paramName =
|
||||
paramName.substring(0, paramName.length() - "Params".length());
|
||||
final var superParamEntry =
|
||||
attrs.getStyle(simpleClassName + "_" + paramName);
|
||||
if (superParamEntry != null) {
|
||||
attributes.addAll(superParamEntry.attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final var sheet = getAttrListSheet();
|
||||
sheet.setItems(filterAppliedAttributes(attributes));
|
||||
sheet.show(getChildFragmentManager(), "attr_list_sheet");
|
||||
|
||||
} else if (position == 1) { // Delete
|
||||
DialogUtils.newYesNoDialog(
|
||||
getContext(),
|
||||
(dialog, which) -> {
|
||||
var handled = selectedView.removeFromParent();
|
||||
if (!handled) {
|
||||
handled =
|
||||
mDeletionFailedListener != null
|
||||
&& mDeletionFailedListener.onDeletionFailed(
|
||||
this.selectedView);
|
||||
}
|
||||
if (!handled) {
|
||||
StudioApp.getInstance()
|
||||
.toast(
|
||||
getString(R.string.msg_view_deletion_failed),
|
||||
Toaster.Type.ERROR);
|
||||
} else {
|
||||
dismiss();
|
||||
}
|
||||
},
|
||||
(dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
} else if (position == 2) { // Select parent
|
||||
if (this.selectedView.getParent() == null) {
|
||||
StudioApp.getInstance()
|
||||
.toast(getString(R.string.msg_no_view_parent), Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedView = this.selectedView.getParent();
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot());
|
||||
setupViewData();
|
||||
}
|
||||
}
|
||||
|
||||
final var sheet = getAttrListSheet();
|
||||
sheet.setItems(filterAppliedAttributes(attributes));
|
||||
sheet.show(getChildFragmentManager(), "attr_list_sheet");
|
||||
|
||||
} else if (position == 1) { // Delete
|
||||
DialogUtils.newYesNoDialog(
|
||||
getContext(),
|
||||
(dialog, which) -> {
|
||||
var handled = selectedView.removeFromParent();
|
||||
if (!handled) {
|
||||
handled =
|
||||
mDeletionFailedListener != null
|
||||
&& mDeletionFailedListener.onDeletionFailed(this.selectedView);
|
||||
}
|
||||
if (!handled) {
|
||||
StudioApp.getInstance()
|
||||
.toast(getString(R.string.msg_view_deletion_failed), Toaster.Type.ERROR);
|
||||
} else {
|
||||
dismiss();
|
||||
}
|
||||
},
|
||||
(dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
} else if (position == 2) { // Select parent
|
||||
if (this.selectedView.getParent() == null) {
|
||||
StudioApp.getInstance().toast(getString(R.string.msg_no_view_parent), Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedView = this.selectedView.getParent();
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot());
|
||||
setupViewData();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
private List<Attr> filterAppliedAttributes(@NonNull TreeSet<Attr> attributes) {
|
||||
attributes.removeIf(attr -> this.selectedView.hasAttribute(attr.namespace, attr.name));
|
||||
return new ArrayList<>(attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Attr attr) {
|
||||
addNewAttribute(attr);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private AttributeListSheet getAttrListSheet() {
|
||||
if (mAttrListSheet == null) {
|
||||
mAttrListSheet = new AttributeListSheet();
|
||||
mAttrListSheet.setCancelable(true);
|
||||
}
|
||||
|
||||
return mAttrListSheet;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public AttrValueEditorSheet getValueEditorSheet(@NonNull XMLAttribute attribute) {
|
||||
|
||||
if (mValueEditorSheet == null) {
|
||||
mValueEditorSheet = AttrValueEditorSheet.newInstance(attribute);
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
private List<Attr> filterAppliedAttributes(@NonNull TreeSet<Attr> attributes) {
|
||||
attributes.removeIf(attr -> this.selectedView.hasAttribute(attr.namespace, attr.name));
|
||||
return new ArrayList<>(attributes);
|
||||
}
|
||||
|
||||
mValueEditorSheet.setAttribute(attribute);
|
||||
return mValueEditorSheet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onValueChanged(@NonNull IAttribute attribute, String newValue) {
|
||||
if (dismissIfSelectedNull()) {
|
||||
LOG.warn("Cannot update attribute with new value. Selected view is null.");
|
||||
return;
|
||||
@Override
|
||||
public void accept(Attr attr) {
|
||||
addNewAttribute(attr);
|
||||
}
|
||||
|
||||
final var attributeName = attribute.getAttributeName();
|
||||
final var namespace = attribute.getNamespace();
|
||||
@NonNull
|
||||
private AttributeListSheet getAttrListSheet() {
|
||||
if (mAttrListSheet == null) {
|
||||
mAttrListSheet = new AttributeListSheet();
|
||||
mAttrListSheet.setCancelable(true);
|
||||
}
|
||||
|
||||
if (this.selectedView.hasAttribute(namespace, attributeName)) {
|
||||
this.selectedView.updateAttribute(namespace, attributeName, newValue);
|
||||
} else {
|
||||
final var attr = new UiAttribute(namespace, attributeName, newValue);
|
||||
this.selectedView.addAttribute(attr);
|
||||
return mAttrListSheet;
|
||||
}
|
||||
}
|
||||
|
||||
private void addNewAttribute(@NonNull Attr attr) {
|
||||
final XMLAttribute attribute = new XMLAttribute(attr.namespace, attr.name, "", false);
|
||||
attribute.setAttr(attr);
|
||||
showValueEditorSheet(attribute);
|
||||
}
|
||||
@NonNull
|
||||
public AttrValueEditorSheet getValueEditorSheet(@NonNull XMLAttribute attribute) {
|
||||
|
||||
private boolean dismissIfSelectedNull() {
|
||||
if (selectedView == null) {
|
||||
dismiss();
|
||||
return true;
|
||||
if (mValueEditorSheet == null) {
|
||||
mValueEditorSheet = AttrValueEditorSheet.newInstance(attribute);
|
||||
}
|
||||
|
||||
mValueEditorSheet.setAttribute(attribute);
|
||||
return mValueEditorSheet;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener can be used to get notified when we fail to remove a view from its parent. This is
|
||||
* used in {@link com.itsaky.androidide.DesignerActivity DesignerActivity}.
|
||||
*
|
||||
* <p>When the user tries to remove the outermost view of the inflated layout, AttrEditorSheet
|
||||
* fails to remove the view from its parent ({@link IView#getParent()} is null for root XML
|
||||
* layout). In this case, DesignerActivity check if the view that we were trying to delete is the
|
||||
* root layout or not. If it is the root layout, then it deletes the layout from the layout
|
||||
* container.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface OnViewDeletionFailedListener {
|
||||
@Override
|
||||
public void onValueChanged(@NonNull IAttribute attribute, String newValue) {
|
||||
if (dismissIfSelectedNull()) {
|
||||
LOG.warn("Cannot update attribute with new value. Selected view is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
final var attributeName = attribute.getAttributeName();
|
||||
final var namespace = attribute.getNamespace();
|
||||
|
||||
if (this.selectedView.hasAttribute(namespace, attributeName)) {
|
||||
this.selectedView.updateAttribute(namespace, attributeName, newValue);
|
||||
} else {
|
||||
final var attr = new UiAttribute(namespace, attributeName, newValue);
|
||||
this.selectedView.addAttribute(attr);
|
||||
}
|
||||
}
|
||||
|
||||
private void addNewAttribute(@NonNull Attr attr) {
|
||||
final XMLAttribute attribute = new XMLAttribute(attr.namespace, attr.name, "", false);
|
||||
attribute.setAttr(attr);
|
||||
showValueEditorSheet(attribute);
|
||||
}
|
||||
|
||||
private boolean dismissIfSelectedNull() {
|
||||
if (selectedView == null) {
|
||||
dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param view The view that was not removed from its parent.
|
||||
* @return {@code true} if the listener handled the error. {@code false} otherwise.
|
||||
* A listener can be used to get notified when we fail to remove a view from its parent. This is
|
||||
* used in {@link com.itsaky.androidide.DesignerActivity DesignerActivity}.
|
||||
*
|
||||
* <p>When the user tries to remove the outermost view of the inflated layout, AttrEditorSheet
|
||||
* fails to remove the view from its parent ({@link IView#getParent()} is null for root XML
|
||||
* layout). In this case, DesignerActivity check if the view that we were trying to delete is
|
||||
* the root layout or not. If it is the root layout, then it deletes the layout from the layout
|
||||
* container.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
boolean onDeletionFailed(IView view);
|
||||
}
|
||||
public interface OnViewDeletionFailedListener {
|
||||
|
||||
/**
|
||||
* @param view The view that was not removed from its parent.
|
||||
* @return {@code true} if the listener handled the error. {@code false} otherwise.
|
||||
*/
|
||||
boolean onDeletionFailed(IView view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,8 @@ import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.adapters.AttrValueFormatTabAdapter;
|
||||
import com.itsaky.androidide.databinding.LayoutAttrValueEditorBinding;
|
||||
@@ -55,10 +53,8 @@ import com.itsaky.androidide.fragments.attr.StringEditor;
|
||||
import com.itsaky.androidide.models.XMLAttribute;
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
/**
|
||||
* Sheet for editing value of an attribute.
|
||||
|
||||
@@ -23,18 +23,15 @@ import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.AttrListAdapter;
|
||||
import com.itsaky.androidide.databinding.LayoutAddAttrSheetBinding;
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -23,9 +23,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.databinding.LayoutSheetBinding;
|
||||
|
||||
|
||||
@@ -24,14 +24,11 @@ import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.itsaky.androidide.adapters.OptionsSheetAdapter;
|
||||
import com.itsaky.androidide.models.SheetOption;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -26,9 +26,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.databinding.LayoutProgressSheetBinding;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
|
||||
@@ -22,13 +22,10 @@ import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
|
||||
import io.github.rosemoe.sora.lang.EmptyLanguage;
|
||||
|
||||
public class TextSheetFragment extends BaseBottomSheetFragment {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.androidide.handlers;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
@@ -31,7 +30,6 @@ import com.itsaky.androidide.tasks.GradleTask;
|
||||
import com.itsaky.androidide.tasks.gradle.build.ApkGeneratingTask;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.androidide.language;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.api.ICompletionProvider;
|
||||
import com.itsaky.lsp.api.ILanguageServer;
|
||||
@@ -26,9 +25,11 @@ import com.itsaky.lsp.models.CompletionItem;
|
||||
import com.itsaky.lsp.models.CompletionParams;
|
||||
import com.itsaky.lsp.models.CompletionResult;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionHelper;
|
||||
import io.github.rosemoe.sora.text.CharPosition;
|
||||
import io.github.rosemoe.sora.text.ContentReference;
|
||||
import io.github.rosemoe.sora.util.MyCharacter;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -37,12 +38,7 @@ import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionHelper;
|
||||
import io.github.rosemoe.sora.text.CharPosition;
|
||||
import io.github.rosemoe.sora.text.ContentReference;
|
||||
import io.github.rosemoe.sora.util.MyCharacter;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
/**
|
||||
* Common implementation of completion provider which requests completions to provided language
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
package com.itsaky.androidide.language;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,17 +17,14 @@
|
||||
package com.itsaky.androidide.language;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.lsp.api.ILanguageServer;
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
|
||||
import io.github.rosemoe.sora.lang.Language;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.rosemoe.sora.lang.Language;
|
||||
|
||||
/**
|
||||
* Base class for language implementations in the IDE.
|
||||
*
|
||||
|
||||
@@ -18,19 +18,11 @@
|
||||
package com.itsaky.androidide.language.groovy;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.language.IDELanguage;
|
||||
import com.itsaky.androidide.lexers.groovy.GroovyLexer;
|
||||
import com.itsaky.androidide.utils.CharSequenceReader;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
import java.io.StringReader;
|
||||
|
||||
import io.github.rosemoe.sora.lang.analysis.AnalyzeManager;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionPublisher;
|
||||
@@ -40,6 +32,9 @@ import io.github.rosemoe.sora.text.CharPosition;
|
||||
import io.github.rosemoe.sora.text.ContentReference;
|
||||
import io.github.rosemoe.sora.text.TextUtils;
|
||||
import io.github.rosemoe.sora.widget.SymbolPairMatch;
|
||||
import java.io.StringReader;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
public class GroovyLanguage extends IDELanguage {
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.get;
|
||||
import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.withoutCompletion;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.language.IAnalyzeManager;
|
||||
import com.itsaky.androidide.lexers.java.JavaLexer;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
@@ -38,22 +37,19 @@ import com.itsaky.lsp.models.DiagnosticItem;
|
||||
import com.itsaky.lsp.models.DiagnosticSeverity;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CodePointCharStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager;
|
||||
import io.github.rosemoe.sora.lang.styling.CodeBlock;
|
||||
import io.github.rosemoe.sora.lang.styling.MappedSpans;
|
||||
import io.github.rosemoe.sora.lang.styling.Span;
|
||||
import io.github.rosemoe.sora.lang.styling.Styles;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CodePointCharStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
/**
|
||||
* Code analyzer for the Java Language
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
package com.itsaky.androidide.language.java;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.language.CommonCompletionProvider;
|
||||
import com.itsaky.androidide.language.IDELanguage;
|
||||
@@ -30,16 +28,6 @@ import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
import com.itsaky.lsp.api.ILanguageServer;
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.rosemoe.sora.lang.analysis.AnalyzeManager;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionPublisher;
|
||||
@@ -49,6 +37,13 @@ import io.github.rosemoe.sora.text.CharPosition;
|
||||
import io.github.rosemoe.sora.text.ContentReference;
|
||||
import io.github.rosemoe.sora.text.TextUtils;
|
||||
import io.github.rosemoe.sora.widget.SymbolPairMatch;
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
public class JavaLanguage extends IDELanguage {
|
||||
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
package com.itsaky.androidide.language.xml;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.language.CommonCompletionProvider;
|
||||
import com.itsaky.androidide.language.IDELanguage;
|
||||
@@ -29,14 +27,6 @@ import com.itsaky.androidide.utils.JavaCharacter;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
import com.itsaky.lsp.api.ILanguageServer;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.rosemoe.sora.lang.analysis.AnalyzeManager;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionPublisher;
|
||||
@@ -44,6 +34,11 @@ import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler;
|
||||
import io.github.rosemoe.sora.text.CharPosition;
|
||||
import io.github.rosemoe.sora.text.ContentReference;
|
||||
import io.github.rosemoe.sora.widget.SymbolPairMatch;
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
public class XMLLanguage extends IDELanguage {
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
@@ -50,7 +49,7 @@ import com.itsaky.lsp.models.Location;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -58,8 +57,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
|
||||
/** AndroidIDE specific implementation of the LanguageClient */
|
||||
public class IDELanguageClientImpl implements ILanguageClient {
|
||||
|
||||
|
||||
@@ -19,18 +19,14 @@ package com.itsaky.androidide.models;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.impl.UiAttribute;
|
||||
import com.itsaky.xml.INamespace;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
/**
|
||||
* A model that is used to show attributes list of a view.
|
||||
@@ -39,140 +35,144 @@ import java.util.Objects;
|
||||
*/
|
||||
public class XMLAttribute extends UiAttribute implements Parcelable {
|
||||
|
||||
public static final Creator<XMLAttribute> CREATOR =
|
||||
new Creator<>() {
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
@Override
|
||||
public XMLAttribute createFromParcel(Parcel in) {
|
||||
return new XMLAttribute(in);
|
||||
}
|
||||
public static final Creator<XMLAttribute> CREATOR =
|
||||
new Creator<>() {
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
@Override
|
||||
public XMLAttribute createFromParcel(Parcel in) {
|
||||
return new XMLAttribute(in);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
@Override
|
||||
public XMLAttribute[] newArray(int size) {
|
||||
return new XMLAttribute[size];
|
||||
}
|
||||
};
|
||||
public Attr attr;
|
||||
/** Is this attribute applied to any view? */
|
||||
private boolean isApplied;
|
||||
/**
|
||||
* Format of this attribute. May be stored here so we don't have to look in {@link
|
||||
* com.itsaky.attrinfo.AttrInfo AttrInfo} again and again.
|
||||
*/
|
||||
private int format = -1;
|
||||
@NonNull
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
@Override
|
||||
public XMLAttribute[] newArray(int size) {
|
||||
return new XMLAttribute[size];
|
||||
}
|
||||
};
|
||||
public Attr attr;
|
||||
/** Is this attribute applied to any view? */
|
||||
private boolean isApplied;
|
||||
/**
|
||||
* Format of this attribute. May be stored here so we don't have to look in {@link
|
||||
* com.itsaky.attrinfo.AttrInfo AttrInfo} again and again.
|
||||
*/
|
||||
private int format = -1;
|
||||
|
||||
public XMLAttribute(IAttribute attribute) {
|
||||
this(attribute, true);
|
||||
}
|
||||
|
||||
public XMLAttribute(@NonNull IAttribute attribute, boolean isApplied) {
|
||||
this(attribute.getNamespace(), attribute.getAttributeName(), attribute.getValue(), isApplied);
|
||||
}
|
||||
|
||||
public XMLAttribute(INamespace namespace, String name, String value, boolean isApplied) {
|
||||
super(namespace, name, value);
|
||||
this.isApplied = isApplied;
|
||||
}
|
||||
|
||||
private XMLAttribute(@NonNull Parcel in) {
|
||||
this(
|
||||
in.readParcelable(INamespace.class.getClassLoader()),
|
||||
in.readString(),
|
||||
in.readString(),
|
||||
in.readByte() != 0);
|
||||
this.attr = in.readParcelable(Attr.class.getClassLoader());
|
||||
this.format = in.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new attribute instance with the android namespace. {@link #isApplied()} will return
|
||||
* true by default.
|
||||
*
|
||||
* @param name The name of the attribute.
|
||||
* @param value The value of the attribute.
|
||||
* @return A new instance of this class with the provided data.
|
||||
*/
|
||||
@NonNull
|
||||
@Contract("_, _ -> new")
|
||||
public static XMLAttribute newAndroidAttribute(String name, String value) {
|
||||
return new XMLAttribute(INamespace.ANDROID, name, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new attribute instance with the android namespace. The {@link #isApplied()} will
|
||||
* return {@code false} by default.
|
||||
*
|
||||
* @param name The name of the attribute.
|
||||
* @return The new attribute instance.
|
||||
*/
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
public static XMLAttribute newAndroidAttribute(String name) {
|
||||
return new XMLAttribute(INamespace.ANDROID, name, "", false);
|
||||
}
|
||||
|
||||
public int getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public void setFormat(int format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public int findFormat() {
|
||||
|
||||
if (getFormat() > 0) {
|
||||
return getFormat();
|
||||
public XMLAttribute(IAttribute attribute) {
|
||||
this(attribute, true);
|
||||
}
|
||||
|
||||
final var attrs = StudioApp.getInstance().attrInfo();
|
||||
this.attr = attrs.getAttribute(getAttributeName());
|
||||
if (this.attr != null) {
|
||||
this.format = this.attr.format;
|
||||
public XMLAttribute(@NonNull IAttribute attribute, boolean isApplied) {
|
||||
this(
|
||||
attribute.getNamespace(),
|
||||
attribute.getAttributeName(),
|
||||
attribute.getValue(),
|
||||
isApplied);
|
||||
}
|
||||
|
||||
return getFormat();
|
||||
}
|
||||
public XMLAttribute(INamespace namespace, String name, String value, boolean isApplied) {
|
||||
super(namespace, name, value);
|
||||
this.isApplied = isApplied;
|
||||
}
|
||||
|
||||
public boolean hasFormat(int format) {
|
||||
return (this.format & format) != 0;
|
||||
}
|
||||
private XMLAttribute(@NonNull Parcel in) {
|
||||
this(
|
||||
in.readParcelable(INamespace.class.getClassLoader()),
|
||||
in.readString(),
|
||||
in.readString(),
|
||||
in.readByte() != 0);
|
||||
this.attr = in.readParcelable(Attr.class.getClassLoader());
|
||||
this.format = in.readInt();
|
||||
}
|
||||
|
||||
public Attr getAttr() {
|
||||
return attr;
|
||||
}
|
||||
/**
|
||||
* Create a new attribute instance with the android namespace. {@link #isApplied()} will return
|
||||
* true by default.
|
||||
*
|
||||
* @param name The name of the attribute.
|
||||
* @param value The value of the attribute.
|
||||
* @return A new instance of this class with the provided data.
|
||||
*/
|
||||
@NonNull
|
||||
@Contract("_, _ -> new")
|
||||
public static XMLAttribute newAndroidAttribute(String name, String value) {
|
||||
return new XMLAttribute(INamespace.ANDROID, name, value, true);
|
||||
}
|
||||
|
||||
public void setAttr(Attr attr) {
|
||||
Objects.requireNonNull(attr);
|
||||
this.attr = attr;
|
||||
setFormat(attr.format);
|
||||
}
|
||||
/**
|
||||
* Create a new attribute instance with the android namespace. The {@link #isApplied()} will
|
||||
* return {@code false} by default.
|
||||
*
|
||||
* @param name The name of the attribute.
|
||||
* @return The new attribute instance.
|
||||
*/
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
public static XMLAttribute newAndroidAttribute(String name) {
|
||||
return new XMLAttribute(INamespace.ANDROID, name, "", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(String value) {
|
||||
super.apply(value);
|
||||
isApplied = true;
|
||||
}
|
||||
public int getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public boolean isApplied() {
|
||||
return isApplied;
|
||||
}
|
||||
public void setFormat(int format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
public int findFormat() {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeParcelable(getNamespace(), flags);
|
||||
dest.writeString(getAttributeName());
|
||||
dest.writeString(getValue());
|
||||
dest.writeByte((byte) (isApplied ? 1 : 0));
|
||||
dest.writeParcelable(getAttr(), flags);
|
||||
dest.writeInt(this.format);
|
||||
}
|
||||
if (getFormat() > 0) {
|
||||
return getFormat();
|
||||
}
|
||||
|
||||
final var attrs = StudioApp.getInstance().attrInfo();
|
||||
this.attr = attrs.getAttribute(getAttributeName());
|
||||
if (this.attr != null) {
|
||||
this.format = this.attr.format;
|
||||
}
|
||||
|
||||
return getFormat();
|
||||
}
|
||||
|
||||
public boolean hasFormat(int format) {
|
||||
return (this.format & format) != 0;
|
||||
}
|
||||
|
||||
public Attr getAttr() {
|
||||
return attr;
|
||||
}
|
||||
|
||||
public void setAttr(Attr attr) {
|
||||
Objects.requireNonNull(attr);
|
||||
this.attr = attr;
|
||||
setFormat(attr.format);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(String value) {
|
||||
super.apply(value);
|
||||
isApplied = true;
|
||||
}
|
||||
|
||||
public boolean isApplied() {
|
||||
return isApplied;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeParcelable(getNamespace(), flags);
|
||||
dest.writeString(getAttributeName());
|
||||
dest.writeString(getValue());
|
||||
dest.writeByte((byte) (isApplied ? 1 : 0));
|
||||
dest.writeParcelable(getAttr(), flags);
|
||||
dest.writeInt(this.format);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,10 @@ import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.itsaky.androidide.R;
|
||||
|
||||
import io.github.rosemoe.sora.widget.base.EditorPopupWindow;
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,10 +38,8 @@ import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.itsaky.androidide.adapters.CompletionListAdapter;
|
||||
@@ -59,10 +57,9 @@ import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
import io.github.rosemoe.sora.lang.EmptyLanguage;
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
import java.io.File;
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Files;
|
||||
@@ -70,10 +67,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
import io.github.rosemoe.sora.lang.EmptyLanguage;
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
/**
|
||||
* A view that handles opened code editors.
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.itsaky.androidide.views.editor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,24 +19,20 @@ package com.itsaky.androidide.views.editor;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.appcompat.view.menu.MenuBuilder;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
import io.github.rosemoe.sora.event.ClickEvent;
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent;
|
||||
import io.github.rosemoe.sora.widget.component.EditorTextActionWindow;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.github.rosemoe.sora.event.ClickEvent;
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent;
|
||||
import io.github.rosemoe.sora.widget.component.EditorTextActionWindow;
|
||||
|
||||
/**
|
||||
* Text action mode actions for the editor.
|
||||
*
|
||||
|
||||
@@ -24,24 +24,14 @@ import android.graphics.drawable.GradientDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.TextActionItemAdapter;
|
||||
import com.itsaky.androidide.databinding.LayoutEditorActionsBinding;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.github.rosemoe.sora.event.HandleStateChangeEvent;
|
||||
import io.github.rosemoe.sora.event.ScrollEvent;
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent;
|
||||
@@ -50,6 +40,12 @@ import io.github.rosemoe.sora.event.Unsubscribe;
|
||||
import io.github.rosemoe.sora.widget.CodeEditor;
|
||||
import io.github.rosemoe.sora.widget.EditorTouchEventHandler;
|
||||
import io.github.rosemoe.sora.widget.base.EditorPopupWindow;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Presents text actions in a popup widow.
|
||||
|
||||
@@ -23,12 +23,10 @@ import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
@@ -60,20 +58,17 @@ import com.itsaky.lsp.models.SignatureHelp;
|
||||
import com.itsaky.lsp.models.SignatureHelpParams;
|
||||
import com.itsaky.lsp.util.DiagnosticUtil;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent;
|
||||
import io.github.rosemoe.sora.event.Unsubscribe;
|
||||
import io.github.rosemoe.sora.widget.CodeEditor;
|
||||
import io.github.rosemoe.sora.widget.component.EditorAutoCompletion;
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
public class IDEEditor extends CodeEditor {
|
||||
|
||||
|
||||
@@ -40,10 +40,10 @@ import java.util.Objects;
|
||||
public class Logger {
|
||||
|
||||
public static final int DEBUG = 0;
|
||||
public static final int WARNING = 0;
|
||||
public static final int ERROR = 0;
|
||||
public static final int INFO = 0;
|
||||
public static final int VERBOSE = 0;
|
||||
public static final int WARNING = 1;
|
||||
public static final int ERROR = 2;
|
||||
public static final int INFO = 3;
|
||||
public static final int VERBOSE = 4;
|
||||
private static final String MSG_SEPARATOR = " "; // Separate messages with a space.
|
||||
private static final List<LogListener> logListeners = new ArrayList<>();
|
||||
private static Logger instance;
|
||||
@@ -77,6 +77,25 @@ public class Logger {
|
||||
return createInstance(tag);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Contract(pure = true)
|
||||
public static String priorityText(int priority) {
|
||||
switch (priority) {
|
||||
case DEBUG:
|
||||
return "DEBUG";
|
||||
case INFO:
|
||||
return "INFO";
|
||||
case VERBOSE:
|
||||
return "VERBOSE";
|
||||
case ERROR:
|
||||
return "ERROR";
|
||||
case WARNING:
|
||||
return "WARNING";
|
||||
default:
|
||||
return "<UNKNOWN>";
|
||||
}
|
||||
}
|
||||
|
||||
public Logger warn(Object... messages) {
|
||||
final var msg = generateMessage(messages);
|
||||
Log.w(TAG, msg);
|
||||
|
||||
+9
-9
@@ -33,19 +33,19 @@ import org.json.JSONObject;
|
||||
*
|
||||
* <p>Examples: {@code # Empty: []
|
||||
*
|
||||
* <p><p><p><p><p><p><p># Single row: [[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]
|
||||
* <p><p><p><p><p><p><p><p># Single row: [[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]
|
||||
*
|
||||
* <p><p><p><p><p><p><p># 2 row: [['ESC','/',{key: '-', popup: '|'},'HOME','UP','END','PGUP'],
|
||||
* <p><p><p><p><p><p><p><p># 2 row: [['ESC','/',{key: '-', popup: '|'},'HOME','UP','END','PGUP'],
|
||||
* ['TAB','CTRL','ALT','LEFT','DOWN','RIGHT','PGDN']]
|
||||
*
|
||||
* <p><p><p><p><p><p><p># Advance: [[ {key: ESC, popup: {macro: "CTRL f d", display: "tmux exit"}},
|
||||
* {key: CTRL, popup: {macro: "CTRL f BKSP", display: "tmux ←"}}, {key: ALT, popup: {macro: "CTRL f
|
||||
* TAB", display: "tmux →"}}, {key: TAB, popup: {macro: "ALT a", display: A-a}}, {key: LEFT, popup:
|
||||
* HOME}, {key: DOWN, popup: PGDN}, {key: UP, popup: PGUP}, {key: RIGHT, popup: END}, {macro: "ALT
|
||||
* j", display: A-j, popup: {macro: "ALT g", display: A-g}}, {key: KEYBOARD, popup: {macro: "CTRL
|
||||
* d", display: exit}} ]]
|
||||
* <p><p><p><p><p><p><p><p># Advance: [[ {key: ESC, popup: {macro: "CTRL f d", display: "tmux
|
||||
* exit"}}, {key: CTRL, popup: {macro: "CTRL f BKSP", display: "tmux ←"}}, {key: ALT, popup: {macro:
|
||||
* "CTRL f TAB", display: "tmux →"}}, {key: TAB, popup: {macro: "ALT a", display: A-a}}, {key: LEFT,
|
||||
* popup: HOME}, {key: DOWN, popup: PGDN}, {key: UP, popup: PGUP}, {key: RIGHT, popup: END}, {macro:
|
||||
* "ALT j", display: A-j, popup: {macro: "ALT g", display: A-g}}, {key: KEYBOARD, popup: {macro:
|
||||
* "CTRL d", display: exit}} ]]
|
||||
*
|
||||
* <p><p><p><p><p><p><p>}
|
||||
* <p><p><p><p><p><p><p><p>}
|
||||
*
|
||||
* <p>Aliases are also allowed for the keys that you can pass as {@code extraKeyAliasMap}. Check
|
||||
* {@link VirtualKeysConstants#CONTROL_CHARS_ALIASES}.
|
||||
|
||||
@@ -18,12 +18,10 @@
|
||||
package com.itsaky.lsp.api;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.models.DocumentChangeEvent;
|
||||
import com.itsaky.lsp.models.DocumentCloseEvent;
|
||||
import com.itsaky.lsp.models.DocumentOpenEvent;
|
||||
import com.itsaky.lsp.models.DocumentSaveEvent;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.itsaky.lsp.util;
|
||||
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,13 +18,11 @@
|
||||
package com.itsaky.lsp.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.api.IDocumentHandler;
|
||||
import com.itsaky.lsp.models.DocumentChangeEvent;
|
||||
import com.itsaky.lsp.models.DocumentCloseEvent;
|
||||
import com.itsaky.lsp.models.DocumentOpenEvent;
|
||||
import com.itsaky.lsp.models.DocumentSaveEvent;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdk
|
||||
@@ -37,6 +38,7 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation common.editor
|
||||
implementation common.utilcode
|
||||
@@ -53,6 +55,18 @@ dependencies {
|
||||
|
||||
implementation project(path: ':lsp:api')
|
||||
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'])
|
||||
|
||||
implementation "androidx.core:core-ktx:1.7.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
testImplementation project(':common')
|
||||
testImplementation 'com.google.truth:truth:1.1.3'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.7.3'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'com.google.truth:truth:1.1.3'
|
||||
testImplementation common.utilcode
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.itsaky.lsp.java;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.api.ICompletionProvider;
|
||||
import com.itsaky.lsp.api.IDocumentHandler;
|
||||
@@ -57,7 +56,6 @@ import com.itsaky.lsp.models.ServerCapabilities;
|
||||
import com.itsaky.lsp.models.SignatureHelp;
|
||||
import com.itsaky.lsp.models.SignatureHelpParams;
|
||||
import com.itsaky.lsp.util.NoCompletionsProvider;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -108,7 +106,7 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler {
|
||||
if (diagnostics == null) {
|
||||
diagnostics = new ArrayList<>(0);
|
||||
}
|
||||
|
||||
|
||||
client.publishDiagnostics(
|
||||
new DiagnosticResult(this.selectedFile, diagnostics));
|
||||
}
|
||||
@@ -149,6 +147,10 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ServerCapabilities getCapabilities() {
|
||||
|
||||
@@ -37,12 +37,9 @@ package com.itsaky.lsp.java.compiler;
|
||||
import static com.itsaky.androidide.utils.Logger.instance;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import kotlin.jvm.functions.Function1;
|
||||
|
||||
public class SynchronizedTask {
|
||||
|
||||
@@ -19,10 +19,8 @@ package com.itsaky.lsp.java.providers;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.java.compiler.CompileTask;
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
@@ -59,9 +57,6 @@ import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
import com.sun.tools.javac.api.ClientCodeWrapper;
|
||||
import com.sun.tools.javac.util.JCDiagnostic;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
@@ -74,7 +69,6 @@ import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
@@ -86,6 +80,7 @@ import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
public class CodeActionProvider {
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ public class CompletionProvider extends AbstractServiceProvider implements IComp
|
||||
String partial = partialIdentifier(contents, (int) cursor);
|
||||
boolean endsWithParen = endsWithParen(contents, (int) cursor);
|
||||
SynchronizedTask synchronizedTask = compiler.compile(Collections.singletonList(source));
|
||||
return synchronizedTask.get (
|
||||
return synchronizedTask.get(
|
||||
task -> {
|
||||
LOG.info(
|
||||
"...compiled in "
|
||||
|
||||
@@ -69,7 +69,7 @@ public class DefinitionProvider {
|
||||
|
||||
public List<Location> find() {
|
||||
SynchronizedTask synchronizedTask = compiler.compile(file);
|
||||
return synchronizedTask.get (
|
||||
return synchronizedTask.get(
|
||||
task -> {
|
||||
Element element = NavigationHelper.findElement(task, file, line, column);
|
||||
if (element == null) {
|
||||
@@ -138,7 +138,7 @@ public class DefinitionProvider {
|
||||
|
||||
List<Location> locations = new ArrayList<>();
|
||||
SynchronizedTask synchronizedTask = compiler.compile(sources);
|
||||
synchronizedTask.run (
|
||||
synchronizedTask.run(
|
||||
task -> {
|
||||
Trees trees = Trees.instance(task.task);
|
||||
Elements elements = task.task.getElements();
|
||||
@@ -170,7 +170,7 @@ public class DefinitionProvider {
|
||||
private List<Location> findRemoteDefinitions(JavaFileObject otherFile) {
|
||||
SynchronizedTask synchronizedTask =
|
||||
compiler.compile(Arrays.asList(new SourceFileObject(file), otherFile));
|
||||
return synchronizedTask.get (
|
||||
return synchronizedTask.get(
|
||||
task -> {
|
||||
Element element = NavigationHelper.findElement(task, file, line, column);
|
||||
return findDefinitions(task, element);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.lsp.java.providers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.app.BaseApplication;
|
||||
import com.itsaky.lsp.java.FileStore;
|
||||
import com.itsaky.lsp.java.R;
|
||||
@@ -38,7 +37,6 @@ import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
@@ -48,7 +46,6 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.Name;
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
@@ -46,7 +46,7 @@ public class JavaDiagnosticProvider {
|
||||
@NonNull
|
||||
public List<DiagnosticItem> analyze(@NonNull Path file) {
|
||||
final SynchronizedTask synchronizedTask = compiler.compile(file);
|
||||
return synchronizedTask.get (
|
||||
return synchronizedTask.get(
|
||||
task -> {
|
||||
if (!isTaskValid(task)) {
|
||||
// Do not use Collections.emptyList ()
|
||||
|
||||
@@ -42,7 +42,7 @@ public class JavaSelectionProvider {
|
||||
@NonNull
|
||||
public Range expandSelection(@NonNull ExpandSelectionParams params) {
|
||||
return compiler.compile(params.getFile())
|
||||
.get (
|
||||
.get(
|
||||
task -> {
|
||||
final CompilationUnitTree root = task.root(params.getFile());
|
||||
final FindBiggerRange rangeFinder =
|
||||
@@ -50,7 +50,7 @@ public class JavaSelectionProvider {
|
||||
final Range range = rangeFinder.scan(root, params.getSelection());
|
||||
|
||||
if (range != null) {
|
||||
LOG.debug("Expanding selection to range", range);
|
||||
LOG.verbose("Expanding selection to range", range);
|
||||
return range;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ public class ReferenceProvider {
|
||||
|
||||
public List<Location> find() {
|
||||
final SynchronizedTask synchronizedTask = compiler.compile(file);
|
||||
return synchronizedTask.get (
|
||||
return synchronizedTask.get(
|
||||
task -> {
|
||||
Element element = NavigationHelper.findElement(task, file, line, column);
|
||||
if (element == null) return NOT_SUPPORTED;
|
||||
@@ -89,13 +89,13 @@ public class ReferenceProvider {
|
||||
private List<Location> findTypeReferences(String className) {
|
||||
Path[] files = compiler.findTypeReferences(className);
|
||||
if (files.length == 0) return Collections.emptyList();
|
||||
return compiler.compile(files).get (this::findReferences);
|
||||
return compiler.compile(files).get(this::findReferences);
|
||||
}
|
||||
|
||||
private List<Location> findMemberReferences(String className, String memberName) {
|
||||
Path[] files = compiler.findMemberReferences(className, memberName);
|
||||
if (files.length == 0) return Collections.emptyList();
|
||||
return compiler.compile(files).get (this::findReferences);
|
||||
return compiler.compile(files).get(this::findReferences);
|
||||
}
|
||||
|
||||
private List<Location> findReferences(CompileTask task) {
|
||||
|
||||
@@ -91,7 +91,7 @@ public class SignatureProvider {
|
||||
|
||||
// TODO prune
|
||||
SynchronizedTask synchronizedTask = compiler.compile(file);
|
||||
return synchronizedTask.get (
|
||||
return synchronizedTask.get(
|
||||
task -> {
|
||||
long cursor = task.root().getLineMap().getPosition(line, column);
|
||||
TreePath path = new FindInvocationAt(task.task).scan(task.root(), cursor);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.lsp.java.compiler.SynchronizedTask;
|
||||
import com.itsaky.lsp.java.utils.FindHelper;
|
||||
@@ -29,11 +28,9 @@ import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
|
||||
public class AddException extends Rewrite {
|
||||
@@ -53,8 +50,7 @@ public class AddException extends Rewrite {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite (
|
||||
@NonNull CompilerProvider compiler) {
|
||||
public Map<Path, TextEdit[]> rewrite(@NonNull CompilerProvider compiler) {
|
||||
Path file = compiler.findTypeDeclaration(className);
|
||||
if (file == CompilerProvider.NOT_FOUND) {
|
||||
return CANCELLED;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
@@ -28,7 +27,6 @@ import com.sun.source.tree.ImportTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.lsp.java.compiler.SynchronizedTask;
|
||||
import com.itsaky.lsp.java.utils.EditHelper;
|
||||
@@ -30,11 +29,9 @@ import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
|
||||
public class AddSuppressWarningAnnotation extends Rewrite {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
@@ -29,11 +28,9 @@ import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
public class ConvertFieldToBlock extends Rewrite {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.lsp.java.visitors.FindVariableAtCursor;
|
||||
@@ -31,7 +30,6 @@ import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.java.compiler.CompileTask;
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
@@ -39,13 +38,11 @@ import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.Name;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.java.compiler.CompileTask;
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
@@ -33,7 +32,6 @@ import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@@ -42,7 +40,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.java.compiler.CompileTask;
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
@@ -36,12 +35,10 @@ import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.util.Trees;
|
||||
import com.sun.tools.javac.util.JCDiagnostic;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
|
||||
@@ -20,7 +20,6 @@ package com.itsaky.lsp.java.rewrite;
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.lsp.models.CodeActionItem;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -32,12 +32,10 @@ import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
|
||||
@@ -18,14 +18,12 @@
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.lsp.java.utils.EditHelper;
|
||||
import com.itsaky.lsp.java.visitors.FindTypeDeclarationAt;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -31,20 +31,18 @@ import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
|
||||
public class RemoveException extends Rewrite {
|
||||
|
||||
|
||||
private static final Pattern THROWS = Pattern.compile("\\s*\\bthrows\\b");
|
||||
final String className, methodName;
|
||||
final String[] erasedParameterTypes;
|
||||
|
||||
@@ -22,15 +22,13 @@ import com.itsaky.lsp.java.utils.FindHelper;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
|
||||
public class RemoveMethod extends Rewrite {
|
||||
|
||||
|
||||
final String className, methodName;
|
||||
final String[] erasedParameterTypes;
|
||||
|
||||
|
||||
@@ -18,13 +18,11 @@
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.lsp.models.CodeActionItem;
|
||||
import com.itsaky.lsp.models.CodeActionKind;
|
||||
import com.itsaky.lsp.models.DocumentChange;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
package com.itsaky.lsp.java.utils;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,10 +28,8 @@ import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.Name;
|
||||
|
||||
@@ -34,7 +34,7 @@ public class FindTypeDeclarationAt extends TreePathScanner<ClassTree, Long> {
|
||||
public FindTypeDeclarationAt(JavacTask task) {
|
||||
pos = Trees.instance(task).getSourcePositions();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ClassTree visitCompilationUnit(CompilationUnitTree t, Long find) {
|
||||
root = t;
|
||||
@@ -60,7 +60,7 @@ public class FindTypeDeclarationAt extends TreePathScanner<ClassTree, Long> {
|
||||
return b;
|
||||
}
|
||||
|
||||
public TreePath getStoredPath () {
|
||||
public TreePath getStoredPath() {
|
||||
return stored;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.lsp.java
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
||||
/**
|
||||
* A cursor dependent test is a test that needs to have cursor positions specified in the content.
|
||||
* `@@cursor@@` denotes the position of the cursor in source files.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
open class CursorDependentTest : LoggingTest() {
|
||||
|
||||
protected val CURSOR = "@@cursor@@"
|
||||
|
||||
fun requireCursor(content: String): Int {
|
||||
val cursor = content.indexOf(CURSOR)
|
||||
assertThat(cursor).isGreaterThan(-1)
|
||||
return cursor
|
||||
}
|
||||
|
||||
fun requireCursor(content: StringBuilder): Int {
|
||||
val cursor = content.indexOf(CURSOR)
|
||||
assertThat(cursor).isGreaterThan(-1)
|
||||
return cursor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.lsp.java
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
/**
|
||||
* Provides paths to source files in 'resources' directory.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class FileProvider {
|
||||
@Test
|
||||
fun testPath() {
|
||||
val path = sourceFile("SourceFileTest")
|
||||
assertThat(Files.exists(path)).isTrue()
|
||||
assertThat(path.fileName.toString()).isEqualTo("SourceFileTest.java")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNested() {
|
||||
val path = sourceFile("package/SourceFileTest")
|
||||
assertThat(Files.exists(path)).isTrue()
|
||||
assertThat(path.fileName.toString()).isEqualTo("SourceFileTest.java")
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Get the path to the 'resources' directory.
|
||||
*
|
||||
* @return The the resources directory.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun resources(): Path {
|
||||
return Paths.get(".", "src", "test", "resources")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the file in resources.
|
||||
*
|
||||
* @param name The name of the file. Nested file paths can be separated using '/'.
|
||||
* @return The path to the file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun sourceFile(name: String): Path {
|
||||
return resources().resolve("$name.java")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun contents(file: Path): StringBuilder {
|
||||
try {
|
||||
Files.newBufferedReader(file, Charset.defaultCharset()).use { reader ->
|
||||
val sb = StringBuilder()
|
||||
var line: String?
|
||||
while (reader.readLine().also { line = it } != null) {
|
||||
sb.append(line)
|
||||
sb.append("\n")
|
||||
}
|
||||
return sb
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.lsp.java
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.utils.Environment
|
||||
import com.itsaky.lsp.java.FileProvider.Companion.resources
|
||||
import com.itsaky.lsp.models.InitializeParams
|
||||
import org.jetbrains.annotations.Contract
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Provides instance to the java language server to test classes.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object LanguageServerProvider {
|
||||
private val server = JavaLanguageServer()
|
||||
fun server(): JavaLanguageServer {
|
||||
|
||||
if (Environment.COMPILER_MODULE == null) {
|
||||
val javaHome = System.getProperty("java.home")
|
||||
assertThat(javaHome).isNotEmpty()
|
||||
Environment.COMPILER_MODULE = File(javaHome)
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
fun initIfNecessary() {
|
||||
if (!server().isInitialized) {
|
||||
server().initialize(createInitParams())
|
||||
}
|
||||
}
|
||||
|
||||
@Contract(" -> new")
|
||||
private fun createInitParams(): InitializeParams {
|
||||
return InitializeParams(setOf(resources()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.lsp.java
|
||||
|
||||
import com.itsaky.androidide.utils.Logger
|
||||
import com.itsaky.lsp.java.LoggingTest
|
||||
import com.itsaky.lsp.java.util.ReflectiveLogListener
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* This class provides an efficient way to log messages.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
open class LoggingTest {
|
||||
companion object {
|
||||
private var logListener: ReflectiveLogListener? = null
|
||||
|
||||
init {
|
||||
logListener = ReflectiveLogListener(LoggingTest::class.java)
|
||||
Logger.addLogListener(logListener)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
fun log(priority: Int, tag: String, message: String) {
|
||||
System.out.printf(Locale.ROOT, "%-8s%-26s%-20s", Logger.priorityText(priority), tag, message)
|
||||
println()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("PropertyName")
|
||||
@JvmField
|
||||
protected val LOG = Logger.instance(javaClass.simpleName)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.lsp.java.providers
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.lsp.java.CursorDependentTest
|
||||
import com.itsaky.lsp.java.FileProvider.Companion.contents
|
||||
import com.itsaky.lsp.java.FileProvider.Companion.sourceFile
|
||||
import com.itsaky.lsp.java.LanguageServerProvider
|
||||
import com.itsaky.lsp.models.*
|
||||
import io.github.rosemoe.sora.text.Content
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class JavaSelectionProviderTest : CursorDependentTest() {
|
||||
private var file: Path? = null
|
||||
private var contents: StringBuilder? = null
|
||||
private var cursor: Int = -1
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
LOG.debug("Initializing JavaSelectionProviderTest")
|
||||
LanguageServerProvider.initIfNecessary()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleSelectionExpansion() {
|
||||
openFile("SimpleSelectionExpansionTest")
|
||||
cursor = requireCursor(contents!!)
|
||||
contents!!.delete(cursor, cursor + CURSOR.length)
|
||||
assertThat(contents!!.indexOf(CURSOR)).isEqualTo(-1)
|
||||
LanguageServerProvider.server().onContentChange(DocumentChangeEvent(file!!, contents.toString(), 1))
|
||||
|
||||
val range = findRange()
|
||||
val expanded = LanguageServerProvider.server()
|
||||
.expandSelection(ExpandSelectionParams(file!!, range))
|
||||
|
||||
assertThat(expanded).isEqualTo(Range(Position(4, 27), Position(4, 41)))
|
||||
}
|
||||
|
||||
private fun findRange(): Range {
|
||||
val pos = Content(contents!!).indexer.getCharPosition(cursor)
|
||||
val position = Position(pos.line, pos.column, pos.index)
|
||||
return Range(position, position)
|
||||
}
|
||||
|
||||
private fun openFile(fileName: String) {
|
||||
file = sourceFile(fileName)
|
||||
contents = contents(file!!)
|
||||
|
||||
LanguageServerProvider.server()
|
||||
.onFileOpened(DocumentOpenEvent(file!!, contents.toString(), 0))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.lsp.java.util
|
||||
|
||||
import com.itsaky.androidide.utils.Logger
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ReflectiveLogListener(private val receiver: Class<out Any>) : Logger.LogListener {
|
||||
override fun log(priority: Int, tag: String, message: String) {
|
||||
val method = receiver.getDeclaredMethod("log", Int::class.java, String::class.java, String::class.java)
|
||||
method.isAccessible = true
|
||||
method.invoke(null, priority, tag, message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.itsaky.androidide.test;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(@@cursor@@"Hello World!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.itsaky.androidide.test;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello World!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.itsaky.androidide.test;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello World!");
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ package com.itsaky.lsp.xml.providers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.app.BaseApplication;
|
||||
import com.itsaky.androidide.lexers.xml.XMLLexer;
|
||||
import com.itsaky.androidide.utils.CharSequenceReader;
|
||||
@@ -38,19 +37,16 @@ import com.itsaky.lsp.models.InsertTextFormat;
|
||||
import com.itsaky.lsp.xml.R;
|
||||
import com.itsaky.sdk.SDKInfo;
|
||||
import com.itsaky.widgets.models.Widget;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
/**
|
||||
* Completion provider for the XML Language
|
||||
|
||||
@@ -19,14 +19,11 @@ package com.itsaky.inflater;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.itsaky.inflater.util.Preconditions;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -38,148 +35,148 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public abstract class ILayoutInflater {
|
||||
|
||||
private static final Pattern XML_DECL = Pattern.compile("<\\?xml.*\\?>");
|
||||
private static final Pattern XML_DECL = Pattern.compile("<\\?xml.*\\?>");
|
||||
|
||||
/** The file whose content is currently being inflated. */
|
||||
private File currentlyInflatingFile;
|
||||
|
||||
/**
|
||||
* Creates an instance of {@code ILayoutInflater}
|
||||
*
|
||||
* @param config The configuration of the LayoutInflater
|
||||
* @return A new layout inflater instance
|
||||
*/
|
||||
@NonNull
|
||||
public static ILayoutInflater newInstance(LayoutInflaterConfiguration config) {
|
||||
return new XMLLayoutInflater(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acatual implementation of the layout inflation
|
||||
*
|
||||
* @param layout The layout to inflate
|
||||
* @return The inflated view
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract IView doInflate(String layout, ViewGroup parent) throws InflateException;
|
||||
|
||||
/**
|
||||
* Reset the {@link ContextProvider} of this inflater
|
||||
*
|
||||
* @param provider The new {@link ContextProvider}
|
||||
*/
|
||||
public abstract void resetContextProvider(ContextProvider provider);
|
||||
|
||||
/**
|
||||
* Register the inflate listener to this inflater.
|
||||
*
|
||||
* <p>NOTE: Do not register too much listeners as they may affect the inflation time.
|
||||
*
|
||||
* @param listener The listener to register.
|
||||
*/
|
||||
public abstract void registerInflateListener(IInflateListener listener);
|
||||
|
||||
/**
|
||||
* Unregister the inflate listener if it is registered.
|
||||
*
|
||||
* @param listener The listener to unregister
|
||||
*/
|
||||
public abstract void unregisterListener(IInflateListener listener);
|
||||
|
||||
/**
|
||||
* Get the resource finder attached to this layout inflater.
|
||||
*
|
||||
* @return The attached resource finder.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract IResourceTable requireResourceTable();
|
||||
|
||||
/**
|
||||
* Inflate the layout from the given file path
|
||||
*
|
||||
* @param path The file path to inflate layout from
|
||||
* @return The inflated layout
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
public IView inflatePath(String path, ViewGroup parent) throws InflateException {
|
||||
Preconditions.assertNotBlank(path, "Layout file path is blank!");
|
||||
return inflate(new File(path), parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout from the given file
|
||||
*
|
||||
* @param file The file to inflate layout from
|
||||
* @return The inflated layout
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
public IView inflate(File file, ViewGroup parent) throws InflateException {
|
||||
|
||||
Preconditions.assertNotnull(file, "Cannot inflate null file");
|
||||
|
||||
// How can we inflate a non existent file?
|
||||
if (!file.exists()) {
|
||||
throw new InflateException("File does not exist!");
|
||||
}
|
||||
|
||||
if (!file.canRead()) {
|
||||
throw new InflateException("Cannot read file. Permission denied!");
|
||||
}
|
||||
|
||||
// Cannot inflate a non UTF-8 too. This will also be true if the file is empty.
|
||||
if (!FileUtils.isUtf8(file)) {
|
||||
throw new InflateException("File is not UTF-8 or is empty!");
|
||||
}
|
||||
|
||||
requireResourceTable().setInflatingFile(file);
|
||||
this.currentlyInflatingFile = file;
|
||||
return inflate(FileIOUtils.readFile2String(file), parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout from the provided XML layout code
|
||||
*
|
||||
* @param layout The XML layout code
|
||||
* @return The inflated layout
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
protected IView inflate(String layout, ViewGroup parent) throws InflateException {
|
||||
|
||||
Preconditions.assertNotBlank(layout, "Layout is blank!");
|
||||
|
||||
// Trim the layout code
|
||||
layout = layout.trim();
|
||||
|
||||
final Matcher matcher = XML_DECL.matcher(layout);
|
||||
if (matcher.find()) {
|
||||
layout = layout.substring(matcher.end()).trim();
|
||||
}
|
||||
|
||||
return doInflate(layout, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently inflating file.
|
||||
*
|
||||
* @return The file.
|
||||
*/
|
||||
@Nullable
|
||||
protected File getFile() {
|
||||
return currentlyInflatingFile;
|
||||
}
|
||||
|
||||
/** Provides a reference to {@link Context}. This will be used to create original view. */
|
||||
public interface ContextProvider {
|
||||
/** The file whose content is currently being inflated. */
|
||||
private File currentlyInflatingFile;
|
||||
|
||||
/**
|
||||
* Provide the context to this inflater.
|
||||
* Creates an instance of {@code ILayoutInflater}
|
||||
*
|
||||
* @return The context
|
||||
* @param config The configuration of the LayoutInflater
|
||||
* @return A new layout inflater instance
|
||||
*/
|
||||
Context getContext();
|
||||
}
|
||||
@NonNull
|
||||
public static ILayoutInflater newInstance(LayoutInflaterConfiguration config) {
|
||||
return new XMLLayoutInflater(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acatual implementation of the layout inflation
|
||||
*
|
||||
* @param layout The layout to inflate
|
||||
* @return The inflated view
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract IView doInflate(String layout, ViewGroup parent) throws InflateException;
|
||||
|
||||
/**
|
||||
* Reset the {@link ContextProvider} of this inflater
|
||||
*
|
||||
* @param provider The new {@link ContextProvider}
|
||||
*/
|
||||
public abstract void resetContextProvider(ContextProvider provider);
|
||||
|
||||
/**
|
||||
* Register the inflate listener to this inflater.
|
||||
*
|
||||
* <p>NOTE: Do not register too much listeners as they may affect the inflation time.
|
||||
*
|
||||
* @param listener The listener to register.
|
||||
*/
|
||||
public abstract void registerInflateListener(IInflateListener listener);
|
||||
|
||||
/**
|
||||
* Unregister the inflate listener if it is registered.
|
||||
*
|
||||
* @param listener The listener to unregister
|
||||
*/
|
||||
public abstract void unregisterListener(IInflateListener listener);
|
||||
|
||||
/**
|
||||
* Get the resource finder attached to this layout inflater.
|
||||
*
|
||||
* @return The attached resource finder.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract IResourceTable requireResourceTable();
|
||||
|
||||
/**
|
||||
* Inflate the layout from the given file path
|
||||
*
|
||||
* @param path The file path to inflate layout from
|
||||
* @return The inflated layout
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
public IView inflatePath(String path, ViewGroup parent) throws InflateException {
|
||||
Preconditions.assertNotBlank(path, "Layout file path is blank!");
|
||||
return inflate(new File(path), parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout from the given file
|
||||
*
|
||||
* @param file The file to inflate layout from
|
||||
* @return The inflated layout
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
public IView inflate(File file, ViewGroup parent) throws InflateException {
|
||||
|
||||
Preconditions.assertNotnull(file, "Cannot inflate null file");
|
||||
|
||||
// How can we inflate a non existent file?
|
||||
if (!file.exists()) {
|
||||
throw new InflateException("File does not exist!");
|
||||
}
|
||||
|
||||
if (!file.canRead()) {
|
||||
throw new InflateException("Cannot read file. Permission denied!");
|
||||
}
|
||||
|
||||
// Cannot inflate a non UTF-8 too. This will also be true if the file is empty.
|
||||
if (!FileUtils.isUtf8(file)) {
|
||||
throw new InflateException("File is not UTF-8 or is empty!");
|
||||
}
|
||||
|
||||
requireResourceTable().setInflatingFile(file);
|
||||
this.currentlyInflatingFile = file;
|
||||
return inflate(FileIOUtils.readFile2String(file), parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout from the provided XML layout code
|
||||
*
|
||||
* @param layout The XML layout code
|
||||
* @return The inflated layout
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
protected IView inflate(String layout, ViewGroup parent) throws InflateException {
|
||||
|
||||
Preconditions.assertNotBlank(layout, "Layout is blank!");
|
||||
|
||||
// Trim the layout code
|
||||
layout = layout.trim();
|
||||
|
||||
final Matcher matcher = XML_DECL.matcher(layout);
|
||||
if (matcher.find()) {
|
||||
layout = layout.substring(matcher.end()).trim();
|
||||
}
|
||||
|
||||
return doInflate(layout, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently inflating file.
|
||||
*
|
||||
* @return The file.
|
||||
*/
|
||||
@Nullable
|
||||
protected File getFile() {
|
||||
return currentlyInflatingFile;
|
||||
}
|
||||
|
||||
/** Provides a reference to {@link Context}. This will be used to create original view. */
|
||||
public interface ContextProvider {
|
||||
|
||||
/**
|
||||
* Provide the context to this inflater.
|
||||
*
|
||||
* @return The context
|
||||
*/
|
||||
Context getContext();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ class XMLLayoutInflater extends BaseLayoutInflater {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected IResourceTable requireResourceTable () {
|
||||
protected IResourceTable requireResourceTable() {
|
||||
return this.resFinder;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user