Wrote tests for JavaSelectionProvider; Formatted code

This commit is contained in:
Akash Yadav
2022-03-30 19:27:06 +05:30
parent 03c90fbd8e
commit 3e9fd6b5fb
89 changed files with 1535 additions and 1290 deletions
+1 -1
View File
@@ -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>
+1
View File
@@ -2,5 +2,6 @@
<project version="4">
<component name="GoogleJavaFormatSettings">
<option name="enabled" value="true" />
<option name="style" value="AOSP" />
</component>
</project>
+1 -1
View File
@@ -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
View File
@@ -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);
@@ -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;
/**
+14
View File
@@ -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;
}