diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..c02cb6032 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Note that Issues are for bugs only. Use the discussions tab for feature requests.** + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index cf3a1e6a2..4985d0a17 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,6 +1,7 @@ name: Android Tests CI on: + workflow_dispatch: push: branches: [ javac-17 ] pull_request: diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml index 2eb9a1f76..0147389d0 100644 --- a/.github/workflows/build-apk.yml +++ b/.github/workflows/build-apk.yml @@ -29,7 +29,7 @@ jobs: - name: Build debug apk uses: eskatos/gradle-command-action@v1 with: - arguments: assembleRelease + arguments: assembleDebug distributions-cache-enabled: true dependencies-cache-enabled: true configuration-cache-enabled: true diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 9005eafe7..e5bc27a19 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -5,7 +5,16 @@ + + + + + + + + + diff --git a/.idea/compiler.xml b/.idea/compiler.xml index df6fe3db7..b9e5a4d1e 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,7 +1,21 @@ + + + + + + + + + + + + + + diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 45c545e55..c4e4c4a93 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -49,5 +49,7 @@ + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index 4dcbce12c..db44475ea 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -31,5 +31,10 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 4554aed29..9fb52ede7 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -20,6 +20,9 @@ + + + @@ -69,6 +72,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -84,7 +109,14 @@ + + + + + + + @@ -95,12 +127,14 @@ + + @@ -108,22 +142,26 @@ + + + + + - - + - + diff --git a/actions-api/build.gradle b/actions-api/build.gradle index fdca0d47c..7e3f7228f 100644 --- a/actions-api/build.gradle +++ b/actions-api/build.gradle @@ -5,17 +5,18 @@ plugins { dependencies { implementation 'androidx.annotation:annotation:1.3.0' implementation project(path: ':build-tools:build-logic') + implementation project(path: ':build-tools:project') + implementation project(path: ':build-tools:logging') // needed for UserDataHolder class implementation project(path: ':build-tools:kotlinc') implementation project(path: ':fileeditor-api') implementation project(path: ':editor-api') - compileOnly project(path: ':android-stubs') testImplementation 'junit:junit:4.+' } java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } \ No newline at end of file diff --git a/actions-api/src/main/java/com/tyron/actions/ActionGroup.java b/actions-api/src/main/java/com/tyron/actions/ActionGroup.java index 031951f33..6f261421c 100644 --- a/actions-api/src/main/java/com/tyron/actions/ActionGroup.java +++ b/actions-api/src/main/java/com/tyron/actions/ActionGroup.java @@ -23,7 +23,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { } public boolean isPopup() { - return false; + return true; } public abstract AnAction[] getChildren(@Nullable AnActionEvent e); diff --git a/actions-api/src/main/java/com/tyron/actions/ActionManager.java b/actions-api/src/main/java/com/tyron/actions/ActionManager.java index e8204c9b7..7b49e20ac 100644 --- a/actions-api/src/main/java/com/tyron/actions/ActionManager.java +++ b/actions-api/src/main/java/com/tyron/actions/ActionManager.java @@ -27,4 +27,6 @@ public static ActionManager getInstance() { public abstract void replaceAction(@NonNull String actionId, @NonNull AnAction newAction); public abstract boolean isGroup(@NonNull String actionId); + + public abstract void performAction(String id, AnActionEvent event); } diff --git a/actions-api/src/main/java/com/tyron/actions/CommonDataKeys.java b/actions-api/src/main/java/com/tyron/actions/CommonDataKeys.java index bbbd0e2e9..601e3a611 100644 --- a/actions-api/src/main/java/com/tyron/actions/CommonDataKeys.java +++ b/actions-api/src/main/java/com/tyron/actions/CommonDataKeys.java @@ -5,12 +5,12 @@ import androidx.fragment.app.Fragment; +import com.tyron.builder.model.DiagnosticWrapper; import com.tyron.builder.project.Project; import com.tyron.editor.Editor; import com.tyron.fileeditor.api.FileEditor; import org.jetbrains.kotlin.com.intellij.openapi.util.Key; -import org.openjdk.javax.tools.Diagnostic; import java.io.File; @@ -32,7 +32,7 @@ public class CommonDataKeys { */ public static final Key FRAGMENT = Key.create("fragment"); - public static final Key> DIAGNOSTIC = Key.create("diagnostic"); + public static final Key DIAGNOSTIC = Key.create("diagnostic"); /** * The current opened project diff --git a/actions-api/src/main/java/com/tyron/actions/impl/ActionManagerImpl.java b/actions-api/src/main/java/com/tyron/actions/impl/ActionManagerImpl.java index a1c11e302..cd2f627d9 100644 --- a/actions-api/src/main/java/com/tyron/actions/impl/ActionManagerImpl.java +++ b/actions-api/src/main/java/com/tyron/actions/impl/ActionManagerImpl.java @@ -1,6 +1,9 @@ package com.tyron.actions.impl; +import android.content.ClipData; +import android.content.ClipboardManager; import android.os.Build; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.SubMenu; @@ -8,6 +11,7 @@ import androidx.annotation.NonNull; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.tyron.actions.ActionGroup; import com.tyron.actions.ActionManager; import com.tyron.actions.AnAction; @@ -29,7 +33,11 @@ public class ActionManagerImpl extends ActionManager { private final Map mActionToId = new HashMap<>(); @Override - public void fillMenu(DataContext context, Menu menu, String place, boolean isContext, boolean isToolbar) { + public void fillMenu(DataContext context, + Menu menu, + String place, + boolean isContext, + boolean isToolbar) { // Inject values context.putData(CommonDataKeys.CONTEXT, context); if (Build.VERSION_CODES.P <= Build.VERSION.SDK_INT) { @@ -38,11 +46,9 @@ public void fillMenu(DataContext context, Menu menu, String place, boolean isCon for (AnAction value : mIdToAction.values()) { - AnActionEvent event = new AnActionEvent(context, - place, - value.getTemplatePresentation(), - isContext, - isToolbar); + AnActionEvent event = + new AnActionEvent(context, place, value.getTemplatePresentation(), isContext, + isToolbar); event.setPresentation(value.getTemplatePresentation()); @@ -94,10 +100,7 @@ private void fillMenu(Menu menu, AnAction action, AnActionEvent event) { menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); } menuItem.setIcon(presentation.getIcon()); - menuItem.setOnMenuItemClickListener(item -> { - action.actionPerformed(event); - return true; - }); + menuItem.setOnMenuItemClickListener(item -> performAction(action, event)); } private void fillMenu(int id, Menu menu, ActionGroup group, AnActionEvent event) { @@ -110,18 +113,14 @@ private void fillMenu(int id, Menu menu, ActionGroup group, AnActionEvent event) event.setPresentation(child.getTemplatePresentation()); child.update(event); if (event.getPresentation().isVisible()) { - MenuItem add = menu.add(id, Menu.NONE, Menu.NONE, - event.getPresentation().getText()); + MenuItem add = menu.add(id, Menu.NONE, Menu.NONE, event.getPresentation().getText()); add.setEnabled(event.getPresentation().isEnabled()); add.setIcon(event.getPresentation().getIcon()); - add.setOnMenuItemClickListener(item -> { - child.actionPerformed(event); - return true; - }); + add.setOnMenuItemClickListener(item -> performAction(child, event)); } } } - + private void fillSubMenu(SubMenu subMenu, AnAction action, AnActionEvent event) { Presentation presentation = event.getPresentation(); @@ -160,10 +159,27 @@ private void fillSubMenu(SubMenu subMenu, AnAction action, AnActionEvent event) } menuItem.setIcon(presentation.getIcon()); menuItem.setContentDescription(presentation.getDescription()); - menuItem.setOnMenuItemClickListener(item -> { + menuItem.setOnMenuItemClickListener(item -> performAction(action, event)); + } + + private boolean performAction(AnAction action, AnActionEvent event) { + try { action.actionPerformed(event); - return true; - }); + } catch (Throwable e) { + ClipboardManager clipboardManager = event.getDataContext() + .getSystemService(ClipboardManager.class); + new MaterialAlertDialogBuilder(event.getDataContext()).setTitle( + "Unable to perform action") + .setMessage(e.getMessage()) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(android.R.string.copy, + (d, w) -> clipboardManager.setPrimaryClip( + ClipData.newPlainText("Error report", + Log.getStackTraceString(e)))) + .show(); + return false; + } + return true; } @Override @@ -197,6 +213,19 @@ public boolean isGroup(@NonNull String actionId) { return isGroup(mIdToAction.get(actionId)); } + @Override + public void performAction(String id, AnActionEvent event) { + AnAction anAction = mIdToAction.get(id); + if (anAction != null) { + anAction.update(event); + if (anAction.getTemplatePresentation().isVisible()) { + anAction.actionPerformed(event); + } + } else { + // TODO: throw exception + } + } + private boolean isGroup(AnAction action) { return action instanceof ActionGroup; } diff --git a/actions-api/src/main/java/com/tyron/actions/menu/ActionPopupMenu.java b/actions-api/src/main/java/com/tyron/actions/menu/ActionPopupMenu.java new file mode 100644 index 000000000..1539a69b7 --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/menu/ActionPopupMenu.java @@ -0,0 +1,37 @@ +package com.tyron.actions.menu; + +import android.view.View; +import android.widget.PopupMenu; + +import com.tyron.actions.ActionManager; +import com.tyron.actions.DataContext; + +/** + * A helper class to show a PopupMenu inflated with actions at the given place + */ +public class ActionPopupMenu { + + /** + * Creates and immediately shows the popup menu. + */ + public static ActionPopupMenu createAndShow(View anchor, DataContext dataContext, String place) { + ActionPopupMenu popupMenu = new ActionPopupMenu(anchor, dataContext, place); + popupMenu.show(); + return popupMenu; + } + + private final PopupMenu popupMenu; + + public ActionPopupMenu(View anchor, DataContext dataContext, String place) { + popupMenu = new PopupMenu(dataContext, anchor); + ActionManager.getInstance().fillMenu(dataContext, popupMenu.getMenu(), place, true, false); + } + + public void show() { + popupMenu.show(); + } + + public void dismiss() { + popupMenu.dismiss(); + } +} diff --git a/android-stubs/build.gradle b/android-stubs/build.gradle index 2306b31ff..5f1dff5cd 100644 --- a/android-stubs/build.gradle +++ b/android-stubs/build.gradle @@ -4,6 +4,8 @@ plugins { dependencies { compileOnlyApi files('libs/android.jar') + + compileOnly 'androidx.annotation:annotation:1.3.0' } java { diff --git a/android-stubs/src/main/java/androidx/appcompat/app/AlertDialog.java b/android-stubs/src/main/java/androidx/appcompat/app/AlertDialog.java new file mode 100644 index 000000000..d825f25f5 --- /dev/null +++ b/android-stubs/src/main/java/androidx/appcompat/app/AlertDialog.java @@ -0,0 +1,241 @@ +package androidx.appcompat.app; + +import android.content.DialogInterface; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListAdapter; + +import androidx.annotation.ArrayRes; +import androidx.annotation.AttrRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; + +import androidx.appcompat.app.AlertDialog; + +public class AlertDialog { + public void dismiss() { + throw new RuntimeException("Stub!"); + } + + public static class Builder { + @NonNull + public AlertDialog create() { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setTitle(@StringRes int titleId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setTitle(@Nullable CharSequence title) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setCustomTitle(@Nullable View customTitleView) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setMessage(@StringRes int messageId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setMessage(@Nullable CharSequence message) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setIcon(@DrawableRes int iconId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setIconAttribute(@AttrRes int attrId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setPositiveButton( + @StringRes int textId, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setPositiveButton( + @Nullable CharSequence text, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setPositiveButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNegativeButton( + @StringRes int textId, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNegativeButton( + @Nullable CharSequence text, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNegativeButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNeutralButton( + @StringRes int textId, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNeutralButton( + @Nullable CharSequence text, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNeutralButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setCancelable(boolean cancelable) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setOnCancelListener( + @Nullable DialogInterface.OnCancelListener onCancelListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setOnDismissListener( + @Nullable DialogInterface.OnDismissListener onDismissListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setOnKeyListener(@Nullable DialogInterface.OnKeyListener onKeyListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setItems( + @ArrayRes int itemsId, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setItems( + @Nullable CharSequence[] items, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setAdapter( + @Nullable final ListAdapter adapter, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setCursor( + @Nullable final Cursor cursor, + @Nullable final DialogInterface.OnClickListener listener, + @NonNull String labelColumn) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setMultiChoiceItems( + @ArrayRes int itemsId, + @Nullable boolean[] checkedItems, + @Nullable final DialogInterface.OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setMultiChoiceItems( + @Nullable CharSequence[] items, + @Nullable boolean[] checkedItems, + @Nullable final DialogInterface.OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setMultiChoiceItems( + @Nullable Cursor cursor, + @NonNull String isCheckedColumn, + @NonNull String labelColumn, + @Nullable final DialogInterface.OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setSingleChoiceItems( + @ArrayRes int itemsId, int checkedItem, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setSingleChoiceItems( + @Nullable Cursor cursor, + int checkedItem, + @NonNull String labelColumn, + @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setSingleChoiceItems( + @Nullable CharSequence[] items, int checkedItem, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setSingleChoiceItems( + @Nullable ListAdapter adapter, int checkedItem, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setOnItemSelectedListener( + @Nullable final AdapterView.OnItemSelectedListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setView(int layoutResId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setView(@Nullable View view) { + throw new RuntimeException("Stub!"); + } + + public AlertDialog show() { + throw new RuntimeException("Stub!"); + } + } +} diff --git a/android-stubs/src/main/java/com/google/android/material/dialog/MaterialAlertDialogBuilder.java b/android-stubs/src/main/java/com/google/android/material/dialog/MaterialAlertDialogBuilder.java new file mode 100644 index 000000000..2209880f3 --- /dev/null +++ b/android-stubs/src/main/java/com/google/android/material/dialog/MaterialAlertDialogBuilder.java @@ -0,0 +1,340 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.material.dialog; + +import android.content.Context; +import android.content.DialogInterface.OnCancelListener; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.DialogInterface.OnKeyListener; +import android.content.DialogInterface.OnMultiChoiceClickListener; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListAdapter; + +import androidx.annotation.ArrayRes; +import androidx.annotation.AttrRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.Px; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; + +/** + * An extension of {@link AlertDialog.Builder} for use with a Material theme (e.g., + * Theme.MaterialComponents). + * + * This Builder must be used in order for AlertDialog objects to respond to color and shape + * theming provided by Material themes. + * + * The type of dialog returned is still an {@link AlertDialog}; there is no specific Material + * implementation of {@link AlertDialog}. + */ +public class MaterialAlertDialogBuilder extends AlertDialog.Builder { + + public MaterialAlertDialogBuilder(@NonNull Context context) { + throw new RuntimeException("Stub!"); + } + + public MaterialAlertDialogBuilder(@NonNull Context context, int overrideThemeResId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public AlertDialog create() { + throw new RuntimeException("Stub!"); + } + + @Nullable + public Drawable getBackground() { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackground(@Nullable Drawable background) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetStart(@Px int backgroundInsetStart) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetTop(@Px int backgroundInsetTop) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetEnd(@Px int backgroundInsetEnd) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetBottom(@Px int backgroundInsetBottom) { + throw new RuntimeException("Stub!"); + } + + // The following methods are all pass-through methods used to specify the return type for the + // builder chain. + + @NonNull + @Override + public MaterialAlertDialogBuilder setTitle(@StringRes int titleId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setTitle(@Nullable CharSequence title) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setCustomTitle(@Nullable View customTitleView) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMessage(@StringRes int messageId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMessage(@Nullable CharSequence message) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setIcon(@DrawableRes int iconId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setIconAttribute(@AttrRes int attrId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setPositiveButton( + @StringRes int textId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setPositiveButton( + @Nullable CharSequence text, @Nullable final OnClickListener listener) { + return (MaterialAlertDialogBuilder) super.setPositiveButton(text, listener); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setPositiveButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNegativeButton( + @StringRes int textId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNegativeButton( + @Nullable CharSequence text, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNegativeButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNeutralButton( + @StringRes int textId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNeutralButton( + @Nullable CharSequence text, @Nullable final OnClickListener listener) { + return (MaterialAlertDialogBuilder) super.setNeutralButton(text, listener); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNeutralButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setCancelable(boolean cancelable) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnCancelListener( + @Nullable OnCancelListener onCancelListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnDismissListener( + @Nullable OnDismissListener onDismissListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnKeyListener(@Nullable OnKeyListener onKeyListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setItems( + @ArrayRes int itemsId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setItems( + @Nullable CharSequence[] items, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setAdapter( + @Nullable final ListAdapter adapter, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setCursor( + @Nullable final Cursor cursor, + @Nullable final OnClickListener listener, + @NonNull String labelColumn) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMultiChoiceItems( + @ArrayRes int itemsId, + @Nullable boolean[] checkedItems, + @Nullable final OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMultiChoiceItems( + @Nullable CharSequence[] items, + @Nullable boolean[] checkedItems, + @Nullable final OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMultiChoiceItems( + @Nullable Cursor cursor, + @NonNull String isCheckedColumn, + @NonNull String labelColumn, + @Nullable final OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @ArrayRes int itemsId, int checkedItem, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @Nullable Cursor cursor, + int checkedItem, + @NonNull String labelColumn, + @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @Nullable CharSequence[] items, int checkedItem, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @Nullable ListAdapter adapter, int checkedItem, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnItemSelectedListener( + @Nullable final AdapterView.OnItemSelectedListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setView(int layoutResId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setView(@Nullable View view) { + throw new RuntimeException("Stub!"); + } +} diff --git a/app/build.gradle b/app/build.gradle index 8068825ec..43597865d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,23 +2,32 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion 31 - buildToolsVersion "30.0.3" + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - applicationId "com.tyron.code" - minSdkVersion 26 - targetSdkVersion 31 - versionCode 9 - versionName "0.2.4.1 ALPHA" + applicationId rootProject.ext.applicationId + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - compileOptions { coreLibraryDesugaringEnabled = true - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + testOptions { + unitTests { + includeAndroidResources = true + } + + unitTests.all { + systemProperty 'robolectric.enabledSdks', '26' + } } buildTypes { @@ -28,53 +37,133 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + packagingOptions { + resources.excludes += "license/*" + + exclude "plugin.properties" + exclude "plugin.xml" + exclude "about.html" + exclude ".api_description" + exclude "about_files/*" + exclude "META-INF/eclipse.inf" + exclude "META-INF/INDEX.LIST" + exclude "META-INF/DEPENDENCIES" + exclude "META-INF/LGPL2.1" + exclude "META-INF/groovy-release-info.properties" + exclude "META-INF/AL2.0" + exclude "README.md" + pickFirst "META-INF/sisu/*" + pickFirst "*/*.kotlin_builtins" + pickFirst "*/*/*.kotlin_builtins" + pickFirst "*/*/*/*.kotlin_builtins" + } } configurations.implementation { exclude group: "org.jetbrains", module: "annotations" + exclude group: 'com.android.tools', module: 'common' + exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on' } dependencies { + implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3' + implementation projects.terminalview + + // TODO: Removed these modules, the features that are using these modules + // should be moved into its own module. + + // TODO: language processing should be on its own module + implementation 'org.antlr:antlr4-runtime:4.9.2' + implementation files ( + 'libs/language-base-0.5.0.jar', + 'libs/language-java-0.5.0.jar' + ) + + // TODO: completion providers should not be included on the main module + // alternate editor impl + implementation 'com.blacksquircle.ui:editorkit:2.1.2' + + implementation project(path: ':code-editor') + implementation project(path: ':xml-completion') + implementation project(path: ':build-tools:viewbinding-inject') + implementation project(path: ':build-tools:xml-repository') + implementation project(path: ':java-completion') + implementation projects.eventManager + // not used for compilation, but for analysis + implementation project(path: ':build-tools:javac') + + implementation project(path: ':language-api') + + // groovy support + implementation projects.buildTools.builderBaseServicesGroovy + + implementation projects.buildTools.builderLauncher + implementation projects.buildTools.builderBaseServices + implementation projects.buildTools.builderCore + implementation projects.buildTools.builderCoreApi + implementation projects.buildTools.builderLogging + implementation projects.buildTools.builderLauncher + implementation projects.buildTools.builderToolingApiBuilders + implementation projects.buildTools.builderBuildOperations + implementation projects.buildTools.builderLogging + implementation projects.buildTools.builderEnterpriseWorkers + implementation projects.buildTools.builderConfigurationCache + implementation projects.buildTools.builderPlugins + implementation projects.buildTools.builderJava + implementation projects.buildTools.builderNative + implementation project(path: ":build-tools:codeassist-builder-plugin") + implementation projects.buildTools.builderIde + implementation projects.buildTools.builderToolingApi + // emulating console + implementation 'org.fusesource.jansi:jansi:2.4.0' + + implementation project(path: ':build-tools:builder-api') + implementation project(path: ':build-tools:builder-java') implementation project(path: ':build-tools:build-logic') + implementation project(path: ':build-tools:manifmerger') + implementation project(path: ':build-tools:project') + implementation project(path: ':build-tools:logging') implementation project(path: ':build-tools:jaxp:jaxp-internal') implementation project(path: ':build-tools:jaxp:xml') implementation project(path: ':common') implementation project(path: ':build-tools:lint') implementation project(path: ':layout-preview') implementation project(path: ':kotlin-completion') - implementation 'io.github.medyo:android-about-page:2.0.0' - + + // Virtual File System + implementation 'org.apache.commons:commons-vfs2:2.9.0' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation project(path: ':dependency-resolver') implementation project(path: ':treeview') - // completions - implementation project(path: ':code-editor') - implementation project(path: ':xml-completion') - implementation project(path: ':java-completion') - // apis implementation project(path: ':completion-api') implementation project(path: ':actions-api') implementation project(path: ':editor-api') implementation project(path: ':fileeditor-api') - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") + // formatters + implementation project(path: ':google-java-format') - implementation 'androidx.appcompat:appcompat:1.4.0' - implementation 'androidx.core:core:1.7.0' + // about + implementation 'com.github.daniel-stoneuk:material-about-library:3.1.2' - implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'androidx.core:core:1.7.0' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.viewpager2:viewpager2:1.0.0' implementation 'androidx.viewpager:viewpager:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' - implementation 'androidx.lifecycle:lifecycle-livedata-core:2.4.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel:2.4.0' - implementation 'androidx.lifecycle:lifecycle-livedata:2.4.0' - implementation 'androidx.fragment:fragment:1.4.0' - implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' + implementation 'androidx.lifecycle:lifecycle-livedata-core:2.4.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel:2.4.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' + implementation 'androidx.lifecycle:lifecycle-livedata:2.4.1' + implementation 'androidx.fragment:fragment:1.4.1' + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' implementation 'androidx.activity:activity:1.4.0' implementation 'androidx.drawerlayout:drawerlayout:1.1.1' implementation 'com.github.angads25:filepicker:1.1.1' @@ -82,26 +171,26 @@ dependencies { // image loading implementation 'com.github.bumptech.glide:glide:4.12.0' - implementation 'org.antlr:antlr4-runtime:4.9.2' - - - implementation files ( - 'libs/google-java-format-1.7.jar', - 'libs/language-base-0.5.0.jar', - 'libs/language-java-0.5.0.jar' - ) - - implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.preference:preference:1.2.0' implementation 'com.github.TutorialsAndroid:crashx:v6.0.19' + implementation project(path: ':eclipse-formatter') + implementation project(path: ':build-tools:builder-core') + + runtimeOnly projects.javaStubs //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' + // testing testImplementation 'junit:junit:4.13.2' testImplementation "com.google.truth:truth:1.1.3" - testImplementation "org.robolectric:robolectric:4.2.1" - testImplementation 'androidx.test:core:1.4.0' testImplementation 'org.mockito:mockito-core:1.10.19' + testImplementation "org.robolectric:robolectric:4.7.3" + debugImplementation 'androidx.test:core:1.4.0' + debugImplementation 'androidx.fragment:fragment-testing:1.4.1' + androidTestImplementation 'com.google.truth:truth:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") } diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index a069db6e2..07a75be86 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 9, - "versionName": "0.2.4.1 ALPHA", + "versionCode": 20, + "versionName": "0.2.9 ALPHA", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b603b7342..6e81fdcb3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,22 +19,25 @@ android:largeHeap="true" android:requestLegacyExternalStorage="true" android:resizeableActivity="true" - android:theme="@style/AppTheme" + android:supportsRtl="true" tools:targetApi="q"> + android:exported="false"> + + android:label="@string/title_activity_settings" + android:theme="@style/AppThemeNew" /> + android:theme="@style/AppThemeNew" + android:windowSoftInputMode="adjustResize"> @@ -42,8 +45,6 @@ - - + + + author + Martin Kühl + comment + Based on the Quiet Light theme for Espresso by Ian Beck. + name + Quiet Light + settings + + + settings + + background + #F5F5F5 + caret + #000000 + foreground + #333333 + invisibles + #AAAAAA + lineHighlight + #E4F6D4 + selection + #C9D0D9 + + + + name + Comments + scope + comment, punctuation.definition.comment + settings + + fontStyle + italic + foreground + #AAAAAA + + + + name + Comments: Preprocessor + scope + comment.block.preprocessor + settings + + fontStyle + + foreground + #AAAAAA + + + + name + Comments: Documentation + scope + comment.documentation, comment.block.documentation + settings + + foreground + #448C27 + + + + name + Invalid - Deprecated + scope + invalid.deprecated + settings + + background + #96000014 + + + + name + Invalid - Illegal + scope + invalid.illegal + settings + + background + #96000014 + foreground + #660000 + + + + name + Operators + scope + keyword.operator + settings + + foreground + #777777 + + + + name + Keywords + scope + keyword, storage + settings + + foreground + #4B83CD + + + + name + Types + scope + storage.type, support.type + settings + + foreground + #7A3E9D + + + + name + Language Constants + scope + constant.language, support.constant, variable.language + settings + + foreground + #AB6526 + + + + name + Variables + scope + variable, support.variable + settings + + foreground + #7A3E9D + + + + name + Functions + scope + entity.name.function, support.function + settings + + fontStyle + bold + foreground + #AA3731 + + + + name + Classes + scope + entity.name.type, entity.other.inherited-class, support.class + settings + + fontStyle + bold + foreground + #7A3E9D + + + + name + Exceptions + scope + entity.name.exception + settings + + foreground + #660000 + + + + name + Sections + scope + entity.name.section + settings + + fontStyle + bold + + + + name + Numbers, Characters + scope + constant.numeric, constant.character, constant + settings + + foreground + #AB6526 + + + + name + Strings + scope + string + settings + + foreground + #448C27 + + + + name + Strings: Escape Sequences + scope + constant.character.escape + settings + + foreground + #777777 + + + + name + Strings: Regular Expressions + scope + string.regexp + settings + + foreground + #4B83CD + + + + name + Strings: Symbols + scope + constant.other.symbol + settings + + foreground + #AB6526 + + + + name + Punctuation + scope + punctuation + settings + + foreground + #777777 + + + + name + Embedded Source + scope + string source, text source + settings + + background + #EAEBE6 + + + + name + ----------------------------------- + settings + + + + name + HTML: Doctype Declaration + scope + meta.tag.sgml.doctype, meta.tag.sgml.doctype string, meta.tag.sgml.doctype + entity.name.tag, meta.tag.sgml punctuation.definition.tag.html + + settings + + foreground + #AAAAAA + + + + name + HTML: Tags + scope + meta.tag, punctuation.definition.tag.html, + punctuation.definition.tag.begin.html, punctuation.definition.tag.end.html + + settings + + foreground + #91B3E0 + + + + name + HTML: Tag Names + scope + entity.name.tag + settings + + foreground + #4B83CD + + + + name + HTML: Attribute Names + scope + meta.tag entity.other.attribute-name, entity.other.attribute-name.html + + settings + + foreground + #91B3E0 + + + + name + HTML: Entities + scope + constant.character.entity, punctuation.definition.entity + settings + + foreground + #AB6526 + + + + name + ----------------------------------- + settings + + + + name + CSS: Selectors + scope + meta.selector, meta.selector entity, meta.selector entity punctuation, + entity.name.tag.css + + settings + + foreground + #7A3E9D + + + + name + CSS: Property Names + scope + meta.property-name, support.type.property-name + settings + + foreground + #AB6526 + + + + name + CSS: Property Values + scope + meta.property-value, meta.property-value constant.other, + support.constant.property-value + + settings + + foreground + #448C27 + + + + name + CSS: Important Keyword + scope + keyword.other.important + settings + + fontStyle + bold + + + + name + ----------------------------------- + settings + + + + name + Markup: Changed + scope + markup.changed + settings + + background + #FFFFDD + foreground + #000000 + + + + name + Markup: Deletion + scope + markup.deleted + settings + + background + #FFDDDD + foreground + #000000 + + + + name + Markup: Emphasis + scope + markup.italic + settings + + fontStyle + italic + + + + name + Markup: Error + scope + markup.error + settings + + background + #96000014 + foreground + #660000 + + + + name + Markup: Insertion + scope + markup.inserted + settings + + background + #DDFFDD + foreground + #000000 + + + + name + Markup: Link + scope + meta.link + settings + + foreground + #4B83CD + + + + name + Markup: Output + scope + markup.output, markup.raw + settings + + foreground + #777777 + + + + name + Markup: Prompt + scope + markup.prompt + settings + + foreground + #777777 + + + + name + Markup: Heading + scope + markup.heading + settings + + foreground + #AA3731 + + + + name + Markup: Strong + scope + markup.bold + settings + + fontStyle + bold + + + + name + Markup: Traceback + scope + markup.traceback + settings + + foreground + #660000 + + + + name + Markup: Underline + scope + markup.underline + settings + + fontStyle + underline + + + + name + Markup Quote + scope + markup.quote + settings + + foreground + #7A3E9D + + + + name + Markup Lists + scope + markup.list + settings + + foreground + #4B83CD + + + + name + Markup Styling + scope + markup.bold, markup.italic + settings + + foreground + #448C27 + + + + name + Markup Inline + scope + markup.inline.raw + settings + + fontStyle + + foreground + #AB6526 + + + + name + ----------------------------------- + settings + + + + name + Extra: Diff Range + scope + meta.diff.range, meta.diff.index, meta.separator + settings + + background + #DDDDFF + foreground + #434343 + + + + name + Extra: Diff From + scope + meta.diff.header.from-file + settings + + background + #FFDDDD + foreground + #434343 + + + + name + Extra: Diff To + scope + meta.diff.header.to-file + settings + + background + #DDFFDD + foreground + #434343 + + + + uuid + 231D6A91-5FD1-4CBE-BD2A-0F36C08693F1 + + \ No newline at end of file diff --git a/app/src/main/assets/textmate/darcula.json b/app/src/main/assets/textmate/darcula.json new file mode 100644 index 000000000..06fbcfe89 --- /dev/null +++ b/app/src/main/assets/textmate/darcula.json @@ -0,0 +1,542 @@ +{ + "name": "darcula", + "settings": [ + { + "settings": { + "background": "#1F1A1B", + "foreground": "#cccccc", + "lineHighlight": "#2B2B2B", + "blockLineColor": "#575757", + "currentBlockLineColor": "#7a7a7a", + "selection": "#214283", + "completionWindowBackground": "#1F1A1B", + "completionWindowStroke": "#555555" + } + }, + { + "name": "Package declaration", + "scope": "storage.modifier.package", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Import declaration", + "scope": "storage.modifier.import", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Class names (Identifiers starting with uppercase)", + "scope": "storage.type.java", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Annotation", + "scope": "storage.type.annotation", + "settings": { + "foreground": "#BBB529" + } + }, + { + "name": "Comment", + "scope": "comment", + "settings": { + "foreground": "#707070" + } + }, + { + "name": "Operator Keywords", + "scope": "keyword.operator,keyword.operator.logical,keyword.operator.relational,keyword.operator.assignment,keyword.operator.comparison,keyword.operator.ternary,keyword.operator.arithmetic,keyword.operator.spread", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Strings", + "scope": "string,string.character.escape,string.template.quoted,string.template.quoted.punctuation,string.template.quoted.punctuation.single,string.template.quoted.punctuation.double,string.type.declaration.annotation,string.template.quoted.punctuation.tag", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "String Interpolation Begin and End", + "scope": "punctuation.definition.template-expression.begin,punctuation.definition.template-expression.end", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "String Interpolation Body", + "scope": "expression.string,meta.template.expression", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Number", + "scope": "constant.numeric", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "Built-in constant", + "scope": "constant.language,variable.language", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "User-defined constant", + "scope": "constant.character, constant.other", + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "Keyword", + "scope": "keyword,keyword.operator.new,keyword.operator.delete,keyword.operator.static,keyword.operator.this,keyword.operator.expression", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Method return type", + "scope": "meta.method.return-type", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Method call identifier", + "scope": "meta.method-call", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Types, Class Types", + "scope": "entity.name.type,meta.return.type,meta.type.annotation,meta.type.parameters,support.type.primitive", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "Storage type", + "scope": "storage,storage.type,storage.modifier,storage.arrow", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Class constructor", + "scope": "class.instance.constructor,new.expr entity.name.type", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Function", + "scope": "support.function, entity.name.function", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Function Types", + "scope": "annotation.meta.ts, annotation.meta.tsx", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Function Argument", + "scope": "variable.parameter, operator.rest.parameters", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Variable, Property", + "scope": "variable.property,variable.other.property,variable.other.object.property,variable.object.property,support.variable.property", + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "Variable name", + "scope": "entity.name.variable", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "CONSTANT", + "scope": "variable.other.constant", + "settings": { + "foreground": "#9876AA" + } + }, + { + "name": "Module Name", + "scope": "quote.module", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "Markup Headings", + "scope": "markup.heading", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Tag name", + "scope": "punctuation.definition.tag.html, punctuation.definition.tag.begin, punctuation.definition.tag.end, entity.name.tag", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Tag attribute", + "scope": "entity.other.attribute-name", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Object Keys", + "scope": "meta.object-literal.key", + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "TypeScript Class Modifiers", + "scope": "storage.modifier.ts", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "TypeScript Type Casting", + "scope": "ts.cast.expr,ts.meta.entity.class.method.new.expr.cast,ts.meta.entity.type.name.new.expr.cast,ts.meta.entity.type.name.var-single-variable.annotation,tsx.cast.expr,tsx.meta.entity.class.method.new.expr.cast,tsx.meta.entity.type.name.new.expr.cast,tsx.meta.entity.type.name.var-single-variable.annotation", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "TypeScript Type Declaration", + "scope": "ts.meta.type.support,ts.meta.type.entity.name,ts.meta.class.inherited-class,tsx.meta.type.support,tsx.meta.type.entity.name,tsx.meta.class.inherited-class,type-declaration,enum-declaration", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "TypeScript Method Declaration", + "scope": "function-declaration,method-declaration,method-overload-declaration,type-fn-type-parameters", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Documentation Block", + "scope": "comment.block.documentation", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "Documentation Highlight (JSDoc)", + "scope": "storage.type.class.jsdoc", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Import-Export-All (*) Keyword", + "scope": "constant.language.import-export-all", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Object Key Seperator", + "scope": "objectliteral.key.separator, punctuation.separator.key-value", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex", + "scope": "regex", + "settings": { + "fontStyle": " italic" + } + }, + { + "name": "Typescript Namespace", + "scope": "ts.meta.entity.name.namespace,tsx.meta.entity.name.namespace", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex Character-class", + "scope": "regex.character-class", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Class Name", + "scope": "entity.name.type.class", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Class Inheritances", + "scope": "entity.other.inherited-class", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "Documentation Entity", + "scope": "entity.name.type.instance.jsdoc", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "YAML entity", + "scope": "yaml.entity.name,yaml.string.entity.name", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "YAML string value", + "scope": "yaml.string.out", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Ignored (Exceptions Rules)", + "scope": "meta.brace.square.ts,block.support.module,block.support.type.module,block.support.function.variable,punctuation.definition.typeparameters.begin,punctuation.definition.typeparameters.end", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex", + "scope": "string.regexp", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Regex Group/Set", + "scope": "punctuation.definition.group.regexp,punctuation.definition.character-class.regexp", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Regex Character Class", + "scope": "constant.other.character-class.regexp, constant.character.escape.ts", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex Or Operator", + "scope": "expr.regex.or.operator", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Tag string", + "scope": "string.template.tag,string.template.punctuation.tag,string.quoted.punctuation.tag,string.quoted.embedded.tag, string.quoted.double.tag", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "Tag function parenthesis", + "scope": "tag.punctuation.begin.arrow.parameters.embedded,tag.punctuation.end.arrow.parameters.embedded", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Object-literal key class", + "scope": "object-literal.object.member.key.field.other,object-literal.object.member.key.accessor,object-literal.object.member.key.array.brace.square", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "CSS Property-value", + "scope": "property-list.property-value,property-list.constant", + "settings": { + "foreground": "#A5C261" + } + }, + { + "name": "CSS Property variable", + "scope": "support.type.property-name.variable.css,support.type.property-name.variable.scss,variable.scss", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "CSS Property entity", + "scope": "entity.other.attribute-name.class.css,entity.other.attribute-name.class.scss,entity.other.attribute-name.parent-selector-suffix.css,entity.other.attribute-name.parent-selector-suffix.scss", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "CSS Property-value", + "scope": "property-list.property-value.rgb-value, keyword.other.unit.css,keyword.other.unit.scss", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "CSS Property-value function", + "scope": "property-list.property-value.function", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "CSS constant variables", + "scope": "support.constant.property-value.css,support.constant.property-value.scss", + "settings": { + "foreground": "#A5C261" + } + }, + { + "name": "CSS Tag", + "scope": "css.entity.name.tag,scss.entity.name.tag", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "CSS ID, Selector", + "scope": "meta.selector.css, entity.attribute-name.id, entity.other.attribute-name.pseudo-class.css,entity.other.attribute-name.pseudo-element.css", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "CSS Keyword", + "scope": "keyword.scss,keyword.css", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Triple-slash Directive Tag", + "scope": "triple-slash.tag", + "settings": { + "foreground": "#CCCCCC", + "fontStyle": "italic" + } + }, + { + "scope": "token.info-token", + "settings": { + "foreground": "#6796e6" + } + }, + { + "scope": "token.warn-token", + "settings": { + "foreground": "#cd9731" + } + }, + { + "scope": "token.error-token", + "settings": { + "foreground": "#f44747" + } + }, + { + "scope": "token.debug-token", + "settings": { + "foreground": "#b267e6" + } + }, + { + "name": "Python operators", + "scope": "keyword.operator.logical.python", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Dart class type", + "scope": "support.class.dart", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "PHP variables", + "scope": [ + "variable.language.php", + "variable.other.php" + ], + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "Perl specific", + "scope": [ + "variable.other.readwrite.perl" + ], + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "PHP variables", + "scope": [ + "variable.other.property.php" + ], + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "PHP variables", + "scope": [ + "support.variable.property.php" + ], + "settings": { + "foreground": "#FFC66D" + } + }, + + { + "name": "XML Namespace prefix", + "scope": "entity.name.tag.namesapce.xml", + "settings": { + "foreground": "#9876AA" + } + } + ] +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/groovy/language-configuration.json b/app/src/main/assets/textmate/groovy/language-configuration.json new file mode 100644 index 000000000..f339aa970 --- /dev/null +++ b/app/src/main/assets/textmate/groovy/language-configuration.json @@ -0,0 +1,43 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"], + ["<", ">"] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/groovy/syntaxes/groovy.tmLanguage b/app/src/main/assets/textmate/groovy/syntaxes/groovy.tmLanguage new file mode 100644 index 000000000..67d5b0861 --- /dev/null +++ b/app/src/main/assets/textmate/groovy/syntaxes/groovy.tmLanguage @@ -0,0 +1,2145 @@ + + + + + fileTypes + + groovy + gvy + + foldingStartMarker + (\{\s*$|^\s*// \{\{\{) + foldingStopMarker + ^\s*(\}|// \}\}\}$) + keyEquivalent + ^~G + name + Groovy + patterns + + + captures + + 1 + + name + punctuation.definition.comment.groovy + + + match + ^(#!).+$\n + name + comment.line.hashbang.groovy + + + captures + + 1 + + name + keyword.other.package.groovy + + 2 + + name + storage.modifier.package.groovy + + 3 + + name + punctuation.terminator.groovy + + + match + ^\s*(package)\b(?:\s*([^ ;$]+)\s*(;)?)? + name + meta.package.groovy + + + begin + (import static)\b\s* + beginCaptures + + 1 + + name + keyword.other.import.static.groovy + + + captures + + 1 + + name + keyword.other.import.groovy + + 2 + + name + storage.modifier.import.groovy + + 3 + + name + punctuation.terminator.groovy + + + contentName + storage.modifier.import.groovy + end + \s*(?:$|(?=%>)(;)) + endCaptures + + 1 + + name + punctuation.terminator.groovy + + + name + meta.import.groovy + patterns + + + match + \. + name + punctuation.separator.groovy + + + match + \s + name + invalid.illegal.character_not_allowed_here.groovy + + + + + begin + (import)\b\s* + beginCaptures + + 1 + + name + keyword.other.import.groovy + + + captures + + 1 + + name + keyword.other.import.groovy + + 2 + + name + storage.modifier.import.groovy + + 3 + + name + punctuation.terminator.groovy + + + contentName + storage.modifier.import.groovy + end + \s*(?:$|(?=%>)|(;)) + endCaptures + + 1 + + name + punctuation.terminator.groovy + + + name + meta.import.groovy + patterns + + + match + \. + name + punctuation.separator.groovy + + + match + \s + name + invalid.illegal.character_not_allowed_here.groovy + + + + + captures + + 1 + + name + keyword.other.import.groovy + + 2 + + name + keyword.other.import.static.groovy + + 3 + + name + storage.modifier.import.groovy + + 4 + + name + punctuation.terminator.groovy + + + match + ^\s*(import)(?:\s+(static)\s+)\b(?:\s*([^ ;$]+)\s*(;)?)? + name + meta.import.groovy + + + include + #groovy + + + repository + + annotations + + patterns + + + begin + (?<!\.)(@[^ (]+)(\() + beginCaptures + + 1 + + name + storage.type.annotation.groovy + + 2 + + name + punctuation.definition.annotation-arguments.begin.groovy + + + end + (\)) + endCaptures + + 1 + + name + punctuation.definition.annotation-arguments.end.groovy + + + name + meta.declaration.annotation.groovy + patterns + + + captures + + 1 + + name + constant.other.key.groovy + + 2 + + name + keyword.operator.assignment.groovy + + + match + (\w*)\s*(=) + + + include + #values + + + match + , + name + punctuation.definition.seperator.groovy + + + + + match + (?<!\.)@\S+ + name + storage.type.annotation.groovy + + + + anonymous-classes-and-new + + begin + \bnew\b + beginCaptures + + 0 + + name + keyword.control.new.groovy + + + end + (?<=\)|\])(?!\s*{)|(?<=})|(?=[;])|$ + patterns + + + begin + (\w+)\s*(?=\[) + beginCaptures + + 1 + + name + storage.type.groovy + + + end + }|(?=\s*(?:,|;|\)))|$ + patterns + + + begin + \[ + end + \] + patterns + + + include + #groovy + + + + + begin + { + end + (?=}) + patterns + + + include + #groovy + + + + + + + begin + (?=\w.*\(?) + end + (?<=\))|$ + patterns + + + include + #object-types + + + begin + \( + beginCaptures + + 1 + + name + storage.type.groovy + + + end + \) + patterns + + + include + #groovy + + + + + + + begin + { + end + } + name + meta.inner-class.groovy + patterns + + + include + #class-body + + + + + + braces + + begin + \{ + end + \} + patterns + + + include + #groovy-code + + + + class + + begin + (?=\w?[\w\s]*(?:class|(?:@)?interface|enum)\s+\w+) + end + } + endCaptures + + 0 + + name + punctuation.section.class.end.groovy + + + name + meta.definition.class.groovy + patterns + + + include + #storage-modifiers + + + include + #comments + + + captures + + 1 + + name + storage.modifier.groovy + + 2 + + name + entity.name.type.class.groovy + + + match + (class|(?:@)?interface|enum)\s+(\w+) + name + meta.class.identifier.groovy + + + begin + extends + beginCaptures + + 0 + + name + storage.modifier.extends.groovy + + + end + (?={|implements) + name + meta.definition.class.inherited.classes.groovy + patterns + + + include + #object-types-inherited + + + include + #comments + + + + + begin + (implements)\s + beginCaptures + + 1 + + name + storage.modifier.implements.groovy + + + end + (?=\s*extends|\{) + name + meta.definition.class.implemented.interfaces.groovy + patterns + + + include + #object-types-inherited + + + include + #comments + + + + + begin + { + end + (?=}) + name + meta.class.body.groovy + patterns + + + include + #class-body + + + + + + class-body + + patterns + + + include + #enum-values + + + include + #constructors + + + include + #groovy + + + + closures + + begin + \{(?=.*?->) + end + \} + patterns + + + begin + (?<=\{)(?=[^\}]*?->) + end + -> + endCaptures + + 0 + + name + keyword.operator.groovy + + + patterns + + + begin + (?!->) + end + (?=->) + name + meta.closure.parameters.groovy + patterns + + + begin + (?!,|->) + end + (?=,|->) + name + meta.closure.parameter.groovy + patterns + + + begin + = + beginCaptures + + 0 + + name + keyword.operator.assignment.groovy + + + end + (?=,|->) + name + meta.parameter.default.groovy + patterns + + + include + #groovy-code + + + + + include + #parameters + + + + + + + + + begin + (?=[^}]) + end + (?=\}) + patterns + + + include + #groovy-code + + + + + + comment-block + + begin + /\* + captures + + 0 + + name + punctuation.definition.comment.groovy + + + end + \*/ + name + comment.block.groovy + + comments + + patterns + + + captures + + 0 + + name + punctuation.definition.comment.groovy + + + match + /\*\*/ + name + comment.block.empty.groovy + + + include + text.html.javadoc + + + include + #comment-block + + + captures + + 1 + + name + punctuation.definition.comment.groovy + + + match + (//).*$\n? + name + comment.line.double-slash.groovy + + + + constants + + patterns + + + match + \b([A-Z][A-Z0-9_]+)\b + name + constant.other.groovy + + + match + \b(true|false|null)\b + name + constant.language.groovy + + + + constructors + + applyEndPatternLast + 1 + begin + (?<=;|^)(?=\s*(?:(?:private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final)\s+)*[A-Z]\w*\() + end + } + patterns + + + include + #method-content + + + + enum-values + + patterns + + + begin + (?<=;|^)\s*\b([A-Z0-9_]+)(?=\s*(?:,|;|}|\(|$)) + beginCaptures + + 1 + + name + constant.enum.name.groovy + + + end + ,|;|(?=})|^(?!\s*\w+\s*(?:,|$)) + patterns + + + begin + \( + end + \) + name + meta.enum.value.groovy + patterns + + + match + , + name + punctuation.definition.seperator.parameter.groovy + + + include + #groovy-code + + + + + + + + groovy + + patterns + + + include + #comments + + + include + #class + + + include + #variables + + + include + #methods + + + include + #annotations + + + include + #groovy-code + + + + groovy-code + + patterns + + + include + #groovy-code-minus-map-keys + + + include + #map-keys + + + + groovy-code-minus-map-keys + + comment + In some situations, maps can't be declared without enclosing []'s, + therefore we create a collection of everything but that + patterns + + + include + #comments + + + include + #annotations + + + include + #support-functions + + + include + #keyword-language + + + include + #values + + + include + #anonymous-classes-and-new + + + include + #keyword-operator + + + include + #types + + + include + #storage-modifiers + + + include + #parens + + + include + #closures + + + include + #braces + + + + keyword + + patterns + + + include + #keyword-operator + + + include + #keyword-language + + + + keyword-language + + patterns + + + match + \b(try|catch|finally|throw)\b + name + keyword.control.exception.groovy + + + match + \b((?<!\.)(?:return|break|continue|default|do|while|for|switch|if|else))\b + name + keyword.control.groovy + + + begin + \bcase\b + beginCaptures + + 0 + + name + keyword.control.groovy + + + end + : + endCaptures + + 0 + + name + punctuation.definition.case-terminator.groovy + + + name + meta.case.groovy + patterns + + + include + #groovy-code-minus-map-keys + + + + + begin + \b(assert)\s + beginCaptures + + 1 + + name + keyword.control.assert.groovy + + + end + $|;|} + name + meta.declaration.assertion.groovy + patterns + + + match + : + name + keyword.operator.assert.expression-seperator.groovy + + + include + #groovy-code-minus-map-keys + + + + + match + \b(throws)\b + name + keyword.other.throws.groovy + + + + keyword-operator + + patterns + + + match + \b(as)\b + name + keyword.operator.as.groovy + + + match + \b(in)\b + name + keyword.operator.in.groovy + + + match + \?\: + name + keyword.operator.elvis.groovy + + + match + \*\: + name + keyword.operator.spreadmap.groovy + + + match + \.\. + name + keyword.operator.range.groovy + + + match + \-> + name + keyword.operator.arrow.groovy + + + match + << + name + keyword.operator.leftshift.groovy + + + match + (?<=\S)\.(?=\S) + name + keyword.operator.navigation.groovy + + + match + (?<=\S)\?\.(?=\S) + name + keyword.operator.safe-navigation.groovy + + + begin + \? + beginCaptures + + 0 + + name + keyword.operator.ternary.groovy + + + end + (?=$|\)|}|]) + name + meta.evaluation.ternary.groovy + patterns + + + match + : + name + keyword.operator.ternary.expression-seperator.groovy + + + include + #groovy-code-minus-map-keys + + + + + match + ==~ + name + keyword.operator.match.groovy + + + match + =~ + name + keyword.operator.find.groovy + + + match + \b(instanceof)\b + name + keyword.operator.instanceof.groovy + + + match + (===|==|!=|<=|>=|<=>|<>|<|>|<<) + name + keyword.operator.comparison.groovy + + + match + = + name + keyword.operator.assignment.groovy + + + match + (\-\-|\+\+) + name + keyword.operator.increment-decrement.groovy + + + match + (\-|\+|\*|\/|%) + name + keyword.operator.arithmetic.groovy + + + match + (!|&&|\|\|) + name + keyword.operator.logical.groovy + + + + language-variables + + patterns + + + match + \b(this|super)\b + name + variable.language.groovy + + + + map-keys + + patterns + + + captures + + 1 + + name + constant.other.key.groovy + + 2 + + name + punctuation.definition.seperator.key-value.groovy + + + match + (\w+)\s*(:) + + + + method-call + + begin + ([\w$]+)(\() + beginCaptures + + 1 + + name + meta.method.groovy + + 2 + + name + punctuation.definition.method-parameters.begin.groovy + + + end + \) + endCaptures + + 0 + + name + punctuation.definition.method-parameters.end.groovy + + + name + meta.method-call.groovy + patterns + + + match + , + name + punctuation.definition.seperator.parameter.groovy + + + include + #groovy-code + + + + method-content + + patterns + + + match + \s + + + include + #annotations + + + begin + (?=(?:\w|<)[^\(]*\s+(?:[\w$]|<)+\s*\() + end + (?=[\w$]+\s*\() + name + meta.method.return-type.java + patterns + + + include + #storage-modifiers + + + include + #types + + + + + begin + ([\w$]+)\s*\( + beginCaptures + + 1 + + name + entity.name.function.java + + + end + \) + name + meta.definition.method.signature.java + patterns + + + begin + (?=[^)]) + end + (?=\)) + name + meta.method.parameters.groovy + patterns + + + begin + (?=[^,)]) + end + (?=,|\)) + name + meta.method.parameter.groovy + patterns + + + match + , + name + punctuation.definition.separator.groovy + + + begin + = + beginCaptures + + 0 + + name + keyword.operator.assignment.groovy + + + end + (?=,|\)) + name + meta.parameter.default.groovy + patterns + + + include + #groovy-code + + + + + include + #parameters + + + + + + + + + begin + (?=<) + end + (?=\s) + name + meta.method.paramerised-type.groovy + patterns + + + begin + < + end + > + name + storage.type.parameters.groovy + patterns + + + include + #types + + + match + , + name + punctuation.definition.seperator.groovy + + + + + + + begin + throws + beginCaptures + + 0 + + name + storage.modifier.groovy + + + end + (?={|;)|^(?=\s*(?:[^{\s]|$)) + name + meta.throwables.groovy + patterns + + + include + #object-types + + + + + begin + { + end + (?=}) + name + meta.method.body.java + patterns + + + include + #groovy-code + + + + + + methods + + applyEndPatternLast + 1 + begin + (?x:(?<=;|^|{)(?=\s* + (?: + (?:private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final) # visibility/modifier + | + (?:def) + | + (?: + (?: + (?:void|boolean|byte|char|short|int|float|long|double) + | + (?:@?(?:[a-zA-Z]\w*\.)*[A-Z]+\w*) # object type + ) + [\[\]]* + (?:<.*>)? + ) + + ) + \s+ + ([^=]+\s+)?\w+\s*\( + )) + end + }|(?=[^{]) + name + meta.definition.method.groovy + patterns + + + include + #method-content + + + + nest_curly + + begin + \{ + captures + + 0 + + name + punctuation.section.scope.groovy + + + end + \} + patterns + + + include + #nest_curly + + + + numbers + + patterns + + + match + ((0(x|X)[0-9a-fA-F]*)|(\+|-)?\b(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)([LlFfUuDdg]|UL|ul)?\b + name + constant.numeric.groovy + + + + object-types + + patterns + + + begin + \b((?:[a-z]\w*\.)*(?:[A-Z]+\w*[a-z]+\w*|UR[LI]))< + end + >|[^\w\s,\?<\[\]] + name + storage.type.generic.groovy + patterns + + + include + #object-types + + + begin + < + comment + This is just to support <>'s with no actual type prefix + end + >|[^\w\s,\[\]<] + name + storage.type.generic.groovy + + + + + begin + \b((?:[a-z]\w*\.)*[A-Z]+\w*[a-z]+\w*)(?=\[) + end + (?=[^\]\s]) + name + storage.type.object.array.groovy + patterns + + + begin + \[ + end + \] + patterns + + + include + #groovy + + + + + + + match + \b(?:[a-zA-Z]\w*\.)*(?:[A-Z]+\w*[a-z]+\w*|UR[LI])\b + name + storage.type.groovy + + + + object-types-inherited + + patterns + + + begin + \b((?:[a-zA-Z]\w*\.)*[A-Z]+\w*[a-z]+\w*)< + end + >|[^\w\s,\?<\[\]] + name + entity.other.inherited-class.groovy + patterns + + + include + #object-types-inherited + + + begin + < + comment + This is just to support <>'s with no actual type prefix + end + >|[^\w\s,\[\]<] + name + storage.type.generic.groovy + + + + + captures + + 1 + + name + keyword.operator.dereference.groovy + + + match + \b(?:[a-zA-Z]\w*(\.))*[A-Z]+\w*[a-z]+\w*\b + name + entity.other.inherited-class.groovy + + + + parameters + + patterns + + + include + #annotations + + + include + #storage-modifiers + + + include + #types + + + match + \w+ + name + variable.parameter.method.groovy + + + + parens + + begin + \( + end + \) + patterns + + + include + #groovy-code + + + + primitive-arrays + + patterns + + + match + \b(?:void|boolean|byte|char|short|int|float|long|double)(\[\])*\b + name + storage.type.primitive.array.groovy + + + + primitive-types + + patterns + + + match + \b(?:void|boolean|byte|char|short|int|float|long|double)\b + name + storage.type.primitive.groovy + + + + regexp + + patterns + + + begin + /(?=[^/]+/([^>]|$)) + beginCaptures + + 0 + + name + punctuation.definition.string.regexp.begin.groovy + + + end + / + endCaptures + + 0 + + name + punctuation.definition.string.regexp.end.groovy + + + name + string.regexp.groovy + patterns + + + match + \\. + name + constant.character.escape.groovy + + + + + begin + ~" + beginCaptures + + 0 + + name + punctuation.definition.string.regexp.begin.groovy + + + end + " + endCaptures + + 0 + + name + punctuation.definition.string.regexp.end.groovy + + + name + string.regexp.compiled.groovy + patterns + + + match + \\. + name + constant.character.escape.groovy + + + + + + storage-modifiers + + patterns + + + match + \b(private|protected|public)\b + name + storage.modifier.access-control.groovy + + + match + \b(static)\b + name + storage.modifier.static.groovy + + + match + \b(final)\b + name + storage.modifier.final.groovy + + + match + \b(native|synchronized|abstract|threadsafe|transient)\b + name + storage.modifier.other.groovy + + + + string-quoted-double + + begin + " + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + " + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.double.groovy + patterns + + + include + #string-quoted-double-contents + + + + string-quoted-double-contents + + patterns + + + match + \\. + name + constant.character.escape.groovy + + + applyEndPatternLast + 1 + begin + \$\w + end + (?=\W) + name + variable.other.interpolated.groovy + patterns + + + match + \w + name + variable.other.interpolated.groovy + + + match + \. + name + keyword.other.dereference.groovy + + + + + begin + \$\{ + captures + + 0 + + name + punctuation.section.embedded.groovy + + + end + \} + name + source.groovy.embedded.source + patterns + + + include + #nest_curly + + + + + + string-quoted-double-multiline + + begin + """ + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + """ + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.double.multiline.groovy + patterns + + + include + #string-quoted-double-contents + + + + string-quoted-single + + begin + ' + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + ' + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.single.groovy + patterns + + + include + #string-quoted-single-contents + + + + string-quoted-single-contents + + patterns + + + match + \\. + name + constant.character.escape.groovy + + + + string-quoted-single-multiline + + begin + ''' + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + ''' + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.single.multiline.groovy + patterns + + + include + #string-quoted-single-contents + + + + strings + + patterns + + + include + #string-quoted-double-multiline + + + include + #string-quoted-single-multiline + + + include + #string-quoted-double + + + include + #string-quoted-single + + + include + #regexp + + + + structures + + begin + \[ + beginCaptures + + 0 + + name + punctuation.definition.structure.begin.groovy + + + end + \] + endCaptures + + 0 + + name + punctuation.definition.structure.end.groovy + + + name + meta.structure.groovy + patterns + + + include + #groovy-code + + + match + , + name + punctuation.definition.separator.groovy + + + + support-functions + + patterns + + + match + (?x)\b(?:sprintf|print(?:f|ln)?)\b + name + support.function.print.groovy + + + match + (?x)\b(?:shouldFail|fail(?:NotEquals)?|ass(?:ume|ert(?:S(?:cript|ame)|N(?:ot(?:Same| + Null)|ull)|Contains|T(?:hat|oString|rue)|Inspect|Equals|False|Length| + ArrayEquals)))\b + name + support.function.testing.groovy + + + + types + + patterns + + + match + \b(def)\b + name + storage.type.def.groovy + + + include + #primitive-types + + + include + #primitive-arrays + + + include + #object-types + + + + values + + patterns + + + include + #language-variables + + + include + #strings + + + include + #numbers + + + include + #constants + + + include + #types + + + include + #structures + + + include + #method-call + + + + variables + + applyEndPatternLast + 1 + patterns + + + begin + (?x:(?= + (?: + (?:private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final) # visibility/modifier + | + (?:def) + | + (?:void|boolean|byte|char|short|int|float|long|double) + | + (?:(?:[a-z]\w*\.)*[A-Z]+\w*) # object type + ) + \s+ + [\w\d_<>\[\],\s]+ + (?:=|$) + + )) + end + ;|$ + name + meta.definition.variable.groovy + patterns + + + match + \s + + + captures + + 1 + + name + constant.variable.groovy + + + match + ([A-Z_0-9]+)\s+(?=\=) + + + captures + + 1 + + name + meta.definition.variable.name.groovy + + + match + (\w[^\s,]*)\s+(?=\=) + + + begin + = + beginCaptures + + 0 + + name + keyword.operator.assignment.groovy + + + end + $ + patterns + + + include + #groovy-code + + + + + captures + + 1 + + name + meta.definition.variable.name.groovy + + + match + (\w[^\s=]*)(?=\s*($|;)) + + + include + #groovy-code + + + + + + + scopeName + source.groovy + uuid + B3A64888-EBBB-4436-8D9E-F1169C5D7613 + + diff --git a/app/src/main/assets/textmate/java/language-configuration.json b/app/src/main/assets/textmate/java/language-configuration.json new file mode 100644 index 000000000..f339aa970 --- /dev/null +++ b/app/src/main/assets/textmate/java/language-configuration.json @@ -0,0 +1,43 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"], + ["<", ">"] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/java/syntaxes/java.tmLanguage.json b/app/src/main/assets/textmate/java/syntaxes/java.tmLanguage.json new file mode 100644 index 000000000..e80a6b5bf --- /dev/null +++ b/app/src/main/assets/textmate/java/syntaxes/java.tmLanguage.json @@ -0,0 +1,1855 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/atom/language-java/blob/master/grammars/java.cson", + "If you want to provide a fix or improvement, please create a pull request against the original repository.", + "Once accepted there, we are happy to receive an update request." + ], + "version": "https://github.com/atom/language-java/commit/29f977dc42a7e2568b39bb6fb34c4ef108eb59b3", + "name": "Java", + "scopeName": "source.java", + "patterns": [ + { + "begin": "\\b(package)\\b\\s*", + "beginCaptures": { + "1": { + "name": "keyword.other.package.java" + } + }, + "end": "\\s*(;)", + "endCaptures": { + "1": { + "name": "punctuation.terminator.java" + } + }, + "name": "meta.package.java", + "contentName": "storage.modifier.package.java", + "patterns": [ + { + "include": "#comments" + }, + { + "match": "(?<=\\.)\\s*\\.|\\.(?=\\s*;)", + "name": "invalid.illegal.character_not_allowed_here.java" + }, + { + "match": "(?", + "endCaptures": { + "0": { + "name": "punctuation.bracket.angle.java" + } + }, + "patterns": [ + { + "match": "\\b(extends|super)\\b", + "name": "storage.modifier.$1.java" + }, + { + "match": "(?>>?|~|\\^)", + "name": "keyword.operator.bitwise.java" + }, + { + "match": "((&|\\^|\\||<<|>>>?)=)", + "name": "keyword.operator.assignment.bitwise.java" + }, + { + "match": "(===?|!=|<=|>=|<>|<|>)", + "name": "keyword.operator.comparison.java" + }, + { + "match": "([+*/%-]=)", + "name": "keyword.operator.assignment.arithmetic.java" + }, + { + "match": "(=)", + "name": "keyword.operator.assignment.java" + }, + { + "match": "(\\-\\-|\\+\\+)", + "name": "keyword.operator.increment-decrement.java" + }, + { + "match": "(\\-|\\+|\\*|\\/|%)", + "name": "keyword.operator.arithmetic.java" + }, + { + "match": "(!|&&|\\|\\|)", + "name": "keyword.operator.logical.java" + }, + { + "match": "(\\||&)", + "name": "keyword.operator.bitwise.java" + }, + { + "match": "\\b(const|goto)\\b", + "name": "keyword.reserved.java" + } + ] + }, + "lambda-expression": { + "patterns": [ + { + "match": "->", + "name": "storage.type.function.arrow.java" + } + ] + }, + "member-variables": { + "begin": "(?=private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final)", + "end": "(?=\\=|;)", + "patterns": [ + { + "include": "#storage-modifiers" + }, + { + "include": "#variables" + }, + { + "include": "#primitive-arrays" + }, + { + "include": "#object-types" + } + ] + }, + "method-call": { + "begin": "(\\.)\\s*([A-Za-z_$][\\w$]*)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "punctuation.separator.period.java" + }, + "2": { + "name": "entity.name.function.java" + }, + "3": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "name": "meta.method-call.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + "methods": { + "begin": "(?!new)(?=[\\w<].*\\s+)(?=([^=/]|/(?!/))+\\()", + "end": "(})|(?=;)", + "endCaptures": { + "1": { + "name": "punctuation.section.method.end.bracket.curly.java" + } + }, + "name": "meta.method.java", + "patterns": [ + { + "include": "#storage-modifiers" + }, + { + "begin": "(\\w+)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "entity.name.function.java" + }, + "2": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "name": "meta.method.identifier.java", + "patterns": [ + { + "include": "#parameters" + }, + { + "include": "#parens" + }, + { + "include": "#comments" + } + ] + }, + { + "include": "#generics" + }, + { + "begin": "(?=\\w.*\\s+\\w+\\s*\\()", + "end": "(?=\\s+\\w+\\s*\\()", + "name": "meta.method.return-type.java", + "patterns": [ + { + "include": "#all-types" + }, + { + "include": "#parens" + }, + { + "include": "#comments" + } + ] + }, + { + "include": "#throws" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.method.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.method.body.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "include": "#comments" + } + ] + }, + "module": { + "begin": "((open)\\s)?(module)\\s+(\\w+)", + "end": "}", + "beginCaptures": { + "1": { + "name": "storage.modifier.java" + }, + "3": { + "name": "storage.modifier.java" + }, + "4": { + "name": "entity.name.type.module.java" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.module.end.bracket.curly.java" + } + }, + "name": "meta.module.java", + "patterns": [ + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.module.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.module.body.java", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#comments-javadoc" + }, + { + "match": "\\b(requires|transitive|exports|opens|to|uses|provides|with)\\b", + "name": "keyword.module.java" + } + ] + } + ] + }, + "numbers": { + "patterns": [ + { + "match": "(?x)\n\\b(?)?(\\()", + "beginCaptures": { + "1": { + "name": "storage.modifier.java" + }, + "2": { + "name": "entity.name.type.record.java" + }, + "3": { + "patterns": [ + { + "include": "#generics" + } + ] + }, + "4": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "name": "meta.record.identifier.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "begin": "(implements)\\s", + "beginCaptures": { + "1": { + "name": "storage.modifier.implements.java" + } + }, + "end": "(?=\\s*\\{)", + "name": "meta.definition.class.implemented.interfaces.java", + "patterns": [ + { + "include": "#object-types-inherited" + }, + { + "include": "#comments" + } + ] + }, + { + "include": "#record-body" + } + ] + }, + "record-body": { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.class.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "name": "meta.record.body.java", + "patterns": [ + { + "include": "#record-constructor" + }, + { + "include": "#class-body" + } + ] + }, + "record-constructor": { + "begin": "(?!new)(?=[\\w<].*\\s+)(?=([^\\(=/]|/(?!/))+(?={))", + "end": "(})|(?=;)", + "endCaptures": { + "1": { + "name": "punctuation.section.method.end.bracket.curly.java" + } + }, + "name": "meta.method.java", + "patterns": [ + { + "include": "#storage-modifiers" + }, + { + "begin": "(\\w+)", + "beginCaptures": { + "1": { + "name": "entity.name.function.java" + } + }, + "end": "(?=\\s*{)", + "name": "meta.method.identifier.java", + "patterns": [ + { + "include": "#comments" + } + ] + }, + { + "include": "#comments" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.method.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.method.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + }, + "static-initializer": { + "patterns": [ + { + "include": "#anonymous-block-and-instance-initializer" + }, + { + "match": "static", + "name": "storage.modifier.java" + } + ] + }, + "storage-modifiers": { + "match": "\\b(public|private|protected|static|final|native|synchronized|abstract|threadsafe|transient|volatile|default|strictfp|sealed|non-sealed)\\b", + "name": "storage.modifier.java" + }, + "strings": { + "patterns": [ + { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.java" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.java" + } + }, + "name": "string.quoted.double.java", + "patterns": [ + { + "match": "\\\\.", + "name": "constant.character.escape.java" + } + ] + }, + { + "begin": "'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.java" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.java" + } + }, + "name": "string.quoted.single.java", + "patterns": [ + { + "match": "\\\\.", + "name": "constant.character.escape.java" + } + ] + } + ] + }, + "throws": { + "begin": "throws", + "beginCaptures": { + "0": { + "name": "storage.modifier.java" + } + }, + "end": "(?={|;)", + "name": "meta.throwables.java", + "patterns": [ + { + "match": ",", + "name": "punctuation.separator.delimiter.java" + }, + { + "match": "[a-zA-Z$_][\\.a-zA-Z0-9$_]*", + "name": "storage.type.java" + } + ] + }, + "try-catch-finally": { + "patterns": [ + { + "begin": "\\btry\\b", + "beginCaptures": { + "0": { + "name": "keyword.control.try.java" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.try.end.bracket.curly.java" + } + }, + "name": "meta.try.java", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.section.try.resources.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.section.try.resources.end.bracket.round.java" + } + }, + "name": "meta.try.resources.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.try.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.try.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + }, + { + "begin": "\\b(catch)\\b", + "beginCaptures": { + "1": { + "name": "keyword.control.catch.java" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.catch.end.bracket.curly.java" + } + }, + "name": "meta.catch.java", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "contentName": "meta.catch.parameters.java", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#storage-modifiers" + }, + { + "begin": "[a-zA-Z$_][\\.a-zA-Z0-9$_]*", + "beginCaptures": { + "0": { + "name": "storage.type.java" + } + }, + "end": "(\\|)|(?=\\))", + "endCaptures": { + "1": { + "name": "punctuation.catch.separator.java" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "match": "\\w+", + "captures": { + "0": { + "name": "variable.parameter.java" + } + } + } + ] + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.catch.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.catch.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + }, + { + "begin": "\\bfinally\\b", + "beginCaptures": { + "0": { + "name": "keyword.control.finally.java" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.finally.end.bracket.curly.java" + } + }, + "name": "meta.finally.java", + "patterns": [ + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.finally.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.finally.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + } + ] + }, + "variables": { + "begin": "(?x)\n(?=\n \\b\n (\n (void|boolean|byte|char|short|int|float|long|double)\n |\n (?>(\\w+\\.)*[A-Z_]+\\w*) # e.g. `javax.ws.rs.Response`, or `String`\n )\n \\b\n \\s*\n (\n <[\\w<>,\\.?\\s\\[\\]]*> # e.g. `HashMap`, or `List`\n )?\n \\s*\n (\n (\\[\\])* # int[][]\n )?\n \\s+\n [A-Za-z_$][\\w$]* # At least one identifier after space\n ([\\w\\[\\],$][\\w\\[\\],\\s]*)? # possibly primitive array or additional identifiers\n \\s*(=|:|;)\n)", + "end": "(?=\\=|:|;)", + "name": "meta.definition.variable.java", + "patterns": [ + { + "match": "([A-Za-z$_][\\w$]*)(?=\\s*(\\[\\])*\\s*(;|:|=|,))", + "captures": { + "1": { + "name": "variable.other.definition.java" + } + } + }, + { + "include": "#all-types" + }, + { + "include": "#code" + } + ] + }, + "variables-local": { + "begin": "(?=\\b(var)\\b\\s+[A-Za-z_$][\\w$]*\\s*(=|:|;))", + "end": "(?=\\=|:|;)", + "name": "meta.definition.variable.local.java", + "patterns": [ + { + "match": "\\bvar\\b", + "name": "storage.type.local.java" + }, + { + "match": "([A-Za-z$_][\\w$]*)(?=\\s*(\\[\\])*\\s*(=|:|;))", + "captures": { + "1": { + "name": "variable.other.definition.java" + } + } + }, + { + "include": "#code" + } + ] + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/json/language-configuration.json b/app/src/main/assets/textmate/json/language-configuration.json new file mode 100644 index 000000000..094b22275 --- /dev/null +++ b/app/src/main/assets/textmate/json/language-configuration.json @@ -0,0 +1,38 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["\"", "\""] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/json/syntaxes/json.tmLanguage.json b/app/src/main/assets/textmate/json/syntaxes/json.tmLanguage.json new file mode 100644 index 000000000..46ee87f21 --- /dev/null +++ b/app/src/main/assets/textmate/json/syntaxes/json.tmLanguage.json @@ -0,0 +1,182 @@ +{ + "scopeName": "source.json", + "name": "JSON", + "fileTypes": [ + "avsc", + "babelrc", + "bowerrc", + "composer.lock", + "geojson", + "gltf", + "htmlhintrc", + "ipynb", + "jscsrc", + "jshintrc", + "jslintrc", + "json", + "jsonl", + "jsonld", + "languagebabel", + "ldj", + "ldjson", + "Pipfile.lock", + "schema", + "stylintrc", + "template", + "tern-config", + "tern-project", + "tfstate", + "tfstate.backup", + "topojson", + "webapp", + "webmanifest" + ], + "patterns": [ + { + "include": "#value" + } + ], + "repository": { + "array": { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.array.begin.json" + } + }, + "end": "(,)?[\\s\\n]*(\\])", + "endCaptures": { + "1": { + "name": "invalid.illegal.trailing-array-separator.json" + }, + "2": { + "name": "punctuation.definition.array.end.json" + } + }, + "name": "meta.structure.array.json", + "patterns": [ + { + "include": "#value" + }, + { + "match": ",", + "name": "punctuation.separator.array.json" + }, + { + "match": "[^\\s\\]]", + "name": "invalid.illegal.expected-array-separator.json" + } + ] + }, + "constant": { + "match": "\\b(true|false|null)\\b", + "name": "constant.language.json" + }, + "number": { + "match": "-?(?=[1-9]|0(?!\\d))\\d+(\\.\\d+)?([eE][+-]?\\d+)?", + "name": "constant.numeric.json" + }, + "object": { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.dictionary.begin.json" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.definition.dictionary.end.json" + } + }, + "name": "meta.structure.dictionary.json", + "patterns": [ + { + "begin": "(?=\")", + "end": "(?<=\")", + "name": "meta.structure.dictionary.key.json", + "patterns": [ + { + "include": "#string" + } + ] + }, + { + "begin": ":", + "beginCaptures": { + "0": { + "name": "punctuation.separator.dictionary.key-value.json" + } + }, + "end": "(,)(?=[\\s\\n]*})|(,)|(?=})", + "endCaptures": { + "1": { + "name": "invalid.illegal.trailing-dictionary-separator.json" + }, + "2": { + "name": "punctuation.separator.dictionary.pair.json" + } + }, + "name": "meta.structure.dictionary.value.json", + "patterns": [ + { + "include": "#value" + }, + { + "match": "[^\\s,]", + "name": "invalid.illegal.expected-dictionary-separator.json" + } + ] + }, + { + "match": "[^\\s}]", + "name": "invalid.illegal.expected-dictionary-separator.json" + } + ] + }, + "string": { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.json" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.json" + } + }, + "name": "string.quoted.double.json", + "patterns": [ + { + "match": "(?x)\n\\\\ # a literal backslash\n( # followed by\n [\"\\\\/bfnrt] # one of these characters\n | # or\n u[0-9a-fA-F]{4} # a u and four hex digits\n)", + "name": "constant.character.escape.json" + }, + { + "match": "\\\\.", + "name": "invalid.illegal.unrecognized-string-escape.json" + } + ] + }, + "value": { + "patterns": [ + { + "include": "#constant" + }, + { + "include": "#number" + }, + { + "include": "#string" + }, + { + "include": "#array" + }, + { + "include": "#object" + } + ] + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/kotlin/language-configuration.json b/app/src/main/assets/textmate/kotlin/language-configuration.json new file mode 100644 index 000000000..b742e9e0c --- /dev/null +++ b/app/src/main/assets/textmate/kotlin/language-configuration.json @@ -0,0 +1,35 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": [ "/*", "*/" ] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["<", ">"] + ], + "autoClosingPairs": [ + { "open": "{", "close": "}" }, + { "open": "[", "close": "]" }, + { "open": "(", "close": ")" }, + { "open": "'", "close": "'", "notIn": ["string", "comment"] }, + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "/*", "close": " */", "notIn": ["string"] } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["<", ">"], + ["'", "'"], + ["\"", "\""] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/kotlin/syntaxes/kotlin.tmLanguage b/app/src/main/assets/textmate/kotlin/syntaxes/kotlin.tmLanguage new file mode 100644 index 000000000..4a61ffeb2 --- /dev/null +++ b/app/src/main/assets/textmate/kotlin/syntaxes/kotlin.tmLanguage @@ -0,0 +1,1067 @@ + + + + + fileTypes + + kt + kts + + foldingStartMarker + (\{\s*(//.*)?$|^\s*// \{\{\{) + foldingStopMarker + ^\s*(\}|// \}\}\}$) + name + Kotlin + patterns + + + include + #comments + + + captures + + 1 + + name + keyword.other.kotlin + + 2 + + name + entity.name.package.kotlin + + + match + ^\s*(package)\b(?:\s*([^ ;$]+)\s*)? + + + captures + + 1 + + name + keyword.other.import.kotlin + + 2 + + name + storage.modifier.import.java + + 3 + + name + keyword.other.kotlin + + 4 + + name + entity.name.type + + + match + ^\s*(import)\s+([^ $.]+(?:\.(?:[`][^$`]+[`]|[^` $.]+))+)(?:\s+(as)\s+([`][^$`]+[`]|[^` $.]+))? + name + meta.import.kotlin + + + include + #code + + + repository + + annotations + + patterns + + + begin + (@[^ (]+)(\()? + beginCaptures + + 1 + + name + storage.type.annotation.kotlin + + 2 + + name + punctuation.definition.annotation-arguments.begin.kotlin + + + end + (\)|\s|$) + endCaptures + + 1 + + name + punctuation.definition.annotation-arguments.end.kotlin + + + name + meta.declaration.annotation.kotlin + patterns + + + captures + + 1 + + name + constant.other.key.kotlin + + 2 + + name + keyword.operator.assignment.kotlin + + + match + (\w*)\s*(=) + + + include + #code + + + match + , + name + punctuation.seperator.property.kotlin + + + + + match + @\w* + name + storage.type.annotation.kotlin + + + + builtin-functions + + patterns + + + match + \b(apply|also|let|takeIf|run|takeUnless|with|print|println)\b\s*(?={|\() + captures + + 1 + + name + support.function.kotlin + + + + + match + \b(mutableListOf|listOf|mutableMapOf|mapOf|mutableSetOf|setOf)\b\s*(?={|\() + captures + + 1 + + name + support.function.kotlin + + + + + + comments + + patterns + + + captures + + 0 + + name + punctuation.definition.comment.kotlin + + + match + /\*\*/ + name + comment.block.empty.kotlin + + + include + #comments-inline + + + + comments-inline + + patterns + + + begin + /\* + captures + + 0 + + name + punctuation.definition.comment.kotlin + + + end + \*/ + name + comment.block.kotlin + + + captures + + 1 + + name + comment.line.double-slash.kotlin + + 2 + + name + punctuation.definition.comment.kotlin + + + match + \s*((//).*$\n?) + + + + class-literal + + begin + (?=\b(?:class|interface|object)\s+\w+)\b + end + (?=\}|$) + name + meta.class.kotlin + patterns + + + include + #keyword-literal + + + begin + \b(class|object|interface)\b\s+(\w+) + beginCaptures + + 1 + + name + storage.modifier.kotlin + + 2 + + name + entity.name.class.kotlin + + + end + (?=\{|\(|:|$) + patterns + + + include + #keyword-literal + + + include + #annotations + + + include + #types + + + + + begin + (:)\s*(\w+) + beginCaptures + + 1 + + name + keyword.operator.declaration.kotlin + + 2 + + name + entity.other.inherited-class.kotlin + + + end + (?={|=|$) + patterns + + + include + #types + + + + + include + #braces + + + include + #parens + + + + literal-functions + + begin + (?=\b(?:fun)\b) + end + (?=$|=|\}) + patterns + + + begin + \b(fun)\b + beginCaptures + + 1 + + name + keyword.other.kotlin + + + end + (?=\() + patterns + + + captures + + 2 + + name + entity.name.function.kotlin + + + match + ([\.<\?>\w]+\.)?(\w+|(`[^`]*`)) + + + include + #types + + + + + begin + (:) + beginCaptures + + 1 + + name + keyword.operator.declaration.kotlin + + + end + (?={|=|$) + patterns + + + include + #types + + + + + include + #parens + + + include + #braces + + + + parameters + + patterns + + + begin + (:) + beginCaptures + + 1 + + name + keyword.operator.declaration.kotlin + + + end + (?=,|=|\)) + patterns + + + include + #types + + + + + match + \w+(?=:) + name + variable.parameter.function.kotlin + + + include + #keyword-literal + + + + keyword-literal + + patterns + + + match + (\!in|\!is|as\?) + name + keyword.operator.kotlin + + + match + \b(in|is|as|assert)\b + name + keyword.operator.kotlin + + + match + \b(const)\b + name + storage.modifier.kotlin + + + match + \b(val|var)\b + name + storage.type.kotlin + + + match + \b(\_)\b + name + punctuation.definition.variable.kotlin + + + match + \b(data|inline|tailrec|operator|infix|typealias|reified)\b + name + storage.type.kotlin + + + match + \b(external|public|private|protected|internal|abstract|final|sealed|enum|open|annotation|override|vararg|typealias|expect|actual|suspend|yield|out|in)\b + name + storage.modifier.kotlin + + + match + \b(try|catch|finally|throw)\b + name + keyword.control.catch-exception.kotlin + + + match + \b(if|else|when)\b + name + keyword.control.conditional.kotlin + + + match + \b(while|for|do|return|break|continue)\b + name + keyword.control.kotlin + + + match + \b(constructor|init)\b + name + entity.name.function.constructor + + + match + \b(companion|object)\b + name + storage.type.kotlin + + + + keyword-operator + + patterns + + + match + \b(and|or|not|inv)\b + name + keyword.operator.bitwise.kotlin + + + match + (==|!=|===|!==|<=|>=|<|>) + name + keyword.operator.comparison.kotlin + + + match + (=) + name + keyword.operator.assignment.kotlin + + + match + (:(?!:)) + name + keyword.operator.declaration.kotlin + + + match + (\?:) + name + keyword.operator.elvis.kotlin + + + match + (\-\-|\+\+) + name + keyword.operator.increment-decrement.kotlin + + + match + (\-|\+|\*|\/|%) + name + keyword.operator.arithmetic.kotlin + + + match + (\+\=|\-\=|\*\=|\/\=) + name + keyword.operator.arithmetic.assign.kotlin + + + match + (\!|\&\&|\|\|) + name + keyword.operator.logical.kotlin + + + match + (\.\.) + name + keyword.operator.range.kotlin + + + + keyword-punctuation + + patterns + + + match + (::) + name + punctuation.accessor.reference.kotlin + + + match + (\?\.) + name + punctuation.accessor.dot.safe.kotlin + + + match + (\.) + name + punctuation.accessor.dot.kotlin + + + match + (\,) + name + punctuation.seperator.kotlin + + + match + (\;) + name + punctuation.terminator.kotlin + + + + keyword-constant + + patterns + + + match + \b(true|false|null|class)\b + name + constant.language.kotlin + + + match + \b(this|super)\b + name + variable.language.kotlin + + + match + \b(0(x|X)[0-9A-Fa-f_]*)[L]?\b + name + constant.numeric.hex.kotlin + + + match + \b(0(b|B)[0-1_]*)[L]?\b + name + constant.numeric.binary.kotlin + + + match + \b([0-9][0-9_]*\.[0-9][0-9_]*[fFL]?)\b + name + constant.numeric.float.kotlin + + + match + \b([0-9][0-9_]*[fFL]?)\b + name + constant.numeric.integer.kotlin + + + + literal-string + + patterns + + + begin + " + beginCaptures + + 0 + + name + punctuation.definition.string.begin.kotlin + + + end + " + endCaptures + + 0 + + name + punctuation.definition.string.end.kotlin + + + name + string.quoted.double.kotlin + patterns + + + include + #string-content + + + + + + literal-raw-string + + patterns + + + begin + """ + beginCaptures + + 0 + + name + punctuation.definition.string.begin.kotlin + + + end + """ + endCaptures + + 0 + + name + punctuation.definition.string.end.kotlin + + + name + string.quoted.triple.kotlin + patterns + + + include + #string-content + + + + + + string-content + + patterns + + + name + constant.character.escape.newline.kotlin + match + \\\s*\n + + + name + constant.character.escape.kotlin + match + \\(x[\da-fA-F]{2}|u[\da-fA-F]{4}|.) + + + begin + (\$)(\{) + beginCaptures + + 1 + + name + punctuation.definition.keyword.kotlin + + 2 + + name + punctuation.section.block.begin.kotlin + + + end + \} + endCaptures + + 0 + + name + punctuation.section.block.end.kotlin + + + name + entity.string.template.element.kotlin + patterns + + + include + #code + + + + + + types + + patterns + + + match + \b(Nothing|Any|Unit|String|CharSequence|Int|Boolean|Char|Long|Double|Float|Short|Byte|Array|List|Map|Set|dynamic)\b(\?)? + name + support.class.kotlin + + + match + \b(IntArray|BooleanArray|CharArray|LongArray|DoubleArray|FloatArray|ShortArray|ByteArray)\b(\?)? + name + support.class.kotlin + + + match + ((?:[a-zA-Z]\w*\.)*[A-Z]+\w*[a-z]+\w*)(\?) + name + entity.name.type.class.kotlin + patterns + + + include + #keyword-punctuation + + + include + #types + + + + + match + \b(?:[a-z]\w*(\.))*[A-Z]+\w*\b + captures + + 1 + + name + keyword.operator.dereference.kotlin + + + name + entity.name.type.class.kotlin + + + begin + \( + beginCaptures + + 0 + + name + punctuation.section.group.begin.kotlin + + + end + \) + endCaptures + + 0 + + name + punctuation.section.group.end.kotlin + + + patterns + + + include + #types + + + + + include + #keyword-punctuation + + + include + #keyword-operator + + + + parens + + patterns + + + begin + \( + beginCaptures + + 0 + + name + punctuation.section.group.begin.kotlin + + + end + \) + endCaptures + + 0 + + name + punctuation.section.group.end.kotlin + + + name + meta.group.kotlin + patterns + + + include + #keyword-punctuation + + + include + #parameters + + + include + #code + + + + + + braces + + patterns + + + begin + \{ + beginCaptures + + 0 + + name + punctuation.section.group.begin.kotlin + + + end + \} + endCaptures + + 0 + + name + punctuation.section.group.end.kotlin + + + name + meta.block.kotlin + patterns + + + include + #code + + + + + + brackets + + patterns + + + begin + \[ + beginCaptures + + 0 + + name + punctuation.section.brackets.begin.kotlin + + + end + \] + endCaptures + + 0 + + name + punctuation.section.brackets.end.kotlin + + + name + meta.brackets.kotlin + patterns + + + include + #code + + + + + + code + + patterns + + + include + #comments + + + include + #comments-inline + + + include + #annotations + + + include + #class-literal + + + include + #parens + + + include + #braces + + + include + #brackets + + + include + #keyword-literal + + + include + #types + + + include + #keyword-operator + + + include + #keyword-constant + + + include + #keyword-punctuation + + + include + #builtin-functions + + + include + #literal-functions + + + include + #builtin-classes + + + include + #literal-raw-string + + + include + #literal-string + + + + + scopeName + source.kotlin + uuid + d9380650-5edc-447d-8dbd-98838c7d0adf + + \ No newline at end of file diff --git a/app/src/main/assets/textmate/xml/language-configuration.json b/app/src/main/assets/textmate/xml/language-configuration.json new file mode 100644 index 000000000..00f0bd9af --- /dev/null +++ b/app/src/main/assets/textmate/xml/language-configuration.json @@ -0,0 +1,42 @@ +{ + "comments": { + "lineComment": [""], + }, + "brackets": [ + ["<", "/>"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"], + ["<", ">"] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/xml/syntaxes/xml.tmLanguage.json b/app/src/main/assets/textmate/xml/syntaxes/xml.tmLanguage.json new file mode 100644 index 000000000..d493c6a3a --- /dev/null +++ b/app/src/main/assets/textmate/xml/syntaxes/xml.tmLanguage.json @@ -0,0 +1,430 @@ +{ + "scopeName": "text.xml", + "name": "XML", + "fileTypes": [ + "aiml", + "atom", + "axml", + "bpmn", + "config", + "cpt", + "csl", + "csproj", + "csproj.user", + "dae", + "dia", + "dita", + "ditamap", + "dtml", + "fodg", + "fodp", + "fods", + "fodt", + "fsproj", + "fxml", + "gir", + "glade", + "gpx", + "graphml", + "icls", + "iml", + "isml", + "jmx", + "jsp", + "kml", + "kst", + "launch", + "menu", + "mxml", + "nunit", + "nuspec", + "opml", + "owl", + "pom", + "ppj", + "proj", + "pt", + "pubxml", + "pubxml.user", + "rdf", + "rng", + "rss", + "sdf", + "shproj", + "siml", + "sld", + "storyboard", + "StyleCop", + "svg", + "targets", + "tld", + "vbox", + "vbox-prev", + "vbproj", + "vbproj.user", + "vcproj", + "vcproj.filters", + "vcxproj", + "vcxproj.filters", + "wixmsp", + "wixmst", + "wixobj", + "wixout", + "wsdl", + "wxs", + "xaml", + "xbl", + "xib", + "xlf", + "xliff", + "xml", + "xpdl", + "xsd", + "xul", + "ui" + ], + "firstLineMatch": "(?x)\n# XML declaration\n(?:\n ^ <\\? xml\n\n # VersionInfo\n \\s+ version\n \\s* = \\s*\n (['\"])\n 1 \\. [0-9]+\n \\1\n\n # EncodingDecl\n (?:\n \\s+ encoding\n \\s* = \\s*\n\n # EncName\n (['\"])\n [A-Za-z]\n [-A-Za-z0-9._]*\n \\2\n )?\n\n # SDDecl\n (?:\n \\s+ standalone\n \\s* = \\s*\n (['\"])\n (?:yes|no)\n \\3\n )?\n\n \\s* \\?>\n)\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n xml\n (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n xml\n (?=\\s|:|$)\n)", + "patterns": [ + { + "begin": "(<\\?)\\s*([-_a-zA-Z0-9]+)", + "captures": { + "1": { + "name": "punctuation.definition.tag.xml" + }, + "2": { + "name": "entity.name.tag.xml" + } + }, + "end": "(\\?>)", + "name": "meta.tag.preprocessor.xml", + "patterns": [ + { + "match": " ([a-zA-Z-]+)", + "name": "entity.other.attribute-name.xml" + }, + { + "include": "#doublequotedString" + }, + { + "include": "#singlequotedString" + } + ] + }, + { + "begin": "()", + "name": "meta.tag.sgml.doctype.xml", + "patterns": [ + { + "include": "#internalSubset" + } + ] + }, + { + "include": "#comments" + }, + { + "begin": "(<)((?:([-_a-zA-Z0-9]+)(:))?([-_a-zA-Z0-9:]+))(?=(\\s[^>]*)?>\\2>)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.tag.xml" + }, + "2": { + "name": "entity.name.tag.xml" + }, + "3": { + "name": "entity.name.tag.namespace.xml" + }, + "4": { + "name": "punctuation.separator.namespace.xml" + }, + "5": { + "name": "entity.name.tag.localname.xml" + } + }, + "end": "(>)()((?:([-_a-zA-Z0-9]+)(:))?([-_a-zA-Z0-9:]+))(>)", + "endCaptures": { + "1": { + "name": "punctuation.definition.tag.xml" + }, + "2": { + "name": "punctuation.definition.tag.xml" + }, + "3": { + "name": "entity.name.tag.xml" + }, + "4": { + "name": "entity.name.tag.namespace.xml" + }, + "5": { + "name": "punctuation.separator.namespace.xml" + }, + "6": { + "name": "entity.name.tag.localname.xml" + }, + "7": { + "name": "punctuation.definition.tag.xml" + } + }, + "name": "meta.tag.no-content.xml", + "patterns": [ + { + "include": "#tagStuff" + } + ] + }, + { + "begin": "(?)(?:([-\\w\\.]+)((:)))?([-\\w\\.:]+)", + "captures": { + "1": { + "name": "punctuation.definition.tag.xml" + }, + "2": { + "name": "entity.name.tag.namespace.xml" + }, + "3": { + "name": "entity.name.tag.xml" + }, + "4": { + "name": "punctuation.separator.namespace.xml" + }, + "5": { + "name": "entity.name.tag.localname.xml" + } + }, + "end": "(/?>)", + "name": "meta.tag.xml", + "patterns": [ + { + "include": "#tagStuff" + } + ] + }, + { + "include": "#entity" + }, + { + "include": "#bare-ampersand" + }, + { + "begin": "<%@", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.xml" + } + }, + "end": "%>", + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.xml" + } + }, + "name": "source.java-props.embedded.xml", + "patterns": [ + { + "match": "page|include|taglib", + "name": "keyword.other.page-props.xml" + } + ] + }, + { + "begin": "<%[!=]?(?!--)", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.xml" + } + }, + "end": "(?!--)%>", + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.xml" + } + }, + "name": "source.java.embedded.xml", + "patterns": [ + { + "include": "source.java" + } + ] + }, + { + "begin": "", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.xml" + } + }, + "name": "string.unquoted.cdata.xml" + } + ], + "repository": { + "EntityDecl": { + "begin": "()", + "patterns": [ + { + "include": "#doublequotedString" + }, + { + "include": "#singlequotedString" + } + ] + }, + "bare-ampersand": { + "match": "&", + "name": "invalid.illegal.bad-ampersand.xml" + }, + "doublequotedString": { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.xml" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.xml" + } + }, + "name": "string.quoted.double.xml", + "patterns": [ + { + "include": "#entity" + }, + { + "include": "#bare-ampersand" + } + ] + }, + "entity": { + "captures": { + "1": { + "name": "punctuation.definition.constant.xml" + }, + "3": { + "name": "punctuation.definition.constant.xml" + } + }, + "match": "(&)([:a-zA-Z_][:a-zA-Z0-9_.-]*|#[0-9]+|#x[0-9a-fA-F]+)(;)", + "name": "constant.character.entity.xml" + }, + "internalSubset": { + "begin": "(\\[)", + "captures": { + "1": { + "name": "punctuation.definition.constant.xml" + } + }, + "end": "(\\])", + "name": "meta.internalsubset.xml", + "patterns": [ + { + "include": "#EntityDecl" + }, + { + "include": "#parameterEntity" + }, + { + "include": "#comments" + } + ] + }, + "parameterEntity": { + "captures": { + "1": { + "name": "punctuation.definition.constant.xml" + }, + "3": { + "name": "punctuation.definition.constant.xml" + } + }, + "match": "(%)([:a-zA-Z_][:a-zA-Z0-9_.-]*)(;)", + "name": "constant.character.parameter-entity.xml" + }, + "singlequotedString": { + "begin": "'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.xml" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.xml" + } + }, + "name": "string.quoted.single.xml", + "patterns": [ + { + "include": "#entity" + }, + { + "include": "#bare-ampersand" + } + ] + }, + "tagStuff": { + "patterns": [ + { + "captures": { + "1": { + "name": "entity.other.attribute-name.namespace.xml" + }, + "2": { + "name": "entity.other.attribute-name.xml" + }, + "3": { + "name": "punctuation.separator.namespace.xml" + }, + "4": { + "name": "entity.other.attribute-name.localname.xml" + } + }, + "match": "(?:^|\\s+)(?:([-\\w.]+)((:)))?([-\\w.:]+)\\s*=" + }, + { + "include": "#doublequotedString" + }, + { + "include": "#singlequotedString" + } + ] + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ApplicationLoader.java b/app/src/main/java/com/tyron/code/ApplicationLoader.java index 7f8535b46..cecee9922 100644 --- a/app/src/main/java/com/tyron/code/ApplicationLoader.java +++ b/app/src/main/java/com/tyron/code/ApplicationLoader.java @@ -2,30 +2,91 @@ import android.app.Application; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; -import android.os.Handler; -import android.os.Looper; +import android.os.Build; import android.widget.Toast; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AppCompatDelegate; import androidx.preference.PreferenceManager; import com.developer.crashx.config.CrashConfig; +import com.tyron.actions.ActionManager; import com.tyron.builder.BuildModule; +import com.tyron.code.event.EventManager; +import com.tyron.code.service.GradleDaemonService; +import com.tyron.code.ui.editor.action.CloseAllEditorAction; +import com.tyron.code.ui.editor.action.CloseFileEditorAction; +import com.tyron.code.ui.editor.action.CloseOtherEditorAction; +import com.tyron.code.ui.editor.action.DiagnosticInfoAction; +import com.tyron.code.ui.editor.action.PreviewLayoutAction; +import com.tyron.code.ui.editor.action.text.TextActionGroup; +import com.tyron.code.ui.file.action.ImportFileActionGroup; +import com.tyron.code.ui.file.action.NewFileActionGroup; +import com.tyron.code.ui.file.action.file.DeleteFileAction; +import com.tyron.code.ui.main.action.compile.CompileActionGroup; +import com.tyron.code.ui.main.action.debug.DebugActionGroup; +import com.tyron.code.ui.main.action.other.FormatAction; +import com.tyron.code.ui.main.action.other.OpenSettingsAction; +import com.tyron.code.ui.main.action.project.ProjectActionGroup; +import com.tyron.code.ui.settings.ApplicationSettingsFragment; import com.tyron.common.ApplicationProvider; +import com.tyron.completion.CompletionProvider; +import com.tyron.completion.index.CompilerService; import com.tyron.completion.java.CompletionModule; +import com.tyron.completion.java.JavaCompilerProvider; +import com.tyron.completion.java.JavaCompletionProvider; import com.tyron.completion.xml.XmlCompletionModule; +import com.tyron.completion.xml.XmlIndexProvider; +import com.tyron.editor.selection.ExpandSelectionProvider; +import com.tyron.kotlin_completion.KotlinCompletionModule; +import com.tyron.language.fileTypes.FileTypeManager; +import com.tyron.language.java.JavaFileType; +import com.tyron.language.java.JavaLanguage; +import com.tyron.language.xml.XmlFileType; +import com.tyron.language.xml.XmlLanguage; +import com.tyron.selection.java.JavaExpandSelectionProvider; +import com.tyron.selection.xml.XmlExpandSelectionProvider; + +import org.gradle.internal.time.Time; +import org.gradle.internal.time.Timer; +import org.lsposed.hiddenapibypass.HiddenApiBypass; + +import java.io.File; +import java.io.IOException; public class ApplicationLoader extends Application { - + + private static ApplicationLoader sInstance; + + public static ApplicationLoader getInstance() { + return sInstance; + } + + private EventManager mEventManager; + + // no memory leaks since applicationContext is a singleton public static Context applicationContext; - public static Handler applicationHandler = new Handler(Looper.getMainLooper()); @Override public void onCreate() { + Timer timer = Time.startTimer(); super.onCreate(); - applicationContext = this; + System.out.println("onCreate took " + timer.getElapsed()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + HiddenApiBypass.addHiddenApiExemptions("Lsun/misc/Unsafe"); + } + + setupTheme(); + + mEventManager = new EventManager(); + sInstance = this; + applicationContext = this; ApplicationProvider.initialize(applicationContext); CompletionModule.initialize(applicationContext); @@ -40,6 +101,88 @@ public void onCreate() { .logErrorOnRestart(true) .trackActivities(true) .apply(); + + runStartup(); + + File userDir = new File(getFilesDir(), "user_dir"); + System.setProperty("codeassist.user.dir", userDir.getAbsolutePath()); + } + + /** + * Can be used to communicate within the application globally + * @return the EventManager + */ + @NonNull + public EventManager getEventManager() { + return mEventManager; + } + + private void setupTheme() { + ApplicationSettingsFragment.ThemeProvider provider = new ApplicationSettingsFragment.ThemeProvider(this); + int theme = provider.getThemeFromPreferences(); + AppCompatDelegate.setDefaultNightMode(theme); + } + + private void runStartup() { + StartupManager startupManager = new StartupManager(); + startupManager.addStartupActivity(() -> { + FileTypeManager manager = FileTypeManager.getInstance(); + manager.registerFileType(JavaFileType.INSTANCE); + manager.registerFileType(XmlFileType.INSTANCE); + }); + startupManager.addStartupActivity(() -> { + ExpandSelectionProvider.registerProvider(JavaLanguage.INSTANCE, + new JavaExpandSelectionProvider()); + ExpandSelectionProvider.registerProvider(XmlLanguage.INSTANCE, + new XmlExpandSelectionProvider()); + }); + startupManager.addStartupActivity(() -> { + CompilerService index = CompilerService.getInstance(); + if (index.isEmpty()) { + index.registerIndexProvider(JavaCompilerProvider.KEY, new JavaCompilerProvider()); + index.registerIndexProvider(XmlIndexProvider.KEY, new XmlIndexProvider()); + } + }); + startupManager.addStartupActivity(() -> { + CompletionProvider.registerCompletionProvider(JavaLanguage.INSTANCE, + new JavaCompletionProvider()); + }); + startupManager.addStartupActivity(() -> { + ActionManager manager = ActionManager.getInstance(); + // main toolbar actions + manager.registerAction(CompileActionGroup.ID, new CompileActionGroup()); + manager.registerAction(ProjectActionGroup.ID, new ProjectActionGroup()); + manager.registerAction(PreviewLayoutAction.ID, new PreviewLayoutAction()); + manager.registerAction(OpenSettingsAction.ID, new OpenSettingsAction()); + manager.registerAction(FormatAction.ID, new FormatAction()); + manager.registerAction(DebugActionGroup.ID, new DebugActionGroup()); + + // editor tab actions + manager.registerAction(CloseFileEditorAction.ID, new CloseFileEditorAction()); + manager.registerAction(CloseOtherEditorAction.ID, new CloseOtherEditorAction()); + manager.registerAction(CloseAllEditorAction.ID, new CloseAllEditorAction()); + + // editor actions + manager.registerAction(TextActionGroup.ID, new TextActionGroup()); + manager.registerAction(DiagnosticInfoAction.ID, new DiagnosticInfoAction()); + + // file manager actions + manager.registerAction(NewFileActionGroup.ID, new NewFileActionGroup()); + manager.registerAction(DeleteFileAction.ID, new DeleteFileAction()); + if(Build.VERSION.SDK_INT { - CompletionEngine engine = CompletionEngine.getInstance(); - CompilerService index = CompilerService.getInstance(); - if (index.isEmpty()) { - index.registerIndexProvider(JavaCompilerProvider.KEY, new JavaCompilerProvider()); - index.registerIndexProvider(XmlIndexProvider.KEY, new XmlIndexProvider()); - engine.registerCompletionProvider(new JavaCompletionProvider()); - engine.registerCompletionProvider(new LayoutXmlCompletionProvider()); - engine.registerCompletionProvider(new AndroidManifestCompletionProvider()); - } - }); - startupManager.addStartupActivity(() -> { - ActionManager manager = ActionManager.getInstance(); - // main toolbar actions - manager.registerAction(SelectJavaParentAction.ID, new SelectJavaParentAction()); - manager.registerAction(CompileActionGroup.ID, new CompileActionGroup()); - manager.registerAction(ProjectActionGroup.ID, new ProjectActionGroup()); - manager.registerAction(PreviewLayoutAction.ID, new PreviewLayoutAction()); - manager.registerAction(OpenSettingsAction.ID, new OpenSettingsAction()); - manager.registerAction(FormatAction.ID, new FormatAction()); - manager.registerAction(DebugActionGroup.ID, new DebugActionGroup()); - - // editor tab actions - manager.registerAction(CloseFileEditorAction.ID, new CloseFileEditorAction()); - manager.registerAction(CloseOtherEditorAction.ID, new CloseOtherEditorAction()); - manager.registerAction(CloseAllEditorAction.ID, new CloseAllEditorAction()); - - // editor actions - manager.registerAction(TextActionGroup.ID, new TextActionGroup()); - manager.registerAction(DiagnosticInfoAction.ID, new DiagnosticInfoAction()); - - // file manager actions - manager.registerAction(NewFileActionGroup.ID, new NewFileActionGroup()); - manager.registerAction(DeleteFileAction.ID, new DeleteFileAction()); - - // java actions - CompletionModule.registerActions(manager); - - // xml actions - XmlCompletionModule.registerActions(manager); - - // kotlin actions - KotlinCompletionModule.registerActions(manager); - }); - startupManager.startup(); + WindowCompat.setDecorFitsSystemWindows(getWindow(), false); if (getSupportFragmentManager().findFragmentByTag(ProjectManagerFragment.TAG) == null) { getSupportFragmentManager().beginTransaction() - .replace(R.id.fragment_container, - new ProjectManagerFragment(), - ProjectManagerFragment.TAG) + .replace(R.id.fragment_container, new ProjectManagerFragment(), + ProjectManagerFragment.TAG) .commit(); } } - - @Override + + @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); } + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) { + return super.onKeyShortcut(keyCode, event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return super.onKeyUp(keyCode, event); + } } diff --git a/app/src/main/java/com/tyron/code/analyzer/BaseTextmateAnalyzer.java b/app/src/main/java/com/tyron/code/analyzer/BaseTextmateAnalyzer.java new file mode 100644 index 000000000..eb7205c53 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/BaseTextmateAnalyzer.java @@ -0,0 +1,61 @@ +package com.tyron.code.analyzer; + +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.tyron.editor.Editor; + +import org.eclipse.tm4e.core.theme.IRawTheme; +import org.eclipse.tm4e.core.theme.Theme; + +import java.io.InputStream; +import java.io.Reader; +import java.lang.reflect.Field; + +import io.github.rosemoe.sora.langs.textmate.TextMateAnalyzer; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.widget.CodeEditor; + +/** + * A text mate analyzer which does not use a TextMateLanguage + */ +public class BaseTextmateAnalyzer extends TextMateAnalyzer { + + private static final Field THEME_FIELD; + + public BaseTextmateAnalyzer(TextMateLanguage language, Editor editor, + String grammarName, + InputStream grammarIns, + Reader languageConfiguration, + IRawTheme theme) throws Exception { + super(language, grammarName, grammarIns, + languageConfiguration, theme); + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + if (!extraArguments.getBoolean("loaded", false)) { + return; + } + super.reset(content, extraArguments); + } + + public Theme getTheme() { + try { + return (Theme) THEME_FIELD.get(this); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + static { + try { + THEME_FIELD = TextMateAnalyzer.class.getDeclaredField("theme"); + THEME_FIELD.setAccessible(true); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } +} diff --git a/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticHighlighter.java b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticHighlighter.java new file mode 100644 index 000000000..e2ea859e7 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticHighlighter.java @@ -0,0 +1,48 @@ +package com.tyron.code.analyzer.semantic; + +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class SemanticHighlighter extends TreePathScanner { + + private final AtomicBoolean mCancelFlag = new AtomicBoolean(false); + + public void cancel() { + mCancelFlag.set(true); + } + + @Override + public Void scan(Tree tree, Void unused) { + if (mCancelFlag.get()) { + return null; + } + return super.scan(tree, unused); + } + + @Override + public Void scan(Iterable extends Tree> iterable, Void unused) { + if (mCancelFlag.get()) { + return null; + } + return super.scan(iterable, unused); + } + + @Override + public Void scan(TreePath treePath, Void unused) { + if (mCancelFlag.get()) { + return null; + } + return super.scan(treePath, unused); + } + + @Override + public Void reduce(Void unused, Void r1) { + if (mCancelFlag.get()) { + return null; + } + return super.reduce(unused, r1); + } +} diff --git a/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticToken.java b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticToken.java new file mode 100644 index 000000000..9f3aeadf0 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticToken.java @@ -0,0 +1,48 @@ +package com.tyron.code.analyzer.semantic; + +import androidx.annotation.NonNull; + +public class SemanticToken { + private final TokenType tokenType; + private final int tokenModifiers; + private final int offset; + private final int length; + + public SemanticToken(int offset, int length, TokenType tokenType, int tokenModifiers) { + this.offset = offset; + this.length = length; + this.tokenType = tokenType; + this.tokenModifiers = tokenModifiers; + } + + public TokenType getTokenType() { + return tokenType; + } + + public int getTokenModifiers() { + return tokenModifiers; + } + + public int getOffset() { + return offset; + } + + public int getLength() { + return length; + } + + @NonNull + @Override + public String toString() { + return "SemanticToken{" + + "tokenType=" + + tokenType + + ", tokenModifiers=" + + tokenModifiers + + ", offset=" + + offset + + ", length=" + + length + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/analyzer/semantic/TokenType.java b/app/src/main/java/com/tyron/code/analyzer/semantic/TokenType.java new file mode 100644 index 000000000..627946160 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/semantic/TokenType.java @@ -0,0 +1,35 @@ +package com.tyron.code.analyzer.semantic; + +import androidx.annotation.NonNull; + +public class TokenType { + + public static final TokenType UNKNOWN = create("token.error-token"); + + public static TokenType create(String scope, String... fallbackScopes) { + return new TokenType(scope, fallbackScopes); + } + + private final String scope; + private final String[] fallbackScopes; + + public TokenType(@NonNull String scope, String[] fallbackScopes) { + this.scope = scope; + this.fallbackScopes = fallbackScopes; + } + + public String getScope() { + return scope; + } + + public String[] getFallbackScopes() { + return fallbackScopes; + } + + @NonNull + @Override + public String toString() { + return scope; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/event/FileCreatedEvent.java b/app/src/main/java/com/tyron/code/event/FileCreatedEvent.java new file mode 100644 index 000000000..f9d931dd5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/event/FileCreatedEvent.java @@ -0,0 +1,19 @@ +package com.tyron.code.event; + +import java.io.File; + +/** + * Called when a new file has been created through the file manager UI + */ +public class FileCreatedEvent extends Event { + + private final File file; + + public FileCreatedEvent(File file) { + this.file = file; + } + + public File getFile() { + return file; + } +} diff --git a/app/src/main/java/com/tyron/code/event/FileDeletedEvent.java b/app/src/main/java/com/tyron/code/event/FileDeletedEvent.java new file mode 100644 index 000000000..7a8127baf --- /dev/null +++ b/app/src/main/java/com/tyron/code/event/FileDeletedEvent.java @@ -0,0 +1,17 @@ +package com.tyron.code.event; + +import java.io.File; + +public class FileDeletedEvent extends Event { + + + private final File deletedFile; + + public FileDeletedEvent(File deletedFile) { + this.deletedFile = deletedFile; + } + + public File getDeletedFile() { + return deletedFile; + } +} diff --git a/app/src/main/java/com/tyron/code/event/PerformShortcutEvent.java b/app/src/main/java/com/tyron/code/event/PerformShortcutEvent.java new file mode 100644 index 000000000..4927031a1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/event/PerformShortcutEvent.java @@ -0,0 +1,23 @@ +package com.tyron.code.event; + +import com.tyron.code.ui.editor.shortcuts.ShortcutItem; +import com.tyron.fileeditor.api.FileEditor; + +public class PerformShortcutEvent extends Event { + + private final ShortcutItem item; + private final FileEditor editor; + + public PerformShortcutEvent(ShortcutItem item, FileEditor editor) { + this.item = item; + this.editor = editor; + } + + public ShortcutItem getItem() { + return item; + } + + public FileEditor getEditor() { + return editor; + } +} diff --git a/app/src/main/java/com/tyron/code/gradle/util/GradleLaunchUtil.java b/app/src/main/java/com/tyron/code/gradle/util/GradleLaunchUtil.java new file mode 100644 index 000000000..7a757ea9d --- /dev/null +++ b/app/src/main/java/com/tyron/code/gradle/util/GradleLaunchUtil.java @@ -0,0 +1,134 @@ +package com.tyron.code.gradle.util; + +import static com.tyron.builder.model.AndroidProject.MODEL_LEVEL_3_VARIANT_OUTPUT_POST_BUILD; +import static com.tyron.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_DISABLE_SRC_DOWNLOAD; +import static com.tyron.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY; +import static com.tyron.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED; +import static com.tyron.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY_VERSIONED; +import static com.tyron.builder.model.InjectedProperties.PROPERTY_INVOKED_FROM_IDE; + +import android.content.SharedPreferences; + +import androidx.annotation.NonNull; + +import com.tyron.code.ApplicationLoader; +import com.tyron.common.SharedPreferenceKeys; + +import org.apache.commons.io.FileUtils; +import org.gradle.tooling.ConfigurableLauncher; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +public class GradleLaunchUtil { + + + private static void addIdeProperties(ConfigurableLauncher> launcher) { + launcher.addArguments(createProjectProperty(PROPERTY_BUILD_MODEL_ONLY, true)); + launcher.addArguments(createProjectProperty(PROPERTY_BUILD_MODEL_ONLY_ADVANCED, true)); + launcher.addArguments(createProjectProperty(PROPERTY_INVOKED_FROM_IDE, true)); + // Sent to plugin starting with Studio 3.0 + launcher.addArguments(createProjectProperty(PROPERTY_BUILD_MODEL_ONLY_VERSIONED, MODEL_LEVEL_3_VARIANT_OUTPUT_POST_BUILD)); + + // Skip download of source and javadoc jars during Gradle sync, this flag only has effect on AGP 3.5. + //noinspection deprecation AGP 3.6 and above do not download sources at all. + launcher.addArguments(createProjectProperty(PROPERTY_BUILD_MODEL_DISABLE_SRC_DOWNLOAD, true)); + } + + /** + * Configures the given launcher to align with the preferences set in the build settings + */ + public static void configureLauncher(ConfigurableLauncher> launcher) { + addIdeProperties(launcher); + + SharedPreferences preferences = ApplicationLoader.getDefaultPreferences(); + + String logLevel = preferences.getString(SharedPreferenceKeys.GRADLE_LOG_LEVEL, "LIFECYCLE"); + switch (logLevel) { + case "QUIET": + launcher.addArguments("--quiet"); + break; + case "WARN": + launcher.addArguments("--warn"); + break; + case "INFO": + launcher.addArguments("--info"); + break; + case "DEBUG": + launcher.addArguments("--debug"); + break; + default: + case "LIFECYCLE": + // intentionally empty + } + + String stacktrace = preferences.getString(SharedPreferenceKeys.GRADLE_STACKTRACE_MODE, + "INTERNAL_EXCEPTIONS"); + switch (stacktrace) { + case "ALWAYS": + launcher.addArguments("--stacktrace"); + break; + case "ALWAYS_FULL": + launcher.addArguments("--full-stacktrace"); + break; + default: + case "INTERNAL_EXCEPTIONS": + // intentionally empty + } + } + + public static void addCodeAssistInitScript(ConfigurableLauncher> launcher) { + try { + launcher.addArguments("--init-script", getOrCreateInitScript().getAbsolutePath()); + } catch (IOException e) { + // this should not happen under normal circumstances. + // throw just in case + throw new RuntimeException(e); + } + } + + private static File getOrCreateInitScript() throws IOException { + File initScript = new File(ApplicationLoader.getInstance().getFilesDir(), "init_scripts/init_script.gradle"); + //noinspection ResultOfMethodCallIgnored + Objects.requireNonNull(initScript.getParentFile()).mkdirs(); + if (!initScript.exists() && !initScript.createNewFile()) { + throw new IOException(); + } + + //language=Groovy + String initScriptCode = "rootProject.buildscript.configurations.classpath {\n" + + " resolutionStrategy.eachDependency {\n" + + " if (it.requested.name == \"gradle\" && it.requested" + + ".group == \"com.android.tools.build\") {\n" + + " throw new GradleException(\"The Android Gradle " + + "Plugin has been applied but is not supported. CodeAssist " + + "maintains its own\" +\n" + + " \"version of the Android Gradle Plugin so " + + "you don't have to include it in the build script's\" +\n" + + " \"classpath.\")\n" + + " }\n" + + " }\n" + + "}\n"; + FileUtils.writeStringToFile(initScript, initScriptCode, StandardCharsets.UTF_8); + return initScript; + } + + private static final String PROJECT_PROPERTY_FORMAT = "-P%1$s=%2$s"; + + @NonNull + public static String createProjectProperty(@NonNull String name, boolean value) { + return createProjectProperty(name, String.valueOf(value)); + } + + @NonNull + public static String createProjectProperty(@NonNull String name, int value) { + return createProjectProperty(name, String.valueOf(value)); + } + + @NonNull + public static String createProjectProperty(@NonNull String name, @NonNull String value) { + return String.format(PROJECT_PROPERTY_FORMAT, name, value); + } +} diff --git a/app/src/main/java/com/tyron/code/language/AbstractAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/AbstractAutoCompleteProvider.java new file mode 100644 index 000000000..6df3095d9 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/AbstractAutoCompleteProvider.java @@ -0,0 +1,27 @@ +package com.tyron.code.language; + +import com.tyron.completion.model.CompletionList; + +import java.util.List; +import java.util.stream.Collectors; + +import io.github.rosemoe.sora.lang.completion.CompletionItem; + +/** + * An auto complete provider that supports cancellation as the user types + */ +public abstract class AbstractAutoCompleteProvider { + + public final List getAutoCompleteItems(String prefix, int line, int column) { + CompletionList list = getCompletionList(prefix, line, column); + if (list == null) { + return null; + } + + return list.items.stream() + .map(CompletionItemWrapper::new) + .collect(Collectors.toList()); + } + + public abstract CompletionList getCompletionList(String prefix, int line, int column); +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/AbstractCodeAnalyzer.java b/app/src/main/java/com/tyron/code/language/AbstractCodeAnalyzer.java similarity index 76% rename from app/src/main/java/com/tyron/code/ui/editor/language/AbstractCodeAnalyzer.java rename to app/src/main/java/com/tyron/code/language/AbstractCodeAnalyzer.java index aedc613a4..27c4f4e0d 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/AbstractCodeAnalyzer.java +++ b/app/src/main/java/com/tyron/code/language/AbstractCodeAnalyzer.java @@ -1,4 +1,4 @@ -package com.tyron.code.ui.editor.language; +package com.tyron.code.language; import android.os.Bundle; @@ -6,7 +6,6 @@ import androidx.annotation.Nullable; import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.code.ui.main.MainViewModel; import com.tyron.editor.Editor; import org.antlr.v4.runtime.CharStream; @@ -21,14 +20,12 @@ import java.util.List; import java.util.Map; -import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; import io.github.rosemoe.sora.lang.analysis.StyleReceiver; import io.github.rosemoe.sora.lang.styling.MappedSpans; import io.github.rosemoe.sora.lang.styling.Styles; import io.github.rosemoe.sora.text.CharPosition; import io.github.rosemoe.sora.text.ContentReference; import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; -import io.github.rosemoe.sora2.text.DiagnosticSpanMapUpdater; public abstract class AbstractCodeAnalyzer extends DiagnosticAnalyzeManager { @@ -37,14 +34,14 @@ public abstract class AbstractCodeAnalyzer extends DiagnosticAnalyzeManager mDiagnostics = new ArrayList<>(); + protected List mDiagnostics = new ArrayList<>(); public AbstractCodeAnalyzer() { setup(); } @Override - public void setReceiver(@NonNull StyleReceiver receiver) { + public void setReceiver(@Nullable StyleReceiver receiver) { super.setReceiver(receiver); mReceiver = receiver; @@ -52,46 +49,12 @@ public void setReceiver(@NonNull StyleReceiver receiver) { @Override public void insert(CharPosition start, CharPosition end, CharSequence insertedContent) { - super.insert(start, end, insertedContent); - - if (start.getLine() != end.getLine()) { - DiagnosticSpanMapUpdater.shiftDiagnosticsOnMultiLineInsert( - mDiagnostics, - start.getLine(), - start.getColumn(), - end.getLine(), - end.getColumn() - ); - } else { - DiagnosticSpanMapUpdater.shiftDiagnosticsOnSingleLineInsert( - mDiagnostics, - start.getLine(), - start.getColumn(), - end.getColumn() - ); - } + rerunWithBg(); } @Override public void delete(CharPosition start, CharPosition end, CharSequence deletedContent) { - super.delete(start, end, deletedContent); - - if (start.getLine() != end.getLine()) { - DiagnosticSpanMapUpdater.shiftDiagnosticsOnMultiLineInsert( - mDiagnostics, - start.getLine(), - start.getColumn(), - end.getLine(), - end.getColumn() - ); - } else { - DiagnosticSpanMapUpdater.shiftDiagnosticsOnSingleLineInsert( - mDiagnostics, - start.getLine(), - start.getColumn(), - end.getColumn() - ); - } + rerunWithBg(); } @Override @@ -101,8 +64,7 @@ public void reset(@NonNull ContentReference content, @NonNull Bundle extraArgume @Override public void setDiagnostics(Editor editor, List diagnostics) { - mDiagnostics.clear(); - mDiagnostics.addAll(diagnostics); + mDiagnostics = diagnostics; } public void setup() { @@ -150,9 +112,13 @@ protected void beforeAnalyze() { @Override protected Styles analyze(StringBuilder text, Delegate delegate) { + Styles styles = new Styles(); + boolean loaded = getExtraArguments().getBoolean("loaded", false); + if (!loaded) { + return styles; + } beforeAnalyze(); - Styles styles = new Styles(); MappedSpans.Builder result = new MappedSpans.Builder(1024); try { diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/CompletionItemWrapper.java b/app/src/main/java/com/tyron/code/language/CompletionItemWrapper.java similarity index 88% rename from app/src/main/java/com/tyron/code/ui/editor/language/CompletionItemWrapper.java rename to app/src/main/java/com/tyron/code/language/CompletionItemWrapper.java index a5faec1c8..37fd64d20 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/CompletionItemWrapper.java +++ b/app/src/main/java/com/tyron/code/language/CompletionItemWrapper.java @@ -1,9 +1,6 @@ -package com.tyron.code.ui.editor.language; - -import android.graphics.drawable.Drawable; +package com.tyron.code.language; import com.tyron.completion.java.drawable.CircleDrawable; -import com.tyron.completion.model.DrawableKind; import com.tyron.editor.Editor; import io.github.rosemoe.sora.lang.completion.CompletionItem; diff --git a/app/src/main/java/com/tyron/code/language/DiagnosticAnalyzeManager.java b/app/src/main/java/com/tyron/code/language/DiagnosticAnalyzeManager.java new file mode 100644 index 000000000..289981b73 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/DiagnosticAnalyzeManager.java @@ -0,0 +1,54 @@ +package com.tyron.code.language; + +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.editor.Editor; + +import java.util.List; + +import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; +import io.github.rosemoe.sora.text.ContentReference; + +public abstract class DiagnosticAnalyzeManager extends SimpleAnalyzeManager { + + protected boolean mShouldAnalyzeInBg = false; + + public abstract void setDiagnostics(Editor editor, List diagnostics); + + public void rerunWithoutBg() { + mShouldAnalyzeInBg = false; + super.rerun(); + } + + public void rerunWithBg() { + mShouldAnalyzeInBg = true; + super.rerun(); + } + + @Override + public void rerun() { + mShouldAnalyzeInBg = false; + super.rerun(); + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + if (extraArguments == null) { + extraArguments = new Bundle(); + } + mShouldAnalyzeInBg = extraArguments.getBoolean("bg", false); + super.reset(content, extraArguments); + } + + @Override + public Bundle getExtraArguments() { + Bundle extraArguments = super.getExtraArguments(); + if (extraArguments == null) { + extraArguments = new Bundle(); + } + return extraArguments; + } +} diff --git a/app/src/main/java/com/tyron/code/language/DiagnosticSpanMapUpdater.java b/app/src/main/java/com/tyron/code/language/DiagnosticSpanMapUpdater.java new file mode 100644 index 000000000..bd66061e3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/DiagnosticSpanMapUpdater.java @@ -0,0 +1,85 @@ +package com.tyron.code.language; + +import com.tyron.builder.model.DiagnosticWrapper; + +import java.util.List; + +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; + +public class DiagnosticSpanMapUpdater { + + public static void shiftDiagnosticsOnSingleLineInsert(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + // diagnostic is located before the insertion index, its not included + if (diagnostic.getEndPosition() <= end.index) { + continue; + } + diagnostic.setStartPosition(diagnostic.getStartPosition() + length); + diagnostic.setEndPosition(diagnostic.getEndPosition() + length); + } + } + + public static void shiftDiagnosticsOnSingleLineDelete(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + if (diagnostic.getStartPosition() > start.index) { + diagnostic.setStartPosition(diagnostic.getStartPosition() - length); + } + if (diagnostic.getEndPosition() > end.index) { + diagnostic.setEndPosition(diagnostic.getEndPosition() - length); + } + } + } + + public static void shiftDiagnosticsOnMultiLineDelete(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + if (diagnostic.getStartPosition() < end.index) { + continue; + } + diagnostic.setStartPosition(diagnostic.getStartPosition() - length); + diagnostic.setEndPosition(diagnostic.getEndPosition() - length); + } + } + + public static void shiftDiagnosticsOnMultiLineInsert(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + if (diagnostic.getEndPosition() < end.index) { + continue; + } + diagnostic.setStartPosition(diagnostic.getStartPosition() + length); + diagnostic.setEndPosition(diagnostic.getEndPosition() + length); + } + } + + public static boolean isValid(DiagnosticWrapper d) { + return d.getStartPosition() >= 0 && d.getEndPosition() >= 0; + } +} diff --git a/app/src/main/java/com/tyron/code/language/EditorFormatter.java b/app/src/main/java/com/tyron/code/language/EditorFormatter.java new file mode 100644 index 000000000..f7c41bc3f --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/EditorFormatter.java @@ -0,0 +1,19 @@ +package com.tyron.code.language; + +import androidx.annotation.NonNull; + +/** + * Marker interface for languages that support formatting + */ +public interface EditorFormatter { + + /** + * Formats the given CharSequence on the specified start and end indices. + * @param text The text to format. + * @param startIndex The 0-based index of where the format starts + * @param endIndex The 0-based index of where the format ends + * @return The formatted text + */ + @NonNull + CharSequence format(@NonNull CharSequence text, int startIndex, int endIndex); +} diff --git a/app/src/main/java/com/tyron/code/language/HighlightUtil.java b/app/src/main/java/com/tyron/code/language/HighlightUtil.java new file mode 100644 index 000000000..f2aa5a661 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/HighlightUtil.java @@ -0,0 +1,199 @@ +package com.tyron.code.language; + +import android.util.Log; + +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.editor.CharPosition; +import com.tyron.editor.Editor; + +import javax.tools.Diagnostic; + +import java.util.ArrayList; +import java.util.List; + +import io.github.rosemoe.sora.lang.styling.Span; +import io.github.rosemoe.sora.lang.styling.Spans; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora2.BuildConfig; + +public class HighlightUtil { + + public static void replaceSpan(Styles styles, Span newSpan, int startLine, int startColumn, int endLine, int endColumn) { + for (int line = startLine; line <= endLine; line++) { + ProgressManager.checkCanceled(); + int start = (line == startLine ? startColumn : 0); + int end = (line == endLine ? endColumn : Integer.MAX_VALUE); + Spans.Reader read = styles.getSpans().read(); + List spans = new ArrayList<>(read.getSpansOnLine(line)); + int increment; + for (int i = 0; i < spans.size(); i += increment) { + ProgressManager.checkCanceled(); + io.github.rosemoe.sora.lang.styling.Span span = spans.get(i); + increment = 1; + if (span.column >= end) { + break; + } + int spanEnd = (i + 1 >= spans.size() ? Integer.MAX_VALUE : spans.get(i + 1).column); + if (spanEnd >= start) { + int regionStartInSpan = Math.max(span.column, start); + int regionEndInSpan = Math.min(end, spanEnd); + if (regionStartInSpan == span.column) { + if (regionEndInSpan != spanEnd) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionEndInSpan; + spans.add(i + 1, nSpan); + } + span.underlineColor = newSpan.underlineColor; + span.style = newSpan.style; + span.renderer = newSpan.renderer; + } else { + //regionStartInSpan > span.column + if (regionEndInSpan == spanEnd - 1) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionStartInSpan; + spans.add(i + 1, nSpan); + span.underlineColor = newSpan.underlineColor; + span.style = newSpan.style; + span.renderer = newSpan.renderer; + } else { + increment = 3; + io.github.rosemoe.sora.lang.styling.Span span1 = span.copy(); + span1.column = regionStartInSpan; + span1.underlineColor = newSpan.underlineColor; + span1.style = newSpan.style; + span1.renderer = newSpan.renderer; + io.github.rosemoe.sora.lang.styling.Span span2 = span.copy(); + span2.column = regionEndInSpan; + spans.add(i + 1, span1); + spans.add(i + 2, span2); + } + } + } + } + + Spans.Modifier modify = styles.getSpans().modify(); + modify.setSpansOnLine(line, spans); + } + } + + public static void markProblemRegion(Styles styles, int newFlag, int startLine, int startColumn, int endLine, int endColumn) { + for (int line = startLine; line <= endLine; line++) { + ProgressManager.checkCanceled(); + int start = (line == startLine ? startColumn : 0); + int end = (line == endLine ? endColumn : Integer.MAX_VALUE); + Spans.Reader read = styles.getSpans().read(); + List spans = new ArrayList<>(read.getSpansOnLine(line)); + int increment; + for (int i = 0; i < spans.size(); i += increment) { + ProgressManager.checkCanceled(); + io.github.rosemoe.sora.lang.styling.Span span = spans.get(i); + increment = 1; + if (span.column >= end) { + break; + } + int spanEnd = (i + 1 >= spans.size() ? Integer.MAX_VALUE : spans.get(i + 1).column); + if (spanEnd >= start) { + int regionStartInSpan = Math.max(span.column, start); + int regionEndInSpan = Math.min(end, spanEnd); + if (regionStartInSpan == span.column) { + if (regionEndInSpan != spanEnd) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionEndInSpan; + spans.add(i + 1, nSpan); + } + } else { + //regionStartInSpan > span.column + if (regionEndInSpan == spanEnd) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionStartInSpan; + spans.add(i + 1, nSpan); + } else { + increment = 3; + io.github.rosemoe.sora.lang.styling.Span span1 = span.copy(); + span1.column = regionStartInSpan; + io.github.rosemoe.sora.lang.styling.Span span2 = span.copy(); + span2.column = regionEndInSpan; + spans.add(i + 1, span1); + spans.add(i + 2, span2); + } + } + } + } + + Spans.Modifier modify = styles.getSpans().modify(); + modify.setSpansOnLine(line, spans); + } + } + + + /** + * Highlights the list of given diagnostics, taking care of conversion between 1-based offsets + * to 0-based offsets. + * It also makes the Diagnostic eligible for shifting as the user types. + */ + public static void markDiagnostics(Editor editor, List diagnostics, + Styles styles) { + diagnostics.forEach(it -> { + ProgressManager.checkCanceled(); + try { + int startLine; + int startColumn; + int endLine; + int endColumn; + if (it.getPosition() != DiagnosticWrapper.USE_LINE_POS) { + if (it.getStartPosition() == -1) { + it.setStartPosition(it.getPosition()); + } + if (it.getEndPosition() == -1) { + it.setEndPosition(it.getPosition()); + } + + if (it.getStartPosition() > editor.getContent().length()) { + return; + } + if (it.getEndPosition() > editor.getContent().length()) { + return; + } + CharPosition start = editor.getCharPosition((int) it.getStartPosition()); + CharPosition end = editor.getCharPosition((int) it.getEndPosition()); + + int sLine = start.getLine(); + int sColumn = start.getColumn(); + int eLine = end.getLine(); + int eColumn = end.getColumn(); + + // the editor does not support marking underline spans for the same start and end + // index + // to work around this, we just subtract one to the start index + if (sLine == eLine && eColumn == sColumn) { + sColumn--; + eColumn++; + } + + it.setStartLine(sLine); + it.setEndLine(eLine); + it.setStartColumn(sColumn); + it.setEndColumn(eColumn); + } + startLine = it.getStartLine(); + startColumn = it.getStartColumn(); + endLine = it.getEndLine(); + endColumn = it.getEndColumn(); + + } catch (IllegalArgumentException | IndexOutOfBoundsException e) { + if (BuildConfig.DEBUG) { + Log.d("HighlightUtil", "Failed to mark diagnostics", e); + } + } + }); + } + + public static void clearDiagnostics(Styles styles) { + + } +} diff --git a/app/src/main/java/com/tyron/code/language/Language.java b/app/src/main/java/com/tyron/code/language/Language.java new file mode 100644 index 000000000..222c893dd --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/Language.java @@ -0,0 +1,32 @@ +package com.tyron.code.language; + +import com.tyron.editor.Editor; + +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystem; +import org.apache.commons.vfs2.provider.local.LocalFile; +import org.apache.commons.vfs2.provider.local.LocalFileSystem; + +import java.io.File; + +public interface Language { + + /** + * Subclasses return whether they support this file extension + */ + boolean isApplicable(File ext); + + default boolean isApplicable(FileObject fileObject) { + if (fileObject instanceof LocalFile) { + return isApplicable(new File(fileObject.getURI())); + } + return false; + } + + /** + * + * @param editor the editor instance + * @return The specific language instance for this editor + */ + io.github.rosemoe.sora.lang.Language get(Editor editor); +} diff --git a/app/src/main/java/com/tyron/code/language/LanguageManager.java b/app/src/main/java/com/tyron/code/language/LanguageManager.java new file mode 100644 index 000000000..c7ba41aa5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/LanguageManager.java @@ -0,0 +1,93 @@ +package com.tyron.code.language; + +import android.content.res.AssetManager; + +import com.tyron.code.ApplicationLoader; +import com.tyron.code.language.groovy.Groovy; +import com.tyron.code.language.java.Java; +import com.tyron.code.language.json.Json; +import com.tyron.code.language.kotlin.Kotlin; +import com.tyron.code.language.xml.Xml; +import com.tyron.editor.Editor; + +import org.apache.commons.vfs2.FileObject; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Supplier; + +import io.github.rosemoe.sora.langs.textmate.TextMateColorScheme; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +import io.github.rosemoe.sora.widget.CodeEditor; + +public class LanguageManager { + + private static LanguageManager Instance = null; + + public static LanguageManager getInstance() { + if (Instance == null) { + Instance = new LanguageManager(); + } + return Instance; + } + + private final Set mLanguages = new HashSet<>(); + + private LanguageManager() { + initLanguages(); + } + + private void initLanguages() { + mLanguages.addAll( + Arrays.asList( + new Xml(), + new Java(), + new Kotlin(), + new Groovy(), + new Json())); + } + + public boolean supports(File file) { + for (Language language : mLanguages) { + if (language.isApplicable(file)) { + return true; + } + } + return false; + } + + public io.github.rosemoe.sora.lang.Language get(Editor editor, FileObject file) { + for (Language lang : mLanguages) { + if (lang.isApplicable(file)) { + return lang.get(editor); + } + } + return null; + } + + public io.github.rosemoe.sora.lang.Language get(Editor editor, File file) { + for (Language lang : mLanguages) { + if (lang.isApplicable(file)) { + return lang.get(editor); + } + } + return null; + } + + public static TextMateLanguage createTextMateLanguage(String grammarName, String grammarPath, String configurationPath, Editor editor) { + AssetManager assets = ApplicationLoader.getInstance().getAssets(); + try { + return TextMateLanguage.createNoCompletion( + grammarName, + assets.open(grammarPath), + new InputStreamReader(assets.open(configurationPath)), + ((TextMateColorScheme) ((CodeEditor) editor).getColorScheme()).getRawTheme()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/groovy/AndroidDelegate.java b/app/src/main/java/com/tyron/code/language/groovy/AndroidDelegate.java new file mode 100644 index 000000000..1e59b5beb --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/AndroidDelegate.java @@ -0,0 +1,18 @@ +package com.tyron.code.language.groovy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AndroidDelegate { + + // Here to support different versions of delegate types. The values of delegate + // types are sorted by priority + private static final Map> delegateMap; + + static { + delegateMap = new HashMap<>(); + + //plugins + } +} diff --git a/app/src/main/java/com/tyron/code/language/groovy/CompletionHandler.java b/app/src/main/java/com/tyron/code/language/groovy/CompletionHandler.java new file mode 100644 index 000000000..63c7aa697 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/CompletionHandler.java @@ -0,0 +1,181 @@ +package com.tyron.code.language.groovy; + +import com.tyron.code.language.java.Java; +import com.tyron.completion.java.provider.JavaSortCategory; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.DrawableKind; + +import org.codehaus.groovy.ast.expr.MethodCallExpression; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class CompletionHandler { + + private static final String BUILD_GRADLE = "build.gradle"; + private static final String SETTING_GRADLE = "settings.gradle"; + private static final String DEPENDENCYHANDLER_CLASS = "org.gradle.api.artifacts.dsl.DependencyHandler"; + private static final List CONFIGURATIONS = List.of("implementation", "compileOnly", "runtimeOnly", "classpath", "api"); + + public List getCompletionItems(MethodCallExpression containingCall, String fileName, String projectPath, Set plugins) { + Set resultSet = new HashSet<>(); + List results = new ArrayList<>(); + List delegateClassNames = new ArrayList<>(); + + if (containingCall == null) { + if (BUILD_GRADLE.equals(fileName)) { + delegateClassNames.add(GradleDelegate.getDefault()); + } else if (SETTING_GRADLE.equals(fileName)) { + delegateClassNames.add(GradleDelegate.getSettings()); + } + results.addAll(getCompletionItemsFromExtClosures(projectPath, resultSet)); + } else { + String methodName = containingCall.getMethodAsString(); + List delegates = GradleDelegate.getDelegateMap().get(methodName); + if (delegates == null) { + return results; + } + delegateClassNames.addAll(delegates); + } + if (delegateClassNames.isEmpty()) { + return Collections.emptyList(); + } + for (String delegateClassName : delegateClassNames) { + Class> delegateClass; + try { + delegateClass = Class.forName(delegateClassName); + } catch (ClassNotFoundException e) { + delegateClass = null; + } + if (delegateClass == null) { + continue; + } + results.addAll(getCompletionItemsFromClass(delegateClass, plugins, resultSet)); + break; + } + + + return results; + } + + private List getCompletionItemsFromClass(Class> javaClass, Set plugins, Set resultSet) { + List results = new ArrayList<>(); + for (Class> superInterface : javaClass.getInterfaces()) { + results.addAll(getCompletionItemsFromClass(superInterface, plugins, resultSet)); + } + Class> superclass = javaClass.getSuperclass(); + if (superclass != null) { + results.addAll(getCompletionItemsFromClass(superclass, plugins, resultSet)); + } + + Method[] methods = javaClass.getMethods(); + for (Method method : methods) { + boolean isMethodDeprecated = isDeprecated(method); + String methodName = method.getName(); + + // When parsing a abstract class, we'll get a "" method which can't be + // called directly, + // So we filter it here. + if (methodName.equals("")) { + continue; + } + List arguments = new ArrayList<>(); + Arrays.asList(method.getParameterTypes()).forEach(type -> + arguments.add(type.getName())); + CompletionItem item = generateCompletionItemForMethod(methodName, arguments, isMethodDeprecated); + if (resultSet.add(item.getLabel())) { + results.add(item); + } + int modifiers = method.getModifiers(); + // See: + // https://docs.gradle.org/current/userguide/custom_gradle_types.html#managed_properties + // we offer managed properties for an abstract getter method; + if (methodName.startsWith("get") && methodName.length() > 3 && Modifier.isPublic(modifiers) + && Modifier.isAbstract(modifiers)) { + String propertyName = methodName.substring(3, 4).toLowerCase() + methodName.substring(4); + CompletionItem property = new CompletionItem(propertyName); + property.commitText = propertyName; + property.detail = "Property"; + property.iconKind = DrawableKind.Attribute; + property.setSortText(JavaSortCategory.ACCESSIBLE_SYMBOL.toString()); + property.addFilterText(propertyName); + if (resultSet.add(propertyName)) { + results.add(property); + } + } + } + boolean shouldAddConfigurations = !Collections.disjoint( + plugins, + List.of("com.android.application", "application", "java", "java-library") + ); + if (shouldAddConfigurations && javaClass.getName().equals(DEPENDENCYHANDLER_CLASS)) { + for (String configuration : CONFIGURATIONS) { + String builder = configuration + "(Object... o)"; + String insertBuilder = configuration + "()"; + CompletionItem item = new CompletionItem(builder); + item.iconKind = DrawableKind.Method; + item.commitText = insertBuilder; + item.addFilterText(configuration); + item.setSortText(JavaSortCategory.ACCESSIBLE_SYMBOL.toString()); + results.add(item); + } + } + return results; + } + + private List getCompletionItemsFromExtClosures(String projectPath, + Set resultSet) { + return Collections.emptyList(); + } + + private static CompletionItem generateCompletionItemForMethod(String name, List arguments, + boolean deprecated) { + StringBuilder labelBuilder = new StringBuilder(); + labelBuilder.append(name); + labelBuilder.append("("); + for (int i = 0; i < arguments.size(); i++) { + String type = arguments.get(i); + String[] classNameSplits = type.split("\\."); + String className = classNameSplits[classNameSplits.length - 1]; + String variableName = className.substring(0, 1).toLowerCase(); + labelBuilder.append(className); + labelBuilder.append(" "); + labelBuilder.append(variableName); + if (i != arguments.size() - 1) { + labelBuilder.append(", "); + } + } + labelBuilder.append(")"); + String label = labelBuilder.toString(); + CompletionItem item = new CompletionItem(label); + item.addFilterText(name); + item.setSortText(JavaSortCategory.ACCESSIBLE_SYMBOL.toString()); + item.iconKind = DrawableKind.Method; + + StringBuilder builder = new StringBuilder(); + builder.append(name); + if (label.endsWith("(Closure c)")) { + // for single closure, we offer curly brackets + builder.append(" {}"); + } else { + builder.append("()"); + } + item.commitText = builder.toString(); + return item; + } + + private static boolean isDeprecated(Method object) { + try { + Deprecated annotation = object.getAnnotation(Deprecated.class); + return annotation != null; + } catch (Throwable t) { + return false; + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/groovy/CompletionVisitor.java b/app/src/main/java/com/tyron/code/language/groovy/CompletionVisitor.java new file mode 100644 index 000000000..729459cbf --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/CompletionVisitor.java @@ -0,0 +1,247 @@ +package com.tyron.code.language.groovy; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.codehaus.groovy.ast.ClassCodeVisitorSupport; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.GStringExpression; +import org.codehaus.groovy.ast.expr.MapEntryExpression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.NamedArgumentListExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; +import org.eclipse.lsp4j.Range; + +public class CompletionVisitor extends ClassCodeVisitorSupport { + + @Override + protected SourceUnit getSourceUnit() { + return sourceUnit; + } + + public class DependencyItem { + private String text; + private Range range; + + public DependencyItem(String text, Range range) { + this.text = text; + this.range = range; + } + + public String getText() { + return this.text; + } + + public Range getRange() { + return this.range; + } + } + + private URI currentUri; + private final Map> dependencies = new HashMap<>(); + private final Map> methodCalls = new HashMap<>(); + private final Map> statements = new HashMap<>(); + private final Map> constants = new HashMap<>(); + private final Map> plugins = new HashMap<>(); + + public List getDependencies(URI uri) { + return this.dependencies.get(uri); + } + + public Set getMethodCalls(URI uri) { + return this.methodCalls.get(uri); + } + + public List getStatements(URI uri) { + return this.statements.get(uri); + } + + public List getConstants(URI uri) { + return this.constants.get(uri); + } + + public Set getPlugins(URI uri) { + return this.plugins.get(uri); + } + + public void visitCompilationUnit(URI uri, CompilationUnit compilationUnit) { + this.currentUri = uri; + compilationUnit.iterator().forEachRemaining(this::visitSourceUnit); + } + + SourceUnit sourceUnit; + + public void visitSourceUnit(SourceUnit unit) { + this.sourceUnit = unit; + ModuleNode moduleNode = unit.getAST(); + if (moduleNode != null) { + this.dependencies.put(this.currentUri, new ArrayList<>()); + this.methodCalls.put(this.currentUri, new HashSet<>()); + this.statements.put(this.currentUri, new ArrayList<>()); + this.constants.put(this.currentUri, new ArrayList<>()); + this.plugins.put(this.currentUri, new HashSet<>()); + visitModule(moduleNode); + } + } + + public void visitModule(ModuleNode node) { + BlockStatement blockStatement = node.getStatementBlock(); + this.statements.put(currentUri, blockStatement.getStatements()); + node.getClasses().forEach(super::visitClass); + } + + @Override + public void visitMethodCallExpression(MethodCallExpression node) { + this.methodCalls.get(this.currentUri).add(node); + if (node.getMethodAsString().equals("dependencies")) { + this.dependencies.get(this.currentUri).addAll(getDependencies(node)); + } else if (node.getMethodAsString().equals("plugins")) { + // match plugins { id: ${id} } + List plugins = getPluginFromPlugins(node); + this.plugins.get(this.currentUri).addAll(plugins); + } else if (node.getMethodAsString().equals("apply")) { + // match apply plugins: '${id}' + String plugin = getPluginFromApply(node); + if (plugin != null) { + this.plugins.get(this.currentUri).add(plugin); + } + } + super.visitMethodCallExpression(node); + } + + private List getDependencies(MethodCallExpression expression) { + Expression argument = expression.getArguments(); + if (argument instanceof ArgumentListExpression) { + return getDependencies((ArgumentListExpression) argument); + } + return Collections.emptyList(); + } + + private List getDependencies(ArgumentListExpression argumentListExpression) { + List expressions = argumentListExpression.getExpressions(); + List symbols = new ArrayList<>(); + for (Expression expression : expressions) { + if (expression instanceof ClosureExpression) { + symbols.addAll(getDependencies((ClosureExpression) expression)); + } else if (expression instanceof GStringExpression || expression instanceof ConstantExpression) { + // GStringExp: implementation + // "org.gradle:gradle-tooling-api:${gradleToolingApi}" + // ConstantExp: implementation "org.gradle:gradle-tooling-api:6.8.0" + symbols.add(new DependencyItem(expression.getText(), GroovyUtils.toDependencyRange(expression))); + } else if (expression instanceof MethodCallExpression) { + symbols.addAll(getDependencies((MethodCallExpression) expression)); + } + } + return symbols; + } + + private List getDependencies(ClosureExpression expression) { + Statement code = expression.getCode(); + if (code instanceof BlockStatement) { + return getDependencies((BlockStatement) code); + } + return Collections.emptyList(); + } + + private List getDependencies(BlockStatement blockStatement) { + List statements = blockStatement.getStatements(); + List results = new ArrayList<>(); + for (Statement statement : statements) { + if (statement instanceof ExpressionStatement) { + results.addAll(getDependencies((ExpressionStatement) statement)); + } + } + return results; + } + + private List getDependencies(ExpressionStatement expressionStatement) { + Expression expression = expressionStatement.getExpression(); + if (expression instanceof MethodCallExpression) { + return getDependencies((MethodCallExpression) expression); + } + return Collections.emptyList(); + } + + private String getPluginFromApply(MethodCallExpression node) { + Expression argument = node.getArguments(); + if (argument instanceof TupleExpression) { + List expressions = ((TupleExpression) argument).getExpressions(); + for (Expression expression : expressions) { + if (expression instanceof NamedArgumentListExpression) { + List mapEntryExpressions = ((NamedArgumentListExpression) expression) + .getMapEntryExpressions(); + for (MapEntryExpression mapEntryExp : mapEntryExpressions) { + Expression keyExpression = mapEntryExp.getKeyExpression(); + if (keyExpression instanceof ConstantExpression && keyExpression.getText().equals("plugin")) { + return mapEntryExp.getValueExpression().getText(); + } + } + } + } + } + return null; + } + + private List getPluginFromPlugins(MethodCallExpression node) { + Expression objectExpression = node.getObjectExpression(); + if (objectExpression instanceof MethodCallExpression) { + return getPluginFromPlugins((MethodCallExpression) objectExpression); + } + List results = new ArrayList<>(); + Expression argument = node.getArguments(); + if (argument instanceof ArgumentListExpression) { + List expressions = ((ArgumentListExpression) argument).getExpressions(); + for (Expression expression : expressions) { + if (expression instanceof ConstantExpression && node.getMethodAsString().equals("id")) { + results.add(expression.getText()); + } else if (expression instanceof ClosureExpression) { + Statement code = ((ClosureExpression) expression).getCode(); + if (code instanceof BlockStatement) { + results.addAll(getPluginFromPlugins((BlockStatement) code)); + } + } + } + } + return results; + } + + private List getPluginFromPlugins(BlockStatement code) { + List results = new ArrayList<>(); + List statements = code.getStatements(); + for (Statement statement : statements) { + if (statement instanceof ExpressionStatement) { + Expression expression = ((ExpressionStatement) statement).getExpression(); + if (expression instanceof MethodCallExpression) { + results.addAll(getPluginFromPlugins((MethodCallExpression) expression)); + } + } + } + return results; + } + + @Override + public void visitConstantExpression(ConstantExpression expression) { + this.constants.get(currentUri).add(expression); + super.visitConstantExpression(expression); + } + + @Override + public void visitGStringExpression(GStringExpression expression) { + this.constants.get(currentUri).add(expression); + super.visitGStringExpression(expression); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/groovy/GradleDelegate.java b/app/src/main/java/com/tyron/code/language/groovy/GradleDelegate.java new file mode 100644 index 000000000..57e98f0be --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/GradleDelegate.java @@ -0,0 +1,57 @@ +package com.tyron.code.language.groovy; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GradleDelegate { + private static final String PROJECT = "org.gradle.api.Project"; + private static final String SETTINGS = "org.gradle.api.initialization.Settings"; + // Here to support different versions of delegate types. The values of delegate + // types are sorted by priority + private static final Map> delegateMap; + + static { + delegateMap = new HashMap<>(); + // plugins + delegateMap.put("application", List.of("org.gradle.api.plugins.JavaApplication", + "org.gradle.api.plugins.ApplicationPluginConvention")); + delegateMap.put("base", List.of("org.gradle.api.plugins.BasePluginExtension", + "org.gradle.api.plugins.BasePluginConvention")); + delegateMap.put("java", List.of("org.gradle.api.plugins.JavaPluginExtension", + "org.gradle.api.plugins.JavaPluginConvention")); + delegateMap.put("war", List.of("org.gradle.api.plugins.WarPluginConvention")); + // basic closures + delegateMap.put("plugins", List.of("org.gradle.plugin.use.PluginDependenciesSpec")); + delegateMap.put("configurations", List.of("org.gradle.api.artifacts.Configuration")); + delegateMap.put("dependencySubstitution", List.of("org.gradle.api.artifacts.DependencySubstitutions")); + delegateMap.put("resolutionStrategy", List.of("org.gradle.api.artifacts.ResolutionStrategy")); + delegateMap.put("artifacts", List.of("org.gradle.api.artifacts.dsl.ArtifactHandler")); + delegateMap.put("components", List.of("org.gradle.api.artifacts.dsl.ComponentMetadataHandler")); + delegateMap.put("modules", List.of("org.gradle.api.artifacts.dsl.ComponentModuleMetadataHandler")); + delegateMap.put("dependencies", List.of("org.gradle.api.artifacts.dsl.DependencyHandler")); + delegateMap.put("repositories", List.of("org.gradle.api.artifacts.dsl.RepositoryHandler")); + delegateMap.put("publishing", List.of("org.gradle.api.publish.PublishingExtension")); + delegateMap.put("publications", List.of("org.gradle.api.publish.PublicationContainer")); + delegateMap.put("sourceSets", List.of("org.gradle.api.tasks.SourceSet")); + delegateMap.put("distributions", List.of("org.gradle.api.distribution.Distribution")); + delegateMap.put("fileTree", List.of("org.gradle.api.file.ConfigurableFileTree")); + delegateMap.put("copySpec", List.of("org.gradle.api.file.CopySpec")); + delegateMap.put("exec", List.of("org.gradle.process.ExecSpec")); + delegateMap.put("files", List.of("org.gradle.api.file.ConfigurableFileCollection")); + delegateMap.put("task", List.of("org.gradle.api.Task")); + } + + public static Map> getDelegateMap() { + return delegateMap; + } + + public static String getDefault() { + return PROJECT; + } + + public static String getSettings() { + return SETTINGS; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/groovy/Groovy.java b/app/src/main/java/com/tyron/code/language/groovy/Groovy.java new file mode 100644 index 000000000..8583866ed --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/Groovy.java @@ -0,0 +1,31 @@ +package com.tyron.code.language.groovy; + +import com.tyron.code.language.Language; +import com.tyron.code.language.LanguageManager; +import com.tyron.editor.Editor; + +import org.apache.commons.vfs2.FileObject; + +import java.io.File; + +public class Groovy implements Language { + + + + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".groovy") || ext.getName().endsWith(".gradle"); + } + + @Override + public boolean isApplicable(FileObject fileObject) { + String extension = fileObject.getName().getExtension(); + return "groovy".equals(extension) || "gradle".equals(extension); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new GroovyLanguage(editor); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/groovy/GroovyLanguage.java b/app/src/main/java/com/tyron/code/language/groovy/GroovyLanguage.java new file mode 100644 index 000000000..200f598ec --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/GroovyLanguage.java @@ -0,0 +1,278 @@ +package com.tyron.code.language.groovy; + +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.code.language.CompletionItemWrapper; +import com.tyron.code.language.LanguageManager; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Editor; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.Phases; +import org.codehaus.groovy.control.SourceUnit; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.util.Ranges; + +import java.io.File; +import java.net.URI; +import java.util.List; +import java.util.Set; + +import groovy.lang.GroovyClassLoader; +import groovy.lang.GroovyCodeSource; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionHelper; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.format.Formatter; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +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.util.MyCharacter; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class GroovyLanguage implements Language { + + private static final String GRAMMAR_NAME = "groovy.tmLanguage"; + private static final String LANGUAGE_PATH = "textmate/groovy/syntaxes/groovy.tmLanguage"; + private static final String CONFIG_PATH = "textmate/groovy/language-configuration.json"; + + private final Editor editor; + private final TextMateLanguage delegate; + + + public GroovyLanguage(Editor editor) { + this.editor = editor; + delegate = LanguageManager.createTextMateLanguage(GRAMMAR_NAME, LANGUAGE_PATH, CONFIG_PATH, editor); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return delegate.getAnalyzeManager(); + } + + @Override + public int getInterruptionLevel() { + return delegate.getInterruptionLevel(); + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) throws CompletionCancelledException { + String prefix = CompletionHelper.computePrefix(content, position, MyCharacter::isJavaIdentifierPart); + + if (true) { + return; + } + try { + File currentFile = editor.getCurrentFile(); + URI uri = currentFile.toURI(); + Position pos = new Position(position.getLine(), position.getColumn()); + + CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); + GroovyClassLoader classLoader = new GroovyClassLoader(getClass().getClassLoader(), compilerConfiguration); + CompilationUnit unit = new CompilationUnit(compilerConfiguration, null, classLoader); + SourceUnit source = new SourceUnit( + currentFile.getName(), + content.getReference().toString(), + unit.getConfiguration(), + classLoader, + unit.getErrorCollector() + ); + unit.addSource(source); + + unit.compile(Phases.CANONICALIZATION); + + CompletionVisitor completionVisitor = new CompletionVisitor(); + completionVisitor.visitCompilationUnit(uri, + unit + ); + + Set methodCalls = completionVisitor.getMethodCalls(uri); + MethodCallExpression containingCall = null; + for (MethodCallExpression call : methodCalls) { + Expression expression = call.getArguments(); + Range range = GroovyUtils.toRange(expression); + if (Ranges.containsPosition(range, pos) + && (containingCall == null || Ranges.containsRange(GroovyUtils.toRange(containingCall.getArguments()), + GroovyUtils.toRange(call.getArguments())))) { + // find inner containing call + containingCall = call; + } + } + + if (containingCall != null) { + CompletionHandler completionHandler = new CompletionHandler(); + List completionItems = completionHandler.getCompletionItems( + containingCall, + currentFile.getName(), + editor.getProject().getRootFile().getAbsolutePath(), + completionVisitor.getPlugins(uri)); + CompletionList.Builder builder = new CompletionList.Builder(prefix); + completionItems.forEach(builder::addItem); + builder.build().getItems().forEach(it -> { + publisher.addItem(new CompletionItemWrapper(it)); + }); + } + } catch (Exception e) { + System.out.println(e); + } + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + return delegate.getIndentAdvance(content, line, column); + } + + @Override + public boolean useTab() { + return false; + } + + @NonNull + @Override + public Formatter getFormatter() { + return delegate.getFormatter(); + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return delegate.getSymbolPairs(); + } + + @Nullable + @Override + public NewlineHandler[] getNewlineHandlers() { + return new NewlineHandler[]{ + new BraceHandler(), + new JavaDocHandler(), + new TwoIndentHandler() + }; + } + + @Override + public void destroy() { + delegate.destroy(); + } + + public int getIndentAdvance(String p1) { + GroovyLexer groovyLexer = new GroovyLexer(CharStreams.fromString(p1)); + Token token; + int advance = 0; + while ((token = groovyLexer.nextToken()).getType() != Token.EOF) { + if (token.getType() == GroovyLexer.LBRACK) { + advance++; + } + } + return (advance * delegate.getTabSize()); + } + + class TwoIndentHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + Log.d("BeforeText", beforeText); + if (beforeText.replace("\r", "").trim().startsWith(".")) { + return false; + } + return beforeText.endsWith(")") && !afterText.startsWith(";"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText) + (4 * 2); + String text; + StringBuilder sb = new StringBuilder().append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = 0; + return new NewlineHandleResult(sb, shiftLeft); + } + + + } + + class BraceHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.endsWith("{") && afterText.startsWith("}"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceBefore = getIndentAdvance(beforeText); + int advanceAfter = getIndentAdvance(afterText); + String text; + StringBuilder sb = new StringBuilder("\n").append( + TextUtils.createIndent(count + advanceBefore, tabSize, useTab())).append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = text.length() + 1; + return new NewlineHandleResult(sb, shiftLeft); + } + } + + class JavaDocStartHandler implements NewlineHandler { + + private boolean shouldCreateEnd = true; + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().startsWith("/**"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText); + String text = ""; + StringBuilder sb = new StringBuilder().append("\n") + .append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())) + .append(" * "); + if (shouldCreateEnd) { + sb.append("\n").append( + text = TextUtils.createIndent(count + advanceAfter, tabSize, + useTab())) + .append(" */"); + } + return new NewlineHandleResult(sb, text.length() + 4); + } + } + + class JavaDocHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().startsWith("*") && !beforeText.trim().startsWith("*/"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText); + StringBuilder sb = new StringBuilder().append("\n") + .append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())) + .append("* "); + return new NewlineHandleResult(sb, 0); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLexer.g4 b/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.g4 similarity index 100% rename from app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLexer.g4 rename to app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.g4 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLexer.java b/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.java similarity index 99% rename from app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLexer.java rename to app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.java index 3f489ba59..bd90d8258 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLexer.java +++ b/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.java @@ -1,5 +1,5 @@ // Generated from C:/Users/bounc/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/groovy\GroovyLexer.g4 by ANTLR 4.9.1 -package com.tyron.code.ui.editor.language.groovy; +package com.tyron.code.language.groovy; import java.util.ArrayDeque; import java.util.Arrays; @@ -10,11 +10,9 @@ import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.*; + import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class GroovyLexer extends Lexer { diff --git a/app/src/main/java/com/tyron/code/language/groovy/GroovyUtils.java b/app/src/main/java/com/tyron/code/language/groovy/GroovyUtils.java new file mode 100644 index 000000000..cc804df4d --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/GroovyUtils.java @@ -0,0 +1,34 @@ +package com.tyron.code.language.groovy; + +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.syntax.SyntaxException; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; + +public class GroovyUtils { + + public static Range toRange(SyntaxException exp) { + // LSP Range start from 0, while groovy classes start from 1 + return new Range(new Position(exp.getStartLine() - 1, exp.getStartColumn() - 1), + new Position(exp.getEndLine() - 1, exp.getEndColumn() - 1)); + } + + public static Range toRange(Expression expression) { + // LSP Range start from 0, while groovy expressions start from 1 + return new Range(new Position(expression.getLineNumber() - 1, expression.getColumnNumber() - 1), + new Position(expression.getLastLineNumber() - 1, expression.getLastColumnNumber() - 1)); + } + + public static Range toRange(Statement statement) { + // LSP Range start from 0, while groovy expressions start from 1 + return new Range(new Position(statement.getLineNumber() - 1, statement.getColumnNumber() - 1), + new Position(statement.getLastLineNumber() - 1, statement.getLastColumnNumber() - 1)); + } + + public static Range toDependencyRange(Expression expression) { + // For dependency, the string includes open/close quotes should be excluded + return new Range(new Position(expression.getLineNumber() - 1, expression.getColumnNumber()), + new Position(expression.getLastLineNumber() - 1, expression.getLastColumnNumber() - 2)); + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/Java.java b/app/src/main/java/com/tyron/code/language/java/Java.java new file mode 100644 index 000000000..eddc2a7ff --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/Java.java @@ -0,0 +1,27 @@ +package com.tyron.code.language.java; + +import com.tyron.code.language.Language; +import com.tyron.editor.Editor; + +import org.apache.commons.vfs2.FileObject; + +import java.io.File; + +public class Java implements Language { + + + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".java"); + } + + @Override + public boolean isApplicable(FileObject fileObject) { + return fileObject.getName().getExtension().equals("java"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new JavaLanguage(editor); + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/JavaAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/java/JavaAutoCompleteProvider.java new file mode 100644 index 000000000..fa0d3d384 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/JavaAutoCompleteProvider.java @@ -0,0 +1,62 @@ +package com.tyron.code.language.java; + +import android.content.SharedPreferences; + +import androidx.annotation.Nullable; + +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.JavaModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.language.AbstractAutoCompleteProvider; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.completion.main.CompletionEngine; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Content; +import com.tyron.editor.Editor; + +import java.util.Optional; + +public class JavaAutoCompleteProvider extends AbstractAutoCompleteProvider { + + private final Editor mEditor; + private final SharedPreferences mPreferences; + + public JavaAutoCompleteProvider(Editor editor) { + mEditor = editor; + mPreferences = ApplicationLoader.getDefaultPreferences(); + } + + + @Nullable + @Override + public CompletionList getCompletionList(String prefix, int line, int column) { + if (!mPreferences.getBoolean(SharedPreferenceKeys.JAVA_CODE_COMPLETION, true)) { + return null; + } + + Project project = ProjectManager.getInstance().getCurrentProject(); + + if (project == null) { + return null; + } + + Module currentModule = project.getModule(mEditor.getCurrentFile()); + + if (currentModule instanceof JavaModule) { + Content content = mEditor.getContent(); + return CompletionEngine.getInstance() + .complete(project, + currentModule, + mEditor, + mEditor.getCurrentFile(), + content.toString(), + prefix, + line, + column, + mEditor.getCaret().getStart()); + } + return null; + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/JavaLanguage.java b/app/src/main/java/com/tyron/code/language/java/JavaLanguage.java new file mode 100644 index 000000000..d53a47773 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/JavaLanguage.java @@ -0,0 +1,332 @@ +package com.tyron.code.language.java; + +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.sun.tools.javac.util.JCDiagnostic; +import com.tyron.builder.project.Project; +import com.tyron.code.language.CompletionItemWrapper; +import com.tyron.code.language.EditorFormatter; +import com.tyron.code.language.LanguageManager; +import com.tyron.completion.CompletionParameters; +import com.tyron.completion.java.JavaCompletionProvider; +import com.tyron.completion.java.compiler.services.NBLog; +import com.tyron.completion.java.parse.CompilationInfo; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Editor; +import com.tyron.language.api.CodeAssistLanguage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; + +import io.github.rosemoe.editor.langs.java.JavaTextTokenizer; +import io.github.rosemoe.editor.langs.java.Tokens; +import io.github.rosemoe.sora.lang.EmptyLanguage; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionHelper; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.diagnostic.DiagnosticRegion; +import io.github.rosemoe.sora.lang.format.AsyncFormatter; +import io.github.rosemoe.sora.lang.format.Formatter; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.Content; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.text.TextRange; +import io.github.rosemoe.sora.text.TextUtils; +import io.github.rosemoe.sora.util.MyCharacter; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class JavaLanguage implements Language, EditorFormatter, CodeAssistLanguage { + + private static final Logger LOGGER = LoggerFactory.getLogger(JavaLanguage.class); + + private static final String GRAMMAR_NAME = "java.tmLanguage.json"; + private static final String LANGUAGE_PATH = "textmate/java/syntaxes/java.tmLanguage.json"; + private static final String CONFIG_PATH = "textmate/java/language-configuration.json"; + + private final Editor editor; + private final TextMateLanguage delegate; + private final Formatter formatter = new AsyncFormatter() { + @Nullable + @Override + public TextRange formatAsync(@NonNull Content text, @NonNull TextRange cursorRange) { + String format = com.tyron.eclipse.formatter.Formatter.format(text.toString(), + cursorRange.getStartIndex(), + cursorRange.getEndIndex() - cursorRange.getStartIndex()); + if (!text.toString().equals(format)) { + text.delete(0, text.getLineCount() - 1); + text.insert(0, 0, format); + } + return cursorRange; + } + + @Nullable + @Override + public TextRange formatRegionAsync(@NonNull Content text, + @NonNull TextRange rangeToFormat, + @NonNull TextRange cursorRange) { + return null; + } + }; + + + public JavaLanguage(Editor editor) { + this.editor = editor; + delegate = LanguageManager.createTextMateLanguage(GRAMMAR_NAME, LANGUAGE_PATH, CONFIG_PATH, editor); + } + + + public boolean isAutoCompleteChar(char p1) { + return p1 == '.' || MyCharacter.isJavaIdentifierPart(p1); + } + + public int getIndentAdvance(String p1) { + JavaTextTokenizer tokenizer = new JavaTextTokenizer(p1); + Tokens token; + int advance = 0; + while ((token = tokenizer.directNextToken()) != Tokens.EOF) { + switch (token) { + case LBRACE: + advance++; + break; + } + } + return (advance * getTabWidth()); + } + + public int getFormatIndent(String line) { + JavaTextTokenizer tokenizer = new JavaTextTokenizer(line); + Tokens token; + int advance = 0; + while ((token = tokenizer.directNextToken()) != Tokens.EOF) { + switch (token) { + case LBRACE: + advance++; + break; + case RBRACE: + advance--; + } + } + return (advance * getTabWidth()); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return delegate.getAnalyzeManager(); + } + + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_SLIGHT; + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) throws CompletionCancelledException { + String prefix = CompletionHelper.computePrefix(content, position, MyCharacter::isJavaIdentifierPart); + CompletionParameters parameters = CompletionParameters.builder() + .setColumn(position.getColumn()) + .setLine(position.getLine()) + .setIndex(position.getIndex()) + .setEditor(editor) + .setFile(editor.getCurrentFile()) + .setProject(editor.getProject()) + .setModule(editor.getProject().getMainModule()) + .setContents(content.getReference().toString()) + .setPrefix(prefix) + .build(); + JavaCompletionProvider provider = new JavaCompletionProvider(); + CompletionList list = provider.complete(parameters); + + publisher.setUpdateThreshold(0); + publisher.addItems(list.getItems().stream().map(CompletionItemWrapper::new) + .collect(Collectors.toList())); + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + String text = content.getLine(line).substring(0, column); + return getIndentAdvance(text); + } + + @Override + public boolean useTab() { + return true; + } + + @NonNull + @Override + public Formatter getFormatter() { + return formatter; + } + + public int getTabWidth() { + return 4; + } + + public CharSequence format(CharSequence p1) { + return format(p1, 0, p1.length()); + } + + @NonNull + @Override + public CharSequence format(@NonNull CharSequence contents, int start, int end) { + return com.tyron.eclipse.formatter.Formatter.format(contents.toString(), start, + end - start); + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return delegate.getSymbolPairs(); + } + + private final NewlineHandler[] newLineHandlers = + new NewlineHandler[]{new BraceHandler(), new TwoIndentHandler(), + new JavaDocStartHandler(), new JavaDocHandler()}; + + @Override + public NewlineHandler[] getNewlineHandlers() { + return newLineHandlers; + } + + @Override + public void destroy() { + delegate.destroy(); + } + + @Override + public void onContentChange(File file, CharSequence content) { + Project project = editor.getProject(); + if (project == null) { + return; + } + CompilationInfo compilationInfo = CompilationInfo.get(project, editor.getCurrentFile()); + if (compilationInfo == null) { + return; + } + JavaFileObject fileObject = new SimpleJavaFileObject(editor.getCurrentFile().toURI(), + JavaFileObject.Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return content; + } + }; + try { + compilationInfo.update(fileObject); + } catch (Throwable t) { + LOGGER.error("Failed to update compilation unit", t); + } + } + + class TwoIndentHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + Log.d("BeforeText", beforeText); + if (beforeText.replace("\r", "").trim().startsWith(".")) { + return false; + } + return beforeText.endsWith(")") && !afterText.startsWith(";"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText) + (4 * 2); + String text; + StringBuilder sb = new StringBuilder().append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = 0; + return new NewlineHandleResult(sb, shiftLeft); + } + + + } + + class BraceHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.endsWith("{") && afterText.startsWith("}"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceBefore = getIndentAdvance(beforeText); + int advanceAfter = getIndentAdvance(afterText); + String text; + StringBuilder sb = new StringBuilder("\n").append( + TextUtils.createIndent(count + advanceBefore, tabSize, useTab())).append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = text.length() + 1; + return new NewlineHandleResult(sb, shiftLeft); + } + } + + class JavaDocStartHandler implements NewlineHandler { + + private boolean shouldCreateEnd = true; + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().startsWith("/**"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText); + String text = ""; + StringBuilder sb = new StringBuilder().append("\n") + .append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())) + .append(" * "); + if (shouldCreateEnd) { + sb.append("\n").append( + text = TextUtils.createIndent(count + advanceAfter, tabSize, + useTab())) + .append(" */"); + } + return new NewlineHandleResult(sb, text.length() + 4); + } + } + + class JavaDocHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().startsWith("*") && !beforeText.trim().startsWith("*/"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText); + StringBuilder sb = new StringBuilder().append("\n") + .append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())) + .append("* "); + return new NewlineHandleResult(sb, 0); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/JavaTokenTypes.java b/app/src/main/java/com/tyron/code/language/java/JavaTokenTypes.java new file mode 100644 index 000000000..20daea507 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/JavaTokenTypes.java @@ -0,0 +1,58 @@ +package com.tyron.code.language.java; + +import com.tyron.code.analyzer.semantic.TokenType; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; +import com.sun.tools.javac.code.Symbol; + +public class JavaTokenTypes { + + public static final TokenType FIELD = TokenType.create("variable.other.object.property.java"); + public static final TokenType CONSTANT = TokenType.create("variable.other.constant"); + public static final TokenType PARAMETER = TokenType.create("variable.parameter"); + public static final TokenType CLASS = TokenType.create("entity.name.type.class"); + public static final TokenType METHOD_CALL = TokenType.create("meta.method-call"); + public static final TokenType METHOD_DECLARATION = TokenType.create("entity.name.function.member"); + public static final TokenType VARIABLE = TokenType.create("entity.name.variable"); + public static final TokenType CONSTRUCTOR = TokenType.create("class.instance.constructor"); + public static final TokenType ANNOTATION = TokenType.create("storage.type.annotation"); + + public static TokenType getApplicableType(Element element) { + if (element == null) { + return null; + } + + switch (element.getKind()) { + case LOCAL_VARIABLE: + Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) element; + if (varSymbol.getModifiers().contains(Modifier.FINAL)) { + return CONSTANT; + } + return VARIABLE; + case METHOD: + Symbol.MethodSymbol methodSymbol = ((Symbol.MethodSymbol) element); + if (methodSymbol.isConstructor()) { + return getApplicableType(methodSymbol.getEnclosingElement()); + } + return METHOD_DECLARATION; + case FIELD: + VariableElement variableElement = ((VariableElement) element); + if (variableElement.getModifiers().contains(Modifier.FINAL)) { + return CONSTANT; + } + return FIELD; + case CLASS: + return CLASS; + case CONSTRUCTOR: + return CONSTRUCTOR; + case PARAMETER: + return PARAMETER; + case ANNOTATION_TYPE: + return ANNOTATION; + default: + return null; + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 b/app/src/main/java/com/tyron/code/language/json/JSON.g4 similarity index 86% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 rename to app/src/main/java/com/tyron/code/language/json/JSON.g4 index ae970bec3..2f11b8b3f 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 +++ b/app/src/main/java/com/tyron/code/language/json/JSON.g4 @@ -8,8 +8,8 @@ json ; obj - : LBRACKET pair (',' pair)* RBRACKET - | LBRACKET RBRACKET + : LBRACE pair (',' pair)* RBRACE + | LBRACE RBRACE ; pair @@ -17,8 +17,8 @@ pair ; arr - : '[' value (COMMA value)* ']' - | '[' ']' + : LBRACKET value (COMMA value)* RBRACKET + | LBRACKET RBRACKET ; value @@ -50,14 +50,23 @@ STRING : '"' (ESC | SAFECODEPOINT)* '"' ; -LBRACKET +LBRACE : '{' ; -RBRACKET +RBRACE : '}' ; + +LBRACKET + : '[' + ; + +RBRACKET + : ']' + ; + COMMA : ',' ; diff --git a/app/src/main/java/com/tyron/code/language/json/JSON.interp b/app/src/main/java/com/tyron/code/language/json/JSON.interp new file mode 100644 index 000000000..2b38429fe --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSON.interp @@ -0,0 +1,40 @@ +token literal names: +null +'true' +'false' +'null' +':' +null +'{' +'}' +'[' +']' +',' +null +null + +token symbolic names: +null +TRUE +FALSE +NULL +COLON +STRING +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +NUMBER +WS + +rule names: +json +obj +pair +arr +value + + +atn: +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 14, 58, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 19, 10, 3, 12, 3, 14, 3, 22, 11, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 28, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 7, 5, 38, 10, 5, 12, 5, 14, 5, 41, 11, 5, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 47, 10, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 56, 10, 6, 3, 6, 2, 2, 7, 2, 4, 6, 8, 10, 2, 2, 2, 62, 2, 12, 3, 2, 2, 2, 4, 27, 3, 2, 2, 2, 6, 29, 3, 2, 2, 2, 8, 46, 3, 2, 2, 2, 10, 55, 3, 2, 2, 2, 12, 13, 5, 10, 6, 2, 13, 3, 3, 2, 2, 2, 14, 15, 7, 8, 2, 2, 15, 20, 5, 6, 4, 2, 16, 17, 7, 12, 2, 2, 17, 19, 5, 6, 4, 2, 18, 16, 3, 2, 2, 2, 19, 22, 3, 2, 2, 2, 20, 18, 3, 2, 2, 2, 20, 21, 3, 2, 2, 2, 21, 23, 3, 2, 2, 2, 22, 20, 3, 2, 2, 2, 23, 24, 7, 9, 2, 2, 24, 28, 3, 2, 2, 2, 25, 26, 7, 8, 2, 2, 26, 28, 7, 9, 2, 2, 27, 14, 3, 2, 2, 2, 27, 25, 3, 2, 2, 2, 28, 5, 3, 2, 2, 2, 29, 30, 7, 7, 2, 2, 30, 31, 7, 6, 2, 2, 31, 32, 5, 10, 6, 2, 32, 7, 3, 2, 2, 2, 33, 34, 7, 10, 2, 2, 34, 39, 5, 10, 6, 2, 35, 36, 7, 12, 2, 2, 36, 38, 5, 10, 6, 2, 37, 35, 3, 2, 2, 2, 38, 41, 3, 2, 2, 2, 39, 37, 3, 2, 2, 2, 39, 40, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 39, 3, 2, 2, 2, 42, 43, 7, 11, 2, 2, 43, 47, 3, 2, 2, 2, 44, 45, 7, 10, 2, 2, 45, 47, 7, 11, 2, 2, 46, 33, 3, 2, 2, 2, 46, 44, 3, 2, 2, 2, 47, 9, 3, 2, 2, 2, 48, 56, 7, 7, 2, 2, 49, 56, 7, 13, 2, 2, 50, 56, 5, 4, 3, 2, 51, 56, 5, 8, 5, 2, 52, 56, 7, 3, 2, 2, 53, 56, 7, 4, 2, 2, 54, 56, 7, 5, 2, 2, 55, 48, 3, 2, 2, 2, 55, 49, 3, 2, 2, 2, 55, 50, 3, 2, 2, 2, 55, 51, 3, 2, 2, 2, 55, 52, 3, 2, 2, 2, 55, 53, 3, 2, 2, 2, 55, 54, 3, 2, 2, 2, 56, 11, 3, 2, 2, 2, 7, 20, 27, 39, 46, 55] \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSON.tokens b/app/src/main/java/com/tyron/code/language/json/JSON.tokens new file mode 100644 index 000000000..44a91db05 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSON.tokens @@ -0,0 +1,21 @@ +TRUE=1 +FALSE=2 +NULL=3 +COLON=4 +STRING=5 +LBRACE=6 +RBRACE=7 +LBRACKET=8 +RBRACKET=9 +COMMA=10 +NUMBER=11 +WS=12 +'true'=1 +'false'=2 +'null'=3 +':'=4 +'{'=6 +'}'=7 +'['=8 +']'=9 +','=10 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseListener.java b/app/src/main/java/com/tyron/code/language/json/JSONBaseListener.java similarity index 98% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseListener.java rename to app/src/main/java/com/tyron/code/language/json/JSONBaseListener.java index f1525f738..0f2740e35 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseListener.java +++ b/app/src/main/java/com/tyron/code/language/json/JSONBaseListener.java @@ -1,5 +1,5 @@ // Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ErrorNode; diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseVisitor.java b/app/src/main/java/com/tyron/code/language/json/JSONBaseVisitor.java similarity index 97% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseVisitor.java rename to app/src/main/java/com/tyron/code/language/json/JSONBaseVisitor.java index 82553f681..38842f071 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseVisitor.java +++ b/app/src/main/java/com/tyron/code/language/json/JSONBaseVisitor.java @@ -1,5 +1,5 @@ // Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; /** diff --git a/app/src/main/java/com/tyron/code/language/json/JSONLexer.interp b/app/src/main/java/com/tyron/code/language/json/JSONLexer.interp new file mode 100644 index 000000000..d8aa9bf91 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONLexer.interp @@ -0,0 +1,59 @@ +token literal names: +null +'true' +'false' +'null' +':' +null +'{' +'}' +'[' +']' +',' +null +null + +token symbolic names: +null +TRUE +FALSE +NULL +COLON +STRING +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +NUMBER +WS + +rule names: +TRUE +FALSE +NULL +COLON +STRING +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +ESC +UNICODE +HEX +SAFECODEPOINT +NUMBER +INT +EXP +WS + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 14, 130, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 7, 6, 61, 10, 6, 12, 6, 14, 6, 64, 11, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 5, 12, 81, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 3, 16, 5, 16, 94, 10, 16, 3, 16, 3, 16, 3, 16, 6, 16, 99, 10, 16, 13, 16, 14, 16, 100, 5, 16, 103, 10, 16, 3, 16, 5, 16, 106, 10, 16, 3, 17, 3, 17, 3, 17, 7, 17, 111, 10, 17, 12, 17, 14, 17, 114, 11, 17, 5, 17, 116, 10, 17, 3, 18, 3, 18, 5, 18, 120, 10, 18, 3, 18, 3, 18, 3, 19, 6, 19, 125, 10, 19, 13, 19, 14, 19, 126, 3, 19, 3, 19, 2, 2, 20, 3, 3, 5, 4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 10, 19, 11, 21, 12, 23, 2, 25, 2, 27, 2, 29, 2, 31, 13, 33, 2, 35, 2, 37, 14, 3, 2, 10, 10, 2, 36, 36, 49, 49, 94, 94, 100, 100, 104, 104, 112, 112, 116, 116, 118, 118, 5, 2, 50, 59, 67, 72, 99, 104, 5, 2, 2, 33, 36, 36, 94, 94, 3, 2, 50, 59, 3, 2, 51, 59, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47, 5, 2, 11, 12, 15, 15, 34, 34, 2, 134, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 3, 39, 3, 2, 2, 2, 5, 44, 3, 2, 2, 2, 7, 50, 3, 2, 2, 2, 9, 55, 3, 2, 2, 2, 11, 57, 3, 2, 2, 2, 13, 67, 3, 2, 2, 2, 15, 69, 3, 2, 2, 2, 17, 71, 3, 2, 2, 2, 19, 73, 3, 2, 2, 2, 21, 75, 3, 2, 2, 2, 23, 77, 3, 2, 2, 2, 25, 82, 3, 2, 2, 2, 27, 88, 3, 2, 2, 2, 29, 90, 3, 2, 2, 2, 31, 93, 3, 2, 2, 2, 33, 115, 3, 2, 2, 2, 35, 117, 3, 2, 2, 2, 37, 124, 3, 2, 2, 2, 39, 40, 7, 118, 2, 2, 40, 41, 7, 116, 2, 2, 41, 42, 7, 119, 2, 2, 42, 43, 7, 103, 2, 2, 43, 4, 3, 2, 2, 2, 44, 45, 7, 104, 2, 2, 45, 46, 7, 99, 2, 2, 46, 47, 7, 110, 2, 2, 47, 48, 7, 117, 2, 2, 48, 49, 7, 103, 2, 2, 49, 6, 3, 2, 2, 2, 50, 51, 7, 112, 2, 2, 51, 52, 7, 119, 2, 2, 52, 53, 7, 110, 2, 2, 53, 54, 7, 110, 2, 2, 54, 8, 3, 2, 2, 2, 55, 56, 7, 60, 2, 2, 56, 10, 3, 2, 2, 2, 57, 62, 7, 36, 2, 2, 58, 61, 5, 23, 12, 2, 59, 61, 5, 29, 15, 2, 60, 58, 3, 2, 2, 2, 60, 59, 3, 2, 2, 2, 61, 64, 3, 2, 2, 2, 62, 60, 3, 2, 2, 2, 62, 63, 3, 2, 2, 2, 63, 65, 3, 2, 2, 2, 64, 62, 3, 2, 2, 2, 65, 66, 7, 36, 2, 2, 66, 12, 3, 2, 2, 2, 67, 68, 7, 125, 2, 2, 68, 14, 3, 2, 2, 2, 69, 70, 7, 127, 2, 2, 70, 16, 3, 2, 2, 2, 71, 72, 7, 93, 2, 2, 72, 18, 3, 2, 2, 2, 73, 74, 7, 95, 2, 2, 74, 20, 3, 2, 2, 2, 75, 76, 7, 46, 2, 2, 76, 22, 3, 2, 2, 2, 77, 80, 7, 94, 2, 2, 78, 81, 9, 2, 2, 2, 79, 81, 5, 25, 13, 2, 80, 78, 3, 2, 2, 2, 80, 79, 3, 2, 2, 2, 81, 24, 3, 2, 2, 2, 82, 83, 7, 119, 2, 2, 83, 84, 5, 27, 14, 2, 84, 85, 5, 27, 14, 2, 85, 86, 5, 27, 14, 2, 86, 87, 5, 27, 14, 2, 87, 26, 3, 2, 2, 2, 88, 89, 9, 3, 2, 2, 89, 28, 3, 2, 2, 2, 90, 91, 10, 4, 2, 2, 91, 30, 3, 2, 2, 2, 92, 94, 7, 47, 2, 2, 93, 92, 3, 2, 2, 2, 93, 94, 3, 2, 2, 2, 94, 95, 3, 2, 2, 2, 95, 102, 5, 33, 17, 2, 96, 98, 7, 48, 2, 2, 97, 99, 9, 5, 2, 2, 98, 97, 3, 2, 2, 2, 99, 100, 3, 2, 2, 2, 100, 98, 3, 2, 2, 2, 100, 101, 3, 2, 2, 2, 101, 103, 3, 2, 2, 2, 102, 96, 3, 2, 2, 2, 102, 103, 3, 2, 2, 2, 103, 105, 3, 2, 2, 2, 104, 106, 5, 35, 18, 2, 105, 104, 3, 2, 2, 2, 105, 106, 3, 2, 2, 2, 106, 32, 3, 2, 2, 2, 107, 116, 7, 50, 2, 2, 108, 112, 9, 6, 2, 2, 109, 111, 9, 5, 2, 2, 110, 109, 3, 2, 2, 2, 111, 114, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 116, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 115, 107, 3, 2, 2, 2, 115, 108, 3, 2, 2, 2, 116, 34, 3, 2, 2, 2, 117, 119, 9, 7, 2, 2, 118, 120, 9, 8, 2, 2, 119, 118, 3, 2, 2, 2, 119, 120, 3, 2, 2, 2, 120, 121, 3, 2, 2, 2, 121, 122, 5, 33, 17, 2, 122, 36, 3, 2, 2, 2, 123, 125, 9, 9, 2, 2, 124, 123, 3, 2, 2, 2, 125, 126, 3, 2, 2, 2, 126, 124, 3, 2, 2, 2, 126, 127, 3, 2, 2, 2, 127, 128, 3, 2, 2, 2, 128, 129, 8, 19, 2, 2, 129, 38, 3, 2, 2, 2, 14, 2, 60, 62, 80, 93, 100, 102, 105, 112, 115, 119, 126, 3, 8, 2, 2] \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSONLexer.java b/app/src/main/java/com/tyron/code/language/json/JSONLexer.java new file mode 100644 index 000000000..1bfa98663 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONLexer.java @@ -0,0 +1,150 @@ +// Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 +package com.tyron.code.language.json; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) +public class JSONLexer extends Lexer { + static { RuntimeMetaData.checkVersion("4.9.2", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + TRUE=1, FALSE=2, NULL=3, COLON=4, STRING=5, LBRACE=6, RBRACE=7, LBRACKET=8, + RBRACKET=9, COMMA=10, NUMBER=11, WS=12; + public static String[] channelNames = { + "DEFAULT_TOKEN_CHANNEL", "HIDDEN" + }; + + public static String[] modeNames = { + "DEFAULT_MODE" + }; + + private static String[] makeRuleNames() { + return new String[] { + "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACE", "RBRACE", "LBRACKET", + "RBRACKET", "COMMA", "ESC", "UNICODE", "HEX", "SAFECODEPOINT", "NUMBER", + "INT", "EXP", "WS" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, "'true'", "'false'", "'null'", "':'", null, "'{'", "'}'", "'['", + "']'", "','" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACE", "RBRACE", + "LBRACKET", "RBRACKET", "COMMA", "NUMBER", "WS" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + + public JSONLexer(CharStream input) { + super(input); + _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @Override + public String getGrammarFileName() { return "JSON.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public String[] getChannelNames() { return channelNames; } + + @Override + public String[] getModeNames() { return modeNames; } + + @Override + public ATN getATN() { return _ATN; } + + public static final String _serializedATN = + "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2\16\u0082\b\1\4\2"+ + "\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4"+ + "\13\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22"+ + "\t\22\4\23\t\23\3\2\3\2\3\2\3\2\3\2\3\3\3\3\3\3\3\3\3\3\3\3\3\4\3\4\3"+ + "\4\3\4\3\4\3\5\3\5\3\6\3\6\3\6\7\6=\n\6\f\6\16\6@\13\6\3\6\3\6\3\7\3\7"+ + "\3\b\3\b\3\t\3\t\3\n\3\n\3\13\3\13\3\f\3\f\3\f\5\fQ\n\f\3\r\3\r\3\r\3"+ + "\r\3\r\3\r\3\16\3\16\3\17\3\17\3\20\5\20^\n\20\3\20\3\20\3\20\6\20c\n"+ + "\20\r\20\16\20d\5\20g\n\20\3\20\5\20j\n\20\3\21\3\21\3\21\7\21o\n\21\f"+ + "\21\16\21r\13\21\5\21t\n\21\3\22\3\22\5\22x\n\22\3\22\3\22\3\23\6\23}"+ + "\n\23\r\23\16\23~\3\23\3\23\2\2\24\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n"+ + "\23\13\25\f\27\2\31\2\33\2\35\2\37\r!\2#\2%\16\3\2\n\n\2$$\61\61^^ddh"+ + "hppttvv\5\2\62;CHch\5\2\2!$$^^\3\2\62;\3\2\63;\4\2GGgg\4\2--//\5\2\13"+ + "\f\17\17\"\"\2\u0086\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2"+ + "\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3"+ + "\2\2\2\2\37\3\2\2\2\2%\3\2\2\2\3\'\3\2\2\2\5,\3\2\2\2\7\62\3\2\2\2\t\67"+ + "\3\2\2\2\139\3\2\2\2\rC\3\2\2\2\17E\3\2\2\2\21G\3\2\2\2\23I\3\2\2\2\25"+ + "K\3\2\2\2\27M\3\2\2\2\31R\3\2\2\2\33X\3\2\2\2\35Z\3\2\2\2\37]\3\2\2\2"+ + "!s\3\2\2\2#u\3\2\2\2%|\3\2\2\2\'(\7v\2\2()\7t\2\2)*\7w\2\2*+\7g\2\2+\4"+ + "\3\2\2\2,-\7h\2\2-.\7c\2\2./\7n\2\2/\60\7u\2\2\60\61\7g\2\2\61\6\3\2\2"+ + "\2\62\63\7p\2\2\63\64\7w\2\2\64\65\7n\2\2\65\66\7n\2\2\66\b\3\2\2\2\67"+ + "8\7<\2\28\n\3\2\2\29>\7$\2\2:=\5\27\f\2;=\5\35\17\2<:\3\2\2\2<;\3\2\2"+ + "\2=@\3\2\2\2><\3\2\2\2>?\3\2\2\2?A\3\2\2\2@>\3\2\2\2AB\7$\2\2B\f\3\2\2"+ + "\2CD\7}\2\2D\16\3\2\2\2EF\7\177\2\2F\20\3\2\2\2GH\7]\2\2H\22\3\2\2\2I"+ + "J\7_\2\2J\24\3\2\2\2KL\7.\2\2L\26\3\2\2\2MP\7^\2\2NQ\t\2\2\2OQ\5\31\r"+ + "\2PN\3\2\2\2PO\3\2\2\2Q\30\3\2\2\2RS\7w\2\2ST\5\33\16\2TU\5\33\16\2UV"+ + "\5\33\16\2VW\5\33\16\2W\32\3\2\2\2XY\t\3\2\2Y\34\3\2\2\2Z[\n\4\2\2[\36"+ + "\3\2\2\2\\^\7/\2\2]\\\3\2\2\2]^\3\2\2\2^_\3\2\2\2_f\5!\21\2`b\7\60\2\2"+ + "ac\t\5\2\2ba\3\2\2\2cd\3\2\2\2db\3\2\2\2de\3\2\2\2eg\3\2\2\2f`\3\2\2\2"+ + "fg\3\2\2\2gi\3\2\2\2hj\5#\22\2ih\3\2\2\2ij\3\2\2\2j \3\2\2\2kt\7\62\2"+ + "\2lp\t\6\2\2mo\t\5\2\2nm\3\2\2\2or\3\2\2\2pn\3\2\2\2pq\3\2\2\2qt\3\2\2"+ + "\2rp\3\2\2\2sk\3\2\2\2sl\3\2\2\2t\"\3\2\2\2uw\t\7\2\2vx\t\b\2\2wv\3\2"+ + "\2\2wx\3\2\2\2xy\3\2\2\2yz\5!\21\2z$\3\2\2\2{}\t\t\2\2|{\3\2\2\2}~\3\2"+ + "\2\2~|\3\2\2\2~\177\3\2\2\2\177\u0080\3\2\2\2\u0080\u0081\b\23\2\2\u0081"+ + "&\3\2\2\2\16\2<>P]dfipsw~\3\b\2\2"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSONLexer.tokens b/app/src/main/java/com/tyron/code/language/json/JSONLexer.tokens new file mode 100644 index 000000000..44a91db05 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONLexer.tokens @@ -0,0 +1,21 @@ +TRUE=1 +FALSE=2 +NULL=3 +COLON=4 +STRING=5 +LBRACE=6 +RBRACE=7 +LBRACKET=8 +RBRACKET=9 +COMMA=10 +NUMBER=11 +WS=12 +'true'=1 +'false'=2 +'null'=3 +':'=4 +'{'=6 +'}'=7 +'['=8 +']'=9 +','=10 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONListener.java b/app/src/main/java/com/tyron/code/language/json/JSONListener.java similarity index 97% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSONListener.java rename to app/src/main/java/com/tyron/code/language/json/JSONListener.java index 97e99d964..6336e40b7 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONListener.java +++ b/app/src/main/java/com/tyron/code/language/json/JSONListener.java @@ -1,5 +1,5 @@ // Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; import org.antlr.v4.runtime.tree.ParseTreeListener; /** diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONParser.java b/app/src/main/java/com/tyron/code/language/json/JSONParser.java similarity index 92% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSONParser.java rename to app/src/main/java/com/tyron/code/language/json/JSONParser.java index c76017e58..425748e39 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONParser.java +++ b/app/src/main/java/com/tyron/code/language/json/JSONParser.java @@ -1,13 +1,10 @@ // Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.misc.*; import org.antlr.v4.runtime.tree.*; import java.util.List; -import java.util.Iterator; -import java.util.ArrayList; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class JSONParser extends Parser { @@ -17,7 +14,7 @@ public class JSONParser extends Parser { protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache(); public static final int - T__0=1, T__1=2, TRUE=3, FALSE=4, NULL=5, COLON=6, STRING=7, LBRACKET=8, + TRUE=1, FALSE=2, NULL=3, COLON=4, STRING=5, LBRACE=6, RBRACE=7, LBRACKET=8, RBRACKET=9, COMMA=10, NUMBER=11, WS=12; public static final int RULE_json = 0, RULE_obj = 1, RULE_pair = 2, RULE_arr = 3, RULE_value = 4; @@ -30,15 +27,15 @@ private static String[] makeRuleNames() { private static String[] makeLiteralNames() { return new String[] { - null, "'['", "']'", "'true'", "'false'", "'null'", "':'", null, "'{'", - "'}'", "','" + null, "'true'", "'false'", "'null'", "':'", null, "'{'", "'}'", "'['", + "']'", "','" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { return new String[] { - null, null, null, "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACKET", - "RBRACKET", "COMMA", "NUMBER", "WS" + null, "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACE", "RBRACE", + "LBRACKET", "RBRACKET", "COMMA", "NUMBER", "WS" }; } private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); @@ -137,14 +134,14 @@ public final JsonContext json() throws RecognitionException { } public static class ObjContext extends ParserRuleContext { - public TerminalNode LBRACKET() { return getToken(JSONParser.LBRACKET, 0); } + public TerminalNode LBRACE() { return getToken(JSONParser.LBRACE, 0); } public List pair() { return getRuleContexts(PairContext.class); } public PairContext pair(int i) { return getRuleContext(PairContext.class,i); } - public TerminalNode RBRACKET() { return getToken(JSONParser.RBRACKET, 0); } + public TerminalNode RBRACE() { return getToken(JSONParser.RBRACE, 0); } public List COMMA() { return getTokens(JSONParser.COMMA); } public TerminalNode COMMA(int i) { return getToken(JSONParser.COMMA, i); @@ -180,7 +177,7 @@ public final ObjContext obj() throws RecognitionException { enterOuterAlt(_localctx, 1); { setState(12); - match(LBRACKET); + match(LBRACE); setState(13); pair(); setState(18); @@ -200,16 +197,16 @@ public final ObjContext obj() throws RecognitionException { _la = _input.LA(1); } setState(21); - match(RBRACKET); + match(RBRACE); } break; case 2: enterOuterAlt(_localctx, 2); { setState(23); - match(LBRACKET); + match(LBRACE); setState(24); - match(RBRACKET); + match(RBRACE); } break; } @@ -276,12 +273,14 @@ public final PairContext pair() throws RecognitionException { } public static class ArrContext extends ParserRuleContext { + public TerminalNode LBRACKET() { return getToken(JSONParser.LBRACKET, 0); } public List value() { return getRuleContexts(ValueContext.class); } public ValueContext value(int i) { return getRuleContext(ValueContext.class,i); } + public TerminalNode RBRACKET() { return getToken(JSONParser.RBRACKET, 0); } public List COMMA() { return getTokens(JSONParser.COMMA); } public TerminalNode COMMA(int i) { return getToken(JSONParser.COMMA, i); @@ -317,7 +316,7 @@ public final ArrContext arr() throws RecognitionException { enterOuterAlt(_localctx, 1); { setState(31); - match(T__0); + match(LBRACKET); setState(32); value(); setState(37); @@ -337,16 +336,16 @@ public final ArrContext arr() throws RecognitionException { _la = _input.LA(1); } setState(40); - match(T__1); + match(RBRACKET); } break; case 2: enterOuterAlt(_localctx, 2); { setState(42); - match(T__0); + match(LBRACKET); setState(43); - match(T__1); + match(RBRACKET); } break; } @@ -414,14 +413,14 @@ public final ValueContext value() throws RecognitionException { match(NUMBER); } break; - case LBRACKET: + case LBRACE: enterOuterAlt(_localctx, 3); { setState(48); obj(); } break; - case T__0: + case LBRACKET: enterOuterAlt(_localctx, 4); { setState(49); @@ -470,15 +469,15 @@ public final ValueContext value() throws RecognitionException { "\26\13\3\3\3\3\3\3\3\3\3\5\3\34\n\3\3\4\3\4\3\4\3\4\3\5\3\5\3\5\3\5\7"+ "\5&\n\5\f\5\16\5)\13\5\3\5\3\5\3\5\3\5\5\5/\n\5\3\6\3\6\3\6\3\6\3\6\3"+ "\6\3\6\5\68\n\6\3\6\2\2\7\2\4\6\b\n\2\2\2>\2\f\3\2\2\2\4\33\3\2\2\2\6"+ - "\35\3\2\2\2\b.\3\2\2\2\n\67\3\2\2\2\f\r\5\n\6\2\r\3\3\2\2\2\16\17\7\n"+ + "\35\3\2\2\2\b.\3\2\2\2\n\67\3\2\2\2\f\r\5\n\6\2\r\3\3\2\2\2\16\17\7\b"+ "\2\2\17\24\5\6\4\2\20\21\7\f\2\2\21\23\5\6\4\2\22\20\3\2\2\2\23\26\3\2"+ - "\2\2\24\22\3\2\2\2\24\25\3\2\2\2\25\27\3\2\2\2\26\24\3\2\2\2\27\30\7\13"+ - "\2\2\30\34\3\2\2\2\31\32\7\n\2\2\32\34\7\13\2\2\33\16\3\2\2\2\33\31\3"+ - "\2\2\2\34\5\3\2\2\2\35\36\7\t\2\2\36\37\7\b\2\2\37 \5\n\6\2 \7\3\2\2\2"+ - "!\"\7\3\2\2\"\'\5\n\6\2#$\7\f\2\2$&\5\n\6\2%#\3\2\2\2&)\3\2\2\2\'%\3\2"+ - "\2\2\'(\3\2\2\2(*\3\2\2\2)\'\3\2\2\2*+\7\4\2\2+/\3\2\2\2,-\7\3\2\2-/\7"+ - "\4\2\2.!\3\2\2\2.,\3\2\2\2/\t\3\2\2\2\608\7\t\2\2\618\7\r\2\2\628\5\4"+ - "\3\2\638\5\b\5\2\648\7\5\2\2\658\7\6\2\2\668\7\7\2\2\67\60\3\2\2\2\67"+ + "\2\2\24\22\3\2\2\2\24\25\3\2\2\2\25\27\3\2\2\2\26\24\3\2\2\2\27\30\7\t"+ + "\2\2\30\34\3\2\2\2\31\32\7\b\2\2\32\34\7\t\2\2\33\16\3\2\2\2\33\31\3\2"+ + "\2\2\34\5\3\2\2\2\35\36\7\7\2\2\36\37\7\6\2\2\37 \5\n\6\2 \7\3\2\2\2!"+ + "\"\7\n\2\2\"\'\5\n\6\2#$\7\f\2\2$&\5\n\6\2%#\3\2\2\2&)\3\2\2\2\'%\3\2"+ + "\2\2\'(\3\2\2\2(*\3\2\2\2)\'\3\2\2\2*+\7\13\2\2+/\3\2\2\2,-\7\n\2\2-/"+ + "\7\13\2\2.!\3\2\2\2.,\3\2\2\2/\t\3\2\2\2\608\7\7\2\2\618\7\r\2\2\628\5"+ + "\4\3\2\638\5\b\5\2\648\7\3\2\2\658\7\4\2\2\668\7\5\2\2\67\60\3\2\2\2\67"+ "\61\3\2\2\2\67\62\3\2\2\2\67\63\3\2\2\2\67\64\3\2\2\2\67\65\3\2\2\2\67"+ "\66\3\2\2\28\13\3\2\2\2\7\24\33\'.\67"; public static final ATN _ATN = diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONVisitor.java b/app/src/main/java/com/tyron/code/language/json/JSONVisitor.java similarity index 96% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSONVisitor.java rename to app/src/main/java/com/tyron/code/language/json/JSONVisitor.java index e273f38d0..6cf9add5b 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONVisitor.java +++ b/app/src/main/java/com/tyron/code/language/json/JSONVisitor.java @@ -1,5 +1,5 @@ // Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; import org.antlr.v4.runtime.tree.ParseTreeVisitor; /** diff --git a/app/src/main/java/com/tyron/code/language/json/Json.java b/app/src/main/java/com/tyron/code/language/json/Json.java new file mode 100644 index 000000000..ada6c514a --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/Json.java @@ -0,0 +1,22 @@ +package com.tyron.code.language.json; + +import com.tyron.code.language.Language; +import com.tyron.code.language.LanguageManager; +import com.tyron.editor.Editor; + +import java.io.File; + +public class Json implements Language { + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".json"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return LanguageManager.createTextMateLanguage("json.tmLanguage.json", + "textmate/json" + "/syntaxes/json" + ".tmLanguage.json", + "textmate/json/language-configuration.json", editor); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JsonAnalyzer.java b/app/src/main/java/com/tyron/code/language/json/JsonAnalyzer.java similarity index 89% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JsonAnalyzer.java rename to app/src/main/java/com/tyron/code/language/json/JsonAnalyzer.java index fab008a25..a0aa49fa0 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JsonAnalyzer.java +++ b/app/src/main/java/com/tyron/code/language/json/JsonAnalyzer.java @@ -1,6 +1,6 @@ -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; +import com.tyron.code.language.AbstractCodeAnalyzer; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Lexer; @@ -11,9 +11,7 @@ import io.github.rosemoe.sora.lang.styling.CodeBlock; import io.github.rosemoe.sora.lang.styling.MappedSpans; import io.github.rosemoe.sora.lang.styling.Styles; -import io.github.rosemoe.sora2.data.BlockLine; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.widget.EditorColorScheme; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; public class JsonAnalyzer extends AbstractCodeAnalyzer { @@ -29,7 +27,7 @@ public Lexer getLexer(CharStream input) { @Override public void setup() { putColor(EditorColorScheme.TEXT_NORMAL, JSONLexer.LBRACKET, - JSONLexer.RBRACKET); + JSONLexer.RBRACKET, JSONLexer.LBRACE, JSONLexer.RBRACE); putColor(EditorColorScheme.KEYWORD, JSONLexer.TRUE, JSONLexer.FALSE, JSONLexer.NULL, JSONLexer.COLON, JSONLexer.COMMA); @@ -65,7 +63,7 @@ public boolean onNextToken(Token currentToken, Styles styles, MappedSpans.Builde } } break; - case JSONLexer.RBRACKET: + case JSONLexer.RBRACE: if (!mBlockLines.isEmpty()) { CodeBlock b = mBlockLines.pop(); b.endLine = line; @@ -75,7 +73,7 @@ public boolean onNextToken(Token currentToken, Styles styles, MappedSpans.Builde } } return false; - case JSONLexer.LBRACKET: + case JSONLexer.LBRACE: if (mBlockLines.isEmpty()) { if (mCurrSwitch > mMaxSwitch) { mMaxSwitch = mCurrSwitch; diff --git a/app/src/main/java/com/tyron/code/language/kotlin/Kotlin.java b/app/src/main/java/com/tyron/code/language/kotlin/Kotlin.java new file mode 100644 index 000000000..882cb8a39 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/Kotlin.java @@ -0,0 +1,19 @@ +package com.tyron.code.language.kotlin; + +import com.tyron.code.language.Language; +import com.tyron.editor.Editor; + +import java.io.File; + +public class Kotlin implements Language { + + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".kt"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new KotlinLanguage(editor); + } +} diff --git a/app/src/main/java/com/tyron/code/language/kotlin/KotlinAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/kotlin/KotlinAutoCompleteProvider.java new file mode 100644 index 000000000..b4d28abc2 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/KotlinAutoCompleteProvider.java @@ -0,0 +1,85 @@ +package com.tyron.code.language.kotlin; + +import android.content.SharedPreferences; + +import androidx.annotation.Nullable; + +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.language.AbstractAutoCompleteProvider; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.completion.java.provider.JavaSortCategory; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.CompletionList; +import com.tyron.completion.util.CompletionUtils; +import com.tyron.editor.Editor; +import com.tyron.kotlin.completion.KotlinEnvironment; +import com.tyron.kotlin.completion.KotlinFile; + +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; + +import java.util.List; + +public class KotlinAutoCompleteProvider extends AbstractAutoCompleteProvider { + + private static final String TAG = KotlinAutoCompleteProvider.class.getSimpleName(); + + private final Editor mEditor; + private final SharedPreferences mPreferences; + + private KotlinCoreEnvironment environment; + + public KotlinAutoCompleteProvider(Editor editor) { + mEditor = editor; + mPreferences = ApplicationLoader.getDefaultPreferences(); + } + + @Nullable + @Override + public CompletionList getCompletionList(String prefix, int line, int column) { + if (!mPreferences.getBoolean(SharedPreferenceKeys.KOTLIN_COMPLETIONS, false)) { + return null; + } + + if (com.tyron.completion.java.provider.CompletionEngine.isIndexing()) { + return null; + } + + if (!mPreferences.getBoolean(SharedPreferenceKeys.KOTLIN_COMPLETIONS, false)) { + return null; + } + + Project project = ProjectManager.getInstance().getCurrentProject(); + if (project == null) { + return null; + } + + Module currentModule = project.getModule(mEditor.getCurrentFile()); + + if (!(currentModule instanceof AndroidModule)) { + return null; + } + + KotlinEnvironment kotlinEnvironment = KotlinEnvironment.Companion.get(currentModule); + if (kotlinEnvironment == null) { + return null; + } + + KotlinFile updatedFile = + kotlinEnvironment.updateKotlinFile(mEditor.getCurrentFile().getAbsolutePath(), + mEditor.getContent().toString()); + List itemList = kotlinEnvironment.complete(updatedFile, + line, + column - 1); + + for (CompletionItem completionItem : itemList) { + completionItem.addFilterText(completionItem.commitText); + completionItem.setSortText(JavaSortCategory.DIRECT_MEMBER.toString()); + } + + return CompletionList.builder(prefix).addItems(itemList).build(); + } +} diff --git a/app/src/main/java/com/tyron/code/language/kotlin/KotlinLanguage.java b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLanguage.java new file mode 100644 index 000000000..6ba4ab5bc --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLanguage.java @@ -0,0 +1,123 @@ +package com.tyron.code.language.kotlin; + +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.builder.BuildModule; +import com.tyron.code.language.CompletionItemWrapper; +import com.tyron.code.language.LanguageManager; +import com.tyron.completion.DefaultInsertHandler; +import com.tyron.completion.java.provider.JavaSortCategory; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.CompletionList; +import com.tyron.completion.util.CompletionUtils; +import com.tyron.editor.Editor; +import com.tyron.kotlin.completion.KotlinEnvironment; +import com.tyron.kotlin.completion.KotlinFile; + +import java.io.File; +import java.util.List; +import java.util.Objects; + +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionHelper; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.format.Formatter; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class KotlinLanguage implements Language { + + private static final String GRAMMAR_NAME = "kotlin.tmLanguage"; + private static final String LANGUAGE_PATH = "textmate/kotlin/syntaxes/kotlin.tmLanguage"; + private static final String CONFIG_PATH = "textmate/kotlin/language-configuration.json"; + + private final TextMateLanguage delegate; + private final Editor editor; + + private KotlinEnvironment kotlinEnvironment; + + public KotlinLanguage(Editor editor) { + this.editor = editor; + delegate = LanguageManager.createTextMateLanguage(GRAMMAR_NAME, + LANGUAGE_PATH, + CONFIG_PATH, + editor); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return delegate.getAnalyzeManager(); + } + + @Override + public int getInterruptionLevel() { + return delegate.getInterruptionLevel(); + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) throws CompletionCancelledException { + String identifierPart = CompletionHelper.computePrefix(content, position, CompletionUtils.JAVA_PREDICATE::test); + KotlinAutoCompleteProvider provider = + new KotlinAutoCompleteProvider(editor); + CompletionList completionList = provider.getCompletionList(identifierPart, + position.getLine(), + position.getColumn()); + if (completionList == null) { + return; + } + completionList.getItems().stream().map(CompletionItemWrapper::new).forEach(publisher::addItem); + } + + private KotlinEnvironment getOrCreateKotlinEnvironment() { + if (kotlinEnvironment == null) { + kotlinEnvironment = + KotlinEnvironment.Companion.with(List.of(Objects.requireNonNull(BuildModule.getAndroidJar()), + BuildModule.getLambdaStubs())); + } + return kotlinEnvironment; + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + return delegate.getIndentAdvance(content, line, column); + } + + @Override + public boolean useTab() { + return delegate.useTab(); + } + + @NonNull + @Override + public Formatter getFormatter() { + return delegate.getFormatter(); + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return delegate.getSymbolPairs(); + } + + @Nullable + @Override + public NewlineHandler[] getNewlineHandlers() { + return new NewlineHandler[0]; + } + + @Override + public void destroy() { + delegate.destroy(); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLexer.g4 b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.g4 similarity index 100% rename from app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLexer.g4 rename to app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.g4 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLexer.java b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.java similarity index 99% rename from app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLexer.java rename to app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.java index baf89eff4..b5dfbab93 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLexer.java +++ b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.java @@ -1,9 +1,7 @@ // Generated from C:/Users/admin/StudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/kotlin\KotlinLexer.g4 by ANTLR 4.9.1 -package com.tyron.code.ui.editor.language.kotlin; +package com.tyron.code.language.kotlin; import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/UnicodeClasses.g4 b/app/src/main/java/com/tyron/code/language/kotlin/UnicodeClasses.g4 similarity index 100% rename from app/src/main/java/com/tyron/code/ui/editor/language/kotlin/UnicodeClasses.g4 rename to app/src/main/java/com/tyron/code/language/kotlin/UnicodeClasses.g4 diff --git a/app/src/main/java/com/tyron/code/language/textmate/BaseIncrementalAnalyzeManager.java b/app/src/main/java/com/tyron/code/language/textmate/BaseIncrementalAnalyzeManager.java new file mode 100644 index 000000000..995144871 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/textmate/BaseIncrementalAnalyzeManager.java @@ -0,0 +1,547 @@ +package com.tyron.code.language.textmate; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import io.github.rosemoe.sora.lang.analysis.IncrementalAnalyzeManager; +import io.github.rosemoe.sora.lang.analysis.StyleReceiver; +import io.github.rosemoe.sora.lang.styling.CodeBlock; +import io.github.rosemoe.sora.lang.styling.Span; +import io.github.rosemoe.sora.lang.styling.Spans; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.Content; +import io.github.rosemoe.sora.text.ContentLine; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.util.IntPair; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +public abstract class BaseIncrementalAnalyzeManager implements IncrementalAnalyzeManager { + + private StyleReceiver receiver; + private ContentReference ref; + private Bundle extraArguments; + private LooperThread thread; + private volatile long runCount; + private static int sThreadId = 0; + private final static int MSG_BASE = 11451400; + private final static int MSG_INIT = MSG_BASE + 1; + private final static int MSG_MOD = MSG_BASE + 2; + private final static int MSG_EXIT = MSG_BASE + 3; + + @Override + public void setReceiver(StyleReceiver receiver) { + this.receiver = receiver; + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + this.ref = content; + this.extraArguments = extraArguments; + rerun(); + } + + @Override + public void insert(CharPosition start, CharPosition end, CharSequence insertedText) { + if (thread != null) { + increaseRunCount(); + thread.handler.sendMessage(thread.handler.obtainMessage(MSG_MOD, new TextModification(IntPair.pack(start.line, start.column), IntPair.pack(end.line, end.column), insertedText))); + sendUpdate(thread.styles); + } + } + + @Override + public void delete(CharPosition start, CharPosition end, CharSequence deletedText) { + if (thread != null) { + increaseRunCount(); + thread.handler.sendMessage(thread.handler.obtainMessage(MSG_MOD, new TextModification(IntPair.pack(start.line, start.column), IntPair.pack(end.line, end.column), null))); + sendUpdate(thread.styles); + } + } + + @Override + public void rerun() { + if (thread != null) { + thread.callback = () -> { throw new CancelledException(); }; + if (thread.isAlive()) { + final Handler handler = thread.handler; + if (handler != null) { + handler.sendMessage(Message.obtain(thread.handler, MSG_EXIT)); + } + thread.abort = true; + } + } + final Content text = ref.getReference().copyText(false); + text.setUndoEnabled(false); + thread = new LooperThread(() -> thread.handler.sendMessage(thread.handler.obtainMessage(MSG_INIT, text))); + thread.setName("AsyncAnalyzer-" + nextThreadId()); + increaseRunCount(); + thread.start(); + sendUpdate(null); + } + + public abstract Result tokenizeLine(CharSequence line, S state); + + @Override + public Result getState(int line) { + final LooperThread thread = this.thread; + if (thread == Thread.currentThread()) { + return thread.states.get(line); + } + throw new SecurityException("Can not get state from non-analytical or abandoned thread"); + } + + private synchronized void increaseRunCount() { + runCount ++; + } + + private synchronized static int nextThreadId() { + sThreadId++; + return sThreadId; + } + + @Override + public void destroy() { + if (thread != null) { + thread.callback = () -> { throw new CancelledException(); }; + if (thread.isAlive()) { + thread.handler.sendMessage(Message.obtain(thread.handler, MSG_EXIT)); + thread.abort = true; + } + } + receiver = null; + ref = null; + extraArguments = null; + thread = null; + } + + private void sendUpdate(Styles styles) { + final StyleReceiver r = receiver; + if (r != null) { + r.setStyles(this, styles); + } + } + + /** + * Compute code blocks + * @param text The text. can be safely accessed. + */ + public abstract List computeBlocks(Content text, CodeBlockAnalyzeDelegate delegate); + + public Bundle getExtraArguments() { + return extraArguments; + } + + /** + * Helper class for analyzing code block + */ + public class CodeBlockAnalyzeDelegate { + + private final LooperThread + thread; + int suppressSwitch; + + CodeBlockAnalyzeDelegate(@NonNull LooperThread lp) { + thread = lp; + } + + public void setSuppressSwitch(int suppressSwitch) { + this.suppressSwitch = suppressSwitch; + } + + void reset() { + suppressSwitch = Integer.MAX_VALUE; + } + + public boolean isCancelled() { + return thread.myRunCount != runCount; + } + + public boolean isNotCancelled() { + return thread.myRunCount == runCount; + } + + } + + private class LooperThread extends Thread { + + volatile boolean abort; + Looper looper; + Handler handler; + Content shadowed; + long myRunCount; + + List> states = new ArrayList<>(); + Styles styles; + LockedSpans spans; + Runnable callback; + CodeBlockAnalyzeDelegate + delegate = new CodeBlockAnalyzeDelegate(this); + + public LooperThread(Runnable callback) { + this.callback = callback; + } + + private void tryUpdate() { + if (!abort) + sendUpdate(styles); + } + + private void initialize() { + styles = new Styles(spans = new LockedSpans()); + S state = getInitialState(); + Spans.Modifier mdf = spans.modify(); + for (int i = 0;i < shadowed.getLineCount();i++) { + ContentLine line = shadowed.getLine(i); + Result result = tokenizeLine(line, state); + state = result.state; + List spans = result.spans != null ? result. spans :generateSpansForLine(result); + states.add(result.clearSpans()); + mdf.addLineAt(i, spans); + } + styles.blocks = computeBlocks(shadowed, delegate); + styles.setSuppressSwitch(delegate.suppressSwitch); + tryUpdate(); + } + + @Override + public void run() { + Looper.prepare(); + looper = Looper.myLooper(); + handler = new Handler(looper) { + + @Override + public void handleMessage(@NonNull Message msg) { + super.handleMessage(msg); + try { + myRunCount = runCount; + delegate.reset(); + switch (msg.what) { + case MSG_INIT: + shadowed = (Content) msg.obj; + if (!abort) { + initialize(); + } + break; + case MSG_MOD: + if (!abort) { + TextModification mod = (TextModification) msg.obj; + int startLine = IntPair.getFirst(mod.start); + int endLine = IntPair.getFirst(mod.end); + if (mod.changedText == null) { + shadowed.delete(IntPair.getFirst(mod.start), IntPair.getSecond(mod.start), + IntPair.getFirst(mod.end), IntPair.getSecond(mod.end)); + S state = startLine == 0 ? getInitialState() : states.get(startLine - 1).state; + // Remove states + if (endLine >= startLine + 1) { + states.subList(startLine + 1, endLine + 1).clear(); + } + Spans.Modifier mdf = spans.modify(); + for (int i = startLine + 1;i <= endLine;i++) { + mdf.deleteLineAt(startLine + 1); + } + int line = startLine; + while (line < shadowed.getLineCount()){ + Result res = tokenizeLine(shadowed.getLine(line), state); + mdf.setSpansOnLine(line, res.spans != null ? res.spans : generateSpansForLine(res)); + Result old = states.set(line, res.clearSpans()); + if (stateEquals(old.state, res.state)) { + break; + } + state = res.state; + line ++; + } + } else { + shadowed.insert(IntPair.getFirst(mod.start), IntPair.getSecond(mod.start), mod.changedText); + S state = startLine == 0 ? getInitialState() : states.get(startLine - 1).state; + int line = startLine; + Spans.Modifier spans = styles.spans.modify(); + // Add Lines + while (line <= endLine) { + Result res = tokenizeLine(shadowed.getLine(line), state); + if (line == startLine) { + spans.setSpansOnLine(line, res.spans != null ? res.spans : generateSpansForLine(res)); + states.set(line, res.clearSpans()); + } else { + spans.addLineAt(line, res.spans != null ? res.spans : generateSpansForLine(res)); + states.add(line, res.clearSpans()); + } + state = res.state; + line++; + } + // line = end.line + 1, check whether the state equals + while (line < shadowed.getLineCount()) { + Result res = tokenizeLine(shadowed.getLine(line), state); + if (stateEquals(res.state, states.get(line).state)) { + break; + } else { + spans.setSpansOnLine(line, res.spans != null ? res.spans : generateSpansForLine(res)); + states.set(line, res.clearSpans()); + } + line ++; + } + } + } + styles.blocks = computeBlocks(shadowed, delegate); + styles.setSuppressSwitch(delegate.suppressSwitch); + tryUpdate(); + break; + case MSG_EXIT: + looper.quit(); + break; + } + } catch (Exception e) { + Log.w("AsyncAnalysis", "Thread " + Thread.currentThread().getName() + " failed", e); + } + } + + }; + + try { + callback.run(); + Looper.loop(); + } catch (CancelledException e) { + //ignored + } + } + } + + private static class LockedSpans implements Spans { + + private final Lock lock; + private final List lines; + + public LockedSpans() { + lines = new ArrayList<>(128); + lock = new ReentrantLock(); + } + + @Override + public void adjustOnDelete(CharPosition start, CharPosition end) { + + } + + @Override + public void adjustOnInsert(CharPosition start, CharPosition end) { + + } + + @Override + public int getLineCount() { + return lines.size(); + } + + @Override + public Reader read() { + return new LockedSpans.ReaderImpl(); + } + + @Override + public Modifier modify() { + return new LockedSpans.ModifierImpl(); + } + + @Override + public boolean supportsModify() { + return true; + } + + private static class Line { + + public Lock lock = new ReentrantLock(); + + public List spans; + + public Line() { + this(null); + } + + public Line(List s) { + spans = s; + } + + } + + private class ReaderImpl implements Spans.Reader { + + private LockedSpans.Line + line; + + public void moveToLine(int line) { + if (line < 0) { + if (this.line != null) { + this.line.lock.unlock(); + } + this.line = null; + } else if (line >= lines.size()) { + if (this.line != null) { + this.line.lock.unlock(); + } + this.line = null; + } else { + if (this.line != null) { + this.line.lock.unlock(); + } + boolean locked = false; + try { + locked = lock.tryLock(1, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (locked) { + try { + Line obj = lines.get(line); + if (obj.lock.tryLock()) { + this.line = obj; + } else { + this.line = null; + } + } finally { + lock.unlock(); + } + } else { + this.line = null; + } + } + } + + @Override + public int getSpanCount() { + return line == null ? 1 : line.spans.size(); + } + + @Override + public Span getSpanAt(int index) { + return line == null ? Span.obtain(0, EditorColorScheme.TEXT_NORMAL) : line.spans.get(index); + } + + @Override + public List getSpansOnLine(int line) { + ArrayList spans = new ArrayList<>(); + boolean locked = false; + try { + locked = lock.tryLock(1, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (locked) { + LockedSpans.Line + obj = null; + try { + if (line < lines.size()) { + obj = lines.get(line); + } + } finally { + lock.unlock(); + } + if (obj != null && obj.lock.tryLock()) { + try { + return Collections.unmodifiableList(obj.spans); + } finally { + obj.lock.unlock(); + } + } else { + spans.add(getSpanAt(0)); + } + } else { + spans.add(getSpanAt(0)); + } + return spans; + } + } + + private class ModifierImpl implements Modifier { + + @Override + public void setSpansOnLine(int line, List spans) { + lock.lock(); + try { + while (lines.size() <= line) { + ArrayList list = new ArrayList<>(); + list.add(Span.obtain(0, EditorColorScheme.TEXT_NORMAL)); + lines.add(new LockedSpans.Line(list)); + } + lines.get(line).spans = spans; + } finally { + lock.unlock(); + } + } + + @Override + public void addLineAt(int line, List spans) { + lock.lock(); + try { + lines.add(line, new LockedSpans.Line(spans)); + } finally { + lock.unlock(); + } + } + + @Override + public void deleteLineAt(int line) { + lock.lock(); + try { + Line obj = lines.get(line); + obj.lock.lock(); + try { + lines.remove(line); + } finally { + obj.lock.unlock(); + } + } finally { + lock.unlock(); + } + } + } + + } + + private static class TextModification { + + private final long start; + private final long end; + /** + * null for deletion + */ + private final CharSequence changedText; + + TextModification(long start, long end, CharSequence text) { + this.start = start; + this.end = end; + changedText = text; + } + } + + public static class Result extends LineTokenizeResult { + + public Result(@NonNull S_ state, @Nullable List tokens) { + super(state, tokens); + } + + public Result(@NonNull S_ state, @Nullable List tokens, @Nullable List spans) { + super(state, tokens, spans); + } + + @Override + public Result clearSpans() { + super.clearSpans(); + return this; + } + } + + private static class CancelledException extends RuntimeException {} +} + diff --git a/app/src/main/java/com/tyron/code/language/textmate/CodeBlockUtils.java b/app/src/main/java/com/tyron/code/language/textmate/CodeBlockUtils.java new file mode 100644 index 000000000..2fae12175 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/textmate/CodeBlockUtils.java @@ -0,0 +1,98 @@ +package com.tyron.code.language.textmate; + +import org.eclipse.tm4e.core.internal.oniguruma.OnigRegExp; +import org.eclipse.tm4e.core.internal.oniguruma.OnigResult; +import org.eclipse.tm4e.core.internal.oniguruma.OnigString; +import org.eclipse.tm4e.languageconfiguration.internal.supports.Folding; + +import java.util.ArrayList; +import java.util.List; + +import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; +import io.github.rosemoe.sora.langs.textmate.folding.FoldingRegions; +import io.github.rosemoe.sora.langs.textmate.folding.IndentRange; +import io.github.rosemoe.sora.langs.textmate.folding.PreviousRegion; +import io.github.rosemoe.sora.langs.textmate.folding.RangesCollector; +import io.github.rosemoe.sora.text.Content; + +public class CodeBlockUtils { + + @SuppressWarnings("rawtype") + public static FoldingRegions computeRanges(Content model, int tabSize , boolean offSide, Folding markers, int foldingRangesLimit, BaseIncrementalAnalyzeManager.CodeBlockAnalyzeDelegate delegate) throws Exception { + + RangesCollector result = new RangesCollector(); + + OnigRegExp pattern = null; + if (markers != null) { + pattern = new OnigRegExp("(" + markers.getMarkersStart() + ")|(?:" + markers.getMarkersEnd() + ")"); + } + + List previousRegions = new ArrayList<>(); + int line = model.getLineCount() + 1; + // sentinel, to make sure there's at least one entry + previousRegions.add(new PreviousRegion(-1, line, line)); + + for (line = model.getLineCount() - 1; line >= 0 && !delegate.isCancelled(); line--) { + String lineContent = model.getLineString(line); + int indent = IndentRange + .computeIndentLevel(model.getLine(line).getRawData(), model.getColumnCount(line), tabSize); + PreviousRegion previous = previousRegions.get(previousRegions.size() - 1); + if (indent == -1) { + if (offSide) { + // for offSide languages, empty lines are associated to the previous block + // note: the next block is already written to the results, so this only + // impacts the end position of the block before + previous.endAbove = line; + } + continue; // only whitespace + } + OnigResult m; + if (pattern != null && (m = pattern.search(new OnigString(lineContent), 0)) != null) { + // folding pattern match + if (m.count() >= 2) { // start pattern match + // discard all regions until the folding pattern + int i = previousRegions.size() - 1; + while (i > 0 && previousRegions.get(i).indent != -2) { + i--; + } + if (i > 0) { + //??? previousRegions.length = i + 1; + previous = previousRegions.get(i); + + // new folding range from pattern, includes the end line + result.insertFirst(line, previous.line, indent); + previous.line = line; + previous.indent = indent; + previous.endAbove = line; + continue; + } else { + // no end marker found, treat line as a regular line + } + } else { // end pattern match + previousRegions.add(new PreviousRegion(-2, line, line)); + continue; + } + } + if (previous.indent > indent) { + // discard all regions with larger indent + do { + previousRegions.remove(previousRegions.size() - 1); + previous = previousRegions.get(previousRegions.size() - 1); + } while (previous.indent > indent); + + // new folding range + int endLineNumber = previous.endAbove - 1; + if (endLineNumber - line >= 1) { // needs at east size 1 + result.insertFirst(line, endLineNumber, indent); + } + } + if (previous.indent == indent) { + previous.endAbove = line; + } else { // previous.indent < indent + // new region with a bigger indent + previousRegions.add(new PreviousRegion(indent, line, line)); + } + } + return result.toIndentRanges(model); + } +} diff --git a/app/src/main/java/com/tyron/code/language/textmate/EmptyTextMateLanguage.java b/app/src/main/java/com/tyron/code/language/textmate/EmptyTextMateLanguage.java new file mode 100644 index 000000000..3cb232831 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/textmate/EmptyTextMateLanguage.java @@ -0,0 +1,7 @@ +package com.tyron.code.language.textmate; + +import io.github.rosemoe.sora.lang.EmptyLanguage; + +public class EmptyTextMateLanguage extends EmptyLanguage { + +} diff --git a/app/src/main/java/com/tyron/code/language/xml/LanguageXML.java b/app/src/main/java/com/tyron/code/language/xml/LanguageXML.java new file mode 100644 index 000000000..9db132700 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/xml/LanguageXML.java @@ -0,0 +1,276 @@ +package com.tyron.code.language.xml; + +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.tyron.builder.compiler.manifest.xml.XmlFormatPreferences; +import com.tyron.builder.compiler.manifest.xml.XmlFormatStyle; +import com.tyron.builder.compiler.manifest.xml.XmlPrettyPrinter; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.event.EventManager; +import com.tyron.code.language.CompletionItemWrapper; +import com.tyron.code.language.LanguageManager; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.util.ProjectUtils; +import com.tyron.completion.CompletionParameters; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.CompletionList; +import com.tyron.completion.xml.lexer.XMLLexer; +import com.tyron.completion.xml.task.InjectResourcesTask; +import com.tyron.completion.xml.v2.AndroidXmlCompletionProvider; +import com.tyron.completion.xml.v2.events.XmlResourceChangeEvent; +import com.tyron.editor.Editor; +import com.tyron.language.api.CodeAssistLanguage; +import com.tyron.viewbinding.task.InjectViewBindingTask; + +import java.io.File; +import java.util.List; + +import io.github.rosemoe.sora.lang.EmptyLanguage; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionHelper; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.format.Formatter; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +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.util.MyCharacter; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class LanguageXML implements Language, CodeAssistLanguage { + + private final Editor mEditor; + private final TextMateLanguage delegate; + + + public LanguageXML(Editor editor) { + mEditor = editor; + + delegate = LanguageManager.createTextMateLanguage("xml.tmLanguage.json", + "textmate/xml/syntaxes/xml.tmLanguage.json", + "textmate/java/language-configuration.json", editor); + } + + public boolean isAutoCompleteChar(char ch) { + return MyCharacter.isJavaIdentifierPart(ch) || + ch == '<' || + ch == '/' || + ch == ':' || + ch == '.'; + } + + @Override + public boolean useTab() { + return true; + } + + @NonNull + @Override + public Formatter getFormatter() { + return EmptyLanguage.EmptyFormatter.INSTANCE; + } + + public CharSequence format(CharSequence text) { + XmlFormatPreferences preferences = XmlFormatPreferences.defaults(); + File file = mEditor.getCurrentFile(); + CharSequence formatted = null; + if ("AndroidManifest.xml".equals(file.getName())) { + formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), preferences, + XmlFormatStyle.MANIFEST, "\n"); + } else { + if (ProjectUtils.isLayoutXMLFile(file)) { + formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), preferences, + XmlFormatStyle.LAYOUT, "\n"); + } else if (ProjectUtils.isResourceXMLFile(file)) { + formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), preferences, + XmlFormatStyle.RESOURCE, "\n"); + } + } + if (formatted == null) { + formatted = text; + } + return formatted; + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return delegate.getSymbolPairs(); + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return new NewlineHandler[]{new StartTagHandler(), new EndTagHandler(), + new EndTagAttributeHandler()}; + } + + @Override + public void destroy() { + delegate.destroy(); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return delegate.getAnalyzeManager(); + } + + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_SLIGHT; + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) throws CompletionCancelledException { + if (mEditor.getProject() == null) { + return; + } + Module module = mEditor.getProject().getModule(mEditor.getCurrentFile()); + if (!(module instanceof AndroidModule)) { + return; + } + String prefix = CompletionHelper.computePrefix(content, position, this::isAutoCompleteChar); + CompletionParameters parameters = CompletionParameters.builder() + .setPrefix(prefix) + .setModule(module) + .setProject(mEditor.getProject()) + .setFile(mEditor.getCurrentFile()) + .setIndex(position.getIndex()) + .setLine(position.getLine()) + .setColumn(position.getColumn()) + .setContents(content.getReference().toString()) + .build(); + CompletionList items = + new AndroidXmlCompletionProvider().complete(parameters); + if (items == null) { + return; + } + for (CompletionItem item : items.getItems()) { + publisher.addItem(new CompletionItemWrapper(item)); + } + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + String text = content.getLine(line).substring(0, column); + return getIndentAdvance(text); + } + + public int getIndentAdvance(String content) { + return getIndentAdvance(content, XMLLexer.DEFAULT_MODE, true); + } + + public int getIndentAdvance(String content, int mode, boolean ignore) { + return 0; +// XMLLexer lexer = new XMLLexer(CharStreams.fromString(content)); +// lexer.pushMode(mode); +// +// int advance = 0; +// while (lexer.nextToken() +// .getType() != Lexer.EOF) { +// switch (lexer.getToken() +// .getType()) { +// case XMLLexer.OPEN: +// advance++; +// break; +// case XMLLexer.CLOSE: +// case XMLLexer.SLASH_CLOSE: +// advance--; +// break; +// } +// } +// +// if (advance == 0 && mode != XMLLexer.INSIDE) { +// return getIndentAdvance(content, XMLLexer.INSIDE, ignore); +// } +// +// return advance * mEditor.getTabCount(); + } + + public int getFormatIndent(String line) { + return getIndentAdvance(line, XMLLexer.DEFAULT_MODE, false); + } + + @Override + public void onContentChange(File file, CharSequence contents) { + if (mEditor.getProject() == null) { + return; + } + EventManager eventManager = mEditor.getProject().getEventManager(); + eventManager.dispatchEvent(new XmlResourceChangeEvent(mEditor.getCurrentFile(), mEditor.getContent())); + } + + private class EndTagHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + String trim = beforeText.trim(); + if (!trim.startsWith("<")) { + return false; + } + if (!trim.endsWith(">")) { + return false; + } + return afterText.trim().startsWith(""); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + String middle; + StringBuilder sb = new StringBuilder(); + sb.append('\n'); + sb.append(TextUtils.createIndent(count + tabSize, tabSize, useTab())); + sb.append('\n'); + sb.append(middle = TextUtils.createIndent(count, tabSize, useTab())); + return new NewlineHandleResult(sb, middle.length() + 1); + } + } + + private class EndTagAttributeHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().endsWith(">") && afterText.trim().startsWith(""); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + String middle; + StringBuilder sb = new StringBuilder(); + sb.append('\n'); + sb.append(TextUtils.createIndent(count, tabSize, useTab())); + sb.append('\n'); + sb.append(middle = TextUtils.createIndent(count - tabSize, tabSize, useTab())); + return new NewlineHandleResult(sb, middle.length() + 1); + } + } + + private class StartTagHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + String trim = beforeText.trim(); + return trim.startsWith("<") && !trim.endsWith(">"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + String text; + StringBuilder sb = new StringBuilder().append("\n") + .append(TextUtils.createIndent(count + tabSize, tabSize, useTab())); + return new NewlineHandleResult(sb, 0); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/xml/XMLAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/xml/XMLAutoCompleteProvider.java new file mode 100644 index 000000000..04716cf40 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/xml/XMLAutoCompleteProvider.java @@ -0,0 +1,41 @@ +package com.tyron.code.language.xml; + +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.language.AbstractAutoCompleteProvider; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.completion.main.CompletionEngine; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Editor; + +import java.io.File; + +public class XMLAutoCompleteProvider extends AbstractAutoCompleteProvider { + + private final Editor mEditor; + + public XMLAutoCompleteProvider(Editor editor) { + mEditor = editor; + } + + @Override + public CompletionList getCompletionList(String prefix, int line, int column) { + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + if (currentProject == null) { + return null; + } + Module module = currentProject.getModule(mEditor.getCurrentFile()); + if (!(module instanceof AndroidModule)) { + return null; + } + + File currentFile = mEditor.getCurrentFile(); + if (currentFile == null) { + return null; + } + return CompletionEngine.getInstance().complete(currentProject, module, mEditor, + currentFile, mEditor.getContent().toString(), prefix, line, column, + mEditor.getCaret().getStart()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLColorScheme.java b/app/src/main/java/com/tyron/code/language/xml/XMLColorScheme.java similarity index 77% rename from app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLColorScheme.java rename to app/src/main/java/com/tyron/code/language/xml/XMLColorScheme.java index 082c82d18..d62a6d3c7 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLColorScheme.java +++ b/app/src/main/java/com/tyron/code/language/xml/XMLColorScheme.java @@ -1,6 +1,6 @@ -package com.tyron.code.ui.editor.language.xml; +package com.tyron.code.language.xml; -import io.github.rosemoe.sora2.widget.EditorColorScheme; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; public class XMLColorScheme extends EditorColorScheme { diff --git a/app/src/main/java/com/tyron/code/language/xml/Xml.java b/app/src/main/java/com/tyron/code/language/xml/Xml.java new file mode 100644 index 000000000..9d2a35a0f --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/xml/Xml.java @@ -0,0 +1,28 @@ +package com.tyron.code.language.xml; + +import com.tyron.code.language.Language; +import com.tyron.code.language.LanguageManager; +import com.tyron.editor.Editor; + +import org.apache.commons.vfs2.FileObject; + +import java.io.File; + + +public class Xml implements Language { + + @Override + public boolean isApplicable(File file) { + return file.getName().endsWith(".xml"); + } + + @Override + public boolean isApplicable(FileObject fileObject) { + return fileObject.getName() .getExtension().equals("xml"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new LanguageXML(editor); + } +} diff --git a/app/src/main/java/com/tyron/code/lint/DefaultLintClient.java b/app/src/main/java/com/tyron/code/lint/DefaultLintClient.java deleted file mode 100644 index 384295dee..000000000 --- a/app/src/main/java/com/tyron/code/lint/DefaultLintClient.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.tyron.code.lint; - -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.tyron.builder.project.api.JavaModule; -import com.tyron.completion.index.CompilerService; -import com.tyron.completion.java.JavaCompilerProvider; -import com.tyron.completion.java.compiler.JavaCompilerService; -import com.tyron.lint.api.Context; -import com.tyron.lint.api.Issue; -import com.tyron.lint.api.Lint; -import com.tyron.lint.api.Location; -import com.tyron.lint.api.Severity; -import com.tyron.lint.api.TextFormat; -import com.tyron.lint.client.LintClient; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -public class DefaultLintClient extends LintClient { - - private final List mIssues = new ArrayList<>(); - private final Lint mLint; - private final JavaCompilerService mCompiler; - - public DefaultLintClient(JavaModule project) { - mCompiler = CompilerService.getInstance() - .getIndex(JavaCompilerProvider.KEY); - mLint = new Lint(mCompiler, project, this); - } - - public void scan(File file) { - mIssues.clear(); - mLint.scanFile(file); - } - - @Override - public void report(@NonNull Context context, @NonNull Issue issue, @NonNull Severity severity, @Nullable Location location, @NonNull String message, @NonNull TextFormat format) { - if (location != null) { - Log.d("default lint client", "adding issue: " + issue.getId()); - mIssues.add(new LintIssue(issue, severity, location)); - } - } - - public List getReportedIssues() { - return mIssues; - } -} diff --git a/app/src/main/java/com/tyron/code/lint/LintIssue.java b/app/src/main/java/com/tyron/code/lint/LintIssue.java deleted file mode 100644 index 492bcc197..000000000 --- a/app/src/main/java/com/tyron/code/lint/LintIssue.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.tyron.code.lint; - -import com.tyron.lint.api.Issue; -import com.tyron.lint.api.Location; -import com.tyron.lint.api.Severity; - -public class LintIssue { - - private final Issue mIssue; - - private final Severity mSeverity; - - private final Location mLocation; - - public LintIssue(Issue mIssue, Severity mSeverity, Location mLocation) { - this.mIssue = mIssue; - this.mSeverity = mSeverity; - this.mLocation = mLocation; - } - - public Issue getIssue() { - return mIssue; - } - - public Severity getSeverity() { - return mSeverity; - } - - public Location getLocation() { - return mLocation; - } -} diff --git a/app/src/main/java/com/tyron/code/service/CompilerService.java b/app/src/main/java/com/tyron/code/service/CompilerService.java deleted file mode 100644 index 7ba559d05..000000000 --- a/app/src/main/java/com/tyron/code/service/CompilerService.java +++ /dev/null @@ -1,279 +0,0 @@ -package com.tyron.code.service; - -import android.app.Notification; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.core.app.NotificationChannelCompat; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; - -import com.tyron.builder.compiler.AndroidAppBuilder; -import com.tyron.builder.compiler.AndroidAppBundleBuilder; -import com.tyron.builder.compiler.ApkBuilder; -import com.tyron.builder.compiler.BuildType; -import com.tyron.builder.compiler.Builder; -import com.tyron.builder.compiler.ProjectBuilder; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.AndroidModule; -import com.tyron.builder.project.api.Module; -import com.tyron.code.BuildConfig; -import com.tyron.code.R; -import com.tyron.code.util.ApkInstaller; -import com.tyron.completion.progress.ProgressIndicator; -import com.tyron.completion.progress.ProgressManager; - -import java.io.File; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.concurrent.Executors; - -public class CompilerService extends Service { - - private final Handler mMainHandler = new Handler(Looper.getMainLooper()); - private final CompilerBinder mBinder = new CompilerBinder(this); - - public static class CompilerBinder extends Binder { - - private final WeakReference mServiceReference; - - public CompilerBinder(CompilerService service) { - mServiceReference = new WeakReference<>(service); - } - - public CompilerService getCompilerService() { - return mServiceReference.get(); - } - } - - private Project mProject; - private ApkBuilder.OnResultListener onResultListener; - private ILogger external; - /** - * Logger that delegates logs to the external logger set - */ - private final ILogger logger = new ILogger() { - - @Override - public void info(DiagnosticWrapper wrapper) { - if (external != null) { - external.info(wrapper); - } - } - - @Override - public void debug(DiagnosticWrapper wrapper) { - if (external != null) { - external.debug(wrapper); - } - } - - @Override - public void warning(DiagnosticWrapper wrapper) { - if (external != null) { - external.warning(wrapper); - } - } - - @Override - public void error(DiagnosticWrapper wrapper) { - if (external != null) { - external.error(wrapper); - } - } - }; - - private boolean shouldShowNotification = true; - - public void setShouldShowNotification(boolean val) { - shouldShowNotification = val; - } - - public void setLogger(ILogger logger) { - this.external = logger; - } - - public void setOnResultListener(ApkBuilder.OnResultListener listener) { - onResultListener = listener; - } - - @Override - public void onCreate() { - super.onCreate(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - - Notification notification = setupNotification(); - startForeground(201, notification); - - return START_STICKY; - } - - private Notification setupNotification() { - return new NotificationCompat.Builder(this, createNotificationChannel()).setContentTitle(getString(R.string.app_name)).setSmallIcon(R.drawable.ic_launcher).setContentText("Preparing").setPriority(NotificationCompat.PRIORITY_HIGH).setOngoing(true).setProgress(100, 0, true).build(); - } - - private void updateNotification(String title, String message, int progress) { - updateNotification(title, message, progress, NotificationCompat.PRIORITY_LOW); - } - - private void updateNotification(String title, String message, int progress, int priority) { - new Handler(Looper.getMainLooper()).post(() -> { - NotificationCompat.Builder builder = - new NotificationCompat.Builder(this, "Compiler").setContentTitle(title).setContentText(message).setSmallIcon(R.drawable.ic_launcher).setPriority(priority); - if (progress != -1) { - builder.setProgress(100, progress, false); - } - NotificationManagerCompat.from(this).notify(201, builder.build()); - }); - } - - private String createNotificationChannel() { - NotificationChannelCompat channel = new NotificationChannelCompat.Builder("Compiler", - NotificationManagerCompat.IMPORTANCE_HIGH).setName("Compiler service").setDescription("Foreground notification for the compiler").build(); - - NotificationManagerCompat.from(this).createNotificationChannel(channel); - - return "Compiler"; - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - public void compile(Project project, BuildType type) { - mProject = project; - - - if (mProject == null) { - if (onResultListener != null) { - mMainHandler.post(() -> onResultListener.onComplete(false, "Failed to open " + - "project (Have you opened a project?)")); - } - - if (shouldShowNotification) { - updateNotification("Compilation failed", "Unable to open project", -1, - NotificationCompat.PRIORITY_HIGH); - } - return; - } - - ProgressIndicator indicator = new ProgressIndicator(); - ProgressManager.getInstance().runAsync(() -> { - if (true) { - buildProject(project, type); - } else { - buildMainModule(project, type); - } - }, i -> { - - }, indicator); - } - - private void buildProject(Project project, BuildType type) { - boolean success = true; - - try { - ProjectBuilder projectBuilder = new ProjectBuilder(project, logger); - projectBuilder.build(type); - } catch (Throwable e) { - String message; - if (BuildConfig.DEBUG) { - message = Log.getStackTraceString(e); - } else { - message = e.getMessage(); - } - mMainHandler.post(() -> onResultListener.onComplete(false, message)); - success = false; - } - - report(success, type, project.getMainModule()); - } - - private void buildMainModule(Project project, BuildType type) { - Module module = project.getMainModule(); - Builder extends Module> projectBuilder = getBuilderForProject(module, type); - - module.clear(); - module.index(); - - boolean success = true; - - projectBuilder.setTaskListener(this::updateNotification); - - try { - projectBuilder.build(type); - } catch (Exception e) { - String message; - if (BuildConfig.DEBUG) { - message = Log.getStackTraceString(e); - } else { - message = e.getMessage(); - } - mMainHandler.post(() -> onResultListener.onComplete(false, message)); - success = false; - } - - report(success, type, module); - } - - private void report(boolean success, BuildType type, Module module) { - if (success) { - mMainHandler.post(() -> onResultListener.onComplete(true, "Success")); - } - - - String projectName = "Project"; - if (!success) { - updateNotification(projectName, getString(R.string.compilation_result_failed), -1 - , NotificationCompat.PRIORITY_HIGH); - } else { - if (shouldShowNotification) { - mMainHandler.post(() -> { - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, - "Compiler").setSmallIcon(R.drawable.ic_launcher).setContentTitle(projectName).setContentText(getString(R.string.compilation_result_success)); - - if (type != BuildType.AAB) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(ApkInstaller.uriFromFile(this, - new File(module.getBuildDirectory(), "bin/signed.apk")), - "application/vnd.android.package-archive"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - PendingIntent pending = PendingIntent.getActivity(this, 0, intent, - PendingIntent.FLAG_IMMUTABLE); - builder.addAction(new NotificationCompat.Action(0, - getString(R.string.compilation_button_install), pending)); - } - NotificationManagerCompat.from(this).notify(201, builder.build()); - }); - } - } - - stopSelf(); - stopForeground(true); - } - - private Builder extends Module> getBuilderForProject(Module module, BuildType type) { - if (module instanceof AndroidModule) { - if (type == BuildType.AAB) { - return new AndroidAppBundleBuilder((AndroidModule) module, logger); - } - return new AndroidAppBuilder((AndroidModule) module, logger); - } - return null; - } -} diff --git a/app/src/main/java/com/tyron/code/service/CompilerServiceConnection.java b/app/src/main/java/com/tyron/code/service/CompilerServiceConnection.java deleted file mode 100644 index b20241360..000000000 --- a/app/src/main/java/com/tyron/code/service/CompilerServiceConnection.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.tyron.code.service; - -import android.content.ComponentName; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.content.SharedPreferences; - -import androidx.preference.PreferenceManager; - -import com.tyron.code.ApplicationLoader; -import com.tyron.common.SharedPreferenceKeys; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.builder.compiler.BuildType; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.log.LogViewModel; -import com.tyron.code.ui.main.MainViewModel; -import com.tyron.code.util.ApkInstaller; - -import org.openjdk.javax.tools.Diagnostic; - -import java.io.File; -import java.util.Objects; - -public class CompilerServiceConnection implements ServiceConnection { - - private final MainViewModel mMainViewModel; - private final LogViewModel mLogViewModel; - - private CompilerService mService; - private BuildType mBuildType; - private boolean mCompiling; - - public CompilerServiceConnection(MainViewModel mainViewModel, LogViewModel logViewModel) { - mMainViewModel = mainViewModel; - mLogViewModel = logViewModel; - } - - public void setBuildType(BuildType type) { - mBuildType = type; - } - - public boolean isCompiling() { - return mCompiling; - } - - public void setShouldShowNotification(boolean val) { - if (mService != null) { - mService.setShouldShowNotification(val); - } - } - - @Override - public void onServiceConnected(ComponentName name, IBinder binder) { - mService = ((CompilerService.CompilerBinder) binder).getCompilerService(); - if (mService == null) { - mLogViewModel.e(LogViewModel.BUILD_LOG, "CompilerService is null!"); - return; - } - mService.setLogger(ILogger.wrap(mLogViewModel)); - mService.setShouldShowNotification(false); - mService.setOnResultListener((success, message) -> { - mMainViewModel.setCurrentState(null); - mMainViewModel.setIndexing(false); - - if (success) { - mLogViewModel.d(LogViewModel.BUILD_LOG, message); - mLogViewModel.clear(LogViewModel.APP_LOG); - - File file = new File(ProjectManager.getInstance().getCurrentProject() - .getMainModule().getBuildDirectory(), "bin/signed.apk"); - if (file.exists() && mBuildType != BuildType.AAB) { - SharedPreferences preference = ApplicationLoader.getDefaultPreferences(); - if (preference.getBoolean(SharedPreferenceKeys.INSTALL_APK_DIRECTLY, true)) { - ApkInstaller.installApplication(mService,file.getAbsolutePath()); - } else { - mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_HALF_EXPANDED); - } - DiagnosticWrapper wrapper = new DiagnosticWrapper(); - wrapper.setKind(Diagnostic.Kind.NOTE); - wrapper.setMessage("Generated APK has been saved to " + file.getAbsolutePath()); - wrapper.setExtra("INSTALL"); - wrapper.setSource(file); - wrapper.setCode(""); - wrapper.setOnClickListener((view) -> { - if (view == null || view.getContext() == null) { - return; - } - ApkInstaller.installApplication(view.getContext(), file.getAbsolutePath()); - }); - mLogViewModel.d(LogViewModel.BUILD_LOG, wrapper); - } - } else { - mLogViewModel.e(LogViewModel.BUILD_LOG, message); - if (BottomSheetBehavior.STATE_COLLAPSED == - Objects.requireNonNull(mMainViewModel.getBottomSheetState().getValue())) { - mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_HALF_EXPANDED); - } - } - }); - - if (mBuildType != null) { - mCompiling = true; - mService.compile(ProjectManager.getInstance().getCurrentProject(), mBuildType); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mService = null; - mMainViewModel.setCurrentState(null); - mMainViewModel.setIndexing(false); - mCompiling = false; - } -} diff --git a/app/src/main/java/com/tyron/code/service/GradleDaemonService.java b/app/src/main/java/com/tyron/code/service/GradleDaemonService.java new file mode 100644 index 000000000..6f0b09ab1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/service/GradleDaemonService.java @@ -0,0 +1,125 @@ +package com.tyron.code.service; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +import androidx.core.app.NotificationChannelCompat; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import com.google.common.base.Throwables; +import com.tyron.code.R; +import com.tyron.common.util.FileUtilsEx; + +import org.apache.commons.io.FileUtils; +import org.gradle.launcher.daemon.bootstrap.DaemonMain; +import org.gradle.util.GradleVersion; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; + +/** + * A service that runs the {@link org.gradle.launcher.daemon.bootstrap.GradleDaemon} in a + * separate process + */ +public class GradleDaemonService extends Service { + private static final String ACTION_STOP_DAEMON = "stopDaemon"; + private static final String EXTRA_NOTIFICATION_ID = "notificationId"; + + private boolean daemonStarted = false; + + public GradleDaemonService() { + + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + Notification notification = setupNotification(); + startForeground(201, notification); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Notification notification = setupNotification(); + startForeground(201, notification); + + if (ACTION_STOP_DAEMON.equals(intent.getAction())) { + System.out.println("User has requested to stop the daemon service."); + System.exit(0); + } + + InputStream originalIn = System.in; + PrintStream originalOut = System.out; + + try { + String dir = intent.getStringExtra("dir"); + File currentDir = new File(dir); + + File daemonInput = new File(currentDir, "daemonInput"); + FileUtilsEx.createFile(daemonInput); + + File daemonOutput = new File(currentDir, "daemonOutput"); + FileUtilsEx.createFile(daemonOutput); + + System.setIn(FileUtils.openInputStream(daemonInput)); + System.setOut(new PrintStream(FileUtils.openOutputStream(daemonOutput, true))); + } catch (IOException e) { + System.out.println("Failed to connect to DaemonClient" + + Throwables.getStackTraceAsString(e)); + System.exit(1); + } + + if (!daemonStarted) { + new Thread(() -> { + daemonStarted = true; + + DaemonMain daemonMain = new DaemonMain(); + daemonMain.run(new String[]{GradleVersion.current().getVersion()});; + }, "GradleDaemonThread").start(); + } + + return START_NOT_STICKY; + } + + private Notification setupNotification() { + Intent stopIntent = new Intent(this, GradleDaemonService.class); + stopIntent.setAction(ACTION_STOP_DAEMON); + stopIntent.putExtra(EXTRA_NOTIFICATION_ID, 201); + PendingIntent snoozePendingIntent = PendingIntent.getBroadcast( + this, + 0, + stopIntent, + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE + ); + return new NotificationCompat.Builder(this, createNotificationChannel()) + .setContentTitle("CodeAssist Gradle Daemon") + .setSmallIcon(R.drawable.ic_stat_code) + .setContentText("Running") + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setOngoing(true) + .addAction(R.drawable.crash_ic_close, "STOP", snoozePendingIntent) + .build(); + } + + private String createNotificationChannel() { + NotificationChannelCompat channel = new NotificationChannelCompat.Builder("GradleDaemon", + NotificationManagerCompat.IMPORTANCE_HIGH).setName("Gradle Daemon Service") + .setDescription("Foreground service to keep the Gradle Daemon alive").build(); + + NotificationManagerCompat.from(this).createNotificationChannel(channel); + + return "GradleDaemon"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/service/IndexService.java b/app/src/main/java/com/tyron/code/service/IndexService.java deleted file mode 100644 index 786c2a04d..000000000 --- a/app/src/main/java/com/tyron/code/service/IndexService.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.tyron.code.service; - -import android.app.Notification; -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; - -import androidx.core.app.NotificationChannelCompat; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; - -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.project.Project; -import com.tyron.code.R; - -import java.lang.ref.WeakReference; - -public class IndexService extends Service { - - private static final int NOTIFICATION_ID = 23; - - private final Handler mMainHandler = new Handler(Looper.getMainLooper()); - private final IndexBinder mBinder = new IndexBinder(this); - - public IndexService() { - } - - public static class IndexBinder extends Binder { - - public final WeakReference mIndexServiceReference; - - public IndexBinder(IndexService service) { - mIndexServiceReference = new WeakReference<>(service); - } - - public void index(Project project, ProjectManager.TaskListener listener, ILogger logger) { - IndexService service = mIndexServiceReference.get(); - if (service == null) { - listener.onComplete(project, false, "Index service is null!"); - } else { - service.index(project, listener, logger); - } - } - } - - - public IBinder onBind(Intent intent) { - return mBinder; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Notification notification = new NotificationCompat.Builder(this, createNotificationChannel()) - .setProgress(100, 0, true) - .setSmallIcon(R.drawable.ic_launcher) - .setContentTitle("Indexing") - .setContentText("Preparing") - .build(); - updateNotification(notification); - startForeground(NOTIFICATION_ID, notification); - return START_STICKY; - } - - @Override - public void onDestroy() { - super.onDestroy(); - } - - private void index(Project project, ProjectManager.TaskListener listener, ILogger logger) { - ProjectManager.TaskListener delegate = new ProjectManager.TaskListener() { - @Override - public void onTaskStarted(String message) { - Notification notification = new NotificationCompat.Builder(IndexService.this, "Index") - .setProgress(100, 0, true) - .setSmallIcon(R.drawable.ic_launcher) - .setContentTitle("Indexing") - .setContentText(message) - .build(); - updateNotification(notification); - mMainHandler.post(() -> listener.onTaskStarted(message)); - } - - @Override - public void onComplete(Project project, boolean success, String message) { - mMainHandler.post(() -> listener.onComplete(project, success, message)); - stopForeground(true); - stopSelf(); - } - }; - - try { - ProjectManager.getInstance() - .openProject(project, true, delegate, logger); - } catch (Throwable e) { - stopForeground(true); - Notification notification = new NotificationCompat.Builder(IndexService.this, "Index") - .setProgress(100, 0, true) - .setSmallIcon(R.drawable.ic_launcher) - .setContentTitle("Indexing error") - .setContentText("Unknown error: " + e.getMessage()) - .build(); - updateNotification(notification); - stopSelf(); - throw e; - } - } - - private String createNotificationChannel() { - NotificationChannelCompat channel = new NotificationChannelCompat.Builder("Index", - NotificationManagerCompat.IMPORTANCE_NONE) - .setName("Index Service") - .setDescription("Service that downloads libraries in the foreground") - .build(); - NotificationManagerCompat.from(this) - .createNotificationChannel(channel); - return "Index"; - } - - private void updateNotification(Notification notification) { - NotificationManagerCompat.from(this) - .notify(NOTIFICATION_ID, notification); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/service/IndexServiceConnection.java b/app/src/main/java/com/tyron/code/service/IndexServiceConnection.java deleted file mode 100644 index b1e730bc8..000000000 --- a/app/src/main/java/com/tyron/code/service/IndexServiceConnection.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.tyron.code.service; - -import android.content.ComponentName; -import android.content.ServiceConnection; -import android.os.IBinder; - -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import com.tyron.builder.model.ProjectSettings; -import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; -import com.tyron.fileeditor.api.FileEditor; -import com.tyron.fileeditor.api.FileEditorSavedState; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.log.LogViewModel; -import com.tyron.builder.project.Project; -import com.tyron.code.ui.main.MainViewModel; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Handles the communication between the Index service and the main fragment - */ -public class IndexServiceConnection implements ServiceConnection { - - private final MainViewModel mMainViewModel; - private final LogViewModel mLogViewModel; - private final ILogger mLogger; - private Project mProject; - - public IndexServiceConnection(MainViewModel mainViewModel, LogViewModel logViewModel) { - mMainViewModel = mainViewModel; - mLogViewModel = logViewModel; - mLogger = ILogger.wrap(logViewModel); - } - - public void setProject(Project project) { - mProject = project; - } - - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - IndexService.IndexBinder binder = (IndexService.IndexBinder) iBinder; - binder.index(mProject, new TaskListener(), mLogger); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - mMainViewModel.setIndexing(false); - mMainViewModel.setCurrentState(null); - } - - private List getOpenedFiles(ProjectSettings settings) { - String openedFilesString = settings.getString(ProjectSettings.SAVED_EDITOR_FILES, null); - if (openedFilesString != null) { - try { - Type type = new TypeToken>(){}.getType(); - List savedStates = new Gson().fromJson(openedFilesString, type); - return savedStates.stream().filter(it -> it.getFile().exists()).map(FileEditorManagerImpl.getInstance()::openFile).collect(Collectors.toList()); - } catch (Throwable e) { - // ignored, users may have edited the file manually and is corrupt - // just return an empty editor list - } - } - return new ArrayList<>(); - } - - private class TaskListener implements ProjectManager.TaskListener { - - @Override - public void onTaskStarted(String message) { - mMainViewModel.setCurrentState(message); - } - - @SuppressWarnings("ConstantConditions") - @Override - public void onComplete(Project project, boolean success, String message) { - mMainViewModel.setIndexing(false); - mMainViewModel.setCurrentState(null); - if (success) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - if (project.equals(currentProject)) { - mMainViewModel.setToolbarTitle(project.getRootFile().getName()); - mMainViewModel.setFiles(getOpenedFiles(currentProject.getSettings())); - } - } else { - if (mMainViewModel.getBottomSheetState().getValue() - != BottomSheetBehavior.STATE_EXPANDED) { - mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_HALF_EXPANDED); - } - mLogViewModel.e(LogViewModel.BUILD_LOG, message); - } - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/BottomEditorFragment.java b/app/src/main/java/com/tyron/code/ui/editor/BottomEditorFragment.java index ad385e5bb..50e2f0dd8 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/BottomEditorFragment.java +++ b/app/src/main/java/com/tyron/code/ui/editor/BottomEditorFragment.java @@ -16,9 +16,12 @@ import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; +import com.google.common.base.Strings; import com.tyron.builder.log.LogViewModel; +import com.tyron.code.ApplicationLoader; import com.tyron.code.R; -import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; +import com.tyron.code.event.EventManager; +import com.tyron.code.event.PerformShortcutEvent; import com.tyron.code.ui.editor.log.AppLogFragment; import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; @@ -29,6 +32,8 @@ import com.tyron.code.ui.editor.shortcuts.action.UndoAction; import com.tyron.code.ui.main.MainViewModel; import com.tyron.common.util.AndroidUtilities; +import com.tyron.editor.Caret; +import com.tyron.editor.Editor; import com.tyron.fileeditor.api.FileEditor; import java.util.ArrayList; @@ -37,9 +42,6 @@ import java.util.List; import java.util.stream.Collectors; -import io.github.rosemoe.sora2.text.Cursor; -import io.github.rosemoe.sora2.widget.CodeEditor; - @SuppressWarnings("FieldCanBeLocal") public class BottomEditorFragment extends Fragment { @@ -65,15 +67,16 @@ public BottomEditorFragment() { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, + ViewGroup container, + Bundle savedInstanceState) { mRoot = inflater.inflate(R.layout.bottom_editor_fragment, container, false); mRowLayout = mRoot.findViewById(R.id.row_layout); mShortcutsRecyclerView = mRoot.findViewById(R.id.recyclerview_shortcuts); mPager = mRoot.findViewById(R.id.viewpager); mTabLayout = mRoot.findViewById(R.id.tablayout); - mFilesViewModel = new ViewModelProvider(requireActivity()) - .get(MainViewModel.class); + mFilesViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); return mRoot; } @@ -87,36 +90,38 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { new TabLayoutMediator(mTabLayout, mPager, (tab, position) -> { switch (position) { case 0: - tab.setText("Build Logs"); + tab.setText(R.string.tab_build_logs_title); break; default: case 1: - tab.setText("App Logs"); + tab.setText(R.string.tab_app_logs_title); break; case 2: - tab.setText("Debug"); + tab.setText(R.string.tab_diagnostics_title); + break; + case 3: + tab.setText(R.string.tab_ide_logs_title); + break; } }).attach(); ShortcutsAdapter adapter = new ShortcutsAdapter(getShortcuts()); - LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext(), - LinearLayoutManager.HORIZONTAL, false); + LinearLayoutManager layoutManager = + new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false); mShortcutsRecyclerView.setLayoutManager(layoutManager); mShortcutsRecyclerView.setAdapter(adapter); adapter.setOnShortcutSelectedListener((item, pos) -> { FileEditor currentFile = mFilesViewModel.getCurrentFileEditor(); if (currentFile != null) { - if (currentFile.getFragment() instanceof CodeEditorFragment) { - ((CodeEditorFragment) currentFile.getFragment()).performShortcut(item); - } + EventManager eventManager = ApplicationLoader.getInstance().getEventManager(); + eventManager.dispatchEvent(new PerformShortcutEvent(item, currentFile)); } }); - getParentFragmentManager().setFragmentResultListener(OFFSET_KEY, getViewLifecycleOwner(), - ((requestKey, result) -> { - setOffset(result.getFloat("offset", 0f)); - })); + getParentFragmentManager().setFragmentResultListener(OFFSET_KEY, + getViewLifecycleOwner(), + ((requestKey, result) -> setOffset(result.getFloat("offset", 0f)))); } private void setOffset(float offset) { @@ -135,8 +140,7 @@ private void setOffset(float offset) { } private void setRowOffset(float offset) { - mRowLayout.getLayoutParams() - .height = Math.round(AndroidUtilities.dp(38) * offset); + mRowLayout.getLayoutParams().height = Math.round(AndroidUtilities.dp(38) * offset); mRowLayout.requestLayout(); } @@ -151,32 +155,39 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { - Cursor cursor = editor.getCursor(); - editor.getText().insert(cursor.getLeftLine(), cursor.getLeftColumn(), "\t"); + public void apply(Editor editor, ShortcutItem item) { + Caret cursor = editor.getCaret(); + if (editor.useTab()) { + editor.insert(cursor.getStartLine(), cursor.getStartColumn(), "\t"); + } else { + editor.insert(cursor.getStartLine(), + cursor.getStartColumn(), + Strings.repeat(" ", editor.getTabCount())); + } } }), "->", "tab")); - items.addAll(strings.stream() - .map(item -> { - ShortcutItem it = new ShortcutItem(); - it.label = item; - it.kind = TextInsertAction.KIND; - it.actions = Collections.singletonList(new TextInsertAction()); - return it; - }).collect(Collectors.toList())); + items.addAll(strings.stream().map(item -> { + ShortcutItem it = new ShortcutItem(); + it.label = item; + it.kind = TextInsertAction.KIND; + it.actions = Collections.singletonList(new TextInsertAction()); + return it; + }).collect(Collectors.toList())); Collections.addAll(items, new ShortcutItem(Collections.singletonList(new UndoAction()), "⬿", UndoAction.KIND), new ShortcutItem(Collections.singletonList(new RedoAction()), "⤳", RedoAction.KIND), - new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.UP, 1)), "↑", CursorMoveAction.KIND), - new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.DOWN, 1)), "↓", CursorMoveAction.KIND), - new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.LEFT, 1)), "←", CursorMoveAction.KIND), - new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.RIGHT, 1)), "→", CursorMoveAction.KIND) - ); + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.UP, + 1)), "↑", CursorMoveAction.KIND), + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.DOWN, + 1)), "↓", CursorMoveAction.KIND), + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.LEFT, + 1)), "←", CursorMoveAction.KIND), + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.RIGHT, + 1)), "→", CursorMoveAction.KIND)); return items; } - @SuppressWarnings("deprecation") private static class PageAdapter extends FragmentStateAdapter { public PageAdapter(@NonNull Fragment fragment) { @@ -194,12 +205,14 @@ public Fragment createFragment(int position) { return AppLogFragment.newInstance(LogViewModel.APP_LOG); case 2: return AppLogFragment.newInstance(LogViewModel.DEBUG); + case 3: + return AppLogFragment.newInstance(LogViewModel.IDE); } } @Override public int getItemCount() { - return 3; + return 4; } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/EditorContainerFragment.java b/app/src/main/java/com/tyron/code/ui/editor/EditorContainerFragment.java index 275d96212..8dd855d57 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/EditorContainerFragment.java +++ b/app/src/main/java/com/tyron/code/ui/editor/EditorContainerFragment.java @@ -1,52 +1,81 @@ package com.tyron.code.ui.editor; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.PopupMenu; +import android.widget.FrameLayout; import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.fragment.app.Fragment; +import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; -import androidx.viewpager2.widget.ViewPager2; +import androidx.lifecycle.ViewModelProviderKt; +import androidx.lifecycle.ViewModelStoreOwner; +import androidx.transition.TransitionManager; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.tabs.TabLayout; -import com.google.android.material.tabs.TabLayoutMediator; -import com.tyron.actions.ActionManager; +import com.google.android.material.transition.MaterialFadeThrough; import com.tyron.actions.ActionPlaces; import com.tyron.actions.CommonDataKeys; import com.tyron.actions.DataContext; -import com.tyron.actions.util.DataContextUtils; +import com.tyron.actions.menu.ActionPopupMenu; +import com.tyron.builder.project.Project; +import com.tyron.code.ApplicationLoader; import com.tyron.code.R; -import com.tyron.code.ui.editor.adapter.PageAdapter; -import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; -import com.tyron.code.ui.editor.impl.xml.LayoutTextEditorFragment; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; import com.tyron.code.ui.main.MainFragment; import com.tyron.code.ui.main.MainViewModel; +import com.tyron.code.ui.main.action.project.SaveEvent; import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.util.EventManagerUtilsKt; +import com.tyron.code.util.Listeners; +import com.tyron.code.util.UiUtilsKt; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.editor.Content; +import com.tyron.editor.event.ContentEvent; +import com.tyron.editor.event.ContentListener; +import com.tyron.editor.util.EditorUtil; +import com.tyron.fileeditor.api.FileDocumentManager; +import com.tyron.fileeditor.api.FileEditor; import com.tyron.fileeditor.api.FileEditorManager; +import com.tyron.fileeditor.api.TextEditor; + +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.VFS; import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; -public class EditorContainerFragment extends Fragment { +public class EditorContainerFragment extends Fragment implements + ProjectManager.OnProjectOpenListener, SharedPreferences.OnSharedPreferenceChangeListener { public static final String SAVE_ALL_KEY = "saveAllEditors"; public static final String PREVIEW_KEY = "previewEditor"; public static final String FORMAT_KEY = "formatEditor"; private TabLayout mTabLayout; - private ViewPager2 mPager; - private PageAdapter mAdapter; + private FrameLayout mContainer; private BottomSheetBehavior mBehavior; private MainViewModel mMainViewModel; + private EditorContainerViewModel mEditorContainerViewModel; private FileEditorManager mFileEditorManager; + private SharedPreferences pref; + private final List mEditors = new ArrayList<>(); private final OnBackPressedCallback mOnBackPressedCallback = new OnBackPressedCallback(false) { @Override @@ -55,13 +84,16 @@ public void handleOnBackPressed() { } }; + private DataContext mDataContext; + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + pref = ApplicationLoader.getDefaultPreferences(); mMainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); - requireActivity().getOnBackPressedDispatcher().addCallback(this, - mOnBackPressedCallback); + mEditorContainerViewModel = new ViewModelProvider((ViewModelStoreOwner) this).get(EditorContainerViewModel.class); + requireActivity().getOnBackPressedDispatcher().addCallback((LifecycleOwner) this, mOnBackPressedCallback); } @Override @@ -76,56 +108,54 @@ public void onSaveInstanceState(@NonNull Bundle outState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.editor_container_fragment, container, false); - - mAdapter = new PageAdapter(getChildFragmentManager(), getLifecycle()); - mPager = root.findViewById(R.id.viewpager); - mPager.setAdapter(mAdapter); - mPager.setUserInputEnabled(false); + // safe cast, getContext() ensures it returns a DataContext + DataContext dataContext = (DataContext) requireContext(); + dataContext.putData(CommonDataKeys.PROJECT, + ProjectManager.getInstance().getCurrentProject()); + dataContext.putData(CommonDataKeys.FRAGMENT, EditorContainerFragment.this); + dataContext.putData(MainFragment.MAIN_VIEW_MODEL_KEY, mMainViewModel); + CoordinatorLayout root = (CoordinatorLayout) inflater.inflate( + R.layout.editor_container_fragment, + container, + false + ); + mContainer = root.findViewById(R.id.viewpager); + ((FileEditorManagerImpl) FileEditorManagerImpl.getInstance()) + .attach(mMainViewModel, getChildFragmentManager()); mTabLayout = root.findViewById(R.id.tablayout); mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabUnselected(TabLayout.Tab p1) { - Fragment fragment = getChildFragmentManager() - .findFragmentByTag("f" + mAdapter.getItemId(p1.getPosition())); - if (fragment instanceof CodeEditorFragment) { - ((CodeEditorFragment) fragment).save(); + FileEditor currentFileEditor = mMainViewModel.getCurrentFileEditor(); + if (currentFileEditor instanceof TextEditor) { + FileDocumentManager instance = FileDocumentManager.getInstance(); + instance.saveContent(((TextEditor) currentFileEditor).getContent()); } } @Override public void onTabReselected(TabLayout.Tab p1) { - PopupMenu popup = new PopupMenu(requireActivity(), p1.view); - - DataContext dataContext = DataContextUtils.getDataContext(mTabLayout); - dataContext.putData(CommonDataKeys.PROJECT, ProjectManager.getInstance().getCurrentProject()); - dataContext.putData(CommonDataKeys.FRAGMENT, EditorContainerFragment.this); - dataContext.putData(MainFragment.MAIN_VIEW_MODEL_KEY, mMainViewModel); - dataContext.putData(CommonDataKeys.FILE_EDITOR_KEY, mMainViewModel.getCurrentFileEditor()); - - ActionManager.getInstance().fillMenu(dataContext, - popup.getMenu(), ActionPlaces.EDITOR_TAB, - true, - false); - popup.show(); + ActionPopupMenu.createAndShow( + p1.view, + (DataContext) requireContext(), + ActionPlaces.EDITOR_TAB + ); } @Override public void onTabSelected(TabLayout.Tab p1) { - mMainViewModel.setCurrentPosition(p1.getPosition(), false); - getParentFragmentManager() - .setFragmentResult(MainFragment.REFRESH_TOOLBAR_KEY, Bundle.EMPTY); + updateTab(p1.getPosition()); + mMainViewModel.setCurrentPosition(p1.getPosition(), true); + + ProgressManager.getInstance().runLater(() -> getParentFragmentManager() + .setFragmentResult(MainFragment.REFRESH_TOOLBAR_KEY, Bundle.EMPTY), 300); } }); + View persistentSheet = root.findViewById(R.id.persistent_sheet); + mBehavior = BottomSheetBehavior.from(persistentSheet); + mBehavior.setGestureInsetBottomIgnored(true); - new TabLayoutMediator(mTabLayout, mPager, true, true, (tab, pos) -> { - File current = Objects.requireNonNull(mMainViewModel.getFiles().getValue()).get(pos) - .getFile(); - tab.setText(current != null ? current.getName() : "Unknown"); - }).attach(); - - mBehavior = BottomSheetBehavior.from(root.findViewById(R.id.persistent_sheet)); mBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull View p1, int state) { @@ -137,12 +167,14 @@ public void onSlide(@NonNull View bottomSheet, float slideOffset) { if (isAdded()) { Bundle bundle = new Bundle(); bundle.putFloat("offset", slideOffset); - getChildFragmentManager().setFragmentResult(BottomEditorFragment.OFFSET_KEY, - bundle); + getChildFragmentManager().setFragmentResult(BottomEditorFragment.OFFSET_KEY, bundle); } } }); mBehavior.setHalfExpandedRatio(0.3f); + mBehavior.setFitToContents(false); + + ProjectManager.getInstance().addOnProjectOpenListener(this); if (savedInstanceState != null) { restoreViewState(savedInstanceState); @@ -150,67 +182,160 @@ public void onSlide(@NonNull View bottomSheet, float slideOffset) { return root; } + private void updateTabs() { + FileEditor fileEditor = mMainViewModel.getCurrentFileEditor(); + int index = mEditors.indexOf(fileEditor); + if (index != -1) { + updateTab(index); + } + } + + private void updateTab(int pos) { + TabLayout.Tab tab = mTabLayout.getTabAt(pos); + if (tab == null) { + return; + } + + List fileEditors = mMainViewModel.getFiles().getValue(); + if (fileEditors == null) { + fileEditors = Collections.emptyList(); + } + List files = fileEditors.stream().map(FileEditor::getFile).collect(Collectors.toList()); + FileEditor currentEditor = + Objects.requireNonNull(fileEditors).get(pos); + File current = currentEditor.getFile(); + + String text = current != null ? + EditorUtil.getUniqueTabTitle(current, files) + : "Unknown"; + if (currentEditor.isModified()) { + text = "*" + text; + } + + tab.setText(text); + } + + @Override + public void onProjectOpen(Project project) { + + } + @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - mMainViewModel.getCurrentPosition().observe(getViewLifecycleOwner(), pos -> { - mPager.setCurrentItem(pos, false); - }); + ApplicationLoader.getDefaultPreferences().registerOnSharedPreferenceChangeListener(this); + mMainViewModel.getFiles().observe(getViewLifecycleOwner(), files -> { - mAdapter.submitList(files); - mTabLayout.setVisibility(files.isEmpty() ? View.GONE : View.VISIBLE); + List oldList = new ArrayList<>(mEditors); + mEditors.clear(); + mEditors.addAll(files); + + TransitionManager.beginDelayedTransition(mContainer, new MaterialFadeThrough()); + if (files.isEmpty()) { + mContainer.removeAllViews(); + mTabLayout.removeAllTabs(); + mTabLayout.setVisibility(View.GONE); + mMainViewModel.setCurrentPosition(-1); + } else { + mTabLayout.setVisibility(View.VISIBLE); + EditorTabUtil.updateTabLayout(mTabLayout, oldList, files); + } }); - mMainViewModel.getCurrentPosition().observe(getViewLifecycleOwner(), pos -> { - mPager.setCurrentItem(pos, false); - if (mBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { - mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED); + + mMainViewModel.getCurrentPosition().observe(getViewLifecycleOwner(), position -> { + mContainer.removeAllViews(); + + FileEditor currentFileEditor = mMainViewModel.getCurrentFileEditor(); + if (position == -1 || currentFileEditor == null) { + return; + } + + if (mTabLayout.getSelectedTabPosition() != position) { + mTabLayout.selectTab(mTabLayout.getTabAt(position), true); + } + MaterialFadeThrough transition = new MaterialFadeThrough(); + transition.setDuration(150L); + TransitionManager.beginDelayedTransition(mContainer, transition); + + UiUtilsKt.removeFromParent(currentFileEditor.getView()); + mContainer.addView(currentFileEditor.getView()); + + try { + File file = currentFileEditor.getFile(); + FileObject fileObject = VFS.getManager().toFileObject(file); + Content content = FileDocumentManager.getInstance().getContent(fileObject); + + if (content != null) { + Listeners.registerListener(new ContentListener() { + @Override + public void contentChanged(@NonNull ContentEvent event) { + updateTabs(); + } + }, getViewLifecycleOwner(), content::addContentListener, content::removeContentListener); + } + } catch (FileSystemException e) { + // safe to ignore here, just don't register the listener then } }); + + + EventManagerUtilsKt.subscribeEvent( + ApplicationLoader.getInstance().getEventManager(), + getViewLifecycleOwner(), + SaveEvent.class, + (event, unsubscribe) -> updateTabs() + ); + mMainViewModel.getBottomSheetState().observe(getViewLifecycleOwner(), state -> { + if (state == BottomSheetBehavior.STATE_DRAGGING || state == BottomSheetBehavior.STATE_SETTLING) { + return; + } mBehavior.setState(state); mOnBackPressedCallback.setEnabled(state == BottomSheetBehavior.STATE_EXPANDED); }); - - getParentFragmentManager().setFragmentResultListener(SAVE_ALL_KEY, - getViewLifecycleOwner(), (requestKey, result) -> saveAll()); - getParentFragmentManager().setFragmentResultListener(PREVIEW_KEY, - getViewLifecycleOwner(), ((requestKey, result) -> previewCurrent())); - getParentFragmentManager().setFragmentResultListener(FORMAT_KEY, - getViewLifecycleOwner(), (((requestKey, result) -> formatCurrent()))); } private void restoreViewState(@NonNull Bundle state) { int behaviorState = state.getInt("bottom_sheet_state", BottomSheetBehavior.STATE_COLLAPSED); mMainViewModel.setBottomSheetState(behaviorState); Bundle floatOffset = new Bundle(); - floatOffset.putFloat("offset", behaviorState == BottomSheetBehavior.STATE_EXPANDED - ? 1 - : 0f); + floatOffset.putFloat("offset", behaviorState == BottomSheetBehavior.STATE_EXPANDED ? 1 : 0f); getChildFragmentManager().setFragmentResult(BottomEditorFragment.OFFSET_KEY, floatOffset); } - private void formatCurrent() { - String tag = "f" + mAdapter.getItemId(mPager.getCurrentItem()); - Fragment fragment = getChildFragmentManager().findFragmentByTag(tag); - if (fragment instanceof CodeEditorFragment) { - ((CodeEditorFragment) fragment).format(); - } + @Override + public void onDestroyView() { + super.onDestroyView(); } - private void previewCurrent() { - String tag = "f" + mAdapter.getItemId(mPager.getCurrentItem()); - Fragment fragment = getChildFragmentManager().findFragmentByTag(tag); - if (fragment instanceof LayoutTextEditorFragment) { - ((LayoutTextEditorFragment) fragment).preview(); + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + switch (key) { + case SharedPreferenceKeys.EDITOR_TAB_UNIQUE_FILE_NAME: + for (int i = 0; i < mTabLayout.getTabCount(); i++) { + updateTab(i); + } + break; } } - private void saveAll() { - for (int i = 0; i < mAdapter.getItemCount(); i++) { - String tag = "f" + mAdapter.getItemId(i); - Fragment fragment = getChildFragmentManager().findFragmentByTag(tag); - if (fragment instanceof Savable) { - ((Savable) fragment).save(); - } + @Override + public void onDestroy() { + super.onDestroy(); + mDataContext = null; + ApplicationLoader.getDefaultPreferences().unregisterOnSharedPreferenceChangeListener(this); + } + + @Nullable + @Override + public Context getContext() { + Context originalContext = super.getContext(); + if (originalContext == null) { + return null; + } + + if (mDataContext == null) { + mDataContext = new DataContext(originalContext); } + return mDataContext; } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/EditorContainerViewModel.kt b/app/src/main/java/com/tyron/code/ui/editor/EditorContainerViewModel.kt new file mode 100644 index 000000000..f6a1c1775 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/EditorContainerViewModel.kt @@ -0,0 +1,19 @@ +package com.tyron.code.ui.editor + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.tyron.code.ApplicationLoader +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme +import io.github.rosemoe.sora2.text.EditorUtil + +class EditorContainerViewModel : ViewModel() { + + private val _editorTheme = MutableLiveData() + val editorTheme: LiveData + get() = _editorTheme + + init { + _editorTheme.value = EditorUtil.getDefaultColorScheme(ApplicationLoader.applicationContext) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/EditorTabUtil.java b/app/src/main/java/com/tyron/code/ui/editor/EditorTabUtil.java new file mode 100644 index 000000000..0cd7894c4 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/EditorTabUtil.java @@ -0,0 +1,51 @@ +package com.tyron.code.ui.editor; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.ListUpdateCallback; + +import com.google.android.material.tabs.TabLayout; +import com.tyron.code.ui.editor.adapter.PageAdapter; +import com.tyron.fileeditor.api.FileEditor; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class EditorTabUtil { + + public static void updateTabLayout(@NonNull TabLayout mTabLayout, List oldList, List files) { + PageAdapter.getDiff(oldList, files, new ListUpdateCallback() { + @Override + public void onInserted(int position, int count) { + FileEditor editor = files.get(position); + TabLayout.Tab tab = getTabLayout(editor); + mTabLayout.addTab(tab, position, false); + mTabLayout.selectTab(tab, true); + } + + @Override + public void onRemoved(int position, int count) { + for (int i = 0; i < count; i++) { + mTabLayout.removeTabAt(position); + } + } + + @Override + public void onMoved(int fromPosition, int toPosition) { + + } + + @Override + public void onChanged(int position, int count, @Nullable Object payload) { + + } + + private TabLayout.Tab getTabLayout(FileEditor editor) { + TabLayout.Tab tab = mTabLayout.newTab(); + tab.setText(editor.getFile().getName()); + return tab; + } + }); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/EditorViewModel.kt b/app/src/main/java/com/tyron/code/ui/editor/EditorViewModel.kt new file mode 100644 index 000000000..9ef0ba314 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/EditorViewModel.kt @@ -0,0 +1,24 @@ +package com.tyron.code.ui.editor + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.tyron.completion.java.JavaCompletionProvider +import com.tyron.completion.model.CompletionList +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch + +class EditorViewModel : ViewModel() { + private val mAnalyzeState = MutableLiveData(false) + + fun setAnalyzeState(analyzing: Boolean) { + mAnalyzeState.value = analyzing + } + + val analyzeState: LiveData + get() = mAnalyzeState + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/Savable.java b/app/src/main/java/com/tyron/code/ui/editor/Savable.java index fe6ff97c4..fdbfec01c 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/Savable.java +++ b/app/src/main/java/com/tyron/code/ui/editor/Savable.java @@ -6,5 +6,14 @@ */ public interface Savable { - void save(); + /** + * @return Whether the content can be saved + */ + boolean canSave(); + + /** + * Saves the contents of this class + * @param toDisk whether the contents will be written to file or stored in memory + */ + void save(boolean toDisk); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/CloseAllEditorAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/CloseAllEditorAction.java index 7d4b18c66..158f5a7fd 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/CloseAllEditorAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/CloseAllEditorAction.java @@ -32,6 +32,8 @@ public void update(@NonNull AnActionEvent event) { @Override public void actionPerformed(@NonNull AnActionEvent e) { MainViewModel mainViewModel = e.getData(MainFragment.MAIN_VIEW_MODEL_KEY); - mainViewModel.clear(); + if (mainViewModel != null) { + mainViewModel.clear(); + } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/CloseFileEditorAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/CloseFileEditorAction.java index 7ca64cd8b..429e134ea 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/CloseFileEditorAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/CloseFileEditorAction.java @@ -17,19 +17,19 @@ public class CloseFileEditorAction extends AnAction { @Override public void update(@NonNull AnActionEvent event) { - MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); - FileEditor fileEditor = event.getData(CommonDataKeys.FILE_EDITOR_KEY); - event.getPresentation().setVisible(false); - if (!ActionPlaces.EDITOR_TAB.equals(event.getPlace())) { + + MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); + if (mainViewModel == null) { return; } - if (fileEditor == null) { + if (!ActionPlaces.EDITOR_TAB.equals(event.getPlace())) { return; } - if (mainViewModel == null) { + FileEditor fileEditor = mainViewModel.getCurrentFileEditor(); + if (fileEditor == null) { return; } @@ -40,7 +40,9 @@ public void update(@NonNull AnActionEvent event) { @Override public void actionPerformed(@NonNull AnActionEvent e) { MainViewModel mainViewModel = e.getRequiredData(MainFragment.MAIN_VIEW_MODEL_KEY); - FileEditor fileEditor = e.getRequiredData(CommonDataKeys.FILE_EDITOR_KEY); - mainViewModel.removeFile(fileEditor.getFile()); + FileEditor fileEditor = mainViewModel.getCurrentFileEditor(); + if (fileEditor != null) { + mainViewModel.removeFile(fileEditor.getFile()); + } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/CloseOtherEditorAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/CloseOtherEditorAction.java index c99501c2d..38f125f95 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/CloseOtherEditorAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/CloseOtherEditorAction.java @@ -17,19 +17,17 @@ public class CloseOtherEditorAction extends AnAction { @Override public void update(@NonNull AnActionEvent event) { - MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); - FileEditor fileEditor = event.getData(CommonDataKeys.FILE_EDITOR_KEY); - event.getPresentation().setVisible(false); if (!ActionPlaces.EDITOR_TAB.equals(event.getPlace())) { return; } - if (fileEditor == null) { + MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); + if (mainViewModel == null) { return; } - - if (mainViewModel == null) { + FileEditor fileEditor = mainViewModel.getCurrentFileEditor(); + if (fileEditor == null) { return; } @@ -39,8 +37,8 @@ public void update(@NonNull AnActionEvent event) { @Override public void actionPerformed(@NonNull AnActionEvent e) { - MainViewModel mainViewModel = e.getData(MainFragment.MAIN_VIEW_MODEL_KEY); - FileEditor fileEditor = e.getData(CommonDataKeys.FILE_EDITOR_KEY); + MainViewModel mainViewModel = e.getRequiredData(MainFragment.MAIN_VIEW_MODEL_KEY); + FileEditor fileEditor = e.getRequiredData(CommonDataKeys.FILE_EDITOR_KEY); mainViewModel.removeOthers(fileEditor.getFile()); } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/DiagnosticInfoAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/DiagnosticInfoAction.java index c13cfa7e7..f3d05b142 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/DiagnosticInfoAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/DiagnosticInfoAction.java @@ -10,7 +10,7 @@ import com.tyron.actions.CommonDataKeys; import com.tyron.actions.Presentation; -import org.openjdk.javax.tools.Diagnostic; +import javax.tools.Diagnostic; import java.util.Locale; diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/ExpandSelectionAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/ExpandSelectionAction.java new file mode 100644 index 000000000..1f2048e0d --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/ExpandSelectionAction.java @@ -0,0 +1,89 @@ +package com.tyron.code.ui.editor.action; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; + +import com.google.common.collect.Range; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.Presentation; +import com.tyron.builder.model.SourceFileObject; +import com.tyron.builder.project.Project; +import com.tyron.code.R; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.completion.java.action.FindCurrentPath; +import com.tyron.completion.java.compiler.Parser; +import com.tyron.editor.CharPosition; +import com.tyron.editor.Editor; +import com.tyron.editor.selection.ExpandSelectionProvider; + +import com.sun.source.tree.ClassTree; +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.io.File; +import java.time.Instant; + +public class ExpandSelectionAction extends AnAction { + + public static final String ID = "expandSelection"; + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + if (!ActionPlaces.EDITOR.equals(event.getPlace())) { + return; + } + + File file = event.getData(CommonDataKeys.FILE); + if (file == null) { + return; + } + + Editor editor = event.getData(CommonDataKeys.EDITOR); + if (editor == null) { + return; + } + + ExpandSelectionProvider provider = ExpandSelectionProvider.forEditor(editor); + if (provider == null) { + return; + } + + if (editor.getProject() == null) { + return; + } + + DataContext context = event.getDataContext(); + presentation.setVisible(true); + presentation.setText(context.getString(R.string.expand_selection)); + presentation.setIcon(ResourcesCompat.getDrawable(context.getResources(), + R.drawable.ic_baseline_code_24, null)); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); + ExpandSelectionProvider provider = ExpandSelectionProvider.forEditor(editor); + if (provider == null) { + AndroidUtilities.showSimpleAlert(e.getDataContext(), "No provider", + "No expand selection provider found."); + return; + } + Range range = provider.expandSelection(editor); + if (range == null) { + AndroidUtilities.showSimpleAlert(e.getDataContext(), + "Error", + "Cannot expand selection"); + return; + } + editor.setSelectionRegion(range.lowerEndpoint(), range.upperEndpoint()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/PreviewLayoutAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/PreviewLayoutAction.java index f9ab7a475..96ab30520 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/PreviewLayoutAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/PreviewLayoutAction.java @@ -41,20 +41,20 @@ public void update(@NonNull AnActionEvent event) { @Override public void actionPerformed(@NonNull AnActionEvent e) { - FileEditor fileEditor = e.getData(CommonDataKeys.FILE_EDITOR_KEY); - Fragment fragment = fileEditor.getFragment(); - if (fragment == null || fragment.isDetached()) { - return; - } - FragmentActivity activity = fragment.requireActivity(); - View currentFocus = activity.getCurrentFocus(); - if (currentFocus == null) { - currentFocus = new View(activity); - } - AndroidUtilities.hideKeyboard(currentFocus); - - if (fragment instanceof LayoutTextEditorFragment) { - ((LayoutTextEditorFragment) fragment).preview(); - } +// FileEditor fileEditor = e.getRequiredData(CommonDataKeys.FILE_EDITOR_KEY); +// Fragment fragment = fileEditor.getFragment(); +// if (fragment == null || fragment.isDetached() || fragment.getActivity() == null) { +// return; +// } +// FragmentActivity activity = fragment.requireActivity(); +// View currentFocus = activity.getCurrentFocus(); +// if (currentFocus == null) { +// currentFocus = new View(activity); +// } +// AndroidUtilities.hideKeyboard(currentFocus); +// +// if (fragment instanceof LayoutTextEditorFragment) { +// ((LayoutTextEditorFragment) fragment).preview(); +// } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/SelectJavaParentAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/SelectJavaParentAction.java deleted file mode 100644 index c788077ee..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/action/SelectJavaParentAction.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.tyron.code.ui.editor.action; - -import androidx.annotation.NonNull; - -import com.tyron.actions.ActionPlaces; -import com.tyron.actions.AnAction; -import com.tyron.actions.AnActionEvent; -import com.tyron.actions.CommonDataKeys; -import com.tyron.actions.Presentation; -import com.tyron.builder.project.Project; -import com.tyron.code.R; -import com.tyron.completion.java.action.FindCurrentPath; -import com.tyron.completion.java.compiler.Parser; -import com.tyron.editor.CharPosition; -import com.tyron.editor.Editor; - -import org.openjdk.source.tree.ClassTree; -import org.openjdk.source.tree.Tree; -import org.openjdk.source.util.SourcePositions; -import org.openjdk.source.util.TreePath; -import org.openjdk.source.util.Trees; - -import java.io.File; - -public class SelectJavaParentAction extends AnAction { - - public static final String ID = "selectScopeParent"; - - @Override - public void update(@NonNull AnActionEvent event) { - Presentation presentation = event.getPresentation(); - presentation.setVisible(false); - - if (!ActionPlaces.EDITOR.equals(event.getPlace())) { - return; - } - - File file = event.getData(CommonDataKeys.FILE); - if (file == null || !file.getName().endsWith(".java")) { - return; - } - - if (event.getData(CommonDataKeys.PROJECT) == null) { - return; - } - - Editor editor = event.getData(CommonDataKeys.EDITOR); - if (editor == null) { - return; - } - - presentation.setVisible(true); - presentation.setText("Select all"); - presentation.setIcon(event.getDataContext().getDrawable(R.drawable.round_select_all_20)); - } - - @Override - public void actionPerformed(@NonNull AnActionEvent e) { - Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); - Project project = e.getRequiredData(CommonDataKeys.PROJECT); - File file = e.getRequiredData(CommonDataKeys.FILE); - Parser parser = Parser.parseFile(project, file.toPath()); - - FindCurrentPath findCurrentPath = new FindCurrentPath(parser.task); - - int cursorStart = editor.getCaret().getStart(); - int cursorEnd = editor.getCaret().getEnd(); - - SourcePositions positions = Trees.instance(parser.task).getSourcePositions(); - TreePath path = findCurrentPath.scan(parser.root, cursorStart, cursorEnd); - if (path != null) { - path = modifyTreePath(path); - - long afterStart; - long afterEnd; - - long currentStart = positions.getStartPosition(parser.root, path.getLeaf()); - long currentEnd = positions.getEndPosition(parser.root, path.getLeaf()); - if (currentStart == cursorStart && currentEnd == cursorEnd) { - TreePath parentPath = path.getParentPath(); - afterStart = positions.getStartPosition(parser.root, parentPath.getLeaf()); - afterEnd = positions.getEndPosition(parser.root, parentPath.getLeaf()); - } else { - afterStart = currentStart; - afterEnd = currentEnd; - } - CharPosition start = editor.getCharPosition((int) afterStart); - CharPosition end = editor.getCharPosition((int) afterEnd); - editor.setSelectionRegion(start.getLine(), start.getColumn(), - end.getLine(), end.getColumn()); - } - } - - @NonNull - private TreePath modifyTreePath(TreePath treePath) { - TreePath parent = treePath.getParentPath(); - - if (treePath.getLeaf().getKind() == Tree.Kind.BLOCK) { - // select the parent of { } - return parent; - } - - if (treePath.getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) { - return modifyTreePath(parent); - } - - if (treePath.getLeaf() instanceof ClassTree - && parent.getLeaf().getKind() == Tree.Kind.NEW_CLASS) { - if (parent.getParentPath().getLeaf().getKind() == Tree.Kind.EXPRESSION_STATEMENT) { - return parent.getParentPath(); - } - - return parent; - } - - if (treePath.getLeaf().getKind() == Tree.Kind.IDENTIFIER) { - if (parent.getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) { - return modifyTreePath(parent); - } - if (parent.getLeaf() .getKind() == Tree.Kind.METHOD_INVOCATION) { - // identifier -> method call -> expression - return parent.getParentPath(); - } - } - - if (treePath.getLeaf().getKind() == Tree.Kind.METHOD_INVOCATION) { - return parent; - } - return treePath; - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/CopyAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/CopyAction.java index b8bba7a89..0faef1937 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/text/CopyAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/CopyAction.java @@ -1,12 +1,15 @@ package com.tyron.code.ui.editor.action.text; import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; import com.tyron.actions.ActionPlaces; import com.tyron.actions.AnAction; import com.tyron.actions.AnActionEvent; import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; import com.tyron.actions.Presentation; +import com.tyron.code.R; import com.tyron.common.util.AndroidUtilities; import com.tyron.editor.Caret; import com.tyron.editor.Editor; @@ -33,8 +36,11 @@ public void update(@NonNull AnActionEvent event) { return; } + DataContext context = event.getDataContext(); presentation.setVisible(true); - presentation.setText(event.getDataContext().getString(io.github.rosemoe.sora2.R.string.copy)); + presentation.setText(context.getString(io.github.rosemoe.sora2.R.string.copy)); + presentation.setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_content_copy_20, context.getTheme())); } @Override @@ -43,6 +49,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { Caret caret = editor.getCaret(); CharSequence textToCopy = editor.getContent().subSequence(caret.getStart(), caret.getEnd()); - AndroidUtilities.copyToClipboard(textToCopy.toString(), true); + AndroidUtilities.copyToClipboard(textToCopy.toString(), false); + AndroidUtilities.showToast(R.string.copied_to_clipoard); } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/CutAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/CutAction.java index 9d9149f94..fef5eaf42 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/text/CutAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/CutAction.java @@ -1,9 +1,11 @@ package com.tyron.code.ui.editor.action.text; import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; import com.tyron.actions.AnActionEvent; import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; import com.tyron.editor.Caret; import com.tyron.editor.Editor; @@ -14,7 +16,10 @@ public void update(@NonNull AnActionEvent event) { super.update(event); if (event.getPresentation().isVisible()) { - event.getPresentation().setText("Cut"); + DataContext context = event.getDataContext(); + event.getPresentation().setText(context.getString(io.github.rosemoe.sora2.R.string.cut)); + event.getPresentation().setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_content_cut_20, context.getTheme())); } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/PasteAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/PasteAction.java new file mode 100644 index 000000000..349c185e5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/PasteAction.java @@ -0,0 +1,78 @@ +package com.tyron.code.ui.editor.action.text; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; + +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.Presentation; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.editor.Caret; +import com.tyron.editor.Editor; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import io.github.rosemoe.sora.text.TextUtils; +import io.github.rosemoe.sora2.text.EditorUtil; + +public class PasteAction extends CopyAction { + + @Override + public void update(@NonNull AnActionEvent event) { + super.update(event); + + Presentation presentation = event.getPresentation(); + if (!presentation.isVisible() && AndroidUtilities.getPrimaryClip() != null) { + presentation.setVisible(true); + } + + if (presentation.isVisible()) { + DataContext context = event.getDataContext(); + presentation.setText(context.getString(io.github.rosemoe.sora2.R.string.paste)); + presentation.setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_content_paste_20, context.getTheme())); + } + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); + Caret caret = editor.getCaret(); + + if (caret.isSelected()) { + editor.delete(caret.getStart(), caret.getEnd()); + } + + String clip = String.valueOf(AndroidUtilities.getPrimaryClip()); + String[] lines = clip.split("\n"); + if (lines.length == 0) { + lines = new String[]{clip}; + } + + int count = TextUtils.countLeadingSpaceCount(lines[0], editor.getTabCount()); + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + if (count < line.length()) { + String whitespace = line.substring(0, count); + if (EditorUtil.isWhitespace(whitespace)) { + line = line.substring(count); + } else { + line = line.trim(); + } + } else { + line = line.trim(); + } + lines[i] = line; + } + + String textToCopy = String.join("\n", lines); + editor.insertMultilineString( + caret.getStartLine(), + caret.getStartColumn(), + textToCopy + ); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/SelectAllAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/SelectAllAction.java new file mode 100644 index 000000000..4824d8122 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/SelectAllAction.java @@ -0,0 +1,30 @@ +package com.tyron.code.ui.editor.action.text; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; + +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.editor.Editor; + +public class SelectAllAction extends CopyAction { + + @Override + public void update(@NonNull AnActionEvent event) { + super.update(event); + + if (event.getPresentation().isVisible()) { + DataContext context = event.getDataContext(); + event.getPresentation().setText(context.getString(io.github.rosemoe.sora2.R.string.selectAll)); + event.getPresentation().setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_select_all_20, context.getTheme())); + } + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); + editor.setSelectionRegion(0, editor.getContent().length()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/TextActionGroup.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/TextActionGroup.java index 752e3306f..77d2ca774 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/text/TextActionGroup.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/TextActionGroup.java @@ -8,6 +8,8 @@ import com.tyron.actions.AnAction; import com.tyron.actions.AnActionEvent; import com.tyron.actions.Presentation; +import com.tyron.code.R; +import com.tyron.code.ui.editor.action.ExpandSelectionAction; public class TextActionGroup extends ActionGroup { @@ -23,19 +25,22 @@ public void update(@NonNull AnActionEvent event) { } presentation.setVisible(true); - presentation.setText("Text Actions"); + presentation.setText(event.getDataContext().getString(R.string.text_actions)); } @Override public boolean isPopup() { - return false; + return true; } @Override public AnAction[] getChildren(@Nullable AnActionEvent e) { return new AnAction[] { + new ExpandSelectionAction(), + new SelectAllAction(), + new CutAction(), new CopyAction(), - new CutAction() + new PasteAction() }; } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/adapter/PageAdapter.java b/app/src/main/java/com/tyron/code/ui/editor/adapter/PageAdapter.java index f20741a2e..f137544ca 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/adapter/PageAdapter.java +++ b/app/src/main/java/com/tyron/code/ui/editor/adapter/PageAdapter.java @@ -1,10 +1,17 @@ package com.tyron.code.ui.editor.adapter; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.Lifecycle; import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListUpdateCallback; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager2.adapter.FragmentStateAdapter; import com.tyron.fileeditor.api.FileEditor; @@ -13,67 +20,59 @@ import java.util.List; import java.util.Objects; -public class PageAdapter extends FragmentStateAdapter { - - private final List data = new ArrayList<>(); - - public PageAdapter(FragmentManager fm, Lifecycle lifecycle) { - super(fm, lifecycle); - } +public class PageAdapter extends RecyclerView.Adapter { - public void submitList(List files) { + public static void getDiff(List oldFiles, List newFiles, ListUpdateCallback callback) { DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { @Override public int getOldListSize() { - return data.size(); + return oldFiles.size(); } @Override public int getNewListSize() { - return files.size(); + return newFiles.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return Objects.equals(data.get(oldItemPosition), files.get(newItemPosition)); + return Objects.equals(oldFiles.get(oldItemPosition), newFiles.get(newItemPosition)); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - return Objects.equals(data.get(oldItemPosition), files.get(newItemPosition)); + return Objects.equals(oldFiles.get(oldItemPosition), newFiles.get(newItemPosition)); } }); - data.clear(); - data.addAll(files); - result.dispatchUpdatesTo(this); + oldFiles.clear(); + oldFiles.addAll(newFiles); + result.dispatchUpdatesTo(callback); } - @Override - public int getItemCount() { - return data.size(); + private final List data = new ArrayList<>(); + + public void submitList(List files) { } + @NonNull @Override - public long getItemId(int position) { - if (data.isEmpty()) { - return -1; - } - return data.get(position).hashCode(); + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(new FrameLayout(parent.getContext())); } @Override - public boolean containsItem(long itemId) { - for (FileEditor d : data) { - if (d.hashCode() == itemId) { - return true; - } - } - return false; + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + } - @NonNull @Override - public Fragment createFragment(int p1) { - return data.get(p1).getFragment(); + public int getItemCount() { + return data.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + public ViewHolder(@NonNull View itemView) { + super(itemView); + } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/completion/CodeCompletionState.kt b/app/src/main/java/com/tyron/code/ui/editor/completion/CodeCompletionState.kt new file mode 100644 index 000000000..0823ff6c8 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/completion/CodeCompletionState.kt @@ -0,0 +1,4 @@ +package com.tyron.code.ui.editor.completion + +class CodeCompletionState { +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/DefaultFileDocumentContentProvider.java b/app/src/main/java/com/tyron/code/ui/editor/impl/DefaultFileDocumentContentProvider.java new file mode 100644 index 000000000..c8be4785b --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/DefaultFileDocumentContentProvider.java @@ -0,0 +1,14 @@ +package com.tyron.code.ui.editor.impl; + +import com.tyron.code.ui.editor.impl.text.rosemoe.ContentWrapper; +import com.tyron.editor.Content; +import com.tyron.fileeditor.api.impl.FileDocumentContentProvider; + +import org.apache.commons.vfs2.FileObject; + +public class DefaultFileDocumentContentProvider implements FileDocumentContentProvider { + @Override + public Content createContent(CharSequence text, FileObject file) { + return new ContentWrapper(text); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorManagerImpl.java b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorManagerImpl.java index 30b5d3c6c..ed734b864 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorManagerImpl.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorManagerImpl.java @@ -3,9 +3,12 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentManager; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.tyron.code.R; +import com.tyron.code.ui.main.MainViewModel; +import com.tyron.common.ApplicationProvider; import com.tyron.fileeditor.api.FileEditor; import com.tyron.fileeditor.api.FileEditorManager; import com.tyron.fileeditor.api.FileEditorProvider; @@ -25,9 +28,60 @@ public static synchronized FileEditorManager getInstance() { return sInstance; } + private MainViewModel mViewModel; + private FragmentManager mFragmentManager; + + FileEditorManagerImpl() { + + } + + public void attach(MainViewModel mainViewModel, FragmentManager fragmentManager) { + mViewModel = mainViewModel; + mFragmentManager = fragmentManager; + } + @Override public void openFile(@NonNull Context context, File file, Consumer callback) { - FileEditor[] fileEditors = openFile(file, true); + checkAttached(); + + FileEditor[] fileEditors = getFileEditors(context, file); + openChooser(context, fileEditors, callback); + } + + @NonNull + @Override + public FileEditor[] openFile(@NonNull Context context, @NonNull File file, boolean focus) { + checkAttached(); + + FileEditor[] editors = getFileEditors(context, file); + openChooser(context, editors, this::openFileEditor); + return editors; + } + + @Override + public FileEditor[] getFileEditors(Context context, @NonNull File file) { + FileEditor[] editors; + FileEditorProvider[] providers = FileEditorProviderManagerImpl.getInstance().getProviders(file); + editors = new FileEditor[providers.length]; + for (int i = 0; i < providers.length; i++) { + FileEditor editor = providers[i].createEditor(context, file); + editors[i] = editor; + } + return editors; + } + + @Override + public void openFileEditor(@NonNull FileEditor fileEditor) { + mViewModel.openFile(fileEditor); + } + + @Override + public void closeFile(@NonNull File file) { + mViewModel.removeFile(file); + } + + @Override + public void openChooser(Context context, FileEditor[] fileEditors, Consumer callback) { if (fileEditors.length == 0) { return; } @@ -46,25 +100,14 @@ public void openFile(@NonNull Context context, File file, Consumer c } } - public FileEditorManagerImpl() { - + public FragmentManager getFragmentManager() { + checkAttached(); + return this.mFragmentManager; } - @NonNull - @Override - public FileEditor[] openFile(@NonNull File file, boolean focus) { - FileEditor[] editors; - FileEditorProvider[] providers = FileEditorProviderManagerImpl.getInstance().getProviders(file); - editors = new FileEditor[providers.length]; - for (int i = 0; i < providers.length; i++) { - FileEditor editor = providers[i].createEditor(file); - editors[i] = editor; + private void checkAttached() { + if (mViewModel == null || mFragmentManager == null) { + throw new IllegalStateException("File editor manager is not yet attached to a ViewModel"); } - return editors; - } - - @Override - public void closeFile(@NonNull File file) { - } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorProviderManagerImpl.java b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorProviderManagerImpl.java index 053af5e2c..44de150d6 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorProviderManagerImpl.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorProviderManagerImpl.java @@ -4,6 +4,7 @@ import com.tyron.code.ui.editor.impl.image.ImageEditorProvider; import com.tyron.code.ui.editor.impl.text.rosemoe.RosemoeEditorProvider; +import com.tyron.code.ui.editor.impl.text.squircle.SquircleEditorProvider; import com.tyron.code.ui.editor.impl.xml.LayoutTextEditorProvider; import com.tyron.fileeditor.api.FileEditorProvider; import com.tyron.fileeditor.api.FileEditorProviderManager; @@ -34,7 +35,6 @@ public FileEditorProviderManagerImpl() { private void registerBuiltInProviders() { registerProvider(new RosemoeEditorProvider()); - registerProvider(new LayoutTextEditorProvider()); registerProvider(new ImageEditorProvider()); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditor.java b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditor.java index bc45120bb..30c131a06 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditor.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditor.java @@ -26,9 +26,14 @@ protected ImageEditorFragment createFragment(@NonNull File file) { return ImageEditorFragment.newInstance(file); } +// @Override +// public Fragment getFragment() { +// return mFragment; +// } + @Override - public Fragment getFragment() { - return mFragment; + public View getView() { + return null; } @Override diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorProvider.java b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorProvider.java index db00b3e37..bb5d443ec 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorProvider.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorProvider.java @@ -1,5 +1,7 @@ package com.tyron.code.ui.editor.impl.image; +import android.content.Context; + import androidx.annotation.NonNull; import com.tyron.fileeditor.api.FileEditor; @@ -32,7 +34,7 @@ public boolean accept(@NonNull File file) { @NonNull @Override - public FileEditor createEditor(@NonNull File file) { + public FileEditor createEditor(@NonNull Context context, @NonNull File file) { return new ImageEditor(file, this); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorFragment.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorFragment.java index 36d85e1b6..81f2c1abc 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorFragment.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorFragment.java @@ -1,136 +1,170 @@ package com.tyron.code.ui.editor.impl.text.rosemoe; + +import static io.github.rosemoe.sora2.text.EditorUtil.getDefaultColorScheme; + +import android.annotation.SuppressLint; import android.content.SharedPreferences; import android.graphics.Color; import android.os.Bundle; -import android.view.LayoutInflater; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.ForwardingListener; +import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; -import androidx.preference.PreferenceManager; +import androidx.lifecycle.ViewModelStoreOwner; -import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.snackbar.Snackbar; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import com.tyron.actions.ActionManager; import com.tyron.actions.ActionPlaces; import com.tyron.actions.CommonDataKeys; import com.tyron.actions.DataContext; import com.tyron.actions.util.DataContextUtils; -import com.tyron.builder.compiler.manifest.xml.XmlFormatPreferences; -import com.tyron.builder.compiler.manifest.xml.XmlFormatStyle; -import com.tyron.builder.compiler.manifest.xml.XmlPrettyPrinter; import com.tyron.builder.log.LogViewModel; import com.tyron.builder.model.DiagnosticWrapper; import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.JavaModule; +import com.tyron.builder.project.api.FileManager; import com.tyron.builder.project.api.Module; +import com.tyron.builder.project.listener.FileListener; import com.tyron.code.ApplicationLoader; import com.tyron.code.R; +import com.tyron.code.analyzer.BaseTextmateAnalyzer; +import com.tyron.code.language.LanguageManager; +import com.tyron.code.language.java.JavaLanguage; +import com.tyron.code.language.textmate.EmptyTextMateLanguage; import com.tyron.code.ui.editor.CodeAssistCompletionAdapter; import com.tyron.code.ui.editor.CodeAssistCompletionLayout; +import com.tyron.code.ui.editor.EditorViewModel; import com.tyron.code.ui.editor.Savable; -import com.tyron.code.ui.editor.language.LanguageManager; -import com.tyron.code.ui.editor.language.java.JavaLanguage; -import com.tyron.code.ui.editor.language.kotlin.KotlinLanguage; -import com.tyron.code.ui.editor.language.xml.LanguageXML; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.editor.scheme.CompiledEditorScheme; import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; -import com.tyron.code.ui.layoutEditor.LayoutEditorFragment; import com.tyron.code.ui.main.MainViewModel; import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.ui.settings.EditorSettingsFragment; +import com.tyron.code.ui.theme.ThemeRepository; +import com.tyron.code.util.CoordinatePopupMenu; +import com.tyron.code.util.PopupMenuHelper; import com.tyron.common.SharedPreferenceKeys; -import com.tyron.completion.index.CompilerService; -import com.tyron.completion.java.JavaCompilerProvider; -import com.tyron.completion.java.compiler.JavaCompilerService; -import com.tyron.completion.java.compiler.ParseTask; -import com.tyron.completion.java.compiler.Parser; -import com.tyron.completion.java.action.CommonJavaContextKeys; -import com.tyron.completion.java.action.FindCurrentPath; -import com.tyron.completion.java.provider.CompletionEngine; -import com.tyron.completion.java.rewrite.AddImport; -import com.tyron.completion.java.util.ActionUtil; +import com.tyron.common.logging.IdeLog; +import com.tyron.common.util.AndroidUtilities; import com.tyron.completion.java.util.DiagnosticUtil; -import com.tyron.completion.model.CompletionItem; -import com.tyron.completion.model.TextEdit; +import com.tyron.completion.java.util.JavaDataContextUtil; import com.tyron.completion.progress.ProgressManager; -import com.tyron.editor.Editor; +import com.tyron.editor.CharPosition; import org.apache.commons.io.FileUtils; -import org.openjdk.source.util.TreePath; +import org.apache.commons.vfs2.FileContent; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.commons.vfs2.VFS; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Map; import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.logging.Logger; +import io.github.rosemoe.sora.event.ClickEvent; import io.github.rosemoe.sora.event.ContentChangeEvent; -import io.github.rosemoe.sora.event.EventReceiver; import io.github.rosemoe.sora.event.LongPressEvent; -import io.github.rosemoe.sora.event.Unsubscribe; import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.langs.textmate.TextMateColorScheme; +import io.github.rosemoe.sora.text.Content; import io.github.rosemoe.sora.text.Cursor; -import io.github.rosemoe.sora.widget.CodeEditor; import io.github.rosemoe.sora.widget.DirectAccessProps; -import io.github.rosemoe.sora.widget.component.DefaultCompletionLayout; import io.github.rosemoe.sora.widget.component.EditorAutoCompletion; import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; -import io.github.rosemoe.sora.widget.schemes.SchemeDarcula; +import io.github.rosemoe.sora2.text.EditorUtil; @SuppressWarnings("FieldCanBeLocal") public class CodeEditorFragment extends Fragment implements Savable, - SharedPreferences.OnSharedPreferenceChangeListener { + SharedPreferences.OnSharedPreferenceChangeListener, FileListener, + ProjectManager.OnProjectOpenListener { + + private static final Logger LOG = IdeLog.getCurrentLogger(CodeEditorFragment.class); + + public static final String KEY_LINE = "line"; + public static final String KEY_COLUMN = "column"; + public static final String KEY_PATH = "path"; + + public static CodeEditorFragment newInstance(File file) { + CodeEditorFragment fragment = new CodeEditorFragment(); + Bundle args = new Bundle(); + args.putString(KEY_PATH, file.getAbsolutePath()); + fragment.setArguments(args); + return fragment; + } + + /** + * Creates a new instance of the editor with the the cursor positioned at the given + * line and column + * + * @param file The file to be r ead + * @param line The 0-based line + * @param column The 0-based column + * @return The editor instance + */ + public static CodeEditorFragment newInstance(File file, int line, int column) { + CodeEditorFragment fragment = new CodeEditorFragment(); + Bundle args = new Bundle(); + args.putInt(KEY_LINE, line); + args.putInt(KEY_COLUMN, column); + args.putString(KEY_PATH, file.getAbsolutePath()); + fragment.setArguments(args); + return fragment; + } + /** + * Keys for saved states + */ private static final String EDITOR_LEFT_LINE_KEY = "line"; private static final String EDITOR_LEFT_COLUMN_KEY = "column"; private static final String EDITOR_RIGHT_LINE_KEY = "rightLine"; private static final String EDITOR_RIGHT_COLUMN_KEY = "rightColumn"; private CodeEditorView mEditor; -// private CodeEditorEventListener mEditorEventListener; private Language mLanguage; private File mCurrentFile = new File(""); private MainViewModel mMainViewModel; - private SharedPreferences mPreferences; - private boolean mCanSave; + private Bundle mSavedInstanceState; - public static CodeEditorFragment newInstance(File file) { - CodeEditorFragment fragment = new CodeEditorFragment(); - Bundle args = new Bundle(); - args.putString("path", file.getAbsolutePath()); - fragment.setArguments(args); - return fragment; + private boolean mCanSave = false; + private boolean mReading = false; + + private View.OnTouchListener mDragToOpenListener; + + public CodeEditorFragment() { + super(R.layout.code_editor_fragment); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - mCurrentFile = new File(requireArguments().getString("path", "")); + mCurrentFile = new File(requireArguments().getString(KEY_PATH, "")); mMainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); + mSavedInstanceState = savedInstanceState; } @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); - if (mCanSave) { - if (ProjectManager.getInstance().getCurrentProject() != null) { - ProjectManager.getInstance().getCurrentProject() - .getModule(mCurrentFile) - .getFileManager() - .setSnapshotContent(mCurrentFile, mEditor.getText().toString()); - } - } - Cursor cursor = mEditor.getCursor(); outState.putInt(EDITOR_LEFT_LINE_KEY, cursor.getLeftLine()); outState.putInt(EDITOR_LEFT_COLUMN_KEY, cursor.getLeftColumn()); @@ -141,94 +175,213 @@ public void onSaveInstanceState(@NonNull Bundle outState) { @Override public void onResume() { super.onResume(); - - if (!CompletionEngine.isIndexing()) { - analyze(); - } - - if (BottomSheetBehavior.STATE_HIDDEN == mMainViewModel.getBottomSheetState().getValue()) { - mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED); - } - - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - if (currentProject != null) { - Module module = currentProject.getModule(mCurrentFile); - Optional fileContent = - module.getFileManager().getFileContent(mCurrentFile); - if (fileContent.isPresent()) { - CharSequence content = fileContent.get(); - if (!content.equals(mEditor.getText())) { - int line = mEditor.getCursor().getLeftLine(); - int column = mEditor.getCursor().getLeftColumn(); - - int targetX = mEditor.getOffsetX(); - int targetY = mEditor.getOffsetY(); - - mEditor.setText(content); - - mEditor.setSelection(line, column, false); - - mEditor.getScroller().startScroll(mEditor.getOffsetX(), mEditor.getOffsetY(), - targetX - mEditor.getOffsetX(), targetY - mEditor.getOffsetY(), 0); - } - } - } } - public void hideEditorWindows() { -// mEditor.getTextActionPresenter().onExit(); mEditor.hideAutoCompleteWindow(); } - @Override - public void onStart() { - super.onStart(); + /** + * Return the {@link EditorColorScheme} from the specified path. + * If the path is null or does not exist, the default color scheme is returned + * depending on the state of the device's theme + * + * @param path The file path to color scheme json file + * @return The color scheme instance + */ + @NonNull + private ListenableFuture getScheme(@Nullable String path) { + if (path != null && new File(path).exists()) { + return EditorSettingsFragment.getColorScheme(new File(path)); + } else { + return Futures.immediateFailedFuture(new Throwable()); + } } + @SuppressLint("RestrictedApi") @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); - - View root = inflater.inflate(R.layout.code_editor_fragment, container, false); - - mEditor = root.findViewById(R.id.code_editor); - configure(mEditor.getProps()); - mEditor.setEditorLanguage(mLanguage = LanguageManager.getInstance().get(mEditor, mCurrentFile)); - SchemeDarcula scheme = new SchemeDarcula(); - scheme.setColor(EditorColorScheme.HTML_TAG, 0xFFF0C56C); - scheme.setColor(EditorColorScheme.ATTRIBUTE_NAME, 0xff9876AA); - scheme.setColor(EditorColorScheme.AUTO_COMP_PANEL_BG, 0xff2b2b2b); - scheme.setColor(EditorColorScheme.AUTO_COMP_PANEL_CORNER, 0xff575757); - mEditor.setColorScheme(scheme); - mEditor.setTextSize(Integer.parseInt(mPreferences.getString(SharedPreferenceKeys.FONT_SIZE, "12"))); - mEditor.openFile(mCurrentFile); - mEditor.getComponent(EditorAutoCompletion.class).setLayout(new CodeAssistCompletionLayout()); - mEditor.setAutoCompletionItemAdapter(new CodeAssistCompletionAdapter()); -// mEditor.setText(CodeEditor.TextActionMode.POPUP_WINDOW); - mEditor.setTypefaceText(ResourcesCompat.getFont(requireContext(), - R.font.jetbrains_mono_regular)); - mEditor.setLigatureEnabled(true); - mEditor.setHighlightCurrentBlock(true); - mEditor.setEdgeEffectColor(Color.TRANSPARENT); - mEditor.setWordwrap(mPreferences.getBoolean(SharedPreferenceKeys.EDITOR_WORDWRAP, false)); - mEditor.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); - if (mPreferences.getBoolean(SharedPreferenceKeys.KEYBOARD_ENABLE_SUGGESTIONS, false)) { - mEditor.setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mCanSave = false; + + mEditor = view.findViewById(R.id.code_editor); + mEditor.setEditable(false); + configureEditor(mEditor); + + View topView = view.findViewById(R.id.top_view); + EditorViewModel viewModel = + new ViewModelProvider((ViewModelStoreOwner) requireParentFragment()).get( + EditorViewModel.class); + viewModel.getAnalyzeState().observe(getViewLifecycleOwner(), analyzing -> { + if (analyzing) { + topView.setVisibility(View.VISIBLE); + } else { + topView.setVisibility(View.GONE); + } + }); + mEditor.setViewModel(viewModel); + ApplicationLoader.getDefaultPreferences().registerOnSharedPreferenceChangeListener(this); + + postConfigureEditor(); + + String schemeValue = ApplicationLoader.getDefaultPreferences() + .getString(SharedPreferenceKeys.SCHEME, null); + if (schemeValue != null && + new File(schemeValue).exists() && + ThemeRepository.getColorScheme(schemeValue) != null) { + TextMateColorScheme scheme = ThemeRepository.getColorScheme(schemeValue); + if (scheme != null) { + mEditor.setColorScheme(scheme); + initializeLanguage(); + mEditor.openFile(mCurrentFile); + readOrWait(); + } } else { - mEditor.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + ListenableFuture scheme = getScheme(schemeValue); + Futures.addCallback(scheme, new FutureCallback() { + @Override + public void onSuccess(@Nullable TextMateColorScheme result) { + if (getContext() == null) { + return; + } + assert result != null; + ThemeRepository.putColorScheme(schemeValue, result); + mEditor.setColorScheme(result); + + initializeLanguage(); + mEditor.openFile(mCurrentFile); + readOrWait(); + } + + @Override + public void onFailure(@NonNull Throwable t) { + if (getContext() == null) { + return; + } + String key = EditorUtil.isDarkMode( + requireContext()) ? ThemeRepository.DEFAULT_NIGHT : + ThemeRepository.DEFAULT_LIGHT; + TextMateColorScheme scheme = ThemeRepository.getColorScheme(key); + if (scheme == null) { + scheme = getDefaultColorScheme(requireContext()); + ThemeRepository.putColorScheme(key, scheme); + } + mEditor.setColorScheme(scheme); + initializeLanguage(); + mEditor.openFile(mCurrentFile); + readOrWait(); + } + }, ContextCompat.getMainExecutor(requireContext())); + } + } + + private void initializeLanguage() { + mLanguage = LanguageManager.getInstance().get(mEditor, mCurrentFile); + if (mLanguage == null) { + mLanguage = new EmptyTextMateLanguage(); } - mPreferences.registerOnSharedPreferenceChangeListener(this); - return root; + mEditor.setEditorLanguage(mLanguage); } - private void configure(DirectAccessProps props) { + private void configureEditor(@NonNull CodeEditorView editor) { + // do not allow the user to edit, since at the time this is called + // the contents may still be loading. + editor.setEditable(false); + editor.setColorScheme(new CompiledEditorScheme(requireContext())); + editor.setBackgroundAnalysisEnabled(false); + editor.setTypefaceText( + ResourcesCompat.getFont(requireContext(), R.font.jetbrains_mono_regular)); + editor.getComponent(EditorAutoCompletion.class).setLayout(new CodeAssistCompletionLayout()); + editor.setLigatureEnabled(true); + editor.setHighlightCurrentBlock(true); + editor.setEdgeEffectColor(Color.TRANSPARENT); + editor.openFile(mCurrentFile); + editor.setAutoCompletionItemAdapter(new CodeAssistCompletionAdapter()); + editor.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); + editor.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | + EditorInfo.TYPE_CLASS_TEXT | + EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | + EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + + SharedPreferences pref = ApplicationLoader.getDefaultPreferences(); + editor.setWordwrap(pref.getBoolean(SharedPreferenceKeys.EDITOR_WORDWRAP, false)); + editor.setTextSize(Integer.parseInt(pref.getString(SharedPreferenceKeys.FONT_SIZE, "12"))); + + DirectAccessProps props = editor.getProps(); props.overScrollEnabled = false; props.allowFullscreen = false; - props.deleteEmptyLineFast = false; + props.deleteEmptyLineFast = pref.getBoolean(SharedPreferenceKeys.DELETE_WHITESPACES, false); } + private void postConfigureEditor() { + // noinspection ClickableViewAccessibility + mEditor.setOnTouchListener((view12, motionEvent) -> { + if (mDragToOpenListener instanceof ForwardingListener) { + PopupMenuHelper.setForwarding((ForwardingListener) mDragToOpenListener); + // noinspection RestrictedApi + mDragToOpenListener.onTouch(view12, motionEvent); + } + return false; + }); + mEditor.subscribeEvent(LongPressEvent.class, (event, unsubscribe) -> { + event.intercept(); + + updateFile(mEditor.getText()); + Cursor cursor = mEditor.getCursor(); + if (cursor.isSelected()) { + int index = mEditor.getCharIndex(event.getLine(), event.getColumn()); + int cursorLeft = cursor.getLeft(); + int cursorRight = cursor.getRight(); + char c = mEditor.getText().charAt(index); + if (Character.isWhitespace(c)) { + mEditor.setSelection(event.getLine(), event.getColumn()); + } else if (index < cursorLeft || index > cursorRight) { + EditorUtil.selectWord(mEditor, event.getLine(), event.getColumn()); + } + } else { + char c = mEditor.getText().charAt(event.getIndex()); + if (!Character.isWhitespace(c)) { + EditorUtil.selectWord(mEditor, event.getLine(), event.getColumn()); + } else { + mEditor.setSelection(event.getLine(), event.getColumn()); + } + } + + ProgressManager.getInstance().runLater(() -> { + showPopupMenu(event); + }); + }); + mEditor.subscribeEvent(ClickEvent.class, (event, unsubscribe) -> { + Cursor cursor = mEditor.getCursor(); + if (mEditor.getCursor().isSelected()) { + int index = mEditor.getCharIndex(event.getLine(), event.getColumn()); + int cursorLeft = cursor.getLeft(); + int cursorRight = cursor.getRight(); + if (!EditorUtil.isWhitespace(mEditor.getText().charAt(index) + "") && + index >= cursorLeft && + index <= cursorRight) { + mEditor.showSoftInput(); + event.intercept(); + } + } + }); + mEditor.subscribeEvent(ContentChangeEvent.class, (event, unsubscribe) -> { + if (event.getAction() == ContentChangeEvent.ACTION_SET_NEW_TEXT) { + return; + } + updateFile(event.getEditor().getText()); + }); + + LogViewModel logViewModel = + new ViewModelProvider(requireActivity()).get(LogViewModel.class); + mEditor.setDiagnosticsListener(diagnostics -> { + for (DiagnosticWrapper diagnostic : diagnostics) { + DiagnosticUtil.setLineAndColumn(diagnostic, mEditor); + } + ProgressManager.getInstance() + .runLater(() -> logViewModel.updateLogs(LogViewModel.DEBUG, diagnostics)); + }); + } @Override public void onSharedPreferenceChanged(SharedPreferences pref, String key) { @@ -239,273 +392,301 @@ public void onSharedPreferenceChanged(SharedPreferences pref, String key) { case SharedPreferenceKeys.FONT_SIZE: mEditor.setTextSize(Integer.parseInt(pref.getString(key, "14"))); break; - case SharedPreferenceKeys.KEYBOARD_ENABLE_SUGGESTIONS: - if (pref.getBoolean(key, false)) { - mEditor.setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); - } else { - mEditor.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); - } - break; case SharedPreferenceKeys.EDITOR_WORDWRAP: mEditor.setWordwrap(pref.getBoolean(key, false)); break; + case SharedPreferenceKeys.DELETE_WHITESPACES: + mEditor.getProps().deleteEmptyLineFast = + pref.getBoolean(SharedPreferenceKeys.DELETE_WHITESPACES, false); + break; + case SharedPreferenceKeys.SCHEME: + ListenableFuture scheme = + getScheme(pref.getString(SharedPreferenceKeys.SCHEME, null)); + Futures.addCallback(scheme, new FutureCallback() { + @Override + public void onSuccess(@Nullable TextMateColorScheme result) { + if (getContext() == null) { + return; + } + assert result != null; + mEditor.setColorScheme(result); + if (mLanguage.getAnalyzeManager() instanceof BaseTextmateAnalyzer) { + ((BaseTextmateAnalyzer) mLanguage.getAnalyzeManager()).updateTheme( + result.getRawTheme()); + mLanguage.getAnalyzeManager().rerun(); + } + } + + @Override + public void onFailure(@NonNull Throwable t) { + if (getContext() == null) { + return; + } + mEditor.setColorScheme(getDefaultColorScheme(requireContext())); + mLanguage.getAnalyzeManager().rerun(); + } + }, ContextCompat.getMainExecutor(requireContext())); + break; } } + /** + * Show the popup menu with the actions api + */ + private void showPopupMenu(LongPressEvent event) { + MotionEvent e = event.getCausingEvent(); + CoordinatePopupMenu popupMenu = + new CoordinatePopupMenu(requireContext(), mEditor, Gravity.BOTTOM); + DataContext dataContext = createDataContext(); + ActionManager.getInstance() + .fillMenu(dataContext, popupMenu.getMenu(), ActionPlaces.EDITOR, true, false); + popupMenu.show((int) e.getX(), ((int) e.getY()) - AndroidUtilities.dp(24)); + + // we don't want to enable the drag to open listener right away, + // this may cause the buttons to be clicked right away + // so wait for a few ms + ProgressManager.getInstance().runLater(() -> { + popupMenu.setOnDismissListener(d -> mDragToOpenListener = null); + mDragToOpenListener = popupMenu.getDragToOpenListener(); + }, 300); + } + @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + public void onDestroyView() { + super.onDestroyView(); - Module module; Project currentProject = ProjectManager.getInstance().getCurrentProject(); if (currentProject != null) { - module = currentProject.getModule(mCurrentFile); - } else { - module = null; - } - if (mCurrentFile.exists()) { - String text; - try { - text = FileUtils.readFileToString(mCurrentFile, StandardCharsets.UTF_8); - mCanSave = true; - } catch (IOException e) { - text = "File does not exist: " + e.getMessage(); - mCanSave = false; - } + Module module = currentProject.getModule(mCurrentFile); if (module != null) { - module.getFileManager().openFileForSnapshot(mCurrentFile, text); + module.getFileManager().removeSnapshotListener(this); } - mEditor.setText(text); - } else { - mCanSave = false; } + ProjectManager.getInstance().removeOnProjectOpenListener(this); + } -// mEditorEventListener = new CodeEditorEventListener(module, mCurrentFile); -// mEditor.setEventListener(mEditorEventListener); -// mEditor.setOnCompletionItemSelectedListener((window, item) -> { -// Cursor cursor = mEditor.getCursor(); -// if (!cursor.isSelected()) { -// window.setCancelShowUp(true); -// -// int length = window.getLastPrefix().length(); -// if (mLanguage instanceof JavaLanguage || mLanguage instanceof KotlinLanguage) { -// if (window.getLastPrefix().contains(".")) { -// length -= window.getLastPrefix().lastIndexOf(".") + 1; -// } -// } - -// window.setSelectedItem(item.commit); -// cursor.onCommitMultilineText(item.commit); -// -// if (item.commit != null && item.cursorOffset != item.commit.length()) { -// int delta = (item.commit.length() - item.cursorOffset); -// int newSel = Math.max(mEditor.getCursor().getLeft() - delta, 0); -// CharPosition charPosition = -// mEditor.getCursor().getIndexer().getCharPosition(newSel); -// mEditor.setSelection(charPosition.line, charPosition.column); -// } -// -// if (item.item == null) { -// return; -// } -// -// if (item.item.additionalTextEdits != null) { -// for (TextEdit edit : item.item.additionalTextEdits) { -// window.applyTextEdit(edit); -// } -// } -// -// if (item.item.action == CompletionItem.Kind.IMPORT) { -// if (module instanceof JavaModule) { -// Parser parser = Parser.parseFile(currentProject, -// mEditor.getCurrentFile().toPath()); -// ParseTask task = new ParseTask(parser.task, parser.root); -// -// boolean samePackage = false; -// String packageName = task.root.getPackageName() == null ? "" : -// task.root.getPackageName().toString(); -// //it's either in the same class or it's already imported -// if (!item.item.data.contains(".") || packageName.equals(item.item.data.substring(0, item.item.data.lastIndexOf(".")))) { -// samePackage = true; -// } -// -// if (!samePackage && !ActionUtil.hasImport(task.root, item.item.data)) { -// AddImport imp = new AddImport(new File(""), item.item.data); -// Map edits = imp.getText(task); -// TextEdit edit = edits.values().iterator().next(); -// window.applyTextEdit(edit); -// } -// } -// } -// window.setCancelShowUp(false); -// } -// mEditor.postHideCompletionWindow(); -// }); - - mEditor.setOnCreateContextMenuListener((menu, view1, contextMenuInfo) -> { - menu.clear(); - - DataContext dataContext = DataContextUtils.getDataContext(view1); - dataContext.putData(CommonDataKeys.PROJECT, currentProject); - dataContext.putData(CommonDataKeys.FILE_EDITOR_KEY, - mMainViewModel.getCurrentFileEditor()); - dataContext.putData(CommonDataKeys.FILE, mCurrentFile); - dataContext.putData(CommonDataKeys.EDITOR, mEditor); - - if (currentProject != null) { - Module currentModule = currentProject.getModule(mCurrentFile); - if ((mLanguage instanceof JavaLanguage) && (currentModule instanceof JavaModule)) { - JavaCompilerProvider service = - CompilerService.getInstance().getIndex(JavaCompilerProvider.KEY); - JavaCompilerService compiler = service.getCompiler(currentProject, - (JavaModule) currentModule); - - compiler.compile(mCurrentFile.toPath()).run(task -> { - if (task != null) { - FindCurrentPath findCurrentPath = new FindCurrentPath(task.task); - TreePath currentPath = findCurrentPath.scan(task.root(), - (long) mEditor.getCursor().getLeft()); - dataContext.putData(CommonJavaContextKeys.CURRENT_PATH, currentPath); - } - }); - dataContext.putData(CommonJavaContextKeys.COMPILER, compiler); - } - } + @Override + public void onDestroy() { + super.onDestroy(); + if (ProjectManager.getInstance().getCurrentProject() != null && mCanSave) { + ProgressManager.getInstance().runNonCancelableAsync( + () -> ProjectManager.getInstance().getCurrentProject().getModule(mCurrentFile) + .getFileManager().closeFileForSnapshot(mCurrentFile)); + } + ApplicationLoader.getDefaultPreferences().unregisterOnSharedPreferenceChangeListener(this); + } - DiagnosticWrapper diagnosticWrapper = - DiagnosticUtil.getDiagnosticWrapper(mEditor.getDiagnostics(), - mEditor.getCursor().getLeft()); - if (diagnosticWrapper == null && mLanguage instanceof LanguageXML) { - diagnosticWrapper = - DiagnosticUtil.getXmlDiagnosticWrapper(mEditor.getDiagnostics(), - mEditor.getCursor().getLeftLine()); - } - dataContext.putData(CommonDataKeys.DIAGNOSTIC, diagnosticWrapper); + @Override + public void onPause() { + super.onPause(); - ActionManager.getInstance().fillMenu(dataContext, menu, ActionPlaces.EDITOR, true, - false); - }); - mEditor.subscribeEvent(LongPressEvent.class, (event, unsubscribe) -> { - MotionEvent e = event.getCausingEvent(); - // wait for the cursor to move - ProgressManager.getInstance().runLater(() -> { - event.getEditor().showContextMenu(e.getX(), e.getY()); - }); - }); - mEditor.subscribeEvent(ContentChangeEvent.class, (event, unsubscibe) -> { - updateFile(event.getEditor().getText()); - }); + hideEditorWindows(); - LogViewModel logViewModel = - new ViewModelProvider(requireActivity()).get(LogViewModel.class); + save(true); + } - mEditor.setDiagnosticsListener(diagnostics -> { - ProgressManager.getInstance().runLater(() -> { - logViewModel.updateLogs(LogViewModel.DEBUG, diagnostics); - }); - }); + @Override + public void onLowMemory() { + super.onLowMemory(); - getChildFragmentManager().setFragmentResultListener(LayoutEditorFragment.KEY_SAVE, - getViewLifecycleOwner(), ((requestKey, result) -> { - String xml = result.getString("text", mEditor.getText().toString()); - xml = XmlPrettyPrinter.prettyPrint(xml, XmlFormatPreferences.defaults(), - XmlFormatStyle.LAYOUT, "\n"); - mEditor.setText(xml); - })); + mEditor.setBackgroundAnalysisEnabled(false); + } - if (savedInstanceState != null) { - restoreState(savedInstanceState); + @Override + public void onSnapshotChanged(File file, CharSequence contents) { + if (mCurrentFile.equals(file)) { + if (mEditor != null) { + if (!mEditor.getText().toString().contentEquals(contents)) { + Cursor cursor = mEditor.getCursor(); + int left = cursor.getLeft(); + mEditor.setText(contents); + + if (left > contents.length()) { + left = contents.length(); + } + CharPosition position = mEditor.getCharPosition(left); + mEditor.setSelection(position.getLine(), position.getColumn()); + } + } } } - private void restoreState(@NonNull Bundle savedInstanceState) { - int leftLine = savedInstanceState.getInt(EDITOR_LEFT_LINE_KEY, 0); - int leftColumn = savedInstanceState.getInt(EDITOR_LEFT_COLUMN_KEY, 0); - int rightLine = savedInstanceState.getInt(EDITOR_RIGHT_LINE_KEY, 0); - int rightColumn = savedInstanceState.getInt(EDITOR_RIGHT_COLUMN_KEY, 0); + @Override + public boolean canSave() { + return mCanSave && !mReading; + } - if (leftLine != rightLine && leftColumn != rightColumn) { - mEditor.setSelectionRegion(leftLine, leftColumn, rightLine, rightColumn, true); + @Override + public void save(boolean toDisk) { + if (!mCanSave || mReading) { + return; + } + + // don't save if the file has been deleted externally but its still opened in the editor, + if (!mCurrentFile.exists()) { + return; + } + + if (ProjectManager.getInstance().getCurrentProject() != null && !toDisk) { + ProjectManager.getInstance().getCurrentProject().getModule(mCurrentFile) + .getFileManager() + .setSnapshotContent(mCurrentFile, mEditor.getText().toString(), false); } else { - mEditor.setSelection(leftLine, leftColumn); + ProgressManager.getInstance().runNonCancelableAsync(() -> { + try { + FileUtils.writeStringToFile(mCurrentFile, mEditor.getText().toString(), + StandardCharsets.UTF_8); + } catch (IOException e) { + LOG.severe("Unable to save file: " + + mCurrentFile.getAbsolutePath() + + "\n" + + "Reason: " + + e.getMessage()); + } + }); } } @Override - public void onDestroyView() { - super.onDestroyView(); - -// mEditorEventListener = null; - mEditor.setEditorLanguage(null); + public void onProjectOpen(Project project) { + ProgressManager.getInstance().runLater(() -> readFile(project, mSavedInstanceState)); } - @Override - public void onDestroy() { - super.onDestroy(); + /** + * Read the file immediately if there is a project open. If not, wait for the project + * to be opened first. + */ + private void readOrWait() { if (ProjectManager.getInstance().getCurrentProject() != null) { - ProjectManager.getInstance().getCurrentProject().getModule(mCurrentFile).getFileManager().closeFileForSnapshot(mCurrentFile); + readFile(ProjectManager.getInstance().getCurrentProject(), mSavedInstanceState); + } else { + ProjectManager.getInstance().addOnProjectOpenListener(this); } - mPreferences.unregisterOnSharedPreferenceChangeListener(this); } - @Override - public void onPause() { - super.onPause(); + private ListenableFuture readFile() { + return Futures.submitAsync(() -> { + FileSystemManager manager = VFS.getManager(); + FileObject fileObject = manager.resolveFile(mCurrentFile.toURI()); + FileContent content = fileObject.getContent(); + return Futures.immediateFuture(content.getString(StandardCharsets.UTF_8)); + }, Executors.newSingleThreadExecutor()); + } - hideEditorWindows(); + private void readFile(@NonNull Project currentProject, @Nullable Bundle savedInstanceState) { + mCanSave = false; + Module module = currentProject.getModule(mCurrentFile); + FileManager fileManager = module.getFileManager(); + fileManager.addSnapshotListener(this); - if (mCanSave) { - if (ProjectManager.getInstance().getCurrentProject() != null) { - ProjectManager.getInstance().getCurrentProject().getModule(mCurrentFile).getFileManager().setSnapshotContent(mCurrentFile, mEditor.getText().toString()); - } else { - try { - FileUtils.writeStringToFile(mCurrentFile, mEditor.getText().toString(), - StandardCharsets.UTF_8); - } catch (IOException e) { - // ignored + + // the file is already opened, so no need to load it. + if (fileManager.isOpened(mCurrentFile)) { + Optional contents = fileManager.getFileContent(mCurrentFile); + if (contents.isPresent()) { + mEditor.setText(contents.get()); + return; + } + } + + mReading = true; + mEditor.setBackgroundAnalysisEnabled(false); + ListenableFuture future = readFile(); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable String result) { + mReading = false; + if (getContext() == null) { + mCanSave = false; + return; } + if (mLanguage == null) { + return; + } + mCanSave = true; + mEditor.setBackgroundAnalysisEnabled(true); + mEditor.setEditable(true); + fileManager.openFileForSnapshot(mCurrentFile, result); + + Bundle bundle = new Bundle(); + bundle.putBoolean("loaded", true); + bundle.putBoolean("bg", true); + mEditor.setText(result, bundle); + + if (savedInstanceState != null) { + restoreState(savedInstanceState); + } else { + int line = requireArguments().getInt(KEY_LINE, 0); + int column = requireArguments().getInt(KEY_COLUMN, 0); + Content text = mEditor.getText(); + if (line < text.getLineCount() && column < text.getColumnCount(line)) { + setCursorPosition(line, column); + } + } + checkCanSave(); + } + + @Override + public void onFailure(@NonNull Throwable t) { + mCanSave = false; + mReading = false; + if (getContext() != null) { + checkCanSave(); + } + + LOG.severe("Unable to read current file: " + + mCurrentFile + + "\n" + + "Reason: " + + t.getMessage()); } + }, ContextCompat.getMainExecutor(requireContext())); + } + + private void checkCanSave() { + if (!mCanSave) { + Snackbar snackbar = + Snackbar.make(mEditor, R.string.editor_error_file, Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.menu_close, v -> FileEditorManagerImpl.getInstance() + .closeFile(mCurrentFile)); + ViewGroup snackbarView = (ViewGroup) snackbar.getView(); + AndroidUtilities.setMargins(snackbarView, 0, 0, 0, 50); + snackbar.show(); } } - private void updateFile(CharSequence contents) { - Project project = ProjectManager.getInstance().getCurrentProject(); - if (project == null) { + private void restoreState(@NonNull Bundle savedInstanceState) { + int leftLine = savedInstanceState.getInt(EDITOR_LEFT_LINE_KEY, 0); + int leftColumn = savedInstanceState.getInt(EDITOR_LEFT_COLUMN_KEY, 0); + int rightLine = savedInstanceState.getInt(EDITOR_RIGHT_LINE_KEY, 0); + int rightColumn = savedInstanceState.getInt(EDITOR_RIGHT_COLUMN_KEY, 0); + + Content text = mEditor.getText(); + if (leftLine > text.getLineCount() || rightLine > text.getLineCount()) { return; } - Module module = project.getModule(mCurrentFile); - if (module != null) { - module.getFileManager().setSnapshotContent(mCurrentFile, contents.toString()); + if (leftLine != rightLine && leftColumn != rightColumn) { + mEditor.setSelectionRegion(leftLine, leftColumn, rightLine, rightColumn, true); + } else { + mEditor.setSelection(leftLine, leftColumn); } } - @Override - public void save() { - if (!mCanSave) { + private void updateFile(CharSequence contents) { + Project project = ProjectManager.getInstance().getCurrentProject(); + if (project == null) { return; } - if (mCurrentFile.exists()) { - ProgressManager.getInstance().runNonCancelableAsync(() -> { - String oldContents = ""; - try { - oldContents = FileUtils.readFileToString(mCurrentFile, StandardCharsets.UTF_8); - } catch (IOException e) { - e.printStackTrace(); - } - if (oldContents.equals(mEditor.getText().toString())) { - return; - } - - try { - FileUtils.writeStringToFile(mCurrentFile, mEditor.getText().toString(), StandardCharsets.UTF_8); - } catch (IOException e) { - // ignored - } - }); + Module module = project.getModule(mCurrentFile); + if (module != null) { + if (!module.getFileManager().isOpened(mCurrentFile)) { + return; + } + module.getFileManager().setSnapshotContent(mCurrentFile, contents.toString(), this); } } - public Editor getEditor() { + public CodeEditorView getEditor() { return mEditor; } @@ -555,9 +736,7 @@ public void performShortcut(ShortcutItem item) { return; } for (ShortcutAction action : item.actions) { - if (action.isApplicable(item.kind)) { - // action.apply(mEditor, item); - } + action.apply(mEditor, item); } } @@ -580,71 +759,30 @@ public void format() { * Notifies the editor to analyze and highlight the current text */ public void analyze() { - if (mEditor != null) { + if (mEditor != null && !mReading) { mEditor.rerunAnalysis(); } } - @Override - public void onLowMemory() { - super.onLowMemory(); + /** + * Create the data context specific to this fragment for use with the actions API. + * + * @return the data context. + */ + private DataContext createDataContext() { + Project currentProject = ProjectManager.getInstance().getCurrentProject(); - mEditor.setBackgroundAnalysisEnabled(false); - } + DataContext dataContext = DataContextUtils.getDataContext(mEditor); + dataContext.putData(CommonDataKeys.PROJECT, currentProject); + dataContext.putData(CommonDataKeys.ACTIVITY, requireActivity()); + dataContext.putData(CommonDataKeys.FILE_EDITOR_KEY, mMainViewModel.getCurrentFileEditor()); + dataContext.putData(CommonDataKeys.FILE, mCurrentFile); + dataContext.putData(CommonDataKeys.EDITOR, mEditor); -// private static final class CodeEditorEventListener implements EditorEventListener { -// -// private final Module mModule; -// private final File mCurrentFile; -// -// public CodeEditorEventListener(Module module, File currentFile) { -// mModule = module; -// mCurrentFile = currentFile; -// } -// -// @Override -// public boolean onRequestFormat(@NonNull CodeEditor editor) { -// return false; -// } -// -// @Override -// public boolean onFormatFail(@NonNull CodeEditor editor, Throwable cause) { -// ApplicationLoader.showToast("Unable to format: " + cause.getMessage()); -// return false; -// } -// -// @Override -// public void onFormatSucceed(@NonNull CodeEditor editor) { -// -// } -// -// @Override -// public void onNewTextSet(@NonNull CodeEditor editor) { -// updateFile(editor.getText().toString()); -// } -// -// @Override -// public void afterDelete(@NonNull CodeEditor editor, @NonNull CharSequence content, -// int startLine, int startColumn, int endLine, int endColumn, -// CharSequence deletedContent) { -// updateFile(content); -// } -// -// @Override -// public void afterInsert(@NonNull CodeEditor editor, @NonNull CharSequence content, -// int startLine, int startColumn, int endLine, int endColumn, -// CharSequence insertedContent) { -// updateFile(content); -// } -// -// @Override -// public void beforeReplace(@NonNull CodeEditor editor, @NonNull CharSequence content) { -// updateFile(content); -// } -// -// @Override -// public void onSelectionChanged(@NonNull CodeEditor editor, @NonNull Cursor cursor) { -// -// } -// } + if (currentProject != null && mLanguage instanceof JavaLanguage) { + JavaDataContextUtil.addEditorKeys(dataContext, currentProject, mCurrentFile, + mEditor.getCursor().getLeft()); + } + return dataContext; + } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorView.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorView.java index 0d6f58ff8..d033c5cd5 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorView.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorView.java @@ -2,85 +2,140 @@ import android.content.Context; import android.graphics.Canvas; +import android.graphics.Paint; import android.util.AttributeSet; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.common.collect.ImmutableSet; import com.tyron.actions.DataContext; import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.code.ui.editor.CodeAssistCompletionAdapter; +import com.tyron.builder.project.Project; +import com.tyron.code.language.xml.LanguageXML; import com.tyron.code.ui.editor.CodeAssistCompletionWindow; +import com.tyron.code.ui.editor.EditorViewModel; import com.tyron.code.ui.editor.NoOpTextActionWindow; -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; -import com.tyron.code.ui.editor.language.DiagnosticAnalyzeManager; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.completion.xml.model.XmlCompletionType; +import com.tyron.completion.xml.util.XmlUtils; import com.tyron.editor.Caret; import com.tyron.editor.CharPosition; import com.tyron.editor.Content; import com.tyron.editor.Editor; +import com.tyron.xml.completion.util.DOMUtils; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMNode; +import org.eclipse.lemminx.dom.DOMParser; +import org.jetbrains.kotlin.com.intellij.util.ReflectionUtil; import java.io.File; -import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.List; +import java.util.Set; import java.util.function.Consumer; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.text.Cursor; +import io.github.rosemoe.sora.text.TextUtils; import io.github.rosemoe.sora.widget.CodeEditor; +import io.github.rosemoe.sora.widget.SymbolPairMatch; import io.github.rosemoe.sora.widget.component.EditorAutoCompletion; import io.github.rosemoe.sora.widget.component.EditorTextActionWindow; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; +import io.github.rosemoe.sora2.text.EditorUtil; public class CodeEditorView extends CodeEditor implements Editor { + private final Set IGNORED_PAIR_ENDS = ImmutableSet.builder() + .add(')') + .add(']') + .add('"') + .add('>') + .add('\'') + .add(';') + .build(); + private boolean mIsBackgroundAnalysisEnabled; private List mDiagnostics; private Consumer> mDiagnosticsListener; private File mCurrentFile; + private EditorViewModel mViewModel; + + private final Paint mDiagnosticPaint; + private CodeAssistCompletionWindow mCompletionWindow; public CodeEditorView(Context context) { - super(DataContext.wrap(context)); + this(DataContext.wrap(context), null); } public CodeEditorView(Context context, AttributeSet attrs) { - super(DataContext.wrap(context), attrs); - - init(); + this(DataContext.wrap(context), attrs, 0); } public CodeEditorView(Context context, AttributeSet attrs, int defStyleAttr) { - super(DataContext.wrap(context), attrs, defStyleAttr); - - init(); + this(DataContext.wrap(context), attrs, defStyleAttr, 0); } public CodeEditorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(DataContext.wrap(context), attrs, defStyleAttr, defStyleRes); + mDiagnosticPaint = new Paint(); + mDiagnosticPaint.setStrokeWidth(getDpUnit() * 2); + init(); } + @Nullable + @Override + public Project getProject() { + return ProjectManager.getInstance().getCurrentProject(); + } + + @Override + public void setEditorLanguage(@Nullable Language lang) { + super.setEditorLanguage(lang); + + if (lang != null) { + // languages should have an option to declare their own tab width + try { + Class extends Language> aClass = lang.getClass(); + Method method = ReflectionUtil.getDeclaredMethod(aClass, "getTabWidth"); + if (method != null) { + Object invoke = method.invoke(getEditorLanguage()); + if (invoke instanceof Integer) { + setTabWidth((Integer) invoke); + } + } + } catch (Throwable e) { + // use default + } + } + } + private void init() { - CodeAssistCompletionWindow window = - new CodeAssistCompletionWindow(this); - window.setAdapter(new CodeAssistCompletionAdapter()); - replaceComponent(EditorAutoCompletion.class, window); + setColorScheme(EditorUtil.getDefaultColorScheme(getContext())); replaceComponent(EditorTextActionWindow.class, new NoOpTextActionWindow(this)); } @Override - public List getDiagnostics() { - return mDiagnostics; + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + hideEditorWindows(); } + @Override + public void setColorScheme(@NonNull EditorColorScheme colors) { + super.setColorScheme(colors); + } + + @Override public void setDiagnostics(List diagnostics) { mDiagnostics = diagnostics; - - AnalyzeManager manager = getEditorLanguage().getAnalyzeManager(); - if (manager instanceof DiagnosticAnalyzeManager) { - ((DiagnosticAnalyzeManager>) manager).setDiagnostics(this, mDiagnostics); - ((DiagnosticAnalyzeManager>) manager).rerunWithoutBg(); - } - if (mDiagnosticsListener != null) { - mDiagnosticsListener.accept(mDiagnostics); - } } public void setDiagnosticsListener(Consumer> listener) { @@ -109,11 +164,137 @@ public int getCharIndex(int line, int column) { return getText().getCharIndex(line, column); } + @Override + public boolean useTab() { + //noinspection ConstantConditions, editor language can be null + if (getEditorLanguage() == null) { + // enabled by default + return true; + } + + return getEditorLanguage().useTab(); + } + + @Override + public int getTabCount() { + return getTabWidth(); + } + @Override public void insert(int line, int column, String string) { getText().insert(line, column, string); } + @Override + public void commitText(CharSequence text) { + super.commitText(text); + } + + @Override + public void commitText(CharSequence text, boolean applyAutoIndent) { + if (text.length() == 1) { + char currentChar = getText().charAt(getCursor().getLeft()); + char c = text.charAt(0); + if (IGNORED_PAIR_ENDS.contains(c) && c == currentChar) { + // ignored pair end, just move the cursor over the character + setSelection(getCursor().getLeftLine(), getCursor().getLeftColumn() + 1); + return; + } + } + super.commitText(text, applyAutoIndent); + + if (text.length() == 1) { + char c = text.charAt(0); + handleAutoInsert(c); + } + } + + private void handleAutoInsert(char c) { + if (getEditorLanguage() instanceof LanguageXML) { + try { + if (c != '>' && c != '/') { + return; + } + boolean full = c == '>'; + + DOMDocument document = DOMParser.getInstance().parse(getText().toString(), "", null); + DOMNode nodeAt = document.findNodeAt(getCursor().getLeft()); + if (!DOMUtils.isClosed(nodeAt) && nodeAt.getNodeName() != null) { + if (XmlUtils.getCompletionType(document, getCursor().getLeft()) == + XmlCompletionType.ATTRIBUTE_VALUE) { + return; + } + String insertText = full ? "" + nodeAt.getNodeName() + ">" : ">"; + commitText(insertText); + setSelection(getCursor().getLeftLine(), + getCursor().getLeftColumn() - (full ? insertText.length() : 0)); + } + } catch (Exception e) { + // ignored, just dont auto insert + } + } + } + + @Override + public void deleteText() { + Cursor cursor = getCursor(); + if (!cursor.isSelected()) { + io.github.rosemoe.sora.text.Content text = getText(); + int startIndex = cursor.getLeft(); + if (startIndex - 1 >= 0) { + char deleteChar = text.charAt(startIndex - 1); + char afterChar = text.charAt(startIndex); + SymbolPairMatch.Replacement replacement = null; + + SymbolPairMatch pairs = getEditorLanguage().getSymbolPairs(); + if (pairs != null) { + replacement = pairs.getCompletion(deleteChar); + } + if (replacement != null) { + if (("" + deleteChar + afterChar + "").equals(replacement.text)) { + text.delete(startIndex - 1, startIndex + 1); + return; + } + } + } + } + super.deleteText(); + } + + @Override + public void insertMultilineString(int line, int column, String string) { + String currentLine = getText().getLineString(line); + + String[] lines = string.split("\\n"); + if (lines.length == 0) { + return; + } + int count = TextUtils.countLeadingSpaceCount(currentLine, getTabWidth()); + for (int i = 0; i < lines.length; i++) { + String trimmed = lines[i].trim(); + + int advance = EditorUtil.getFormatIndent(getEditorLanguage(), trimmed); + + if (advance < 0) { + count += advance; + } + + if (i != 0) { + String indent = TextUtils.createIndent(count, getTabWidth(), useTab()); + trimmed = indent + trimmed; + } + + lines[i] = trimmed; + + if (advance > 0) { + count += advance; + } + } + + String textToInsert = String.join("\n", lines); + getText().insert(line, column, textToInsert); + } + @Override public void delete(int startLine, int startColumn, int endLine, int endColumn) { getText().delete(startLine, startColumn, endLine, endColumn); @@ -139,6 +320,16 @@ public void setSelectionRegion(int lineLeft, int columnLeft, int lineRight, int CodeEditorView.super.setSelectionRegion(lineLeft, columnLeft, lineRight, columnRight); } + @Override + public void setSelectionRegion(int startIndex, int endIndex) { + CharPosition start = getCharPosition(startIndex); + CharPosition end = getCharPosition(endIndex); + CodeEditorView.super.setSelectionRegion(start.getLine(), + start.getColumn(), + end.getLine(), + end.getColumn()); + } + @Override public void beginBatchEdit() { getText().beginBatchEdit(); @@ -155,9 +346,7 @@ public synchronized boolean formatCodeAsync() { } @Override - public synchronized boolean formatCodeAsync(int start, int end) { -// CodeEditorView.super.formatCodeAsync(); -// return CodeEditorView.super.formatCodeAsync(start, end); + public boolean formatCodeAsync(int startIndex, int endIndex) { return false; } @@ -168,7 +357,7 @@ public Caret getCaret() { @Override public Content getContent() { - return new ContentWrapper(CodeEditorView.this.getText()); + return (Content) getText(); } /** @@ -178,4 +367,29 @@ public Content getContent() { public void setBackgroundAnalysisEnabled(boolean enabled) { mIsBackgroundAnalysisEnabled = enabled; } + + @Override + public boolean isBackgroundAnalysisEnabled() { + return mIsBackgroundAnalysisEnabled; + } + + public void setAnalyzing(boolean analyzing) { + if (mViewModel != null) { + mViewModel.setAnalyzeState(analyzing); + } + } + + @Override + public void requireCompletion() { + getComponent(EditorAutoCompletion.class).requireCompletion(); + } + + public void setViewModel(EditorViewModel editorViewModel) { + mViewModel = editorViewModel; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/ContentWrapper.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/ContentWrapper.java index 6b62e4fbb..018ab8259 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/ContentWrapper.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/ContentWrapper.java @@ -3,65 +3,160 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.tyron.editor.Content; +import com.google.common.collect.Maps; +import com.tyron.editor.AbstractContent; +import com.tyron.editor.event.ContentEvent; +import com.tyron.editor.event.ContentListener; +import com.tyron.editor.event.impl.ContentEventImpl; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; -public class ContentWrapper implements Content { +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.Content; - private final io.github.rosemoe.sora.text.Content mContent; +public class ContentWrapper extends Content implements com.tyron.editor.Content { - public ContentWrapper(io.github.rosemoe.sora.text.Content content) { - mContent = content; + private AtomicInteger sequence; + + private boolean hasCalledSuper = false; + + public ContentWrapper() { + + } + + public ContentWrapper(CharSequence text) { + super(text, true); + + hasCalledSuper = true; + } + + private long modificationStamp = 0; + private final Map dataMap = Maps.newConcurrentMap(); + private final List contentListeners = new CopyOnWriteArrayList<>(); + + @Override + public void insert(int index, CharSequence text) { + CharPosition pos = getIndexer().getCharPosition(index); + insert(pos.line, pos.column, text); + } + + @Override + public void insert(int line, int column, CharSequence text) { + super.insert(line, column, text); + + if (!hasCalledSuper) { + return; + } + int offset = getCharIndex(line, column); + Content newText = this; + CharSequence newString = newText.subSequence(offset, offset + text.length()); + updateText(newText, offset, "", newString, false, System.currentTimeMillis(), offset, 0, + offset); } @Override - public int length() { - return mContent.length(); + public void replace(int start, int end, CharSequence text) { + CharPosition startPos = getIndexer().getCharPosition(start); + CharPosition endPos = getIndexer().getCharPosition(end); + replace(startPos.line, startPos.column, endPos.line, endPos.column, text); } @Override - public char charAt(int index) { - return mContent.charAt(index); + public void delete(int start, int end) { + CharPosition startPos = getIndexer().getCharPosition(start); + CharPosition endPos = getIndexer().getCharPosition(end); + delete(startPos.line, startPos.column, endPos.line, endPos.column); } - @NonNull @Override - public CharSequence subSequence(int start, int end) { - return mContent.subSequence(start, end); + public void delete(int startLine, int columnOnStartLine, int endLine, int columnOnEndLine) { + // need to get the offset before deleting since the end offset will be invalid + // if it has been deleted before + int startOffset = getCharIndex(startLine, columnOnStartLine); + int endOffset = getCharIndex(endLine, columnOnEndLine); + CharSequence oldString = subSequence(startOffset, endOffset); + + super.delete(startLine, columnOnStartLine, endLine, columnOnEndLine); + + if (!hasCalledSuper) { + return; + } + + Content newText = this; + updateText(newText, startOffset, oldString, "", false, System.currentTimeMillis(), + startOffset, endOffset - startOffset, startOffset); + } + + private AtomicInteger getSequence() { + if (sequence == null) { + sequence = new AtomicInteger(0); + } + return sequence; + } + + protected void updateText(@NonNull CharSequence text, + int offset, + @NonNull CharSequence oldString, + @NonNull CharSequence newString, + boolean wholeTextReplaced, + long newModificationStamp, + int initialStartOffset, + int initialOldLength, + int moveOffset) { + assert moveOffset >= 0 && moveOffset <= length() : "Invalid moveOffset: " + moveOffset; + ContentEvent event = + new ContentEventImpl(this, offset, oldString, newString, modificationStamp, + wholeTextReplaced, initialStartOffset, initialOldLength, moveOffset); + getSequence().incrementAndGet(); + + CharSequence prevText = this; + changedUpdate(event, newModificationStamp, prevText); + } + + protected void changedUpdate(@NonNull ContentEvent event, + long newModificationStamp, + @NonNull CharSequence prevText) { +// assert event.getOldFragment().length() == event.getOldLength(); +// assert event.getNewFragment().length() == event.getNewLength(); + if (contentListeners == null) { + return; + } + for (ContentListener contentListener : contentListeners) { + contentListener.contentChanged(event); + } } - @NonNull @Override - public IntStream chars() { - return mContent.chars(); + public void setData(String key, Object object) { + dataMap.put(key, object); } - @NonNull @Override - public IntStream codePoints() { - return mContent.codePoints(); + public Object getData(String key) { + return dataMap.get(key); } @Override - public int hashCode() { - return mContent.hashCode(); + public void addContentListener(ContentListener listener) { + contentListeners.add(listener); } - @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override - public boolean equals(@Nullable Object obj) { - return mContent.equals(obj); + public void removeContentListener(ContentListener listener) { + contentListeners.remove(listener); } - @NonNull @Override - public String toString() { - return mContent.toString(); + public void setModificationStamp(long stamp) { + modificationStamp = stamp; } @Override - public String getLineString(int line) { - return mContent.getLineString(line); + public long getModificationStamp() { + return modificationStamp; } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CursorWrapper.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CursorWrapper.java index 4f95d1864..5ad87a578 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CursorWrapper.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CursorWrapper.java @@ -42,4 +42,9 @@ public int getEndLine() { public int getEndColumn() { return mCursor.getRightColumn(); } + + @Override + public boolean isSelected() { + return mCursor.isSelected(); + } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeCodeEditor.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeCodeEditor.java index 833478e0a..ef7a2c731 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeCodeEditor.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeCodeEditor.java @@ -1,12 +1,20 @@ package com.tyron.code.ui.editor.impl.text.rosemoe; +import android.content.Context; import android.view.View; import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelStore; +import androidx.lifecycle.ViewModelStoreOwner; +import com.tyron.editor.Content; +import com.tyron.fileeditor.api.FileDocumentManager; import com.tyron.fileeditor.api.TextEditor; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.VFS; + import java.io.File; import java.util.Objects; @@ -14,26 +22,32 @@ public class RosemoeCodeEditor implements TextEditor { private final File mFile; private final RosemoeEditorProvider mProvider; - private final CodeEditorFragment mFragment; + private final RosemoeEditorFacade mEditor; - public RosemoeCodeEditor(File file, RosemoeEditorProvider provider) { + public RosemoeCodeEditor(Context context, File file, RosemoeEditorProvider provider) { mFile = file; mProvider = provider; - mFragment = createFragment(file); + try { + mEditor = createEditor(context, VFS.getManager().resolveFile(file.toURI())); + } catch (FileSystemException e) { + throw new RuntimeException(e); + } } - protected CodeEditorFragment createFragment(File file) { - return CodeEditorFragment.newInstance(file); + @Override + public Content getContent() { + return mEditor.getContent(); } + @Override - public Fragment getFragment() { - return mFragment; + public View getView() { + return mEditor.getView(); } @Override public View getPreferredFocusedView() { - return mFragment.getView(); + return mEditor.getView(); } @NonNull @@ -44,12 +58,13 @@ public String getName() { @Override public boolean isModified() { - return false; + FileDocumentManager instance = FileDocumentManager.getInstance(); + return instance.isContentUnsaved(getContent()); } @Override public boolean isValid() { - return true; + return mFile.exists(); } @Override @@ -69,4 +84,13 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(mFile); } + + private RosemoeEditorFacade createEditor(Context context, FileObject file) { + try { + Content content = FileDocumentManager.getInstance().getContent(file); + return new RosemoeEditorFacade(this, context, content, file); + } catch (FileSystemException e) { + throw new RuntimeException(e); + } + } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorFacade.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorFacade.java new file mode 100644 index 000000000..aa623e662 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorFacade.java @@ -0,0 +1,314 @@ +package com.tyron.code.ui.editor.impl.text.rosemoe; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Bundle; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.FrameLayout; + +import androidx.appcompat.widget.ForwardingListener; +import androidx.core.content.res.ResourcesCompat; + +import com.tyron.actions.ActionManager; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.util.DataContextUtils; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.R; +import com.tyron.code.event.EventManager; +import com.tyron.code.event.PerformShortcutEvent; +import com.tyron.code.language.LanguageManager; +import com.tyron.code.language.java.JavaLanguage; +import com.tyron.code.ui.editor.CodeAssistCompletionAdapter; +import com.tyron.code.ui.editor.CodeAssistCompletionWindow; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.util.CoordinatePopupMenu; +import com.tyron.code.util.PopupMenuHelper; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.common.util.DebouncerStore; +import com.tyron.completion.java.util.JavaDataContextUtil; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.diagnostics.DiagnosticProvider; +import com.tyron.editor.Content; +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.language.api.CodeAssistLanguage; + +import org.apache.commons.vfs2.FileObject; +import org.jetbrains.kotlin.com.intellij.util.ReflectionUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.function.Function; + +import javax.tools.Diagnostic; + +import io.github.rosemoe.sora.event.ClickEvent; +import io.github.rosemoe.sora.event.ContentChangeEvent; +import io.github.rosemoe.sora.event.EditorKeyEvent; +import io.github.rosemoe.sora.event.Event; +import io.github.rosemoe.sora.event.InterceptTarget; +import io.github.rosemoe.sora.event.LongPressEvent; +import io.github.rosemoe.sora.lang.EmptyLanguage; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.diagnostic.DiagnosticRegion; +import io.github.rosemoe.sora.lang.diagnostic.DiagnosticsContainer; +import io.github.rosemoe.sora.text.Cursor; +import io.github.rosemoe.sora.widget.component.EditorAutoCompletion; +import io.github.rosemoe.sora2.text.EditorUtil; + +public class RosemoeEditorFacade { + + private static final Logger LOGGER = LoggerFactory.getLogger(RosemoeEditorFacade.class); + + private final FileEditor fileEditor; + private final Content content; + private final FrameLayout container; + private final CodeEditorView editor; + + private View.OnTouchListener dragToOpenListener; + + RosemoeEditorFacade(RosemoeCodeEditor rosemoeCodeEditor, + Context context, + Content content, + FileObject file) { + this.fileEditor = rosemoeCodeEditor; + this.content = content; + + container = new FrameLayout(context); + + editor = new CodeEditorView(context); + configureEditor(editor, file); + container.addView(editor); + + + EventManager eventManager = ApplicationLoader.getInstance().getEventManager(); + eventManager.subscribeEvent(PerformShortcutEvent.class, (event, unsubscribe) -> { + if (event.getEditor() == rosemoeCodeEditor) { + event.getItem().actions.forEach(it -> it.apply(editor, event.getItem())); + } + }); + } + + /** + * Background updates + * + * @param content the current content when the update is called + */ + private void onContentChange(Content content) { + Language language = editor.getEditorLanguage(); + File currentFile = editor.getCurrentFile(); + Project project = editor.getProject(); + if (project == null) { + return; + } + Module module = project.getModule(currentFile); + if (module == null) { + return; + } + + if (language instanceof CodeAssistLanguage) { + ((CodeAssistLanguage) language).onContentChange(currentFile, content); + } + + Objects.requireNonNull(editor.getDiagnostics()).reset(); + + ServiceLoader providers = ServiceLoader.load(DiagnosticProvider.class); + for (DiagnosticProvider provider : providers) { + List extends Diagnostic>> diagnostics = + provider.getDiagnostics(module, currentFile); + Function severitySupplier = it -> { + switch (it) { + case ERROR: + return DiagnosticRegion.SEVERITY_ERROR; + case MANDATORY_WARNING: + case WARNING: + return DiagnosticRegion.SEVERITY_WARNING; + default: + case OTHER: + case NOTE: + return DiagnosticRegion.SEVERITY_NONE; + } + }; + diagnostics.stream() + .map(it -> new DiagnosticRegion((int) it.getStartPosition(), + (int) it.getEndPosition(), + severitySupplier.apply(it.getKind()))) + .forEach(Objects.requireNonNull(editor.getDiagnostics())::addDiagnostic); + } + } + + @SuppressLint("ClickableViewAccessibility") + private void configureEditor(CodeEditorView editor, FileObject file) { + Language language = LanguageManager.getInstance().get(editor, file); + if (language == null) { + language = new EmptyLanguage(); + } + editor.setEditorLanguage(language); + + // only used for checking the extension + editor.openFile(file.getPath().toFile()); + Bundle bundle = new Bundle(); + bundle.putBoolean("loaded", true); + bundle.putBoolean("bg", true); + editor.setText(content, bundle); + editor.setHighlightBracketPair(false); + editor.setDiagnostics(new DiagnosticsContainer()); + editor.setTypefaceText(ResourcesCompat.getFont(editor.getContext(), + R.font.jetbrains_mono_regular)); + + editor.replaceComponent(EditorAutoCompletion.class, new CodeAssistCompletionWindow(editor)); + editor.setAutoCompletionItemAdapter(new CodeAssistCompletionAdapter()); + editor.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); + editor.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | + EditorInfo.TYPE_CLASS_TEXT | + EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | + EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + + editor.setOnTouchListener((v, motionEvent) -> { + if (dragToOpenListener instanceof ForwardingListener) { + PopupMenuHelper.setForwarding((ForwardingListener) dragToOpenListener); + // noinspection RestrictedApi + dragToOpenListener.onTouch(v, motionEvent); + } + return false; + }); + editor.subscribeEvent(LongPressEvent.class, (event, unsubscribe) -> { + event.intercept(); + + Cursor cursor = editor.getCursor(); + if (cursor.isSelected()) { + int index = editor.getCharIndex(event.getLine(), event.getColumn()); + int cursorLeft = cursor.getLeft(); + int cursorRight = cursor.getRight(); + char c = editor.getText().charAt(index); + if (Character.isWhitespace(c)) { + editor.setSelection(event.getLine(), event.getColumn()); + } else if (index < cursorLeft || index > cursorRight) { + EditorUtil.selectWord(editor, event.getLine(), event.getColumn()); + } + } else { + char c = editor.getText().charAt(event.getIndex()); + if (!Character.isWhitespace(c)) { + EditorUtil.selectWord(editor, event.getLine(), event.getColumn()); + } else { + editor.setSelection(event.getLine(), event.getColumn()); + } + } + + ProgressManager.getInstance().runLater(() -> showPopupMenu(event)); + }); + editor.subscribeEvent(ClickEvent.class, (event, unsubscribe) -> { + Cursor cursor = editor.getCursor(); + if (editor.getCursor().isSelected()) { + int index = editor.getCharIndex(event.getLine(), event.getColumn()); + int cursorLeft = cursor.getLeft(); + int cursorRight = cursor.getRight(); + if (!EditorUtil.isWhitespace(editor.getText().charAt(index) + "") && + index >= cursorLeft && + index <= cursorRight) { + editor.showSoftInput(); + event.intercept(); + } + } + }); + + editor.subscribeEvent(EditorKeyEvent.class, (event, unsubscribe) -> { + CodeAssistCompletionWindow window = + (CodeAssistCompletionWindow) editor.getComponent(EditorAutoCompletion.class); + if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER || + event.getKeyCode() == KeyEvent.KEYCODE_TAB) { + if (window.isShowing() && window.trySelect()) { + event.setResult(true); + + // KeyEvent cannot be intercepted??? + // workaround + Field mInterceptTargets = + ReflectionUtil.getDeclaredField(Event.class, "mInterceptTargets"); + mInterceptTargets.setAccessible(true); + try { + mInterceptTargets.set(event, InterceptTarget.TARGET_EDITOR); + } catch (IllegalAccessException e) { + throw new RuntimeException("REFLECTION FAILED"); + } + + editor.requestFocus(); + } + } + }); + editor.subscribeEvent(ContentChangeEvent.class, + (event, unsubscribe) -> ProgressManager.getInstance() + .runNonCancelableAsync(() -> DebouncerStore.DEFAULT.registerOrGetDebouncer( + "contentChange").debounce(300, () -> { + try { + onContentChange(editor.getContent()); + } catch (Throwable t) { + LOGGER.error("Error in onContentChange", t); + } + }))); + } + + /** + * Show the popup menu with the actions api + */ + private void showPopupMenu(LongPressEvent event) { + MotionEvent e = event.getCausingEvent(); + CoordinatePopupMenu popupMenu = + new CoordinatePopupMenu(editor.getContext(), editor, Gravity.BOTTOM); + DataContext dataContext = createDataContext(); + ActionManager.getInstance() + .fillMenu(dataContext, popupMenu.getMenu(), ActionPlaces.EDITOR, true, false); + popupMenu.show((int) e.getX(), ((int) e.getY()) - AndroidUtilities.dp(24)); + + // we don't want to enable the drag to open listener right away, + // this may cause the buttons to be clicked right away + // so wait for a few ms + ProgressManager.getInstance().runLater(() -> { + popupMenu.setOnDismissListener(d -> dragToOpenListener = null); + dragToOpenListener = popupMenu.getDragToOpenListener(); + }, 300); + } + + /** + * Create the data context specific to this fragment for use with the actions API. + * + * @return the data context. + */ + private DataContext createDataContext() { + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + + DataContext dataContext = DataContextUtils.getDataContext(editor); + dataContext.putData(CommonDataKeys.PROJECT, currentProject); + dataContext.putData(CommonDataKeys.FILE_EDITOR_KEY, fileEditor); + dataContext.putData(CommonDataKeys.FILE, editor.getCurrentFile()); + dataContext.putData(CommonDataKeys.EDITOR, editor); + + if (currentProject != null && editor.getEditorLanguage() instanceof JavaLanguage) { + JavaDataContextUtil.addEditorKeys(dataContext, + currentProject, + editor.getCurrentFile(), + editor.getCursor().getLeft()); + } + return dataContext; + } + + public View getView() { + return container; + } + + public Content getContent() { + return content; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorProvider.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorProvider.java index 828064eec..8946403d9 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorProvider.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorProvider.java @@ -1,25 +1,41 @@ package com.tyron.code.ui.editor.impl.text.rosemoe; +import android.content.Context; + import androidx.annotation.NonNull; +import com.google.common.collect.ImmutableSet; import com.tyron.fileeditor.api.FileEditor; import com.tyron.fileeditor.api.FileEditorProvider; import java.io.File; +import java.util.Set; + +import kotlin.io.FilesKt; public class RosemoeEditorProvider implements FileEditorProvider { + private static final Set NON_TEXT_FILES = ImmutableSet.builder() + .add("jar", "zip", "png", "jpg") + .add("jpeg", "mp4", "mp3", "ogg") + .add("7zip", "tar") + .build(); private static final String TYPE_ID = "rosemoe-code-editor"; @Override public boolean accept(@NonNull File file) { + boolean nonText = NON_TEXT_FILES.stream() + .anyMatch(it -> FilesKt.getExtension(file).endsWith(it)); + if (nonText) { + return false; + } return file.exists() && !file.isDirectory(); } @NonNull @Override - public FileEditor createEditor(@NonNull File file) { - return new RosemoeCodeEditor(file, this); + public FileEditor createEditor(@NonNull Context context, @NonNull File file) { + return new RosemoeCodeEditor(context, file, this); } @NonNull diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/window/ActionsWindow.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/window/ActionsWindow.java new file mode 100644 index 000000000..c98d5160f --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/window/ActionsWindow.java @@ -0,0 +1,43 @@ +package com.tyron.code.ui.editor.impl.text.rosemoe.window; + +import android.annotation.SuppressLint; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; +import android.widget.PopupWindow; + +import androidx.appcompat.view.menu.ListMenuPresenter; +import androidx.appcompat.view.menu.MenuBuilder; +import androidx.appcompat.view.menu.MenuPopupHelper; +import androidx.appcompat.view.menu.MenuView; +import androidx.appcompat.widget.ActionMenuView; +import androidx.appcompat.widget.ListPopupWindow; +import androidx.appcompat.widget.MenuPopupWindow; + +import com.tyron.actions.ActionManager; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.util.DataContextUtils; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; + +import io.github.rosemoe.sora.widget.CodeEditor; +import io.github.rosemoe.sora.widget.component.EditorTextActionWindow; + +@SuppressLint("RestrictedApi") +public class ActionsWindow extends EditorTextActionWindow { + + /** + * Create a panel for the given editor + * + * @param editor Target editor + */ + public ActionsWindow(CodeEditor editor) { + super(editor); + } + + @Override + public void unregister() { + super.unregister(); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/ContentImpl.kt b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/ContentImpl.kt new file mode 100644 index 000000000..20efc543c --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/ContentImpl.kt @@ -0,0 +1,108 @@ +package com.tyron.code.ui.editor.impl.text.squircle + +import android.text.Editable +import android.text.InputFilter +import android.text.SpannableStringBuilder +import com.blacksquircle.ui.editorkit.plugin.base.EditorPlugin +import com.tyron.editor.Content +import com.tyron.editor.event.ContentListener + +class ContentImpl( + private val baseContent: Content +) : EditorPlugin("content"), Editable { + + val editable = SpannableStringBuilder() + + override fun get(index: Int): Char { + return editable[index] + } + + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { + return editable.subSequence(startIndex, endIndex) + } + + override fun getChars(p0: Int, p1: Int, p2: CharArray?, p3: Int) { + return editable.getChars(p0, p1, p2, p3) + } + + override fun getSpans(p0: Int, p1: Int, p2: Class?): Array { + return editable.getSpans(p0, p1, p2) + } + + override fun getSpanStart(p0: Any?): Int { + return editable.getSpanStart(p0) + } + + override fun getSpanEnd(p0: Any?): Int { + return editable.getSpanEnd(p0) + } + + override fun getSpanFlags(p0: Any?): Int { + return editable.getSpanFlags(p0) + } + + override fun nextSpanTransition(p0: Int, p1: Int, p2: Class<*>?): Int { + return editable.nextSpanTransition(p0, p1, p2) + } + + override fun setSpan(p0: Any?, p1: Int, p2: Int, p3: Int) { + editable.setSpan(p0, p1, p2, p3) + } + + override fun removeSpan(p0: Any?) { + editable.removeSpan(p0) + } + + override fun append(p0: CharSequence?): Editable { + return editable.append(p0) + } + + override fun append(p0: CharSequence?, p1: Int, p2: Int): Editable { + return editable.append(p0, p1, p2) + } + + override fun append(p0: Char): Editable { + return editable.append(p0) + } + + override fun replace(p0: Int, p1: Int, p2: CharSequence?, p3: Int, p4: Int): Editable { + return this + } + + override fun replace(p0: Int, p1: Int, p2: CharSequence?): Editable { + TODO("Not yet implemented") + } + + override fun insert(p0: Int, p1: CharSequence?, p2: Int, p3: Int): Editable { + TODO("Not yet implemented") + } + + override fun insert(p0: Int, p1: CharSequence?): Editable { + TODO("Not yet implemented") + } + + override fun delete(p0: Int, p1: Int): Editable { + return editable.delete(p0, p1) + } + + override fun clear() { + editable.clear() + } + + override fun clearSpans() { + editable.clearSpans() + } + + override fun setFilters(p0: Array?) { + editable.filters = p0 + } + + override fun getFilters(): Array { + return editable.filters + } + + override val length: Int + get() = editable.length + + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditor.kt b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditor.kt new file mode 100644 index 000000000..f4f39aa92 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditor.kt @@ -0,0 +1,46 @@ +package com.tyron.code.ui.editor.impl.text.squircle + +import android.content.Context +import android.view.View +import com.blacksquircle.ui.editorkit.widget.TextProcessor +import com.tyron.editor.Content +import com.tyron.fileeditor.api.FileDocumentManager +import com.tyron.fileeditor.api.FileEditor +import org.apache.commons.vfs2.VFS +import java.io.File + +class SquircleEditor( + val context: Context, + private val ioFile: File, + val provider: SquircleEditorProvider +) : FileEditor { + + private val editorView = TextProcessor(context) + + init { + val fileObject = VFS.getManager().toFileObject(ioFile) + val content = FileDocumentManager.getInstance().getContent(fileObject) + val contentPlugin = ContentImpl(content!!) + + editorView.text = contentPlugin + editorView.installPlugin(contentPlugin) + } + + override fun getView() = editorView + + override fun getPreferredFocusedView() = view + + override fun getName() = "Squircle Editor" + + override fun isModified(): Boolean { + return false + } + + override fun isValid(): Boolean { + return true + } + + override fun getFile(): File { + return ioFile + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditorProvider.kt b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditorProvider.kt new file mode 100644 index 000000000..ecc346bbf --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditorProvider.kt @@ -0,0 +1,21 @@ +package com.tyron.code.ui.editor.impl.text.squircle + +import android.content.Context +import com.tyron.fileeditor.api.FileEditor +import com.tyron.fileeditor.api.FileEditorProvider +import java.io.File + +class SquircleEditorProvider : FileEditorProvider { + + override fun accept(file: File): Boolean { + return true + } + + override fun createEditor(context: Context, file: File): FileEditor { + return SquircleEditor(context, file, this) + } + + override fun getEditorTypeId(): String { + return "squircle-editor" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutEditor.java b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutEditor.java index 504d5f5a6..44a7be5d0 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutEditor.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutEditor.java @@ -1,5 +1,7 @@ package com.tyron.code.ui.editor.impl.xml; +import android.content.Context; + import androidx.annotation.NonNull; import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; @@ -10,11 +12,10 @@ public class LayoutEditor extends RosemoeCodeEditor { - public LayoutEditor(File file, RosemoeEditorProvider provider) { - super(file, provider); + public LayoutEditor(Context context, File file, RosemoeEditorProvider provider) { + super(context, file, provider); } - @Override protected CodeEditorFragment createFragment(File file) { return LayoutTextEditorFragment.newInstance(file); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorFragment.java b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorFragment.java index 7301b97ba..417e07c1c 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorFragment.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorFragment.java @@ -1,7 +1,18 @@ package com.tyron.code.ui.editor.impl.xml; import android.os.Bundle; +import android.util.Pair; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.os.BundleKt; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentResultListener; + +import com.tyron.builder.compiler.manifest.xml.XmlFormatPreferences; +import com.tyron.builder.compiler.manifest.xml.XmlFormatStyle; +import com.tyron.builder.compiler.manifest.xml.XmlPrettyPrinter; import com.tyron.code.R; import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; import com.tyron.code.ui.layoutEditor.LayoutEditorFragment; @@ -22,6 +33,25 @@ public static LayoutTextEditorFragment newInstance(File file) { return fragment; } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + FragmentManager fragmentManager = getChildFragmentManager(); + FragmentResultListener listener = ((requestKey, result) -> { + String xml = result.getString("text", getEditor().getText() + .toString()); + xml = XmlPrettyPrinter.prettyPrint(xml, XmlFormatPreferences.defaults(), + XmlFormatStyle.LAYOUT, "\n"); + Bundle bundle = new Bundle(); + bundle.putBoolean("loaded", true); + bundle.putBoolean("bg", true); + getEditor().setText(xml, bundle); + }); + fragmentManager.setFragmentResultListener(LayoutEditorFragment.KEY_SAVE, + getViewLifecycleOwner(), listener); + } + public void preview() { File currentFile = getEditor().getCurrentFile(); diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorProvider.java b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorProvider.java index 4d30d0267..7bb9da4f5 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorProvider.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorProvider.java @@ -1,5 +1,7 @@ package com.tyron.code.ui.editor.impl.xml; +import android.content.Context; + import androidx.annotation.NonNull; import com.tyron.fileeditor.api.FileEditor; @@ -22,8 +24,8 @@ public boolean accept(@NonNull File file) { @NonNull @Override - public FileEditor createEditor(@NonNull File file) { - return new LayoutEditor(file, this); + public FileEditor createEditor(@NonNull Context context, @NonNull File file) { + return new LayoutEditor(context, file, this); } @NonNull diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/AbstractAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/ui/editor/language/AbstractAutoCompleteProvider.java deleted file mode 100644 index 4923f46ac..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/AbstractAutoCompleteProvider.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.tyron.code.ui.editor.language; - -import androidx.annotation.Nullable; - -import com.tyron.completion.model.CompletionList; - -import java.util.List; -import java.util.stream.Collectors; - -import io.github.rosemoe.sora2.data.CompletionItem; -import io.github.rosemoe.sora2.interfaces.AutoCompleteProvider; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; - -/** - * An auto complete provider that supports cancellation as the user types - */ -public abstract class AbstractAutoCompleteProvider implements AutoCompleteProvider { - - @Override - public final List getAutoCompleteItems(String prefix, TextAnalyzeResult colors, int line, int column) { - CompletionList list = getCompletionList(prefix, colors, - line, column); - if (list == null) { - return null; - } - - return list.items.stream() - .map(CompletionItem::new) - .collect(Collectors.toList()); - } - - @Nullable - public abstract CompletionList getCompletionList(String prefix, TextAnalyzeResult colors, int line, int column); -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/DiagnosticAnalyzeManager.java b/app/src/main/java/com/tyron/code/ui/editor/language/DiagnosticAnalyzeManager.java deleted file mode 100644 index ee1badd50..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/DiagnosticAnalyzeManager.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.tyron.code.ui.editor.language; - -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.editor.Editor; - -import java.util.List; - -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; - -public abstract class DiagnosticAnalyzeManager extends SimpleAnalyzeManager { - - protected boolean mShouldAnalyzeInBg; - - public abstract void setDiagnostics(Editor editor, List diagnostics); - - public void rerunWithoutBg() { - mShouldAnalyzeInBg = false; - super.rerun(); - } - - @Override - public void rerun() { - mShouldAnalyzeInBg = true; - super.rerun(); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/HighlightUtil.java b/app/src/main/java/com/tyron/code/ui/editor/language/HighlightUtil.java deleted file mode 100644 index d1dd60f08..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/HighlightUtil.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.tyron.code.ui.editor.language; - -import android.util.Log; - -import com.android.tools.r8.naming.T; -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.editor.CharPosition; -import com.tyron.editor.Editor; - -import org.openjdk.javax.tools.Diagnostic; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import io.github.rosemoe.sora.lang.styling.MappedSpans; -import io.github.rosemoe.sora.lang.styling.Spans; -import io.github.rosemoe.sora.lang.styling.Styles; -import io.github.rosemoe.sora2.BuildConfig; -import io.github.rosemoe.sora2.data.Span; -import io.github.rosemoe.sora2.text.Indexer; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.widget.CodeEditor; -import io.github.rosemoe.sora2.widget.EditorColorScheme; - -public class HighlightUtil { - - public static void markProblemRegion(Styles styles, int newFlag, int startLine, int startColumn, int endLine, int endColumn) { - for (int line = startLine; line <= endLine; line++) { - int start = (line == startLine ? startColumn : 0); - int end = (line == endLine ? endColumn : Integer.MAX_VALUE); - Spans.Reader read = styles.getSpans().read(); - List spans = new ArrayList<>(read.getSpansOnLine(line)); - int increment; - for (int i = 0; i < spans.size(); i += increment) { - io.github.rosemoe.sora.lang.styling.Span span = spans.get(i); - increment = 1; - if (span.column >= end) { - break; - } - int spanEnd = (i + 1 >= spans.size() ? Integer.MAX_VALUE : spans.get(i + 1).column); - if (spanEnd >= start) { - int regionStartInSpan = Math.max(span.column, start); - int regionEndInSpan = Math.min(end, spanEnd); - if (regionStartInSpan == span.column) { - if (regionEndInSpan != spanEnd) { - increment = 2; - io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); - nSpan.column = regionEndInSpan; - spans.add(i + 1, nSpan); - } - span.problemFlags |= newFlag; - } else { - //regionStartInSpan > span.column - if (regionEndInSpan == spanEnd) { - increment = 2; - io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); - nSpan.column = regionStartInSpan; - spans.add(i + 1, nSpan); - nSpan.problemFlags |= newFlag; - } else { - increment = 3; - io.github.rosemoe.sora.lang.styling.Span span1 = span.copy(); - span1.column = regionStartInSpan; - span1.problemFlags |= newFlag; - io.github.rosemoe.sora.lang.styling.Span span2 = span.copy(); - span2.column = regionEndInSpan; - spans.add(i + 1, span1); - spans.add(i + 2, span2); - } - } - } - } - - Spans.Modifier modify = styles.getSpans().modify(); - modify.setSpansOnLine(line, spans); - } - } - - - /** - * Highlights the list of given diagnostics, taking care of conversion between 1-based offsets - * to 0-based offsets. - * It also makes the Diagnostic eligible for shifting as the user types. - */ - public static void markDiagnostics(Editor editor, List diagnostics, - Styles styles) { - diagnostics.forEach(it -> { - try { - int startLine; - int startColumn; - int endLine; - int endColumn; - if (it.getPosition() != DiagnosticWrapper.USE_LINE_POS) { - if (it.getStartPosition() == -1) { - it.setStartPosition(it.getPosition()); - } - if (it.getEndPosition() == -1) { - it.setEndPosition(it.getPosition()); - } - CharPosition start = editor.getCharPosition((int) it.getStartPosition()); - CharPosition end = editor.getCharPosition((int) it.getEndPosition()); - - int sLine = start.getLine(); - int sColumn = start.getColumn(); - int eLine = end.getLine(); - int eColumn = end.getColumn(); - - // the editor does not support marking underline spans for the same start and end - // index - // to work around this, we just subtract one to the start index - if (sLine == eLine && eColumn == sColumn) { - sColumn--; - eColumn++; - } - - it.setStartLine(sLine); - it.setEndLine(eLine); - it.setStartColumn(sColumn); - it.setEndColumn(eColumn); - } - startLine = it.getStartLine(); - startColumn = it.getStartColumn(); - endLine = it.getEndLine(); - endColumn = it.getEndColumn(); - - int flag = it.getKind() == Diagnostic.Kind.ERROR ? Span.FLAG_ERROR : - Span.FLAG_WARNING; - markProblemRegion(styles, flag, startLine, startColumn, endLine, endColumn); - } catch (IllegalArgumentException | IndexOutOfBoundsException e) { - if (BuildConfig.DEBUG) { - Log.d("HighlightUtil", "Failed to mark diagnostics", e); - } - } - }); - } - - public static int[] setErrorSpan(TextAnalyzeResult colors, int line, int column) { - int lineCount = colors.getSpanMap().size(); - int realLine = line - 1; - List spans = colors.getSpanMap().get(Math.min(realLine, lineCount - 1)); - - int[] end = new int[2]; - end[0] = Math.min(realLine, lineCount - 1); - - if (realLine >= lineCount) { - Span span = Span.obtain(0, EditorColorScheme.PROBLEM_ERROR); - span.problemFlags = Span.FLAG_ERROR; - colors.add(realLine, span); - end[0]++; - } else { - Span last = null; - for (int i = 0; i < spans.size(); i++) { - Span span = spans.get(i); - if (last != null) { - if (last.column <= column - 1 && span.column >= column - 1) { - span.problemFlags = Span.FLAG_ERROR; - last.problemFlags = Span.FLAG_ERROR; - end[1] = last.column; - break; - } - } - if (i == spans.size() - 1 && span.column <= column - 1) { - span.problemFlags = Span.FLAG_ERROR; - end[1] = span.column; - break; - } - last = span; - } - } - return end; - } - - /** - * Used in xml diagnostics where line is only given - */ - public static void setErrorSpan(TextAnalyzeResult colors, int line) { - int lineCount = colors.getSpanMap().size(); - int realLine = line - 1; - List spans = colors.getSpanMap().get(Math.min(realLine, lineCount - 1)); - - for (Span span : spans) { - span.problemFlags = Span.FLAG_ERROR; - } - } - - /** - * Used in xml diagnostics where line is only given - */ - public static void setErrorSpan(Styles colors, int line) { - try { - Spans.Reader reader = colors.getSpans().read(); - int realLine = line - 1; - List spans = reader.getSpansOnLine(realLine); - - for (io.github.rosemoe.sora.lang.styling.Span span : spans) { - span.problemFlags = Span.FLAG_ERROR; - } - } catch (IndexOutOfBoundsException e) { - // ignored - } - } - -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/Language.java b/app/src/main/java/com/tyron/code/ui/editor/language/Language.java deleted file mode 100644 index 7907d336f..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/Language.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.tyron.code.ui.editor.language; - -import com.tyron.editor.Editor; - -import io.github.rosemoe.sora.widget.CodeEditor; -import java.io.File; - -public interface Language { - - /** - * Subclasses return whether they support this file extension - */ - boolean isApplicable(File ext); - - /** - * - * @param editor the editor instance - * @return The specific language instance for this editor - */ - io.github.rosemoe.sora.lang.Language get(Editor editor); -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/LanguageManager.java b/app/src/main/java/com/tyron/code/ui/editor/language/LanguageManager.java deleted file mode 100644 index 740e40843..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/LanguageManager.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.tyron.code.ui.editor.language; - -import com.tyron.code.ui.editor.language.groovy.Groovy; -import com.tyron.code.ui.editor.language.java.Java; -import com.tyron.code.ui.editor.language.json.Json; -import com.tyron.code.ui.editor.language.kotlin.Kotlin; -import com.tyron.code.ui.editor.language.xml.Xml; -import com.tyron.editor.Editor; - -import java.io.File; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -public class LanguageManager { - - private static LanguageManager Instance = null; - - public static LanguageManager getInstance() { - if (Instance == null) { - Instance = new LanguageManager(); - } - return Instance; - } - - private final Set mLanguages = new HashSet<>(); - - private LanguageManager() { - initLanguages(); - } - - private void initLanguages() { - mLanguages.addAll( - Arrays.asList( - new Xml(), - new Java(), - new Kotlin(), - new Groovy(), - new Json())); - } - - public boolean supports(File file) { - for (Language language : mLanguages) { - if (language.isApplicable(file)) { - return true; - } - } - return false; - } - - public io.github.rosemoe.sora.lang.Language get(Editor editor, File file) { - for (Language lang : mLanguages) { - if (lang.isApplicable(file)) { - return lang.get(editor); - } - } - return null; - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/Groovy.java b/app/src/main/java/com/tyron/code/ui/editor/language/groovy/Groovy.java deleted file mode 100644 index 2bfc9cd4b..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/Groovy.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.tyron.code.ui.editor.language.groovy; - -import com.tyron.code.ui.editor.language.Language; -import com.tyron.editor.Editor; - -import java.io.File; - -import io.github.rosemoe.sora.widget.CodeEditor; - -public class Groovy implements Language { - @Override - public boolean isApplicable(File ext) { - return ext.getName().endsWith(".groovy") || ext.getName().endsWith(".gradle"); - } - - @Override - public io.github.rosemoe.sora.lang.Language get(Editor editor) { - return new GroovyLanguage(editor); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyAnalyzer.java b/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyAnalyzer.java deleted file mode 100644 index bb5a1a848..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyAnalyzer.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.tyron.code.ui.editor.language.groovy; - -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.Token; - -import java.util.Stack; - -import io.github.rosemoe.sora.lang.styling.CodeBlock; -import io.github.rosemoe.sora.lang.styling.MappedSpans; -import io.github.rosemoe.sora.lang.styling.Styles; -import io.github.rosemoe.sora.widget.CodeEditor; -import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; - -public class GroovyAnalyzer extends AbstractCodeAnalyzer { - - private final Editor mEditor; - - int maxSwitch = 1; - int currSwitch; - private final Stack mBlockLines = new Stack<>(); - - public GroovyAnalyzer(Editor editor) { - mEditor = editor; - } - - @Override - public Lexer getLexer(CharStream input) { - return new GroovyLexer(input); - } - - @Override - public void setup() { - putColor(EditorColorScheme.KEYWORD, GroovyLexer.KW_DO, - GroovyLexer.KW_ABSTRACT, GroovyLexer.KW_FALSE, - GroovyLexer.KW_TRUE, GroovyLexer.KW_CASE, - GroovyLexer.KW_CATCH, GroovyLexer.KW_AS, - GroovyLexer.KW_WHILE, GroovyLexer.KW_TRY, - GroovyLexer.KW_BREAK, GroovyLexer.KW_THIS, - GroovyLexer.KW_ASSERT, GroovyLexer.KW_VOLATILE, - GroovyLexer.KW_NULL, GroovyLexer.KW_NEW, - GroovyLexer.KW_RETURN, GroovyLexer.KW_PACKAGE, - GroovyLexer.KW_FOR, GroovyLexer.KW_IF, - GroovyLexer.SEMICOLON); - putColor(EditorColorScheme.LITERAL, GroovyLexer.STRING, - GroovyLexer.INTEGER); - putColor(EditorColorScheme.OPERATOR, GroovyLexer.PLUS, - GroovyLexer.MINUS, GroovyLexer.MULT, GroovyLexer.DIV, - GroovyLexer.PLUS_ASSIGN, GroovyLexer.MINUS_ASSIGN, - GroovyLexer.MULT_ASSIGN, GroovyLexer.DIV_ASSIGN, - GroovyLexer.MOD, GroovyLexer.MOD_ASSIGN, - GroovyLexer.OR, GroovyLexer.AND, GroovyLexer.BAND, - GroovyLexer.BAND_ASSIGN, GroovyLexer.LSHIFT, - GroovyLexer.LSHIFT_ASSIGN, GroovyLexer.RSHIFT_ASSIGN, - GroovyLexer.XOR_ASSIGN, GroovyLexer.XOR); - putColor(EditorColorScheme.IDENTIFIER_NAME, GroovyLexer.IDENTIFIER); - } - - @Override - public void analyzeInBackground(CharSequence contents) { - - } - - @Override - protected void beforeAnalyze() { - mBlockLines.clear(); - maxSwitch = 1; - currSwitch = 0; - } - - @Override - public boolean onNextToken(Token currentToken, Styles styles, MappedSpans.Builder colors) { - int line = currentToken.getLine() - 1; - int column = currentToken.getCharPositionInLine(); - - switch (currentToken.getType()) { - case GroovyLexer.RCURVE: - if (!mBlockLines.isEmpty()) { - CodeBlock b = mBlockLines.pop(); - b.endLine = line; - b.endColumn = column; - if (b.startLine != b.endLine) { - styles.addCodeBlock(b); - } - } - return true; - case GroovyLexer.LCURVE: - if (mBlockLines.isEmpty()) { - if (currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - currSwitch = 0; - } - currSwitch++; - CodeBlock block = styles.obtainNewBlock(); - block.startLine = line; - block.startColumn = column; - mBlockLines.push(block); - return true; - } - return false; - } - - @Override - protected void afterAnalyze(CharSequence content, Styles styles, MappedSpans.Builder colors) { - if (mBlockLines.isEmpty()) { - if (currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - } - styles.setSuppressSwitch(maxSwitch + 10); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLanguage.java b/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLanguage.java deleted file mode 100644 index 97fe5438e..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLanguage.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.tyron.code.ui.editor.language.groovy; - -import android.os.Bundle; - -import androidx.annotation.NonNull; - -import com.tyron.editor.Editor; - -import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; -import io.github.rosemoe.sora.lang.completion.CompletionPublisher; -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.CodeEditor; -import io.github.rosemoe.sora.widget.SymbolPairMatch; - -public class GroovyLanguage implements Language { - - private final Editor mEditor; - private final GroovyAnalyzer mAnalyzer; - - public GroovyLanguage(Editor editor) { - mEditor = editor; - mAnalyzer = new GroovyAnalyzer(editor); - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return mAnalyzer; - } - - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_STRONG; - } - - @Override - public void requireAutoComplete(@NonNull ContentReference content, @NonNull CharPosition position, @NonNull CompletionPublisher publisher, @NonNull Bundle extraArguments) throws CompletionCancelledException { - - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return 0; - } - - @Override - public boolean useTab() { - return true; - } - - @Override - public CharSequence format(CharSequence text) { - return text; - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return null; - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return new NewlineHandler[0]; - } - - @Override - public void destroy() { - - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/java/IncrementalJavaAnalyzeManager.java b/app/src/main/java/com/tyron/code/ui/editor/language/java/IncrementalJavaAnalyzeManager.java deleted file mode 100644 index 8956c1e8a..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/java/IncrementalJavaAnalyzeManager.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.tyron.code.ui.editor.language.java; - -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.jetbrains.kotlin.com.intellij.lang.java.lexer.JavaLexer; -import org.jetbrains.kotlin.com.intellij.lexer.Lexer; -import org.jetbrains.kotlin.com.intellij.lexer.LexerPosition; -import org.jetbrains.kotlin.com.intellij.pom.java.LanguageLevel; - -import java.util.List; - -import io.github.rosemoe.sora.lang.analysis.IncrementalAnalyzeManager; -import io.github.rosemoe.sora.lang.analysis.StyleReceiver; -import io.github.rosemoe.sora.lang.styling.Span; -import io.github.rosemoe.sora.text.CharPosition; -import io.github.rosemoe.sora.text.ContentReference; - -public class IncrementalJavaAnalyzeManager - implements IncrementalAnalyzeManager { - - private final Lexer mLexer; - - public IncrementalJavaAnalyzeManager() { - mLexer = new JavaLexer(LanguageLevel.HIGHEST); - } - - @Override - public LexerPosition getInitialState() { - return mLexer.getCurrentPosition(); - } - - @Override - public boolean stateEquals(LexerPosition state, LexerPosition another) { - return state.equals(another); - } - - @Override - public LineTokenizeResult tokenizeLine(CharSequence line, LexerPosition state) { - return null; - } - - @Override - public List generateSpansForLine(LineTokenizeResult tokens) { - return null; - } - - @Override - public void setReceiver(@Nullable StyleReceiver receiver) { - - } - - @Override - public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { - mLexer.start(content.getReference().toString()); - } - - @Override - public void insert(CharPosition start, CharPosition end, CharSequence insertedContent) { - - } - - @Override - public void delete(CharPosition start, CharPosition end, CharSequence deletedContent) { - - } - - @Override - public void rerun() { - - } - - @Override - public void destroy() { - - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/java/Java.java b/app/src/main/java/com/tyron/code/ui/editor/language/java/Java.java deleted file mode 100644 index fe069b727..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/java/Java.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.tyron.code.ui.editor.language.java; - -import com.tyron.code.ui.editor.language.Language; -import com.tyron.editor.Editor; - -import io.github.rosemoe.sora.lang.EmptyLanguage; -import io.github.rosemoe.sora2.interfaces.EditorLanguage; -import io.github.rosemoe.sora2.widget.CodeEditor; -import java.io.File; - -public class Java implements Language { - - @Override - public boolean isApplicable(File ext) { - return ext.getName().endsWith(".java"); - } - - @Override - public io.github.rosemoe.sora.lang.Language get(Editor editor) { - return new JavaLanguage(editor); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAnalyzer.java b/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAnalyzer.java deleted file mode 100644 index 2143e958f..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAnalyzer.java +++ /dev/null @@ -1,402 +0,0 @@ -package com.tyron.code.ui.editor.language.java; - -import android.content.SharedPreferences; -import android.util.Log; - -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.builder.model.SourceFileObject; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.JavaModule; -import com.tyron.builder.project.api.Module; -import com.tyron.code.ApplicationLoader; -import com.tyron.code.BuildConfig; -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; -import com.tyron.code.ui.editor.language.HighlightUtil; -import com.tyron.code.ui.editor.language.kotlin.KotlinLexer; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.common.util.Debouncer; -import com.tyron.completion.index.CompilerService; -import com.tyron.completion.java.JavaCompilerProvider; -import com.tyron.completion.java.compiler.CompileTask; -import com.tyron.completion.java.compiler.CompilerContainer; -import com.tyron.completion.java.compiler.JavaCompilerService; -import com.tyron.completion.java.provider.CompletionEngine; -import com.tyron.completion.java.util.ErrorCodes; -import com.tyron.completion.java.util.TreeUtil; -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Lexer; -import org.openjdk.javax.tools.Diagnostic; -import org.openjdk.javax.tools.JavaFileObject; -import org.openjdk.source.tree.BlockTree; -import org.openjdk.source.tree.ClassTree; -import org.openjdk.source.tree.CompilationUnitTree; -import org.openjdk.source.tree.MethodTree; -import org.openjdk.source.tree.Tree; -import org.openjdk.source.util.SourcePositions; -import org.openjdk.source.util.TreePath; -import org.openjdk.source.util.Trees; -import org.openjdk.tools.javac.api.ClientCodeWrapper; -import org.openjdk.tools.javac.tree.JCTree; -import org.openjdk.tools.javac.util.JCDiagnostic; - -import java.lang.ref.WeakReference; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Stack; -import java.util.stream.Collectors; - -import io.github.rosemoe.sora.lang.styling.CodeBlock; -import io.github.rosemoe.sora.lang.styling.MappedSpans; -import io.github.rosemoe.sora.lang.styling.Styles; -import io.github.rosemoe.sora2.data.BlockLine; -import io.github.rosemoe.sora2.data.NavigationItem; -import io.github.rosemoe.sora2.langs.java.JavaTextTokenizer; -import io.github.rosemoe.sora2.langs.java.Tokens; -import io.github.rosemoe.sora2.text.LineNumberCalculator; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.text.TextAnalyzer; -import io.github.rosemoe.sora2.widget.EditorColorScheme; -import kotlin.Unit; -import kotlin.jvm.functions.Function0; - -public class JavaAnalyzer extends AbstractCodeAnalyzer { - - private static final Debouncer sDebouncer = new Debouncer(Duration.ofMillis(700)); - private static final String TAG = JavaAnalyzer.class.getSimpleName(); - /** - * These are tokens that cannot exist before a valid function identifier - */ - private static final Tokens[] sKeywordsBeforeFunctionName = new Tokens[]{Tokens.RETURN, - Tokens.BREAK, Tokens.IF, Tokens.AND, Tokens.OR, Tokens.OREQ, Tokens.OROR, - Tokens.ANDAND, Tokens.ANDEQ, Tokens.RPAREN, Tokens.LPAREN, Tokens.LBRACE, Tokens.NEW, - Tokens.DOT, Tokens.SEMICOLON, Tokens.EQ, Tokens.NOTEQ, Tokens.NOT, Tokens.RBRACE, - Tokens.COMMA, Tokens.PLUS, Tokens.PLUSEQ, Tokens.MINUS, Tokens.MINUSEQ, Tokens.MULT, - Tokens.MULTEQ, Tokens.DIV, Tokens.DIVEQ}; - - private final WeakReference mEditorReference; - private List mDiagnostics; - private final List mPreviousDiagnostics = new ArrayList<>(); - private final SharedPreferences mPreferences; - - public JavaAnalyzer(Editor editor) { - mEditorReference = new WeakReference<>(editor); - mPreferences = ApplicationLoader.getDefaultPreferences(); - mDiagnostics = new ArrayList<>(); - } - - @Override - public void setDiagnostics(Editor editor, List diagnostics) { - mDiagnostics = diagnostics; - } - - @Override - public Lexer getLexer(CharStream input) { - return new KotlinLexer(input); - } - - @Override - public void analyzeInBackground(CharSequence contents) { - sDebouncer.schedule(cancel -> { - doAnalyzeInBackground(cancel, contents); - return Unit.INSTANCE; - }); - } - - private JavaCompilerService getCompiler(Editor editor) { - Project project = ProjectManager.getInstance().getCurrentProject(); - if (project == null) { - return null; - } - Module module = project.getModule(editor.getCurrentFile()); - if (module instanceof JavaModule) { - JavaCompilerProvider provider = - CompilerService.getInstance().getIndex(JavaCompilerProvider.KEY); - if (provider != null) { - return provider.getCompiler(project, (JavaModule) module); - } - } - return null; - } - - private void doAnalyzeInBackground(Function0 cancel, CharSequence contents) { - Editor editor = mEditorReference.get(); - if (editor == null) { - return; - } - if (cancel.invoke()) { - return; - } - // do not compile the file if it not yet closed as it will cause issues when - // compiling multiple files at the same time - if (mPreferences.getBoolean("code_editor_error_highlight", true) && !CompletionEngine.isIndexing()) { - JavaCompilerService service = getCompiler(editor); - if (service != null) { - try { - SourceFileObject sourceFileObject = - new SourceFileObject(editor.getCurrentFile().toPath(), - contents.toString(), Instant.now()); - CompilerContainer container = - service.compile(Collections.singletonList(sourceFileObject)); - container.run(task -> { - if (!cancel.invoke()) { - List collect = - task.diagnostics.stream() - .map(d -> modifyDiagnostic(task, d)) - .collect(Collectors.toList()); - editor.setDiagnostics(collect); - } - }); - } catch (Throwable e) { - if (BuildConfig.DEBUG) { - Log.e(TAG, "Unable to get diagnostics", e); - } - service.close(); - } - } - } - } - - private DiagnosticWrapper modifyDiagnostic(CompileTask task, Diagnostic extends JavaFileObject> diagnostic) { - DiagnosticWrapper wrapped = new DiagnosticWrapper(diagnostic); - - if (diagnostic instanceof ClientCodeWrapper.DiagnosticSourceUnwrapper) { - Trees trees = Trees.instance(task.task); - SourcePositions positions = trees.getSourcePositions(); - - JCDiagnostic jcDiagnostic = ((ClientCodeWrapper.DiagnosticSourceUnwrapper) diagnostic).d; - JCDiagnostic.DiagnosticPosition diagnosticPosition = - jcDiagnostic.getDiagnosticPosition(); - JCTree tree = diagnosticPosition.getTree(); - - if (tree != null) { - TreePath treePath = trees.getPath(task.root(), tree); - String code = jcDiagnostic.getCode(); - - long start = diagnostic.getStartPosition(); - long end = diagnostic.getEndPosition(); - switch (code) { - case ErrorCodes.MISSING_RETURN_STATEMENT: - TreePath block = TreeUtil.findParentOfType(treePath, - BlockTree.class); - if (block != null) { - // show error span only at the end parenthesis - end = positions.getEndPosition(task.root(), block.getLeaf()) + 1; - start = end - 2; - } - break; - } - - wrapped.setStartPosition(start); - wrapped.setEndPosition(end); - } - } - return wrapped; - } - - @Override - protected Styles analyze(StringBuilder text, Delegate delegate) { - Styles styles = new Styles(); - MappedSpans.Builder colors = new MappedSpans.Builder(); - - Editor editor = mEditorReference.get(); - if (editor == null) { - return styles; - } - JavaTextTokenizer tokenizer = new JavaTextTokenizer(text); - tokenizer.setCalculateLineColumn(false); - Tokens token, previous = Tokens.UNKNOWN; - int line = 0, column = 0; - LineNumberCalculator helper = new LineNumberCalculator(text); - - Stack stack = new Stack<>(); - List labels = new ArrayList<>(); - int maxSwitch = 1, currSwitch = 0; - - boolean first = true; - - while (!delegate.isCancelled()) { - try { - // directNextToken() does not skip any token - token = tokenizer.directNextToken(); - } catch (RuntimeException e) { - //When a spelling input is in process, this will happen because of format mismatch - token = Tokens.CHARACTER_LITERAL; - } - if (token == Tokens.EOF) { - break; - } - // Backup values because looking ahead in function name match will change them - int thisIndex = tokenizer.getIndex(); - int thisLength = tokenizer.getTokenLength(); - - switch (token) { - case WHITESPACE: - case NEWLINE: - if (first) { - colors.addNormalIfNull(); - } - break; - case IDENTIFIER: - //Add a identifier to auto complete - - //The previous so this will be the annotation's type name - if (previous == Tokens.AT) { - colors.addIfNeeded(line, column, EditorColorScheme.ANNOTATION); - break; - } - //Here we have to get next token to see if it is function - //We can only get the next token in stream. - //If more tokens required, we have to use a stack in tokenizer - Tokens next = tokenizer.directNextToken(); - //The next is LPAREN,so this is function name or type name - if (next == Tokens.LPAREN) { - boolean found = false; - for (Tokens before : sKeywordsBeforeFunctionName) { - if (before == previous) { - found = true; - break; - } - } - if (!found) { - colors.addIfNeeded(line, column, EditorColorScheme.FUNCTION_NAME); - tokenizer.pushBack(tokenizer.getTokenLength()); - break; - } - } - //Push back the next token - tokenizer.pushBack(tokenizer.getTokenLength()); - //This is a class definition - - colors.addIfNeeded(line, column, EditorColorScheme.TEXT_NORMAL); - break; - case CHARACTER_LITERAL: - case STRING: - case FLOATING_POINT_LITERAL: - case INTEGER_LITERAL: - colors.addIfNeeded(line, column, EditorColorScheme.LITERAL); - break; - case INT: - case LONG: - case BOOLEAN: - case BYTE: - case CHAR: - case FLOAT: - case DOUBLE: - case SHORT: - case VOID: - case ABSTRACT: - case ASSERT: - case CLASS: - case DO: - case FINAL: - case FOR: - case IF: - case NEW: - case PUBLIC: - case PRIVATE: - case PROTECTED: - case PACKAGE: - case RETURN: - case STATIC: - case SUPER: - case SWITCH: - case ELSE: - case VOLATILE: - case SYNCHRONIZED: - case STRICTFP: - case GOTO: - case CONTINUE: - case BREAK: - case TRANSIENT: - case TRY: - case CATCH: - case FINALLY: - case WHILE: - case CASE: - case DEFAULT: - case CONST: - case ENUM: - case EXTENDS: - case IMPLEMENTS: - case IMPORT: - case INSTANCEOF: - case INTERFACE: - case NATIVE: - case THIS: - case THROW: - case THROWS: - case TRUE: - case FALSE: - case NULL: - case SEMICOLON: - colors.addIfNeeded(line, column, EditorColorScheme.KEYWORD); - break; - case LBRACE: { - colors.addIfNeeded(line, column, EditorColorScheme.OPERATOR); - if (stack.isEmpty()) { - if (currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - currSwitch = 0; - } - currSwitch++; - CodeBlock block = styles.obtainNewBlock(); - block.startLine = line; - block.startColumn = column; - stack.push(block); - break; - } - case RBRACE: { - colors.addIfNeeded(line, column, EditorColorScheme.OPERATOR); - if (!stack.isEmpty()) { - CodeBlock block = stack.pop(); - block.endLine = line; - block.endColumn = column; - if (block.startLine != block.endLine) { - styles.addCodeBlock(block); - } - } - break; - } - case LINE_COMMENT: - case LONG_COMMENT: - colors.addIfNeeded(line, column, EditorColorScheme.COMMENT); - break; - default: - if (token == Tokens.LBRACK || (token == Tokens.RBRACK && previous == Tokens.LBRACK)) { - colors.addIfNeeded(line, column, EditorColorScheme.OPERATOR); - break; - } - colors.addIfNeeded(line, column, EditorColorScheme.OPERATOR); - } - - - first = false; - helper.update(thisLength); - line = helper.getLine(); - column = helper.getColumn(); - if (token != Tokens.WHITESPACE && token != Tokens.NEWLINE) { - previous = token; - } - } - if (stack.isEmpty()) { - if (currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - } - colors.determine(line); - styles.setSuppressSwitch(maxSwitch + 10); - styles.spans = colors.build(); - - if (mShouldAnalyzeInBg) { - analyzeInBackground(text); - } - HighlightUtil.markDiagnostics(editor, mDiagnostics, styles); - return styles; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAutoCompleteProvider.java deleted file mode 100644 index 4fd3312d6..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAutoCompleteProvider.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.tyron.code.ui.editor.language.java; - -import android.content.SharedPreferences; - -import androidx.annotation.Nullable; -import androidx.preference.PreferenceManager; - -import com.tyron.code.ApplicationLoader; -import com.tyron.code.ui.editor.language.AbstractAutoCompleteProvider; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.JavaModule; -import com.tyron.builder.project.api.Module; -import com.tyron.completion.main.CompletionEngine; -import com.tyron.completion.model.CompletionList; -import com.tyron.editor.Editor; - -import java.util.Optional; - -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.widget.CodeEditor; - -public class JavaAutoCompleteProvider extends AbstractAutoCompleteProvider { - - private final Editor mEditor; - private final SharedPreferences mPreferences; - - public JavaAutoCompleteProvider(Editor editor) { - mEditor = editor; - mPreferences = ApplicationLoader.getDefaultPreferences(); - } - - - @Nullable - @Override - public CompletionList getCompletionList( - String prefix, TextAnalyzeResult colors, int line, int column) { - if (!mPreferences.getBoolean("code_editor_completion", true)) { - return null; - } - - Project project = ProjectManager.getInstance().getCurrentProject(); - - if (project == null) { - return null; - } - - Module currentModule = project.getModule(mEditor.getCurrentFile()); - - if (currentModule instanceof JavaModule) { - Optional content = currentModule.getFileManager() - .getFileContent(mEditor.getCurrentFile()); - if (content.isPresent()) { - return CompletionEngine.getInstance() - .complete(project, - currentModule, - mEditor.getCurrentFile(), - content.get().toString(), - prefix, - line, - column, - mEditor.getCaret().getStart()); - } - } - return null; - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaLanguage.java b/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaLanguage.java deleted file mode 100644 index 1e0cac0e7..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaLanguage.java +++ /dev/null @@ -1,244 +0,0 @@ -package com.tyron.code.ui.editor.language.java; - -import android.os.Bundle; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.google.common.collect.Range; -import com.google.googlejavaformat.java.Formatter; -import com.google.googlejavaformat.java.FormatterException; -import com.google.googlejavaformat.java.JavaFormatterOptions; -import com.tyron.code.ui.editor.JavaCompletionItem; -import com.tyron.code.ui.editor.language.CompletionItemWrapper; -import com.tyron.completion.model.CompletionItem; -import com.tyron.completion.model.CompletionList; -import com.tyron.editor.Editor; - -import java.util.ArrayList; -import java.util.Collection; - -import io.github.rosemoe.editor.langs.java.JavaTextTokenizer; -import io.github.rosemoe.editor.langs.java.Tokens; -import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; -import io.github.rosemoe.sora.lang.completion.CompletionHelper; -import io.github.rosemoe.sora.lang.completion.CompletionPublisher; -import io.github.rosemoe.sora.lang.completion.SimpleCompletionItem; -import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; -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.text.TextUtils; -import io.github.rosemoe.sora.util.MyCharacter; -import io.github.rosemoe.sora.widget.SymbolPairMatch; - -public class JavaLanguage implements Language { - - private Editor mEditor; - - private final JavaAnalyzer mAnalyzer; - - public JavaLanguage(Editor editor) { - mEditor = editor; - mAnalyzer = new JavaAnalyzer(editor); - } - - public boolean isAutoCompleteChar(char p1) { - return p1 == '.' || MyCharacter.isJavaIdentifierPart(p1); - } - - public int getIndentAdvance(String p1) { - JavaTextTokenizer tokenizer = new JavaTextTokenizer(p1); - Tokens token; - int advance = 0; - while ((token = tokenizer.directNextToken()) != Tokens.EOF) { - switch (token) { - case LBRACE: - advance++; - break; -// case RBRACE: -// advance--; -// break; - } - } - advance = Math.max(0, advance); - return advance * 4; - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return mAnalyzer; - } - - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_SLIGHT; - } - - @Override - public void requireAutoComplete(@NonNull ContentReference content, - @NonNull CharPosition position, - @NonNull CompletionPublisher publisher, - @NonNull Bundle extraArguments) throws CompletionCancelledException { - char c = content.charAt(position.getIndex() - 1); - if (!isAutoCompleteChar(c)) { - return; - } - String prefix = CompletionHelper.computePrefix(content, position, this::isAutoCompleteChar); - JavaAutoCompleteProvider provider = new JavaAutoCompleteProvider(mEditor); - CompletionList list = provider.getCompletionList(prefix, null, position.getLine(), - position.getColumn()); - if (list == null) { - return; - } - for (CompletionItem item : list.getItems()) { - CompletionItemWrapper wrapper = new CompletionItemWrapper(item); - publisher.addItem(wrapper); - } - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - String text = content.getLine(line).substring(0, column); - return getIndentAdvance(text); - } - - @Override - public boolean useTab() { - return true; - } - - @Override - public CharSequence format(CharSequence p1) { - try { - return new Formatter(JavaFormatterOptions.builder().style(JavaFormatterOptions.Style.AOSP).build()).formatSourceAndFixImports(p1.toString()); - } catch (FormatterException e) { - Log.e("JavaFormatter", e.getMessage()); - return p1; - } - } - - public CharSequence format(CharSequence contents, int start, int end) { - JavaFormatterOptions options = - JavaFormatterOptions.builder().style(JavaFormatterOptions.Style.AOSP).build(); - Formatter formatter = new Formatter(options); - Range range = Range.closed(start, end); - Collection> ranges = new ArrayList<>(); - ranges.add(range); - try { - return formatter.formatSource(contents.toString(), ranges); - } catch (FormatterException e) { - Log.d("Formatter", "Unable to format file", e); - return contents; - } - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } - - private final NewlineHandler[] newLineHandlers = new NewlineHandler[]{new BraceHandler(), - new TwoIndentHandler(), new JavaDocStartHandler(), new JavaDocHandler()}; - - @Override - public NewlineHandler[] getNewlineHandlers() { - return newLineHandlers; - } - - @Override - public void destroy() { - - } - - class TwoIndentHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - Log.d("BeforeText", beforeText); - if (beforeText.replace("\r", "").trim().startsWith(".")) { - return false; - } - return beforeText.endsWith(")") && !afterText.startsWith(";"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceAfter = getIndentAdvance(afterText) + (4 * 2); - String text; - StringBuilder sb = new StringBuilder().append('\n').append(text = - TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); - int shiftLeft = 0; - return new NewlineHandleResult(sb, shiftLeft); - } - - - } - - class BraceHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.endsWith("{") && afterText.startsWith("}"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceBefore = getIndentAdvance(beforeText); - int advanceAfter = getIndentAdvance(afterText); - String text; - StringBuilder sb = - new StringBuilder("\n").append(TextUtils.createIndent(count + advanceBefore, - tabSize, useTab())).append('\n').append(text = - TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); - int shiftLeft = text.length() + 1; - return new NewlineHandleResult(sb, shiftLeft); - } - } - - class JavaDocStartHandler implements NewlineHandler { - - private boolean shouldCreateEnd = true; - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.trim().startsWith("/**"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceAfter = getIndentAdvance(afterText); - String text = ""; - StringBuilder sb = - new StringBuilder().append("\n").append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())).append(" * "); - if (shouldCreateEnd) { - sb.append("\n").append(text = TextUtils.createIndent(count + advanceAfter, - tabSize, useTab())).append(" */"); - } - return new NewlineHandleResult(sb, text.length() + 4); - } - } - - class JavaDocHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.trim().startsWith("*") && !beforeText.trim().startsWith("*/"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceAfter = getIndentAdvance(afterText); - StringBuilder sb = - new StringBuilder().append("\n").append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())).append("* "); - return new NewlineHandleResult(sb, 0); - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.interp b/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.interp deleted file mode 100644 index def52b815..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.interp +++ /dev/null @@ -1,40 +0,0 @@ -token literal names: -null -'[' -']' -'true' -'false' -'null' -':' -null -'{' -'}' -',' -null -null - -token symbolic names: -null -null -null -TRUE -FALSE -NULL -COLON -STRING -LBRACKET -RBRACKET -COMMA -NUMBER -WS - -rule names: -json -obj -pair -arr -value - - -atn: -[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 14, 58, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 19, 10, 3, 12, 3, 14, 3, 22, 11, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 28, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 7, 5, 38, 10, 5, 12, 5, 14, 5, 41, 11, 5, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 47, 10, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 56, 10, 6, 3, 6, 2, 2, 7, 2, 4, 6, 8, 10, 2, 2, 2, 62, 2, 12, 3, 2, 2, 2, 4, 27, 3, 2, 2, 2, 6, 29, 3, 2, 2, 2, 8, 46, 3, 2, 2, 2, 10, 55, 3, 2, 2, 2, 12, 13, 5, 10, 6, 2, 13, 3, 3, 2, 2, 2, 14, 15, 7, 10, 2, 2, 15, 20, 5, 6, 4, 2, 16, 17, 7, 12, 2, 2, 17, 19, 5, 6, 4, 2, 18, 16, 3, 2, 2, 2, 19, 22, 3, 2, 2, 2, 20, 18, 3, 2, 2, 2, 20, 21, 3, 2, 2, 2, 21, 23, 3, 2, 2, 2, 22, 20, 3, 2, 2, 2, 23, 24, 7, 11, 2, 2, 24, 28, 3, 2, 2, 2, 25, 26, 7, 10, 2, 2, 26, 28, 7, 11, 2, 2, 27, 14, 3, 2, 2, 2, 27, 25, 3, 2, 2, 2, 28, 5, 3, 2, 2, 2, 29, 30, 7, 9, 2, 2, 30, 31, 7, 8, 2, 2, 31, 32, 5, 10, 6, 2, 32, 7, 3, 2, 2, 2, 33, 34, 7, 3, 2, 2, 34, 39, 5, 10, 6, 2, 35, 36, 7, 12, 2, 2, 36, 38, 5, 10, 6, 2, 37, 35, 3, 2, 2, 2, 38, 41, 3, 2, 2, 2, 39, 37, 3, 2, 2, 2, 39, 40, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 39, 3, 2, 2, 2, 42, 43, 7, 4, 2, 2, 43, 47, 3, 2, 2, 2, 44, 45, 7, 3, 2, 2, 45, 47, 7, 4, 2, 2, 46, 33, 3, 2, 2, 2, 46, 44, 3, 2, 2, 2, 47, 9, 3, 2, 2, 2, 48, 56, 7, 9, 2, 2, 49, 56, 7, 13, 2, 2, 50, 56, 5, 4, 3, 2, 51, 56, 5, 8, 5, 2, 52, 56, 7, 5, 2, 2, 53, 56, 7, 6, 2, 2, 54, 56, 7, 7, 2, 2, 55, 48, 3, 2, 2, 2, 55, 49, 3, 2, 2, 2, 55, 50, 3, 2, 2, 2, 55, 51, 3, 2, 2, 2, 55, 52, 3, 2, 2, 2, 55, 53, 3, 2, 2, 2, 55, 54, 3, 2, 2, 2, 56, 11, 3, 2, 2, 2, 7, 20, 27, 39, 46, 55] \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.tokens b/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.tokens deleted file mode 100644 index 29212732a..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.tokens +++ /dev/null @@ -1,21 +0,0 @@ -T__0=1 -T__1=2 -TRUE=3 -FALSE=4 -NULL=5 -COLON=6 -STRING=7 -LBRACKET=8 -RBRACKET=9 -COMMA=10 -NUMBER=11 -WS=12 -'['=1 -']'=2 -'true'=3 -'false'=4 -'null'=5 -':'=6 -'{'=8 -'}'=9 -','=10 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.interp b/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.interp deleted file mode 100644 index f20f69f9c..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.interp +++ /dev/null @@ -1,59 +0,0 @@ -token literal names: -null -'[' -']' -'true' -'false' -'null' -':' -null -'{' -'}' -',' -null -null - -token symbolic names: -null -null -null -TRUE -FALSE -NULL -COLON -STRING -LBRACKET -RBRACKET -COMMA -NUMBER -WS - -rule names: -T__0 -T__1 -TRUE -FALSE -NULL -COLON -STRING -LBRACKET -RBRACKET -COMMA -ESC -UNICODE -HEX -SAFECODEPOINT -NUMBER -INT -EXP -WS - -channel names: -DEFAULT_TOKEN_CHANNEL -HIDDEN - -mode names: -DEFAULT_MODE - -atn: -[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 14, 130, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 7, 8, 65, 10, 8, 12, 8, 14, 8, 68, 11, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 5, 12, 81, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 3, 16, 5, 16, 94, 10, 16, 3, 16, 3, 16, 3, 16, 6, 16, 99, 10, 16, 13, 16, 14, 16, 100, 5, 16, 103, 10, 16, 3, 16, 5, 16, 106, 10, 16, 3, 17, 3, 17, 3, 17, 7, 17, 111, 10, 17, 12, 17, 14, 17, 114, 11, 17, 5, 17, 116, 10, 17, 3, 18, 3, 18, 5, 18, 120, 10, 18, 3, 18, 3, 18, 3, 19, 6, 19, 125, 10, 19, 13, 19, 14, 19, 126, 3, 19, 3, 19, 2, 2, 20, 3, 3, 5, 4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 10, 19, 11, 21, 12, 23, 2, 25, 2, 27, 2, 29, 2, 31, 13, 33, 2, 35, 2, 37, 14, 3, 2, 10, 10, 2, 36, 36, 49, 49, 94, 94, 100, 100, 104, 104, 112, 112, 116, 116, 118, 118, 5, 2, 50, 59, 67, 72, 99, 104, 5, 2, 2, 33, 36, 36, 94, 94, 3, 2, 50, 59, 3, 2, 51, 59, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47, 5, 2, 11, 12, 15, 15, 34, 34, 2, 134, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 3, 39, 3, 2, 2, 2, 5, 41, 3, 2, 2, 2, 7, 43, 3, 2, 2, 2, 9, 48, 3, 2, 2, 2, 11, 54, 3, 2, 2, 2, 13, 59, 3, 2, 2, 2, 15, 61, 3, 2, 2, 2, 17, 71, 3, 2, 2, 2, 19, 73, 3, 2, 2, 2, 21, 75, 3, 2, 2, 2, 23, 77, 3, 2, 2, 2, 25, 82, 3, 2, 2, 2, 27, 88, 3, 2, 2, 2, 29, 90, 3, 2, 2, 2, 31, 93, 3, 2, 2, 2, 33, 115, 3, 2, 2, 2, 35, 117, 3, 2, 2, 2, 37, 124, 3, 2, 2, 2, 39, 40, 7, 93, 2, 2, 40, 4, 3, 2, 2, 2, 41, 42, 7, 95, 2, 2, 42, 6, 3, 2, 2, 2, 43, 44, 7, 118, 2, 2, 44, 45, 7, 116, 2, 2, 45, 46, 7, 119, 2, 2, 46, 47, 7, 103, 2, 2, 47, 8, 3, 2, 2, 2, 48, 49, 7, 104, 2, 2, 49, 50, 7, 99, 2, 2, 50, 51, 7, 110, 2, 2, 51, 52, 7, 117, 2, 2, 52, 53, 7, 103, 2, 2, 53, 10, 3, 2, 2, 2, 54, 55, 7, 112, 2, 2, 55, 56, 7, 119, 2, 2, 56, 57, 7, 110, 2, 2, 57, 58, 7, 110, 2, 2, 58, 12, 3, 2, 2, 2, 59, 60, 7, 60, 2, 2, 60, 14, 3, 2, 2, 2, 61, 66, 7, 36, 2, 2, 62, 65, 5, 23, 12, 2, 63, 65, 5, 29, 15, 2, 64, 62, 3, 2, 2, 2, 64, 63, 3, 2, 2, 2, 65, 68, 3, 2, 2, 2, 66, 64, 3, 2, 2, 2, 66, 67, 3, 2, 2, 2, 67, 69, 3, 2, 2, 2, 68, 66, 3, 2, 2, 2, 69, 70, 7, 36, 2, 2, 70, 16, 3, 2, 2, 2, 71, 72, 7, 125, 2, 2, 72, 18, 3, 2, 2, 2, 73, 74, 7, 127, 2, 2, 74, 20, 3, 2, 2, 2, 75, 76, 7, 46, 2, 2, 76, 22, 3, 2, 2, 2, 77, 80, 7, 94, 2, 2, 78, 81, 9, 2, 2, 2, 79, 81, 5, 25, 13, 2, 80, 78, 3, 2, 2, 2, 80, 79, 3, 2, 2, 2, 81, 24, 3, 2, 2, 2, 82, 83, 7, 119, 2, 2, 83, 84, 5, 27, 14, 2, 84, 85, 5, 27, 14, 2, 85, 86, 5, 27, 14, 2, 86, 87, 5, 27, 14, 2, 87, 26, 3, 2, 2, 2, 88, 89, 9, 3, 2, 2, 89, 28, 3, 2, 2, 2, 90, 91, 10, 4, 2, 2, 91, 30, 3, 2, 2, 2, 92, 94, 7, 47, 2, 2, 93, 92, 3, 2, 2, 2, 93, 94, 3, 2, 2, 2, 94, 95, 3, 2, 2, 2, 95, 102, 5, 33, 17, 2, 96, 98, 7, 48, 2, 2, 97, 99, 9, 5, 2, 2, 98, 97, 3, 2, 2, 2, 99, 100, 3, 2, 2, 2, 100, 98, 3, 2, 2, 2, 100, 101, 3, 2, 2, 2, 101, 103, 3, 2, 2, 2, 102, 96, 3, 2, 2, 2, 102, 103, 3, 2, 2, 2, 103, 105, 3, 2, 2, 2, 104, 106, 5, 35, 18, 2, 105, 104, 3, 2, 2, 2, 105, 106, 3, 2, 2, 2, 106, 32, 3, 2, 2, 2, 107, 116, 7, 50, 2, 2, 108, 112, 9, 6, 2, 2, 109, 111, 9, 5, 2, 2, 110, 109, 3, 2, 2, 2, 111, 114, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 116, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 115, 107, 3, 2, 2, 2, 115, 108, 3, 2, 2, 2, 116, 34, 3, 2, 2, 2, 117, 119, 9, 7, 2, 2, 118, 120, 9, 8, 2, 2, 119, 118, 3, 2, 2, 2, 119, 120, 3, 2, 2, 2, 120, 121, 3, 2, 2, 2, 121, 122, 5, 33, 17, 2, 122, 36, 3, 2, 2, 2, 123, 125, 9, 9, 2, 2, 124, 123, 3, 2, 2, 2, 125, 126, 3, 2, 2, 2, 126, 124, 3, 2, 2, 2, 126, 127, 3, 2, 2, 2, 127, 128, 3, 2, 2, 2, 128, 129, 8, 19, 2, 2, 129, 38, 3, 2, 2, 2, 14, 2, 64, 66, 80, 93, 100, 102, 105, 112, 115, 119, 126, 3, 8, 2, 2] \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.java b/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.java deleted file mode 100644 index 54f3a31ae..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.java +++ /dev/null @@ -1,153 +0,0 @@ -// Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.*; -import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.misc.*; - -@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) -public class JSONLexer extends Lexer { - static { RuntimeMetaData.checkVersion("4.9.2", RuntimeMetaData.VERSION); } - - protected static final DFA[] _decisionToDFA; - protected static final PredictionContextCache _sharedContextCache = - new PredictionContextCache(); - public static final int - T__0=1, T__1=2, TRUE=3, FALSE=4, NULL=5, COLON=6, STRING=7, LBRACKET=8, - RBRACKET=9, COMMA=10, NUMBER=11, WS=12; - public static String[] channelNames = { - "DEFAULT_TOKEN_CHANNEL", "HIDDEN" - }; - - public static String[] modeNames = { - "DEFAULT_MODE" - }; - - private static String[] makeRuleNames() { - return new String[] { - "T__0", "T__1", "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACKET", - "RBRACKET", "COMMA", "ESC", "UNICODE", "HEX", "SAFECODEPOINT", "NUMBER", - "INT", "EXP", "WS" - }; - } - public static final String[] ruleNames = makeRuleNames(); - - private static String[] makeLiteralNames() { - return new String[] { - null, "'['", "']'", "'true'", "'false'", "'null'", "':'", null, "'{'", - "'}'", "','" - }; - } - private static final String[] _LITERAL_NAMES = makeLiteralNames(); - private static String[] makeSymbolicNames() { - return new String[] { - null, null, null, "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACKET", - "RBRACKET", "COMMA", "NUMBER", "WS" - }; - } - private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); - public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); - - /** - * @deprecated Use {@link #VOCABULARY} instead. - */ - @Deprecated - public static final String[] tokenNames; - static { - tokenNames = new String[_SYMBOLIC_NAMES.length]; - for (int i = 0; i < tokenNames.length; i++) { - tokenNames[i] = VOCABULARY.getLiteralName(i); - if (tokenNames[i] == null) { - tokenNames[i] = VOCABULARY.getSymbolicName(i); - } - - if (tokenNames[i] == null) { - tokenNames[i] = ""; - } - } - } - - @Override - @Deprecated - public String[] getTokenNames() { - return tokenNames; - } - - @Override - - public Vocabulary getVocabulary() { - return VOCABULARY; - } - - - public JSONLexer(CharStream input) { - super(input); - _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); - } - - @Override - public String getGrammarFileName() { return "JSON.g4"; } - - @Override - public String[] getRuleNames() { return ruleNames; } - - @Override - public String getSerializedATN() { return _serializedATN; } - - @Override - public String[] getChannelNames() { return channelNames; } - - @Override - public String[] getModeNames() { return modeNames; } - - @Override - public ATN getATN() { return _ATN; } - - public static final String _serializedATN = - "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2\16\u0082\b\1\4\2"+ - "\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4"+ - "\13\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22"+ - "\t\22\4\23\t\23\3\2\3\2\3\3\3\3\3\4\3\4\3\4\3\4\3\4\3\5\3\5\3\5\3\5\3"+ - "\5\3\5\3\6\3\6\3\6\3\6\3\6\3\7\3\7\3\b\3\b\3\b\7\bA\n\b\f\b\16\bD\13\b"+ - "\3\b\3\b\3\t\3\t\3\n\3\n\3\13\3\13\3\f\3\f\3\f\5\fQ\n\f\3\r\3\r\3\r\3"+ - "\r\3\r\3\r\3\16\3\16\3\17\3\17\3\20\5\20^\n\20\3\20\3\20\3\20\6\20c\n"+ - "\20\r\20\16\20d\5\20g\n\20\3\20\5\20j\n\20\3\21\3\21\3\21\7\21o\n\21\f"+ - "\21\16\21r\13\21\5\21t\n\21\3\22\3\22\5\22x\n\22\3\22\3\22\3\23\6\23}"+ - "\n\23\r\23\16\23~\3\23\3\23\2\2\24\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n"+ - "\23\13\25\f\27\2\31\2\33\2\35\2\37\r!\2#\2%\16\3\2\n\n\2$$\61\61^^ddh"+ - "hppttvv\5\2\62;CHch\5\2\2!$$^^\3\2\62;\3\2\63;\4\2GGgg\4\2--//\5\2\13"+ - "\f\17\17\"\"\2\u0086\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2"+ - "\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3"+ - "\2\2\2\2\37\3\2\2\2\2%\3\2\2\2\3\'\3\2\2\2\5)\3\2\2\2\7+\3\2\2\2\t\60"+ - "\3\2\2\2\13\66\3\2\2\2\r;\3\2\2\2\17=\3\2\2\2\21G\3\2\2\2\23I\3\2\2\2"+ - "\25K\3\2\2\2\27M\3\2\2\2\31R\3\2\2\2\33X\3\2\2\2\35Z\3\2\2\2\37]\3\2\2"+ - "\2!s\3\2\2\2#u\3\2\2\2%|\3\2\2\2\'(\7]\2\2(\4\3\2\2\2)*\7_\2\2*\6\3\2"+ - "\2\2+,\7v\2\2,-\7t\2\2-.\7w\2\2./\7g\2\2/\b\3\2\2\2\60\61\7h\2\2\61\62"+ - "\7c\2\2\62\63\7n\2\2\63\64\7u\2\2\64\65\7g\2\2\65\n\3\2\2\2\66\67\7p\2"+ - "\2\678\7w\2\289\7n\2\29:\7n\2\2:\f\3\2\2\2;<\7<\2\2<\16\3\2\2\2=B\7$\2"+ - "\2>A\5\27\f\2?A\5\35\17\2@>\3\2\2\2@?\3\2\2\2AD\3\2\2\2B@\3\2\2\2BC\3"+ - "\2\2\2CE\3\2\2\2DB\3\2\2\2EF\7$\2\2F\20\3\2\2\2GH\7}\2\2H\22\3\2\2\2I"+ - "J\7\177\2\2J\24\3\2\2\2KL\7.\2\2L\26\3\2\2\2MP\7^\2\2NQ\t\2\2\2OQ\5\31"+ - "\r\2PN\3\2\2\2PO\3\2\2\2Q\30\3\2\2\2RS\7w\2\2ST\5\33\16\2TU\5\33\16\2"+ - "UV\5\33\16\2VW\5\33\16\2W\32\3\2\2\2XY\t\3\2\2Y\34\3\2\2\2Z[\n\4\2\2["+ - "\36\3\2\2\2\\^\7/\2\2]\\\3\2\2\2]^\3\2\2\2^_\3\2\2\2_f\5!\21\2`b\7\60"+ - "\2\2ac\t\5\2\2ba\3\2\2\2cd\3\2\2\2db\3\2\2\2de\3\2\2\2eg\3\2\2\2f`\3\2"+ - "\2\2fg\3\2\2\2gi\3\2\2\2hj\5#\22\2ih\3\2\2\2ij\3\2\2\2j \3\2\2\2kt\7\62"+ - "\2\2lp\t\6\2\2mo\t\5\2\2nm\3\2\2\2or\3\2\2\2pn\3\2\2\2pq\3\2\2\2qt\3\2"+ - "\2\2rp\3\2\2\2sk\3\2\2\2sl\3\2\2\2t\"\3\2\2\2uw\t\7\2\2vx\t\b\2\2wv\3"+ - "\2\2\2wx\3\2\2\2xy\3\2\2\2yz\5!\21\2z$\3\2\2\2{}\t\t\2\2|{\3\2\2\2}~\3"+ - "\2\2\2~|\3\2\2\2~\177\3\2\2\2\177\u0080\3\2\2\2\u0080\u0081\b\23\2\2\u0081"+ - "&\3\2\2\2\16\2@BP]dfipsw~\3\b\2\2"; - public static final ATN _ATN = - new ATNDeserializer().deserialize(_serializedATN.toCharArray()); - static { - _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; - for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { - _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.tokens b/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.tokens deleted file mode 100644 index 29212732a..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.tokens +++ /dev/null @@ -1,21 +0,0 @@ -T__0=1 -T__1=2 -TRUE=3 -FALSE=4 -NULL=5 -COLON=6 -STRING=7 -LBRACKET=8 -RBRACKET=9 -COMMA=10 -NUMBER=11 -WS=12 -'['=1 -']'=2 -'true'=3 -'false'=4 -'null'=5 -':'=6 -'{'=8 -'}'=9 -','=10 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/Json.java b/app/src/main/java/com/tyron/code/ui/editor/language/json/Json.java deleted file mode 100644 index c11317e68..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/Json.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.tyron.code.ui.editor.language.json; - -import com.tyron.code.ui.editor.language.Language; -import com.tyron.editor.Editor; - -import java.io.File; - -import io.github.rosemoe.sora.widget.CodeEditor; - -public class Json implements Language { - @Override - public boolean isApplicable(File ext) { - return ext.getName().endsWith(".json"); - } - - @Override - public io.github.rosemoe.sora.lang.Language get(Editor editor) { - return new JsonLanguage(editor); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JsonLanguage.java b/app/src/main/java/com/tyron/code/ui/editor/language/json/JsonLanguage.java deleted file mode 100644 index 44e45a7ca..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JsonLanguage.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.tyron.code.ui.editor.language.json; - -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.tyron.completion.java.rewrite.EditHelper; -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.Token; -import org.apache.commons.io.input.CharSequenceReader; - -import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; -import io.github.rosemoe.sora.lang.analysis.StyleReceiver; -import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; -import io.github.rosemoe.sora.lang.completion.CompletionPublisher; -import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; -import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; -import io.github.rosemoe.sora.lang.styling.Styles; -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.CodeEditor; -import io.github.rosemoe.sora.widget.SymbolPairMatch; - -public class JsonLanguage implements Language { - - private final Editor mEditor; - - private final JsonAnalyzer mAnalyzer; - - public JsonLanguage(Editor editor) { - mEditor = editor; - - mAnalyzer = new JsonAnalyzer(); - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return mAnalyzer; - } - - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_STRONG; - } - - @Override - public void requireAutoComplete(@NonNull ContentReference content, @NonNull CharPosition position, @NonNull CompletionPublisher publisher, @NonNull Bundle extraArguments) throws CompletionCancelledException { - - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return getIndentAdvance(String.valueOf(content.getReference()), line, column); - } - - private int getIndentAdvance(String content, int line, int column) { - JSONLexer lexer = new JSONLexer(CharStreams.fromString(content)); - Token token; - int advance = 0; - while ((token = lexer.nextToken()).getType() != Token.EOF) { - if (token.getType() == JSONLexer.LBRACKET) { - advance++; - } - } - advance = Math.max(0, advance); - return advance * 2; - } - - @Override - public boolean useTab() { - return false; - } - - @Override - public CharSequence format(CharSequence text) { - try { - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - JsonElement jsonElement = JsonParser.parseString(text.toString()); - return gson.toJson(jsonElement); - } catch (Throwable e) { - // format error, return the original string - return text; - } - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return new NewlineHandler[] { - new IndentHandler("{", "}"), - new IndentHandler("[", "]") - }; - } - - @Override - public void destroy() { - - } - - class IndentHandler implements NewlineHandler { - - private final String start; - private final String end; - - public IndentHandler(String start, String end) { - this.start = start; - this.end = end; - } - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.endsWith(start) && afterText.startsWith(end); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceBefore = getIndentAdvance(beforeText, -1, -1); - int advanceAfter = getIndentAdvance(afterText, -1, -1); - String text; - StringBuilder sb = new StringBuilder("\n") - .append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab())) - .append('\n') - .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); - int shiftLeft = text.length() + 1; - return new NewlineHandleResult(sb, shiftLeft); - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/Kotlin.java b/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/Kotlin.java deleted file mode 100644 index cb565bb69..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/Kotlin.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.tyron.code.ui.editor.language.kotlin; - -import com.tyron.code.ui.editor.language.Language; -import com.tyron.editor.Editor; - -import java.io.File; - -import io.github.rosemoe.sora2.interfaces.EditorLanguage; -import io.github.rosemoe.sora2.widget.CodeEditor; - -public class Kotlin implements Language { - @Override - public boolean isApplicable(File ext) { - return ext.getName().endsWith(".kt"); - } - - @Override - public io.github.rosemoe.sora.lang.Language get(Editor editor) { - return new KotlinLanguage(editor); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAnalyzer.java b/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAnalyzer.java deleted file mode 100644 index 7af2da9dc..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAnalyzer.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.tyron.code.ui.editor.language.kotlin; - -import android.graphics.Color; -import android.util.Log; - -import androidx.preference.PreferenceManager; - -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.AndroidModule; -import com.tyron.builder.project.api.Module; -import com.tyron.code.ApplicationLoader; -import com.tyron.code.BuildConfig; -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; -import com.tyron.code.ui.editor.language.HighlightUtil; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.common.SharedPreferenceKeys; -import com.tyron.completion.progress.ProgressManager; -import com.tyron.editor.Editor; -import com.tyron.kotlin_completion.CompletionEngine; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CodePointCharStream; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenSource; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; - -import io.github.rosemoe.sora.lang.styling.Styles; -import io.github.rosemoe.sora2.data.BlockLine; -import io.github.rosemoe.sora2.data.Span; -import io.github.rosemoe.sora2.interfaces.CodeAnalyzer; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.text.TextAnalyzer; -import io.github.rosemoe.sora2.widget.CodeEditor; -import io.github.rosemoe.sora2.widget.EditorColorScheme; - -public class KotlinAnalyzer extends AbstractCodeAnalyzer { - - private final WeakReference mEditorReference; - private final List mDiagnostics; - - public KotlinAnalyzer(Editor editor) { - mEditorReference = new WeakReference<>(editor); - mDiagnostics = new ArrayList<>(); - } - - @Override - public void setup() { - putColor(EditorColorScheme.KEYWORD, KotlinLexer.OVERRIDE, - KotlinLexer.FUN, KotlinLexer.PACKAGE, KotlinLexer.IMPORT, - KotlinLexer.CLASS, KotlinLexer.INTERFACE); - - // todo add block lines - } - - @Override - public void setDiagnostics(Editor editor, List diagnostics) { - mDiagnostics.clear(); - mDiagnostics.addAll(diagnostics); - } - - @Override - public Lexer getLexer(CharStream input) { - return new KotlinLexer(input); - } - - @Override - public void analyzeInBackground(CharSequence content) { - Editor editor = mEditorReference.get(); - if (editor == null) { - return; - } - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - if (currentProject != null) { - Module module = currentProject.getModule(editor.getCurrentFile()); - if (module instanceof AndroidModule) { - if (ApplicationLoader.getDefaultPreferences() - .getBoolean(SharedPreferenceKeys.KOTLIN_HIGHLIGHTING, true)) { - ProgressManager.getInstance().runLater(() -> { - CompletionEngine.getInstance((AndroidModule) module) - .doLint(editor.getCurrentFile(), content.toString(), editor::setDiagnostics); - }, 1500); - } - } - } - } - - private static class UnknownToken implements Token { - - public static UnknownToken INSTANCE = new UnknownToken(); - - @Override - public String getText() { - return ""; - } - - @Override - public int getType() { - return -1; - } - - @Override - public int getLine() { - return 0; - } - - @Override - public int getCharPositionInLine() { - return 0; - } - - @Override - public int getChannel() { - return 0; - } - - @Override - public int getTokenIndex() { - return 0; - } - - @Override - public int getStartIndex() { - return 0; - } - - @Override - public int getStopIndex() { - return 0; - } - - @Override - public TokenSource getTokenSource() { - return null; - } - - @Override - public CharStream getInputStream() { - return null; - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAutoCompleteProvider.java deleted file mode 100644 index de6fc2daf..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAutoCompleteProvider.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.tyron.code.ui.editor.language.kotlin; - -import android.content.SharedPreferences; - -import androidx.annotation.Nullable; -import androidx.preference.PreferenceManager; - -import com.tyron.code.ui.editor.language.AbstractAutoCompleteProvider; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.AndroidModule; -import com.tyron.builder.project.api.Module; -import com.tyron.common.SharedPreferenceKeys; -import com.tyron.completion.model.CompletionList; -import com.tyron.kotlin_completion.CompletionEngine; - -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.widget.CodeEditor; - -public class KotlinAutoCompleteProvider extends AbstractAutoCompleteProvider { - - private static final String TAG = KotlinAutoCompleteProvider.class.getSimpleName(); - - private final CodeEditor mEditor; - private final SharedPreferences mPreferences; - - - public KotlinAutoCompleteProvider(CodeEditor editor) { - mEditor = editor; - mPreferences = PreferenceManager.getDefaultSharedPreferences(editor.getContext()); - } - - @Nullable - @Override - public CompletionList getCompletionList( - String prefix, TextAnalyzeResult colors, int line, int column) { - if (!mPreferences.getBoolean(SharedPreferenceKeys.KOTLIN_COMPLETIONS, false)) { - return null; - } - - if (com.tyron.completion.java.provider.CompletionEngine.isIndexing()) { - return null; - } - - if (!mPreferences.getBoolean(SharedPreferenceKeys.KOTLIN_COMPLETIONS, false)) { - return null; - } - - Project project = ProjectManager.getInstance() - .getCurrentProject(); - if (project == null) { - return null; - } - - Module currentModule = project.getModule(mEditor.getCurrentFile()); - - if (!(currentModule instanceof AndroidModule)) { - return null; - } - - CompletionEngine engine = CompletionEngine.getInstance((AndroidModule) currentModule); - - if (engine.isIndexing()) { - return null; - } - - // waiting for code editor to support async code completions - return engine.complete(mEditor.getCurrentFile(), mEditor.getText().toString(), prefix, line, column, mEditor.getCursor().getLeft()); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLanguage.java b/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLanguage.java deleted file mode 100644 index c82787a18..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLanguage.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.tyron.code.ui.editor.language.kotlin; - -import android.os.Bundle; - -import androidx.annotation.NonNull; - -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.Token; - -import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; -import io.github.rosemoe.sora.lang.completion.CompletionPublisher; -import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; -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.text.TextUtils; -import io.github.rosemoe.sora.widget.SymbolPairMatch; - -public class KotlinLanguage implements Language { - - private final Editor mEditor; - private final KotlinAnalyzer mAnalyzer; - - public KotlinLanguage(Editor editor) { - mEditor = editor; - mAnalyzer = new KotlinAnalyzer(mEditor); - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return mAnalyzer; - } - - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_SLIGHT; - } - - @Override - public void requireAutoComplete(@NonNull ContentReference content, @NonNull CharPosition position, @NonNull CompletionPublisher publisher, @NonNull Bundle extraArguments) throws CompletionCancelledException { - - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return getIndentAdvance(String.valueOf(content.getReference())); - } - - public int getIndentAdvance(String p1) { - KotlinLexer lexer = new KotlinLexer(CharStreams.fromString(p1)); - Token token; - int advance = 0; - while ((token = lexer.nextToken()) != null) { - if (token.getType() == KotlinLexer.EOF) { - break; - } - if (token.getType() == KotlinLexer.LCURL) { - advance++; - /*case RBRACE: - advance--; - break;*/ - } - } - advance = Math.max(0, advance); - return advance * 4; - } - - @Override - public boolean useTab() { - return true; - } - - @Override - public CharSequence format(CharSequence text) { - return text; - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return handlers; - } - - @Override - public void destroy() { - - } - - private final NewlineHandler[] handlers = new NewlineHandler[]{new BraceHandler()}; - - class BraceHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.endsWith("{") && afterText.startsWith("}"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceBefore = getIndentAdvance(beforeText); - int advanceAfter = getIndentAdvance(afterText); - String text; - StringBuilder sb = new StringBuilder("\n") - .append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab())) - .append('\n') - .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); - int shiftLeft = text.length() + 1; - return new NewlineHandleResult(sb, shiftLeft); - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/BasicXmlPullAnalyzer.java b/app/src/main/java/com/tyron/code/ui/editor/language/xml/BasicXmlPullAnalyzer.java deleted file mode 100644 index bde05651e..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/BasicXmlPullAnalyzer.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.tyron.code.ui.editor.language.xml; - -import com.tyron.builder.util.CharSequenceReader; -import com.tyron.code.ui.editor.language.HighlightUtil; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -import io.github.rosemoe.sora2.interfaces.CodeAnalyzer; -import io.github.rosemoe.sora2.text.LineNumberCalculator; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.text.TextAnalyzer; - -public class BasicXmlPullAnalyzer implements CodeAnalyzer { - - @Override - public void analyze(CharSequence content, TextAnalyzeResult result, TextAnalyzer.AnalyzeThread.Delegate delegate) { - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); - XmlPullParser parser = factory.newPullParser(); - - LineNumberCalculator calculator = new LineNumberCalculator(content); - calculator.update(content.length()); - int errLine = 0; - int errColumn = 0; - parser.setInput(new CharSequenceReader(content)); - while (delegate.shouldAnalyze()) { - try { - if (calculator.getLine() + 1 == parser.getLineNumber() && - calculator.getColumn() + 1 == parser.getColumnNumber()) { - break; - } - parser.next(); - } catch (XmlPullParserException e) { - if (errLine == parser.getLineNumber() && errColumn == parser.getColumnNumber()) { - break; - } - errLine = parser.getLineNumber(); - errColumn = parser.getColumnNumber(); - HighlightUtil.setErrorSpan(result, errLine, errColumn); - } - } - } catch (Exception ignored) { - - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/LanguageXML.java b/app/src/main/java/com/tyron/code/ui/editor/language/xml/LanguageXML.java deleted file mode 100644 index 15b9c2708..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/LanguageXML.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.tyron.code.ui.editor.language.xml; - -import android.os.Bundle; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tyron.builder.compiler.manifest.xml.XmlFormatPreferences; -import com.tyron.builder.compiler.manifest.xml.XmlFormatStyle; -import com.tyron.builder.compiler.manifest.xml.XmlPrettyPrinter; -import com.tyron.code.util.ProjectUtils; -import com.tyron.completion.xml.lexer.XMLLexer; -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.Token; - -import java.io.File; -import java.util.List; - -import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; -import io.github.rosemoe.sora.lang.completion.CompletionHelper; -import io.github.rosemoe.sora.lang.completion.CompletionPublisher; -import io.github.rosemoe.sora.lang.completion.SimpleCompletionItem; -import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; -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.text.TextUtils; -import io.github.rosemoe.sora.util.MyCharacter; -import io.github.rosemoe.sora.widget.CodeEditor; -import io.github.rosemoe.sora.widget.SymbolPairMatch; -import io.github.rosemoe.sora2.data.CompletionItem; - -public class LanguageXML implements Language { - - private final Editor mEditor; - - private final XMLAnalyzer mAnalyzer; - - public LanguageXML(Editor codeEditor) { - mEditor = codeEditor; - - mAnalyzer = new XMLAnalyzer(codeEditor); - } - - public boolean isAutoCompleteChar(char ch) { - return MyCharacter.isJavaIdentifierPart(ch) - || ch == '<' - || ch == '/' - || ch == ':' - || ch == '.'; - } - - @Override - public boolean useTab() { - return true; - } - - @Override - public CharSequence format(CharSequence text) { - XmlFormatPreferences preferences = XmlFormatPreferences.defaults(); - File file = mEditor.getCurrentFile(); - CharSequence formatted = null; - if ("AndroidManifest.xml".equals(file.getName())) { - formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), - preferences, XmlFormatStyle.MANIFEST, "\n"); - } else { - if (ProjectUtils.isLayoutXMLFile(file)) { - formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), - preferences, XmlFormatStyle.LAYOUT, "\n"); - } else if (ProjectUtils.isResourceXMLFile(file)) { - formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), - preferences, XmlFormatStyle.RESOURCE, "\n"); - } - } - if (formatted == null) { - formatted = text; - } - return formatted; - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return new NewlineHandler[]{new StartTagHandler()}; - } - - @Override - public void destroy() { - - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return mAnalyzer; - } - - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_SLIGHT; - } - - @Override - public void requireAutoComplete(@NonNull ContentReference content, - @NonNull CharPosition position, - @NonNull CompletionPublisher publisher, - @NonNull Bundle extraArguments) throws CompletionCancelledException { - String prefix = CompletionHelper.computePrefix(content, position, this::isAutoCompleteChar); - List items = - new XMLAutoCompleteProvider(mEditor).getAutoCompleteItems(prefix, null, - position.getLine(), position.getColumn()); - for (CompletionItem item : items) { - SimpleCompletionItem simpleCompletionItem = - new SimpleCompletionItem(item.label, item.desc, prefix.length(), item.commit); - publisher.addItem(simpleCompletionItem); - } - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return getIndentAdvance(String.valueOf(content.getReference())); - } - - public int getIndentAdvance(String content) { - XMLLexer lexer = new XMLLexer(CharStreams.fromString(content)); - int advance = 0; - Token token; - while ((token = lexer.nextToken()) != null) { - if (token.getType() == XMLLexer.EOF) { - break; - } - - if (token.getType() == XMLLexer.OPEN) { - advance++; - } else if (token.getType() == XMLLexer.SLASH_CLOSE) { - advance--; - } else if (token.getType() == XMLLexer.CLOSE) { - advance--; - } - } - advance = Math.max(0, advance); - return advance * 4; - } - - private class StartTagHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - Log.d("StartTagHandler", "beforeText: " + beforeText + " afterText: " + afterText); - return beforeText.trim().startsWith("<"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - String text; - StringBuilder sb = new StringBuilder() - .append("\n") - .append(text = TextUtils.createIndent(count + 4, tabSize, useTab())); - return new NewlineHandleResult(sb, 0); - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAnalyzer.java b/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAnalyzer.java deleted file mode 100644 index 064647635..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAnalyzer.java +++ /dev/null @@ -1,358 +0,0 @@ -package com.tyron.code.ui.editor.language.xml; - -import android.graphics.Color; -import android.os.Handler; -import android.util.Log; - -import com.tyron.builder.compiler.BuildType; -import com.tyron.builder.compiler.incremental.resource.IncrementalAapt2Task; -import com.tyron.builder.exception.CompilationFailedException; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.AndroidModule; -import com.tyron.builder.project.api.Module; -import com.tyron.code.BuildConfig; -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; -import com.tyron.code.ui.editor.language.HighlightUtil; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.code.util.ProjectUtils; -import com.tyron.completion.index.CompilerService; -import com.tyron.completion.java.compiler.CompilerContainer; -import com.tyron.completion.java.JavaCompilerProvider; -import com.tyron.completion.java.compiler.JavaCompilerService; -import com.tyron.completion.xml.lexer.XMLLexer; -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.Token; -import org.apache.commons.io.FileUtils; - -import java.io.File; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; -import java.util.concurrent.Executors; -import java.util.stream.Collectors; - -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 io.github.rosemoe.sora.lang.styling.CodeBlock; -import io.github.rosemoe.sora.lang.styling.TextStyle; -import io.github.rosemoe.sora.text.CharPosition; -import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; -import io.github.rosemoe.sora2.text.DiagnosticSpanMapUpdater; - -public class XMLAnalyzer extends AbstractCodeAnalyzer { - - private final WeakReference mEditorReference; - private final Stack mBlockLine = new Stack<>(); - private int mMaxSwitch = 1; - private int mCurrSwitch = 0; - - public XMLAnalyzer(Editor codeEditor) { - mEditorReference = new WeakReference<>(codeEditor); - } - - @Override - public Lexer getLexer(CharStream input) { - return new XMLLexer(input); - } - - @Override - public void analyzeInBackground(CharSequence contents) { - Editor editor = mEditorReference.get(); - if (editor == null) { - return; - } - - File currentFile = editor.getCurrentFile(); - if (currentFile == null) { - return; - } - - List diagnosticWrappers = new ArrayList<>(); - - compile(currentFile, contents.toString(), new ILogger() { - @Override - public void info(DiagnosticWrapper wrapper) { - addMaybe(wrapper); - } - - @Override - public void debug(DiagnosticWrapper wrapper) { - addMaybe(wrapper); - } - - @Override - public void warning(DiagnosticWrapper wrapper) { - addMaybe(wrapper); - } - - @Override - public void error(DiagnosticWrapper wrapper) { - addMaybe(wrapper); - } - - private void addMaybe(DiagnosticWrapper wrapper) { - if (currentFile.equals(wrapper.getSource())) { - diagnosticWrappers.add(wrapper); - } - } - }, () -> { - editor.setDiagnostics(diagnosticWrappers.stream() - .filter(it -> it.getLineNumber() > 0).collect(Collectors.toList())); - }); - } - - @Override - public void setup() { - putColor(EditorColorScheme.COMMENT, XMLLexer.COMMENT); - putColor(EditorColorScheme.HTML_TAG, XMLLexer.Name); - } - - @Override - protected void beforeAnalyze() { - mBlockLine.clear(); - mMaxSwitch = 1; - mCurrSwitch = 0; - } - - @Override - public boolean onNextToken(Token token, Styles styles, MappedSpans.Builder colors) { - int line = token.getLine() - 1; - int column = token.getCharPositionInLine(); - - Token previous = getPreviousToken(); - - switch (token.getType()) { - case XMLLexer.COMMENT: - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.COMMENT)); - return true; - case XMLLexer.Name: - if (previous != null && previous.getType() == XMLLexer.SLASH) { - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.HTML_TAG)); - return true; - } else if (previous != null && previous.getType() == XMLLexer.OPEN) { - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.HTML_TAG)); - CodeBlock block = new CodeBlock(); - block.startLine = previous.getLine() - 1; - block.startColumn = previous.getCharPositionInLine(); - mBlockLine.push(block); - return true; - } - String attribute = token.getText(); - if (attribute.contains(":")) { - colors.addIfNeeded(line, column, EditorColorScheme.ATTRIBUTE_NAME); - colors.addIfNeeded(line, column + attribute.indexOf(":"), - EditorColorScheme.TEXT_NORMAL); - return true; - } - colors.addIfNeeded(line, column, EditorColorScheme.IDENTIFIER_NAME); - return true; - case XMLLexer.EQUALS: - colors.addIfNeeded(line, column, EditorColorScheme.OPERATOR); - return true; - case XMLLexer.STRING: - String text = token.getText(); - if (text.startsWith("\"#")) { - try { - int color = Color.parseColor(text.substring(1, text.length() - 1)); - colors.addIfNeeded(line, column, EditorColorScheme.LITERAL); - - Span span = Span.obtain(column + 1, EditorColorScheme.LITERAL); - span.setUnderlineColor(color); - colors.add(line, span); - - Span middle = Span.obtain(column + text.length() - 1, - EditorColorScheme.LITERAL); - middle.setUnderlineColor(Color.TRANSPARENT); - colors.add(line, middle); - - Span end = Span.obtain(column + text.length(), TextStyle.makeStyle(EditorColorScheme.TEXT_NORMAL)); - end.setUnderlineColor(Color.TRANSPARENT); - colors.add(line, end); - break; - } catch (Exception ignore) { - } - } - colors.addIfNeeded(line, column, EditorColorScheme.LITERAL); - return true; - case XMLLexer.SLASH_CLOSE: - colors.addIfNeeded(line, column, EditorColorScheme.HTML_TAG); - if (!mBlockLine.isEmpty()) { - CodeBlock block = mBlockLine.pop(); - block.endLine = line; - block.endColumn = column; - if (block.startLine != block.endLine) { - if (previous != null && previous.getLine() == token.getLine()) { - block.toBottomOfEndLine = true; - } - styles.addCodeBlock(block); - } - } - return true; - case XMLLexer.SLASH: - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.HTML_TAG)); - if (previous != null && previous.getType() == XMLLexer.OPEN) { - if (!mBlockLine.isEmpty()) { - CodeBlock block = mBlockLine.pop(); - block.endLine = previous.getLine() - 1; - block.endColumn = previous.getCharPositionInLine(); - if (block.startLine != block.endLine) { - if (previous.getLine() == token.getLine()) { - block.toBottomOfEndLine = true; - } - styles.addCodeBlock(block); - } - } - } - return true; - case XMLLexer.OPEN: - case XMLLexer.CLOSE: - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.HTML_TAG)); - return true; - case XMLLexer.SEA_WS: - case XMLLexer.S: - // skip white spaces - return true; - default: - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.TEXT_NORMAL)); - return true; - } - - return false; - } - - @Override - protected void afterAnalyze(CharSequence content, Styles styles, MappedSpans.Builder colors) { - if (mBlockLine.isEmpty()) { - if (mCurrSwitch > mMaxSwitch) { - mMaxSwitch = mCurrSwitch; - } - } - styles.setSuppressSwitch(mMaxSwitch + 10); - - for (DiagnosticWrapper d : mDiagnostics) { - HighlightUtil.setErrorSpan(styles, d.getStartLine()); - } - } - - private final Handler handler = new Handler(); - long delay = 1000L; - long lastTime; - - private void compile(File file, String contents, ILogger logger, Runnable callback) { - handler.removeCallbacks(runnable); - lastTime = System.currentTimeMillis(); - runnable.setContents(contents); - runnable.setFile(file); - runnable.setLogger(logger); - runnable.setCallback(callback); - handler.postDelayed(runnable, delay); - } - - CompileRunnable runnable = new CompileRunnable(); - - private class CompileRunnable implements Runnable { - - private ILogger logger; - private File file; - private String contents; - private Runnable callback; - - public CompileRunnable() { - } - - public void setCallback(Runnable callback) { - this.callback = callback; - } - - public void setLogger(ILogger logger) { - this.logger = logger; - } - - public void setFile(File file) { - this.file = file; - } - - private void setContents(String contents) { - this.contents = contents; - } - - @Override - public void run() { - if (logger == null) { - return; - } - if (System.currentTimeMillis() < (lastTime - 500)) { - return; - } - - Executors.newSingleThreadExecutor().execute(() -> { - if (file == null || logger == null || contents == null) { - return; - } - boolean isResource = ProjectUtils.isResourceXMLFile(file); - - if (isResource) { - Project project = ProjectManager.getInstance().getCurrentProject(); - if (project != null) { - Module module = project.getModule(file); - if (module instanceof AndroidModule) { - try { - doGenerate(project, (AndroidModule) module, file, contents); - } catch (IOException | CompilationFailedException e) { - if (BuildConfig.DEBUG) { - Log.e("XMLAnalyzer", "Failed compiling", e); - } - } - } - } - } - }); - } - - private void doGenerate(Project project, AndroidModule module, File file, - String contents) throws IOException, CompilationFailedException { - if (!file.canWrite() || !file.canRead()) { - return; - } - - FileUtils.writeStringToFile(file, contents, StandardCharsets.UTF_8); - IncrementalAapt2Task task = new IncrementalAapt2Task(module, logger, false); - - try { - task.prepare(BuildType.DEBUG); - task.run(); - } catch (CompilationFailedException e) { - if (callback != null) { - handler.post(callback); - } - throw e; - } - - if (callback != null) { - handler.post(callback); - } - - // work around to refresh R.java file - File resourceClass = module.getJavaFile(module.getPackageName() + ".R"); - if (resourceClass != null) { - JavaCompilerProvider provider = - CompilerService.getInstance().getIndex(JavaCompilerProvider.KEY); - JavaCompilerService service = provider.getCompiler(project, module); - - CompilerContainer container = service.compile(resourceClass.toPath()); - container.run(__ -> { - - }); - } - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAutoCompleteProvider.java deleted file mode 100644 index cc892735a..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAutoCompleteProvider.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.tyron.code.ui.editor.language.xml; - -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.AndroidModule; -import com.tyron.builder.project.api.Module; -import com.tyron.builder.util.CharSequenceReader; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.completion.main.CompletionEngine; -import com.tyron.completion.model.CompletionList; -import com.tyron.editor.Editor; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -import io.github.rosemoe.sora2.interfaces.AutoCompleteProvider; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import io.github.rosemoe.sora2.data.CompletionItem; -import io.github.rosemoe.sora2.widget.CodeEditor; - -import java.util.Stack; -import java.util.stream.Collectors; - -public class XMLAutoCompleteProvider implements AutoCompleteProvider { - - private final Editor mEditor; - - public XMLAutoCompleteProvider(Editor editor) { - mEditor = editor; - } - - @Override - public List getAutoCompleteItems(String prefix, - TextAnalyzeResult analyzeResult, int line, - int column) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - if (currentProject == null) { - return null; - } - Module module = currentProject.getModule(mEditor.getCurrentFile()); - if (!(module instanceof AndroidModule)) { - return null; - } - - File currentFile = mEditor.getCurrentFile(); - if (currentFile == null) { - return null; - } - CompletionList complete = CompletionEngine.getInstance().complete(currentProject, module, - currentFile, mEditor.getContent().toString(), prefix, line, column, - mEditor.getCaret().getStart()); - return complete.items.stream().map(CompletionItem::new).collect(Collectors.toList()); - } - - private List getClosingTagSuggestions() { - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); - XmlPullParser parser = factory.newPullParser(); - parser.setInput(new CharSequenceReader(mEditor.getContent())); - - Stack stack = new Stack<>(); - - int next = parser.nextTag(); - while (true) { - int type = parser.getEventType(); - switch (type) { - case XmlPullParser.START_TAG: - stack.push(parser.getName()); - break; - case XmlPullParser.END_TAG: - stack.pop(); - break; - } - - try { - next = parser.nextTag(); - if (next == XmlPullParser.END_DOCUMENT) { - break; - } - } catch (Exception e) { - break; - } - } - - if (!stack.isEmpty()) { - List list = new ArrayList<>(); - for (int i = stack.size() - 1; i >= 0; i--) { - String s = stack.get(i); - list.add(new CompletionItem(s, "tag")); - } - return list; - } - - } catch (XmlPullParserException | IOException e) { - return null; - } - return null; - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/Xml.java b/app/src/main/java/com/tyron/code/ui/editor/language/xml/Xml.java deleted file mode 100644 index 2c5645cf5..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/Xml.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.tyron.code.ui.editor.language.xml; - -import com.tyron.code.ui.editor.language.Language; -import com.tyron.editor.Editor; - -import java.io.File; - - -public class Xml implements Language { - - @Override - public boolean isApplicable(File file) { - return file.getName().endsWith(".xml"); - } - - @Override - public io.github.rosemoe.sora.lang.Language get(Editor editor) { - return new LanguageXML(editor); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/log/AppLogFragment.java b/app/src/main/java/com/tyron/code/ui/editor/log/AppLogFragment.java index d6d38a582..fdf020eca 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/log/AppLogFragment.java +++ b/app/src/main/java/com/tyron/code/ui/editor/log/AppLogFragment.java @@ -19,12 +19,25 @@ import com.tyron.code.ui.editor.log.adapter.LogAdapter; import com.tyron.code.ui.main.MainViewModel; import com.tyron.code.ui.project.ProjectManager; - +import com.tyron.common.util.AndroidUtilities; +import com.tyron.common.util.ShareUtils; +import com.tyron.fileeditor.api.FileEditorManager; +import com.tyron.terminal.TerminalSession; +import com.tyron.terminal.TerminalSessionClientAdapter; +import com.tyron.terminal.view.TerminalView; +import com.tyron.terminal.view.TerminalViewClientAdapter; + +import java.io.IOException; +import java.io.OutputStream; import java.util.List; +import java.util.logging.Handler; public class AppLogFragment extends Fragment implements ProjectManager.OnProjectOpenListener { + /** Only used in IDE Logs **/ + private Handler mHandler; + public static AppLogFragment newInstance(int id) { AppLogFragment fragment = new AppLogFragment(); Bundle bundle = new Bundle(); @@ -38,6 +51,10 @@ public static AppLogFragment newInstance(int id) { private LogViewModel mModel; private LogAdapter mAdapter; private RecyclerView mRecyclerView; + private TerminalView mTerminalView; + + public static OutputStream outputStream; + public static OutputStream errorOutputStream; public AppLogFragment() { @@ -56,11 +73,56 @@ public void onCreate(Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { FrameLayout mRoot = new FrameLayout(requireContext()); + if (id == LogViewModel.BUILD_LOG) { + TerminalSession session = new TerminalSession("", "", new String[0], new String[0], + 0, new TerminalSessionClientAdapter() { + @Override + public void onCopyTextToClipboard(TerminalSession session, String text) { + AndroidUtilities.copyToClipboard(text); + } + + @Override + public void onShareText(TerminalSession terminalSession, String transcriptText) { + ShareUtils.shareText(requireContext(), "Build Logs", transcriptText); + } + }); + + mTerminalView = new TerminalView(requireContext(), null); + mTerminalView.setTextSize(20); + mTerminalView.setTerminalViewClient(new TerminalViewClientAdapter(mTerminalView)); + mTerminalView.attachSession(session); + + outputStream = new OutputStream() { + @Override + public void write(int b) throws IOException { + write(new byte[]{(byte) b}, 0, 1); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + mTerminalView.mEmulator.append(b, off + len); + mTerminalView.postInvalidate(); + } + }; + + mRoot.addView(mTerminalView, new ViewGroup.LayoutParams(-1, -1)); + return mRoot; + } mAdapter = new LogAdapter(); mAdapter.setListener(diagnostic -> { if (diagnostic.getSource() != null) { if (getContext() != null) { - FileEditorManagerImpl.getInstance().openFile(requireContext(), diagnostic.getSource(), fileEditor -> mMainViewModel.openFile(fileEditor)); + FileEditorManager manager = FileEditorManagerImpl.getInstance(); + manager.openFile(requireContext(), diagnostic.getSource(), it -> { +// if (diagnostic.getLineNumber() > 0 && diagnostic.getColumnNumber() > 0) { +// Bundle bundle = new Bundle(it.getFragment() +// .getArguments()); +// bundle.putInt(CodeEditorFragment.KEY_LINE, (int) diagnostic.getLineNumber()); +// bundle.putInt(CodeEditorFragment.KEY_COLUMN, (int) diagnostic.getColumnNumber()); +// it.getFragment().setArguments(bundle); +// manager.openFileEditor(it); +// } + }); } } }); @@ -69,6 +131,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, mRecyclerView.setAdapter(mAdapter); mRoot.addView(mRecyclerView, new FrameLayout.LayoutParams(-1, -1)); + return mRoot; } @@ -76,6 +139,17 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + if (id == LogViewModel.BUILD_LOG) { + mModel.getLogs(id).observe(getViewLifecycleOwner(), diagnosticWrappers -> { + if (diagnosticWrappers.isEmpty()) { + if (mTerminalView.mEmulator != null && mTerminalView.mEmulator.getScreen() != null) { + mTerminalView.mEmulator.clearTranscript(); + mTerminalView.invalidate(); + } + } + }); + return; + } mModel.getLogs(id).observe(getViewLifecycleOwner(), this::process); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/log/adapter/LogAdapter.java b/app/src/main/java/com/tyron/code/ui/editor/log/adapter/LogAdapter.java index 3fd49942f..b7c531ae3 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/log/adapter/LogAdapter.java +++ b/app/src/main/java/com/tyron/code/ui/editor/log/adapter/LogAdapter.java @@ -20,7 +20,7 @@ import com.tyron.builder.model.DiagnosticWrapper; import com.tyron.code.R; -import org.openjdk.javax.tools.Diagnostic; +import javax.tools.Diagnostic; import java.util.ArrayList; import java.util.List; @@ -108,18 +108,20 @@ public ViewHolder(FrameLayout layout) { } public void bind(DiagnosticWrapper diagnostic) { - SpannableStringBuilder builder = new SpannableStringBuilder(); - if (diagnostic.getKind() != null) { - builder.append(diagnostic.getKind().name() + ": ", - new ForegroundColorSpan(getColor(diagnostic.getKind())), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (diagnostic.getMessage(Locale.getDefault()) == null) { + return; } + SpannableStringBuilder builder = new SpannableStringBuilder(); +// if (diagnostic.getKind() != null) { +// builder.append(new ForegroundColorSpan(getColor(diagnostic.getKind())), +// Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); +// } if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { builder.append(diagnostic.getMessage(Locale.getDefault()), new ForegroundColorSpan(getColor(diagnostic.getKind())), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { - builder.append(diagnostic.getMessage(Locale.getDefault())); + builder.append(diagnostic.getMessageCharSequence()); } if (diagnostic.getSource() != null) { builder.append(' '); diff --git a/app/src/main/java/com/tyron/code/ui/editor/scheme/CodeAssistColorScheme.java b/app/src/main/java/com/tyron/code/ui/editor/scheme/CodeAssistColorScheme.java new file mode 100644 index 000000000..b98870887 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/scheme/CodeAssistColorScheme.java @@ -0,0 +1,170 @@ +package com.tyron.code.ui.editor.scheme; + +import android.graphics.Color; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +/** + * An editor color scheme that can be serialized and deserialized as json + */ +@Keep +public class CodeAssistColorScheme extends EditorColorScheme { + + @WorkerThread + public static CodeAssistColorScheme fromFile(@NonNull File file) throws IOException { + String contents = FileUtils.readFileToString(file, StandardCharsets.UTF_8); + CodeAssistColorScheme scheme = + new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().create().fromJson(contents, CodeAssistColorScheme.class); + if (scheme == null) { + throw new IOException("Unable to parse scheme file."); + } + if (scheme.mName == null) { + throw new IOException("Scheme does not contain a name."); + } + if (scheme.colors == null) { + throw new IOException("Scheme does not have colors."); + } + return scheme; + } + + @Expose + @SerializedName("name") + private String mName; + + @Expose + @SerializedName("colors") + private Map mNameToColorMap; + + public CodeAssistColorScheme() { + for (Integer id : Keys.sIdToNameMap.keySet()) { + int color = getColor(id); + setColor(id, color); + } + } + + /** + * Return the key of the id that will be serialized. + * @param id The editor color scheme id + * @return the mapped key + */ + protected String getName(int id) { + return Keys.sIdToNameMap.get(id); + } + + @Override + public void setColor(int type, int color) { + super.setColor(type, color); + + if (mNameToColorMap == null) { + mNameToColorMap = new HashMap<>(); + } + + String name = getName(type); + if (name != null) { + mNameToColorMap.remove(name); + mNameToColorMap.put(name, "#" + Integer.toHexString(color)); + } + } + + @Override + public int getColor(int type) { + if (mNameToColorMap == null) { + mNameToColorMap = new HashMap<>(); + } + + String name = getName(type); + if (name != null) { + String color = mNameToColorMap.get(name); + if (color != null) { + try { + return Color.parseColor(color); + } catch (IllegalArgumentException ignored) { + // fall through + } + } + } + + return super.getColor(type); + } + + @NonNull + public String toString() { + return new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .setPrettyPrinting() + .create() + .toJson(this); + } + + public static final class Keys { + private static final BiMap sIdToNameMap = HashBiMap.create(); + + static { + sIdToNameMap.put(WHOLE_BACKGROUND, "wholeBackground"); + sIdToNameMap.put(COMPLETION_WND_BACKGROUND, "completionPanelBackground"); + sIdToNameMap.put(COMPLETION_WND_CORNER, "completionPanelStrokeColor"); + sIdToNameMap.put(LINE_NUMBER, "lineNumber"); + sIdToNameMap.put(LINE_NUMBER_BACKGROUND, "lineNumberBackground"); + sIdToNameMap.put(LINE_NUMBER_PANEL, "lineNumberPanel"); + sIdToNameMap.put(LINE_NUMBER_PANEL_TEXT, "lineNumberPanelText"); + sIdToNameMap.put(LINE_DIVIDER, "lineDivider"); + sIdToNameMap.put(SELECTION_HANDLE, "selectionHandle"); + sIdToNameMap.put(SELECTION_INSERT, "selectionInsert"); + sIdToNameMap.put(SCROLL_BAR_TRACK, "scrollbarTrack"); + sIdToNameMap.put(SCROLL_BAR_THUMB, "scrollbarThumb"); + sIdToNameMap.put(SCROLL_BAR_THUMB_PRESSED, "scrollbarThumbPressed"); + + sIdToNameMap.put(PROBLEM_TYPO, "problemTypo"); + sIdToNameMap.put(PROBLEM_ERROR, "problemError"); + sIdToNameMap.put(PROBLEM_WARNING, "problemWarning"); + + sIdToNameMap.put(BLOCK_LINE, "blockLine"); + sIdToNameMap.put(BLOCK_LINE_CURRENT, "blockLineCurrent"); + sIdToNameMap.put(UNDERLINE, "underline"); + sIdToNameMap.put(CURRENT_LINE, "currentLine"); + + sIdToNameMap.put(TEXT_NORMAL, "textNormal"); + sIdToNameMap.put(SELECTED_TEXT_BACKGROUND, "selectedTextBackground"); + sIdToNameMap.put(MATCHED_TEXT_BACKGROUND, "matchedTextBackground"); + sIdToNameMap.put(ATTRIBUTE_NAME, "attributeName"); + sIdToNameMap.put(ATTRIBUTE_VALUE, "attributeValue"); + sIdToNameMap.put(HTML_TAG, "htmlTag"); + sIdToNameMap.put(ANNOTATION, "annotation"); + sIdToNameMap.put(FUNCTION_NAME, "functionName"); + sIdToNameMap.put(IDENTIFIER_NAME, "identifierName"); + sIdToNameMap.put(IDENTIFIER_VAR, "identifierVar"); + sIdToNameMap.put(LITERAL, "literal"); + sIdToNameMap.put(OPERATOR, "operator"); + sIdToNameMap.put(COMMENT, "comment"); + sIdToNameMap.put(KEYWORD, "keyword"); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/scheme/CompiledEditorScheme.java b/app/src/main/java/com/tyron/code/ui/editor/scheme/CompiledEditorScheme.java new file mode 100644 index 000000000..967f07cc1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/scheme/CompiledEditorScheme.java @@ -0,0 +1,85 @@ +package com.tyron.code.ui.editor.scheme; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.TypedValue; + +import androidx.annotation.StyleableRes; +import androidx.appcompat.widget.ThemeUtils; + +import com.google.android.material.color.MaterialColors; +import com.google.common.collect.ImmutableMap; +import com.tyron.code.R; + +import java.util.Map; + +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +/** + * An editor color scheme that is based on compiled xml files. + */ +public class CompiledEditorScheme extends EditorColorScheme { + + private static final Map sResIdMap = ImmutableMap.builder() + .put(R.styleable.EditorColorScheme_keyword, KEYWORD) + .put(R.styleable.EditorColorScheme_operator, OPERATOR) + .put(R.styleable.EditorColorScheme_annotation, ANNOTATION) + .put(R.styleable.EditorColorScheme_xmlAttributeName, ATTRIBUTE_NAME) + .put(R.styleable.EditorColorScheme_xmlAttributeValue, ATTRIBUTE_VALUE) + .put(R.styleable.EditorColorScheme_comment, COMMENT) + .put(R.styleable.EditorColorScheme_htmlTag, HTML_TAG) + .put(R.styleable.EditorColorScheme_identifierName, IDENTIFIER_NAME) + .put(R.styleable.EditorColorScheme_identifierVar, IDENTIFIER_VAR) + .put(R.styleable.EditorColorScheme_functionName, FUNCTION_NAME) + .put(R.styleable.EditorColorScheme_literal, LITERAL) + .put(R.styleable.EditorColorScheme_textNormal, TEXT_NORMAL) + .put(R.styleable.EditorColorScheme_blockLineColor, BLOCK_LINE) + .put(R.styleable.EditorColorScheme_problemError, PROBLEM_ERROR) + .put(R.styleable.EditorColorScheme_problemWarning, PROBLEM_WARNING) + .put(R.styleable.EditorColorScheme_problemTypo, PROBLEM_TYPO) + .put(R.styleable.EditorColorScheme_selectedTextBackground, SELECTED_TEXT_BACKGROUND) + .put(R.styleable.EditorColorScheme_completionPanelBackground, COMPLETION_WND_BACKGROUND) + .put(R.styleable.EditorColorScheme_completionPanelStrokeColor, COMPLETION_WND_CORNER) + .put(R.styleable.EditorColorScheme_lineNumberBackground, LINE_NUMBER_BACKGROUND) + .put(R.styleable.EditorColorScheme_lineNumberTextColor, LINE_NUMBER_PANEL_TEXT) + .put(R.styleable.EditorColorScheme_lineNumberDividerColor, LINE_DIVIDER) + .put(R.styleable.EditorColorScheme_wholeBackground, WHOLE_BACKGROUND) + .build(); + + public CompiledEditorScheme(Context context) { + Resources.Theme theme = context.getTheme(); + TypedValue value = new TypedValue(); + theme.resolveAttribute(R.attr.editorColorScheme, value, true); + TypedArray typedArray = context.obtainStyledAttributes(value.data, R.styleable.EditorColorScheme); + for (Integer resId : sResIdMap.keySet()) { + putColor(context, resId, typedArray); + } + typedArray.recycle(); + } + + private void putColor(Context context, @StyleableRes int res, TypedArray array) { + if (!array.hasValue(res)) { + return; + } + + Integer integer = sResIdMap.get(res); + if (integer == null) { + return; + } + + if (array.getType(res) == TypedValue.TYPE_ATTRIBUTE) { + TypedValue typedValue = new TypedValue(); + array.getValue(res, typedValue); + int color = MaterialColors.getColor(context, typedValue.data, -1); + setColorInternal(integer, color); + return; + } + setColorInternal(integer, array.getColor(res, 0)); + } + + private void setColorInternal(int id, int value) { + colors.put(id, value); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutAction.java index b502fec2e..92d4d0dec 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutAction.java @@ -1,10 +1,10 @@ package com.tyron.code.ui.editor.shortcuts; -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Editor; public interface ShortcutAction { boolean isApplicable(String kind); - void apply(CodeEditor editor, ShortcutItem item); + void apply(Editor editor, ShortcutItem item); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutsAdapter.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutsAdapter.java index e21ba41c4..947654495 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutsAdapter.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutsAdapter.java @@ -64,7 +64,6 @@ public ViewHolder(View view) { super(view); textView = view.findViewById(R.id.shortcut_label); - textView.setTextColor(0xffffffff); } public void bind(ShortcutItem item) { diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/CursorMoveAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/CursorMoveAction.java index 72ffb911c..dcfec0a15 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/CursorMoveAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/CursorMoveAction.java @@ -2,8 +2,7 @@ import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; - -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Editor; public class CursorMoveAction implements ShortcutAction { @@ -28,7 +27,7 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { + public void apply(Editor editor, ShortcutItem item) { switch (mDirection) { case UP: editor.moveSelectionUp(); break; case DOWN: editor.moveSelectionDown(); break; diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/RedoAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/RedoAction.java index a14012d3d..9c974e35d 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/RedoAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/RedoAction.java @@ -2,8 +2,7 @@ import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; - -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Editor; public class RedoAction implements ShortcutAction { @@ -15,9 +14,9 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { - if (editor.canRedo()) { - editor.redo(); + public void apply(Editor editor, ShortcutItem item) { + if (editor.getContent().canRedo()) { + editor.getContent().redo(); } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextEditAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextEditAction.java index 7b8e611eb..583b62367 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextEditAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextEditAction.java @@ -2,8 +2,7 @@ import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; - -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Editor; public class TextEditAction implements ShortcutAction { @@ -13,7 +12,7 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { + public void apply(Editor editor, ShortcutItem item) { } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextInsertAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextInsertAction.java index 3b95543dc..3ea262802 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextInsertAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextInsertAction.java @@ -1,10 +1,10 @@ package com.tyron.code.ui.editor.shortcuts.action; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; - -import io.github.rosemoe.sora2.text.Cursor; -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Caret; +import com.tyron.editor.Editor; public class TextInsertAction implements ShortcutAction { @@ -16,8 +16,14 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { - Cursor cursor = editor.getCursor(); - editor.getText().insert(cursor.getLeftLine(), cursor.getLeftColumn(), item.label); + public void apply(Editor editor, ShortcutItem item) { + Caret cursor = editor.getCaret(); + + // temporary solution + if (editor instanceof CodeEditorView) { + ((CodeEditorView) editor).commitText(item.label); + } else { + editor.insert(cursor.getStartLine(), cursor.getEndColumn(), item.label); + } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/UndoAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/UndoAction.java index b42303895..48644bcb9 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/UndoAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/UndoAction.java @@ -2,8 +2,7 @@ import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; - -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Editor; public class UndoAction implements ShortcutAction { @@ -15,9 +14,9 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { - if (editor.canUndo()) { - editor.undo(); + public void apply(Editor editor, ShortcutItem item) { + if (editor.getContent().canUndo()) { + editor.getContent().undo(); } } } diff --git a/app/src/main/java/com/tyron/code/ui/file/FilePickerDialogFixed.java b/app/src/main/java/com/tyron/code/ui/file/FilePickerDialogFixed.java index 5e2e22be5..2af7d2292 100644 --- a/app/src/main/java/com/tyron/code/ui/file/FilePickerDialogFixed.java +++ b/app/src/main/java/com/tyron/code/ui/file/FilePickerDialogFixed.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.util.Log; import android.widget.Button; +import android.widget.TextView; import com.github.angads25.filepicker.controller.adapters.FileListAdapter; import com.github.angads25.filepicker.model.DialogProperties; @@ -48,6 +49,7 @@ protected void onStart() { Field mAdapterField = FilePickerDialog.class.getDeclaredField("mFileListAdapter"); mAdapterField.setAccessible(true); FileListAdapter adapter = (FileListAdapter) mAdapterField.get(this); + assert adapter != null; adapter.setNotifyItemCheckedListener(() -> { int size = MarkedItemList.getFileCount(); if (size == 0) { @@ -64,4 +66,13 @@ protected void onStart() { Log.w("WizardFragment", "Unable to get declared field", e); } } + + /** + * Return the current path of the current directory + * @return the absolute path of the directory + */ + public String getCurrentPath() { + TextView path = findViewById(com.github.angads25.filepicker.R.id.dir_path); + return String.valueOf(path.getText()); + } } diff --git a/app/src/main/java/com/tyron/code/ui/file/action/ImportFileActionGroup.java b/app/src/main/java/com/tyron/code/ui/file/action/ImportFileActionGroup.java new file mode 100644 index 000000000..462fe6e57 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/ImportFileActionGroup.java @@ -0,0 +1,39 @@ +package com.tyron.code.ui.file.action; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.actions.ActionGroup; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.Presentation; +import com.tyron.code.R; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.file.ImportDirectoryAction; +import com.tyron.code.ui.file.action.file.ImportFileAction; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.ui.treeview.TreeNode; + +public class ImportFileActionGroup extends ActionGroup { + + public static final String ID = "fileManagerImportGroup"; + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + TreeNode data = event.getData(CommonFileKeys.TREE_NODE); + if (data == null) { + return; + } + + presentation.setVisible(true); + presentation.setText(event.getDataContext().getString(R.string.menu_import)); + } + + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[]{new ImportFileAction(),new ImportDirectoryAction()}; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/CreateDirectoryAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateDirectoryAction.java index d36d19e9a..05594cb9f 100644 --- a/app/src/main/java/com/tyron/code/ui/file/action/file/CreateDirectoryAction.java +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateDirectoryAction.java @@ -10,6 +10,7 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; import com.tyron.actions.AnActionEvent; import com.tyron.actions.CommonDataKeys; @@ -52,7 +53,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { File currentDir = e.getData(CommonDataKeys.FILE); TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); - AlertDialog dialog = new AlertDialog.Builder(fragment.requireContext()) + AlertDialog dialog = new MaterialAlertDialogBuilder(fragment.requireContext()) .setView(R.layout.create_class_dialog) .setTitle(R.string.menu_action_new_directory) .setPositiveButton(R.string.create_class_dialog_positive, null) @@ -71,7 +72,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { File fileToCreate = new File(currentDir, editText.getText().toString()); if (!fileToCreate.mkdirs()) { progress.runLater(() -> { - new AlertDialog.Builder(fragment.requireContext()) + new MaterialAlertDialogBuilder(fragment.requireContext()) .setTitle(R.string.error) .setMessage(R.string.error_dir_access) .setPositiveButton(android.R.string.ok, null) diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/CreateFileAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateFileAction.java index 5a1412260..a5405ffac 100644 --- a/app/src/main/java/com/tyron/code/ui/file/action/file/CreateFileAction.java +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateFileAction.java @@ -10,10 +10,15 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; import com.tyron.actions.AnActionEvent; import com.tyron.actions.CommonDataKeys; +import com.tyron.builder.project.Project; import com.tyron.code.R; +import com.tyron.code.event.FileCreatedEvent; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.completion.xml.task.InjectResourcesTask; import com.tyron.ui.treeview.TreeNode; import com.tyron.code.ui.file.CommonFileKeys; import com.tyron.code.ui.file.action.ActionContext; @@ -40,8 +45,8 @@ public boolean isApplicable(File file) { @Override public void actionPerformed(@NonNull AnActionEvent e) { - TreeFileManagerFragment fragment = (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); - TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + TreeFileManagerFragment fragment = (TreeFileManagerFragment) e.getRequiredData(CommonDataKeys.FRAGMENT); + TreeNode currentNode = e.getRequiredData(CommonFileKeys.TREE_NODE); ActionContext actionContext = new ActionContext(fragment, fragment.getTreeView(), currentNode); onMenuItemClick(actionContext); @@ -50,7 +55,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { @SuppressWarnings("ConstantConditions") private void onMenuItemClick(ActionContext context) { File currentDir = context.getCurrentNode().getValue().getFile(); - AlertDialog dialog = new AlertDialog.Builder(context.getFragment().requireContext()) + AlertDialog dialog = new MaterialAlertDialogBuilder(context.getFragment().requireContext()) .setView(R.layout.create_class_dialog) .setTitle(R.string.menu_action_new_file) .setPositiveButton(R.string.create_class_dialog_positive, null) @@ -74,6 +79,13 @@ private void onMenuItemClick(ActionContext context) { } else { refreshTreeView(context); dialog.dismiss(); + + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + if (currentProject != null) { + currentProject.getEventManager().dispatchEvent( + new FileCreatedEvent(fileToCreate) + ); + } } }); editText.addTextChangedListener(new SingleTextWatcher() { diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/DeleteFileAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/DeleteFileAction.java index 2bf9fb072..4bba54474 100644 --- a/app/src/main/java/com/tyron/code/ui/file/action/file/DeleteFileAction.java +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/DeleteFileAction.java @@ -5,8 +5,13 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.tyron.actions.AnActionEvent; import com.tyron.actions.CommonDataKeys; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.FileManager; +import com.tyron.code.event.FileDeletedEvent; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; import com.tyron.code.ui.file.tree.TreeUtil; import com.tyron.completion.progress.ProgressManager; import com.tyron.ui.treeview.TreeNode; @@ -25,6 +30,8 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import kotlin.io.FileWalkDirection; import kotlin.io.FilesKt; @@ -50,7 +57,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { TreeView treeView = fragment.getTreeView(); TreeNode currentNode = e.getRequiredData(CommonFileKeys.TREE_NODE); - new AlertDialog.Builder(fragment.requireContext()) + new MaterialAlertDialogBuilder(fragment.requireContext()) .setMessage(String.format(fragment.getString(R.string.dialog_confirm_delete), currentNode.getValue().getFile().getName())) .setPositiveButton(fragment.getString(R.string.dialog_delete), (d, which) -> { @@ -65,8 +72,10 @@ public void actionPerformed(@NonNull AnActionEvent e) { treeView.deleteNode(currentNode); TreeUtil.updateNode(currentNode.getParent()); treeView.refreshTreeView(); + FileEditorManagerImpl.getInstance().closeFile(currentNode.getValue() + .getFile()); } else { - new AlertDialog.Builder(fragment.requireContext()) + new MaterialAlertDialogBuilder(fragment.requireContext()) .setTitle(R.string.error) .setMessage("Failed to delete file.") .setPositiveButton(android.R.string.ok, null) @@ -82,32 +91,53 @@ public void actionPerformed(@NonNull AnActionEvent e) { private boolean deleteFiles(TreeNode currentNode, TreeFileManagerFragment fragment) { + List deletedFiles = new ArrayList<>(); File currentFile = currentNode.getContent().getFile(); FilesKt.walk(currentFile, FileWalkDirection.TOP_DOWN).iterator().forEachRemaining(file -> { + Module module = ProjectManager.getInstance() + .getCurrentProject() + .getModule(file); + if (file.getName().endsWith(".java")) { // todo: add .kt and .xml checks ProgressManager.getInstance().runLater(() -> fragment.getMainViewModel().removeFile(file)); - Module module = ProjectManager.getInstance() - .getCurrentProject() - .getModule(file); if (module instanceof JavaModule) { String packageName = StringSearch.packageName(file); if (packageName != null) { packageName += "." + file.getName() .substring(0, file.getName().lastIndexOf(".")); + ((JavaModule) module).removeJavaFile(packageName); } - ((JavaModule) module).removeJavaFile(packageName); } } + + ProgressManager.getInstance().runLater(() -> { + FileManager fileManager = module.getFileManager(); + if (fileManager.isOpened(file)) { + fileManager.closeFileForSnapshot(file); + } + }); + + deletedFiles.add(file); }); try { FileUtils.forceDelete(currentFile); } catch (IOException e) { return false; } + + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + if (currentProject != null) { + for (File deletedFile : deletedFiles) { + currentProject.getEventManager().dispatchEvent( + new FileDeletedEvent(deletedFile) + ); + } + } + return true; } } diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/ImportDirectoryAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportDirectoryAction.java new file mode 100644 index 000000000..192883c14 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportDirectoryAction.java @@ -0,0 +1,92 @@ +package com.tyron.code.ui.file.action.file; + +import android.content.Context; +import android.os.Environment; + +import androidx.annotation.NonNull; + +import com.github.angads25.filepicker.model.DialogConfigs; +import com.github.angads25.filepicker.model.DialogProperties; +import com.github.angads25.filepicker.view.FilePickerDialog; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.TreeUtil; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +public class ImportDirectoryAction extends FileAction { + + public static final String ID = "fileManagerImportDirectoryAction"; + + @Override + public String getTitle(Context context) { + return context.getString(R.string.menu_action_new_directory); + } + + @Override + public boolean isApplicable(File file) { + return file.isDirectory(); + } + + private void refreshTreeView(TreeNode currentNode, TreeView> treeView) { + TreeUtil.updateNode(currentNode); + treeView.refreshTreeView(); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment fragment = + (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + File currentDir = e.getData(CommonDataKeys.FILE); + TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + + DialogProperties properties = new DialogProperties(); + properties.selection_mode = DialogConfigs.SINGLE_MODE; + properties.selection_type = DialogConfigs.DIR_SELECT; + properties.root = Environment.getExternalStorageDirectory(); + properties.error_dir = fragment.requireContext().getExternalFilesDir(null); + + FilePickerDialog dialog = new FilePickerDialog(fragment.requireContext(), properties); + dialog.setDialogSelectionListener(files -> { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + String file = files[0]; + try { + FileUtils.copyDirectoryToDirectory(new File(file), currentDir); + } catch (IOException ioException) { + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + AndroidUtilities.showSimpleAlert(e.getDataContext(), R.string.error, + ioException.getLocalizedMessage()); + }); + } + + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + refreshTreeView(currentNode, fragment.getTreeView()); + }); + + }); + + }); + dialog.show(); + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/ImportFileAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportFileAction.java new file mode 100644 index 000000000..ccbde3c47 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportFileAction.java @@ -0,0 +1,89 @@ +package com.tyron.code.ui.file.action.file; + +import android.content.Context; +import android.os.Environment; + +import androidx.annotation.NonNull; + +import com.github.angads25.filepicker.model.DialogConfigs; +import com.github.angads25.filepicker.model.DialogProperties; +import com.github.angads25.filepicker.view.FilePickerDialog; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.TreeUtil; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +public class ImportFileAction extends FileAction { + public static final String ID = "fileManagerImportFileAction"; + + @Override + public String getTitle(Context context) { + return context.getString(R.string.menu_action_new_file); + } + + @Override + public boolean isApplicable(File file) { + return file.isDirectory(); + } + + private void refreshTreeView(TreeNode currentNode, TreeView> treeView) { + TreeUtil.updateNode(currentNode); + treeView.refreshTreeView(); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment fragment = + (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + File currentDir = e.getData(CommonDataKeys.FILE); + TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + + DialogProperties properties = new DialogProperties(); + properties.selection_mode = DialogConfigs.MULTI_MODE; + properties.selection_type = DialogConfigs.FILE_SELECT; + properties.root = Environment.getExternalStorageDirectory(); + properties.error_dir = fragment.requireContext().getExternalFilesDir(null); + + FilePickerDialog dialog = new FilePickerDialog(fragment.requireContext(), properties); + dialog.setDialogSelectionListener(files -> { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + for (String file : files) { + try { + FileUtils.copyFileToDirectory(new File(file), currentDir); + } catch (IOException ioException) { + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + AndroidUtilities.showSimpleAlert(e.getDataContext(), R.string.error, + ioException.getLocalizedMessage()); + }); + } + } + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + refreshTreeView(currentNode, fragment.getTreeView()); + }); + + }); + }); + dialog.show(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/file/action/xml/CreateLayoutAction.java b/app/src/main/java/com/tyron/code/ui/file/action/xml/CreateLayoutAction.java index 65e7060f1..9318afd9d 100644 --- a/app/src/main/java/com/tyron/code/ui/file/action/xml/CreateLayoutAction.java +++ b/app/src/main/java/com/tyron/code/ui/file/action/xml/CreateLayoutAction.java @@ -10,8 +10,6 @@ import com.tyron.code.R; import com.tyron.code.template.CodeTemplate; import com.tyron.code.template.xml.LayoutTemplate; -import com.tyron.ui.treeview.TreeNode; -import com.tyron.ui.treeview.TreeView; import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; import com.tyron.code.ui.file.CommonFileKeys; import com.tyron.code.ui.file.RegexReason; @@ -21,6 +19,8 @@ import com.tyron.code.ui.file.tree.model.TreeFile; import com.tyron.code.ui.project.ProjectManager; import com.tyron.code.util.ProjectUtils; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; import java.io.File; import java.io.IOException; @@ -31,7 +31,7 @@ public class CreateLayoutAction extends FileAction { @Override public String getTitle(Context context) { - return context.getString(R.string.menu_new); + return context.getString(R.string.menu_new_layout); } @Override @@ -44,9 +44,10 @@ public boolean isApplicable(File file) { @Override public void actionPerformed(@NonNull AnActionEvent e) { - TreeFileManagerFragment fragment = (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + TreeFileManagerFragment fragment = + (TreeFileManagerFragment) e.getRequiredData(CommonDataKeys.FRAGMENT); TreeView treeView = fragment.getTreeView(); - TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + TreeNode currentNode = e.getRequiredData(CommonFileKeys.TREE_NODE); CreateClassDialogFragment dialogFragment = CreateClassDialogFragment.newInstance(getTemplates(), @@ -55,16 +56,15 @@ public void actionPerformed(@NonNull AnActionEvent e) { dialogFragment.show(fragment.getChildFragmentManager(), null); dialogFragment.setOnClassCreatedListener((className, template) -> { try { - File createdFile = ProjectManager.createFile( - currentNode.getContent().getFile(), - className, - template - ); + File createdFile = ProjectManager.createFile(currentNode.getContent().getFile(), + className, template); + + if (createdFile == null) { + throw new IOException(fragment.getString(R.string.error_file_creation)); + } - TreeNode newNode = new TreeNode<>( - TreeFile.fromFile(createdFile), - currentNode.getLevel() + 1 - ); + TreeNode newNode = new TreeNode<>(TreeFile.fromFile(createdFile), + currentNode.getLevel() + 1); treeView.addNode(currentNode, newNode); treeView.refreshTreeView(); @@ -72,11 +72,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { createdFile, fileEditor -> fragment.getMainViewModel().openFile(fileEditor)); } catch (IOException exception) { - new MaterialAlertDialogBuilder(fragment.requireContext()) - .setMessage(exception.getMessage()) - .setPositiveButton(android.R.string.ok, null) - .setTitle(R.string.error) - .show(); + new MaterialAlertDialogBuilder(fragment.requireContext()).setMessage(exception.getMessage()).setPositiveButton(android.R.string.ok, null).setTitle(R.string.error).show(); } }); } diff --git a/app/src/main/java/com/tyron/code/ui/file/dialog/CreateClassDialogFragment.java b/app/src/main/java/com/tyron/code/ui/file/dialog/CreateClassDialogFragment.java index b7aee604e..5f7611892 100644 --- a/app/src/main/java/com/tyron/code/ui/file/dialog/CreateClassDialogFragment.java +++ b/app/src/main/java/com/tyron/code/ui/file/dialog/CreateClassDialogFragment.java @@ -22,7 +22,7 @@ import com.tyron.code.ui.file.RegexReason; import com.tyron.common.util.SingleTextWatcher; -import org.openjdk.javax.lang.model.SourceVersion; +import javax.lang.model.SourceVersion; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/com/tyron/code/ui/file/dialog/FileManagerFragment.java b/app/src/main/java/com/tyron/code/ui/file/dialog/FileManagerFragment.java index 1e4a8a42e..c4a7ed56a 100644 --- a/app/src/main/java/com/tyron/code/ui/file/dialog/FileManagerFragment.java +++ b/app/src/main/java/com/tyron/code/ui/file/dialog/FileManagerFragment.java @@ -119,7 +119,7 @@ private void openFile(File file) { if (parent != null) { if (parent instanceof MainFragment) { - ((MainFragment) parent).openFile(FileEditorManagerImpl.getInstance().openFile(file, true)[0]); + ((MainFragment) parent).openFile(FileEditorManagerImpl.getInstance().openFile(requireContext(), file, true)[0]); } } } diff --git a/app/src/main/java/com/tyron/code/ui/file/event/RefreshRootEvent.java b/app/src/main/java/com/tyron/code/ui/file/event/RefreshRootEvent.java new file mode 100644 index 000000000..dbad88457 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/event/RefreshRootEvent.java @@ -0,0 +1,24 @@ +package com.tyron.code.ui.file.event; + +import androidx.annotation.NonNull; + +import com.tyron.code.event.Event; + +import java.io.File; + +/** + * Used to notify the file manager that its root needs to be refreshed + */ +public class RefreshRootEvent extends Event { + + private final File mRoot; + + public RefreshRootEvent(@NonNull File root) { + mRoot = root; + } + + @NonNull + public File getRoot() { + return mRoot; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/tree/TreeFileManagerFragment.java b/app/src/main/java/com/tyron/code/ui/file/tree/TreeFileManagerFragment.java index 469e32621..15f7f6429 100644 --- a/app/src/main/java/com/tyron/code/ui/file/tree/TreeFileManagerFragment.java +++ b/app/src/main/java/com/tyron/code/ui/file/tree/TreeFileManagerFragment.java @@ -1,14 +1,21 @@ package com.tyron.code.ui.file.tree; +import android.annotation.SuppressLint; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.widget.FrameLayout; +import android.widget.HorizontalScrollView; import android.widget.PopupMenu; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.widget.ThemeUtils; +import androidx.core.view.ViewCompat; +import androidx.core.widget.NestedScrollView; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -17,6 +24,17 @@ import com.tyron.actions.ActionPlaces; import com.tyron.actions.CommonDataKeys; import com.tyron.actions.DataContext; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.BuildConfig; +import com.tyron.code.R; +import com.tyron.code.event.EventManager; +import com.tyron.code.event.EventReceiver; +import com.tyron.code.event.SubscriptionReceipt; +import com.tyron.code.event.Unsubscribe; +import com.tyron.code.ui.file.event.RefreshRootEvent; +import com.tyron.code.util.ApkInstaller; +import com.tyron.code.util.EventManagerUtilsKt; +import com.tyron.code.util.UiUtilsKt; import com.tyron.completion.progress.ProgressManager; import com.tyron.ui.treeview.TreeNode; import com.tyron.ui.treeview.TreeView; @@ -52,6 +70,10 @@ public static TreeFileManagerFragment newInstance(File root) { private FileViewModel mFileViewModel; private TreeView treeView; + public TreeFileManagerFragment() { + super(R.layout.tree_file_manager_fragment); + } + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -60,50 +82,62 @@ public void onCreate(@Nullable Bundle savedInstanceState) { mFileViewModel = new ViewModelProvider(requireActivity()).get(FileViewModel.class); } - @Nullable + @SuppressLint("ClickableViewAccessibility") @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - FrameLayout root = new FrameLayout(requireContext()); - root.setBackgroundColor(0xff212121); - root.setLayoutParams( - new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT)); + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + ViewCompat.requestApplyInsets(view); + UiUtilsKt.addSystemWindowInsetToPadding(view, false, true, false, true); + + SwipeRefreshLayout refreshLayout = view.findViewById(R.id.refreshLayout); + refreshLayout.setOnRefreshListener(() -> partialRefresh(() -> { + refreshLayout.setRefreshing(false); + treeView.refreshTreeView(); + })); + treeView = new TreeView<>( requireContext(), TreeNode.root(Collections.emptyList())); - root.addView(treeView.getView(), new FrameLayout.LayoutParams(-1, -1)); - - SwipeRefreshLayout refreshLayout = new SwipeRefreshLayout(requireContext()); - refreshLayout.addView(root); - refreshLayout.setOnRefreshListener(() -> { - ProgressManager.getInstance().runNonCancelableAsync(() -> { - if (!treeView.getAllNodes().isEmpty()) { - TreeNode node = treeView.getAllNodes().get(0); - TreeUtil.updateNode(node); - if (getActivity() != null) { - requireActivity().runOnUiThread(() -> { - refreshLayout.setRefreshing(false); - treeView.refreshTreeView(); - }); - } - } - }); + HorizontalScrollView horizontalScrollView = view.findViewById(R.id.horizontalScrollView); + horizontalScrollView.addView(treeView.getView(), new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.MATCH_PARENT + )); + treeView.getView().setNestedScrollingEnabled(false); + + EventManager eventManager = ApplicationLoader.getInstance() + .getEventManager(); + + EventManagerUtilsKt.subscribeEvent(eventManager, getViewLifecycleOwner(), RefreshRootEvent.class, (event, unsubscribe) -> { + File refreshRoot = event.getRoot(); + TreeNode currentRoot = treeView.getRoot(); + if (currentRoot != null && refreshRoot.equals(currentRoot.getValue().getFile())) { + partialRefresh(() -> treeView.refreshTreeView()); + } else { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + TreeNode node = TreeNode.root(TreeUtil.getNodes(refreshRoot)); + ProgressManager.getInstance().runLater(() -> { + if (getActivity() == null) { + return; + } + treeView.refreshTreeView(node); + }); + }); + } }); - return refreshLayout; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { treeView.setAdapter(new TreeFileNodeViewFactory(new TreeFileNodeListener() { @Override public void onNodeToggled(TreeNode treeNode, boolean expanded) { if (treeNode.isLeaf()) { - if (treeNode.getValue().getFile().isFile()) { - FileEditorManagerImpl.getInstance().openFile(requireContext(), treeNode.getValue().getFile(), fileEditor -> { - mMainViewModel.openFile(fileEditor); - }); + File file = treeNode.getValue().getFile(); + if (file.isFile()) { + // TODO: cleaner api to do this + if (file.getName().endsWith(".apk")) { + ApkInstaller.installApplication(requireContext(), BuildConfig.APPLICATION_ID, file.getAbsolutePath()); + } else { + FileEditorManagerImpl.getInstance().openFile(requireContext(), treeNode.getValue().getFile(), true); + } } } } @@ -121,6 +155,22 @@ public boolean onNodeLongClicked(View view, TreeNode treeNode, boolean }); } + + private void partialRefresh(Runnable callback) { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + if (!treeView.getAllNodes().isEmpty()) { + TreeNode node = treeView.getAllNodes().get(0); + TreeUtil.updateNode(node); + ProgressManager.getInstance().runLater(() -> { + if (getActivity() == null) { + return; + } + callback.run(); + }); + } + }); + } + @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); diff --git a/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFile.java b/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFile.java index 1f9fa0109..36cecfa81 100644 --- a/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFile.java +++ b/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFile.java @@ -9,6 +9,7 @@ import com.tyron.code.R; import java.io.File; +import java.util.Objects; public class TreeFile { @@ -40,4 +41,21 @@ public Drawable getIcon(Context context) { return AppCompatResources.getDrawable(context, R.drawable.round_insert_drive_file_24); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TreeFile treeFile = (TreeFile) o; + return Objects.equals(mFile, treeFile.mFile); + } + + @Override + public int hashCode() { + return Objects.hash(mFile); + } } diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorDialogFragment.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorDialogFragment.java index 877ae2241..6076611c5 100644 --- a/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorDialogFragment.java +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorDialogFragment.java @@ -19,19 +19,23 @@ import com.tyron.builder.project.api.AndroidModule; import com.tyron.builder.project.api.Module; import com.tyron.code.R; +import com.tyron.code.ui.layoutEditor.dom.FakeDomElement; import com.tyron.code.ui.project.ProjectManager; import com.tyron.completion.index.CompilerService; -import com.tyron.completion.xml.util.StyleUtils; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.xml.completion.repository.api.AttrResourceValue; +import com.tyron.xml.completion.repository.api.ResourceNamespace; +import com.tyron.completion.xml.util.AttributeProcessingUtil; import com.tyron.completion.xml.XmlIndexProvider; import com.tyron.completion.xml.XmlRepository; -import com.tyron.completion.xml.model.AttributeInfo; -import com.tyron.completion.xml.model.DeclareStyleable; +import org.eclipse.lemminx.commons.TextDocument; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMElement; + +import java.io.IOException; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; import kotlin.Pair; @@ -154,44 +158,39 @@ private void onAttributeItemClick(int pos, Pair attribute) { MaterialAutoCompleteTextView editText = v.findViewById(R.id.value); XmlRepository xmlRepository = getXmlRepository(); - String attributeName = attribute.getFirst(); - String attributeNamespace = ""; - if (attributeName.contains(":")) { - attributeNamespace = attributeName.substring(0, attributeName.indexOf(':')); - attributeName = attributeName.substring(attributeName.indexOf(':') + 1); - } if (xmlRepository != null) { - List values = new ArrayList<>(); - Set styles = new HashSet<>(); - Map declareStyleables = - xmlRepository.getDeclareStyleables(); - styles.addAll(StyleUtils.getStyles(declareStyleables, mTag, mParentTag)); - - for (DeclareStyleable style : styles) { - for (AttributeInfo attributeInfo : style.getAttributeInfos()) { - if (!attributeNamespace.equals(attributeInfo.getNamespace())) { - continue; - } - if (!attributeName.equals(attributeInfo.getName())) { - continue; - } + FakeDomElement fakeDomElement = new FakeDomElement(-1, -1); + fakeDomElement.setTagName(mTag); - if (attributeInfo.getFormats() == null || attributeInfo.getFormats().isEmpty()) { - AttributeInfo extraAttribute = - xmlRepository.getExtraAttribute(attributeName); - if (extraAttribute != null) { - attributeInfo = extraAttribute; - } - } - values.addAll(attributeInfo.getValues()); - } + FakeDomElement fakeParent = new FakeDomElement(-1, -1); + fakeParent.setTagName(mParentTag); + fakeDomElement.setParent(fakeParent); + + String attributeName = attribute.getFirst(); + if (attributeName.contains(":")) { + // strip the namespace prefix + attributeName = attributeName.substring(attributeName.indexOf(':') + 1); } + + AttrResourceValue attr = + AttributeProcessingUtil.getLayoutAttributeFromNode( + xmlRepository.getRepository(), fakeDomElement, + attributeName, + ResourceNamespace.RES_AUTO); + + List values = new ArrayList<>(); + if (attr != null) { + values.addAll(attr.getAttributeValues().keySet()); + } + ArrayAdapter adapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, values); - editText.setThreshold(1); - editText.showDropDown(); - editText.setAdapter(adapter); + ProgressManager.getInstance().runLater(() -> { + editText.setThreshold(1); + editText.showDropDown(); + editText.setAdapter(adapter); + }, 300); } editText.setText(attribute.getSecond(), false); @@ -220,7 +219,11 @@ private XmlRepository getXmlRepository() { XmlIndexProvider index = CompilerService.getInstance().getIndex(XmlIndexProvider.KEY); XmlRepository xmlRepository = index.get(currentProject, mainModule); - xmlRepository.initialize((AndroidModule) mainModule); + try { + xmlRepository.initialize((AndroidModule) mainModule); + } catch (IOException e) { + // ignored + } return xmlRepository; } } diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/dom/FakeDomElement.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/dom/FakeDomElement.java new file mode 100644 index 000000000..7b8457d17 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/dom/FakeDomElement.java @@ -0,0 +1,42 @@ +package com.tyron.code.ui.layoutEditor.dom; + +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.dom.DOMNode; + +public class FakeDomElement extends DOMElement { + + private DOMElement parent; + + private String tagName; + + public FakeDomElement(int start, int end) { + super(start, end); + } + + @Override + public String getTagName() { + return tagName; + } + + public void setTagName(String tagName) { + this.tagName = tagName; + } + + public DOMElement getParent() { + return parent; + } + + @Override + public DOMElement getParentElement() { + return getParent(); + } + + @Override + public DOMNode getParentNode() { + return getParent(); + } + + public void setParent(DOMElement parent) { + this.parent = parent; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/library/AddDependencyDialogFragment.java b/app/src/main/java/com/tyron/code/ui/library/AddDependencyDialogFragment.java deleted file mode 100644 index e2befd9b7..000000000 --- a/app/src/main/java/com/tyron/code/ui/library/AddDependencyDialogFragment.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.tyron.code.ui.library; - -import android.app.Dialog; -import android.os.Bundle; -import android.text.Editable; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.tyron.code.R; -import com.tyron.common.util.SingleTextWatcher; - -public class AddDependencyDialogFragment extends DialogFragment { - - public static final String TAG = AddDependencyDialogFragment.class.getSimpleName(); - public static final String ADD_KEY = "addDependency"; - - @SuppressWarnings("ConstantConditions") - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()); - // noinspection InflateParams - View inflate = getLayoutInflater().inflate(R.layout.add_dependency_dialog, null); - EditText groupId = inflate.findViewById(R.id.et_group_id); - EditText artifactId = inflate.findViewById(R.id.et_artifact_id); - EditText versionName = inflate.findViewById(R.id.et_version_name); - - builder.setView(inflate); - - builder.setPositiveButton(R.string.wizard_create, (d, w) -> { - Bundle bundle = new Bundle(); - bundle.putString("groupId", String.valueOf(groupId.getText())); - bundle.putString("artifactId", String.valueOf(artifactId.getText())); - bundle.putString("versionName", String.valueOf(versionName.getText())); - getParentFragmentManager().setFragmentResult(ADD_KEY, bundle); - }); - builder.setNegativeButton(android.R.string.cancel, null); - - AlertDialog dialog = builder.create(); - dialog.setOnShowListener(d -> { - final Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); - positiveButton.setEnabled(false); - - SingleTextWatcher textWatcher = new SingleTextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - boolean valid = validate(groupId, artifactId, versionName); - positiveButton.setEnabled(valid); - } - }; - groupId.addTextChangedListener(textWatcher); - artifactId.addTextChangedListener(textWatcher); - versionName.addTextChangedListener(textWatcher); - }); - return dialog; - } - - private boolean validate(EditText groupId, EditText artifactId, EditText versionName) { - String groupIdString = String.valueOf(groupId.getText()); - String artifactIdString = String.valueOf(artifactId.getText()); - String versionNameString = String.valueOf(versionName.getText()); - if (groupIdString.contains(":")) { - return false; - } - if (groupIdString.isEmpty()) { - return false; - } - if (artifactIdString.isEmpty()) { - return false; - } - if (artifactIdString.contains(":")) { - return false; - } - if (versionNameString.isEmpty()) { - return false; - } - return !versionNameString.contains(":"); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/library/LibraryManagerFragment.java b/app/src/main/java/com/tyron/code/ui/library/LibraryManagerFragment.java deleted file mode 100644 index 3b668e63f..000000000 --- a/app/src/main/java/com/tyron/code/ui/library/LibraryManagerFragment.java +++ /dev/null @@ -1,353 +0,0 @@ -package com.tyron.code.ui.library; - -import android.app.ProgressDialog; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; -import androidx.core.view.MenuProvider; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.transition.TransitionManager; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.transition.MaterialFade; -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.JavaModule; -import com.tyron.builder.project.api.Module; -import com.tyron.code.ApplicationLoader; -import com.tyron.code.R; -import com.tyron.code.ui.library.adapter.LibraryManagerAdapter; -import com.tyron.code.ui.project.DependencyManager; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.code.util.DependencyUtils; -import com.tyron.completion.progress.ProgressManager; -import com.tyron.resolver.DependencyResolver; -import com.tyron.resolver.model.Dependency; -import com.tyron.resolver.model.Pom; -import com.tyron.resolver.repository.Repository; -import com.tyron.resolver.repository.RepositoryManager; -import com.tyron.resolver.repository.RepositoryManagerImpl; - -import org.apache.commons.io.FileUtils; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.stream.Collectors; - -public class LibraryManagerFragment extends Fragment implements ProjectManager.OnProjectOpenListener { - - public static final String TAG = LibraryManagerFragment.class.getSimpleName(); - private static final String ARG_PATH = "path"; - private static final Type TYPE = new TypeToken>(){}.getType(); - - - public static LibraryManagerFragment newInstance(String modulePath) { - Bundle args = new Bundle(); - args.putString(ARG_PATH, modulePath); - LibraryManagerFragment fragment = new LibraryManagerFragment(); - fragment.setArguments(args); - return fragment; - } - - private RepositoryManager mRepositoryManager; - private String mModulePath; - private boolean isDumb = false; - private LibraryManagerAdapter mAdapter; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - File cacheDir = ApplicationLoader.applicationContext.getExternalFilesDir("cache"); - mRepositoryManager = new RepositoryManagerImpl(); - mRepositoryManager.setCacheDirectory(cacheDir); - mRepositoryManager.addRepository("maven", "https://repo1.maven.org/maven2"); - mRepositoryManager.addRepository("maven-google", "https://maven.google.com"); - mRepositoryManager.addRepository("jitpack", "https://jitpack.io"); - mRepositoryManager.addRepository("jcenter", "https://jcenter.bintray.com"); - mRepositoryManager.initialize(); - mModulePath = requireArguments().getString(ARG_PATH); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.library_manager_fragment, container, false); - - mAdapter = new LibraryManagerAdapter(); - - RecyclerView recyclerView = view.findViewById(R.id.libraries_recyclerview); - recyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); - recyclerView.setAdapter(mAdapter); - - Toolbar toolbar = view.findViewById(R.id.toolbar); - toolbar.setNavigationOnClickListener(v -> - getParentFragmentManager().popBackStack()); - toolbar.addMenuProvider(new MenuProvider() { - @Override - public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) { - menu.add(R.string.menu_add_libs_gradle) - .setOnMenuItemClickListener(item -> { - Project currentProject = - ProjectManager.getInstance().getCurrentProject(); - if (currentProject != null) { - Module mainModule = currentProject.getMainModule(); - File rootFile = mainModule.getRootFile(); - File gradleFile = new File(rootFile, "build.gradle"); - if (gradleFile.exists()) { - try { - List poms = DependencyUtils.parseGradle(mRepositoryManager, - gradleFile, ILogger.EMPTY); - List
This Builder must be used in order for AlertDialog objects to respond to color and shape + * theming provided by Material themes. + * + *
The type of dialog returned is still an {@link AlertDialog}; there is no specific Material + * implementation of {@link AlertDialog}. + */ +public class MaterialAlertDialogBuilder extends AlertDialog.Builder { + + public MaterialAlertDialogBuilder(@NonNull Context context) { + throw new RuntimeException("Stub!"); + } + + public MaterialAlertDialogBuilder(@NonNull Context context, int overrideThemeResId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public AlertDialog create() { + throw new RuntimeException("Stub!"); + } + + @Nullable + public Drawable getBackground() { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackground(@Nullable Drawable background) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetStart(@Px int backgroundInsetStart) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetTop(@Px int backgroundInsetTop) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetEnd(@Px int backgroundInsetEnd) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetBottom(@Px int backgroundInsetBottom) { + throw new RuntimeException("Stub!"); + } + + // The following methods are all pass-through methods used to specify the return type for the + // builder chain. + + @NonNull + @Override + public MaterialAlertDialogBuilder setTitle(@StringRes int titleId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setTitle(@Nullable CharSequence title) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setCustomTitle(@Nullable View customTitleView) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMessage(@StringRes int messageId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMessage(@Nullable CharSequence message) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setIcon(@DrawableRes int iconId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setIconAttribute(@AttrRes int attrId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setPositiveButton( + @StringRes int textId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setPositiveButton( + @Nullable CharSequence text, @Nullable final OnClickListener listener) { + return (MaterialAlertDialogBuilder) super.setPositiveButton(text, listener); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setPositiveButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNegativeButton( + @StringRes int textId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNegativeButton( + @Nullable CharSequence text, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNegativeButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNeutralButton( + @StringRes int textId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNeutralButton( + @Nullable CharSequence text, @Nullable final OnClickListener listener) { + return (MaterialAlertDialogBuilder) super.setNeutralButton(text, listener); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNeutralButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setCancelable(boolean cancelable) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnCancelListener( + @Nullable OnCancelListener onCancelListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnDismissListener( + @Nullable OnDismissListener onDismissListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnKeyListener(@Nullable OnKeyListener onKeyListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setItems( + @ArrayRes int itemsId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setItems( + @Nullable CharSequence[] items, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setAdapter( + @Nullable final ListAdapter adapter, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setCursor( + @Nullable final Cursor cursor, + @Nullable final OnClickListener listener, + @NonNull String labelColumn) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMultiChoiceItems( + @ArrayRes int itemsId, + @Nullable boolean[] checkedItems, + @Nullable final OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMultiChoiceItems( + @Nullable CharSequence[] items, + @Nullable boolean[] checkedItems, + @Nullable final OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMultiChoiceItems( + @Nullable Cursor cursor, + @NonNull String isCheckedColumn, + @NonNull String labelColumn, + @Nullable final OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @ArrayRes int itemsId, int checkedItem, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @Nullable Cursor cursor, + int checkedItem, + @NonNull String labelColumn, + @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @Nullable CharSequence[] items, int checkedItem, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @Nullable ListAdapter adapter, int checkedItem, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnItemSelectedListener( + @Nullable final AdapterView.OnItemSelectedListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setView(int layoutResId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setView(@Nullable View view) { + throw new RuntimeException("Stub!"); + } +} diff --git a/app/build.gradle b/app/build.gradle index 8068825ec..43597865d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,23 +2,32 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion 31 - buildToolsVersion "30.0.3" + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - applicationId "com.tyron.code" - minSdkVersion 26 - targetSdkVersion 31 - versionCode 9 - versionName "0.2.4.1 ALPHA" + applicationId rootProject.ext.applicationId + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - compileOptions { coreLibraryDesugaringEnabled = true - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + testOptions { + unitTests { + includeAndroidResources = true + } + + unitTests.all { + systemProperty 'robolectric.enabledSdks', '26' + } } buildTypes { @@ -28,53 +37,133 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + packagingOptions { + resources.excludes += "license/*" + + exclude "plugin.properties" + exclude "plugin.xml" + exclude "about.html" + exclude ".api_description" + exclude "about_files/*" + exclude "META-INF/eclipse.inf" + exclude "META-INF/INDEX.LIST" + exclude "META-INF/DEPENDENCIES" + exclude "META-INF/LGPL2.1" + exclude "META-INF/groovy-release-info.properties" + exclude "META-INF/AL2.0" + exclude "README.md" + pickFirst "META-INF/sisu/*" + pickFirst "*/*.kotlin_builtins" + pickFirst "*/*/*.kotlin_builtins" + pickFirst "*/*/*/*.kotlin_builtins" + } } configurations.implementation { exclude group: "org.jetbrains", module: "annotations" + exclude group: 'com.android.tools', module: 'common' + exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on' } dependencies { + implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3' + implementation projects.terminalview + + // TODO: Removed these modules, the features that are using these modules + // should be moved into its own module. + + // TODO: language processing should be on its own module + implementation 'org.antlr:antlr4-runtime:4.9.2' + implementation files ( + 'libs/language-base-0.5.0.jar', + 'libs/language-java-0.5.0.jar' + ) + + // TODO: completion providers should not be included on the main module + // alternate editor impl + implementation 'com.blacksquircle.ui:editorkit:2.1.2' + + implementation project(path: ':code-editor') + implementation project(path: ':xml-completion') + implementation project(path: ':build-tools:viewbinding-inject') + implementation project(path: ':build-tools:xml-repository') + implementation project(path: ':java-completion') + implementation projects.eventManager + // not used for compilation, but for analysis + implementation project(path: ':build-tools:javac') + + implementation project(path: ':language-api') + + // groovy support + implementation projects.buildTools.builderBaseServicesGroovy + + implementation projects.buildTools.builderLauncher + implementation projects.buildTools.builderBaseServices + implementation projects.buildTools.builderCore + implementation projects.buildTools.builderCoreApi + implementation projects.buildTools.builderLogging + implementation projects.buildTools.builderLauncher + implementation projects.buildTools.builderToolingApiBuilders + implementation projects.buildTools.builderBuildOperations + implementation projects.buildTools.builderLogging + implementation projects.buildTools.builderEnterpriseWorkers + implementation projects.buildTools.builderConfigurationCache + implementation projects.buildTools.builderPlugins + implementation projects.buildTools.builderJava + implementation projects.buildTools.builderNative + implementation project(path: ":build-tools:codeassist-builder-plugin") + implementation projects.buildTools.builderIde + implementation projects.buildTools.builderToolingApi + // emulating console + implementation 'org.fusesource.jansi:jansi:2.4.0' + + implementation project(path: ':build-tools:builder-api') + implementation project(path: ':build-tools:builder-java') implementation project(path: ':build-tools:build-logic') + implementation project(path: ':build-tools:manifmerger') + implementation project(path: ':build-tools:project') + implementation project(path: ':build-tools:logging') implementation project(path: ':build-tools:jaxp:jaxp-internal') implementation project(path: ':build-tools:jaxp:xml') implementation project(path: ':common') implementation project(path: ':build-tools:lint') implementation project(path: ':layout-preview') implementation project(path: ':kotlin-completion') - implementation 'io.github.medyo:android-about-page:2.0.0' - + + // Virtual File System + implementation 'org.apache.commons:commons-vfs2:2.9.0' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation project(path: ':dependency-resolver') implementation project(path: ':treeview') - // completions - implementation project(path: ':code-editor') - implementation project(path: ':xml-completion') - implementation project(path: ':java-completion') - // apis implementation project(path: ':completion-api') implementation project(path: ':actions-api') implementation project(path: ':editor-api') implementation project(path: ':fileeditor-api') - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") + // formatters + implementation project(path: ':google-java-format') - implementation 'androidx.appcompat:appcompat:1.4.0' - implementation 'androidx.core:core:1.7.0' + // about + implementation 'com.github.daniel-stoneuk:material-about-library:3.1.2' - implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'androidx.core:core:1.7.0' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.viewpager2:viewpager2:1.0.0' implementation 'androidx.viewpager:viewpager:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' - implementation 'androidx.lifecycle:lifecycle-livedata-core:2.4.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel:2.4.0' - implementation 'androidx.lifecycle:lifecycle-livedata:2.4.0' - implementation 'androidx.fragment:fragment:1.4.0' - implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' + implementation 'androidx.lifecycle:lifecycle-livedata-core:2.4.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel:2.4.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' + implementation 'androidx.lifecycle:lifecycle-livedata:2.4.1' + implementation 'androidx.fragment:fragment:1.4.1' + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' implementation 'androidx.activity:activity:1.4.0' implementation 'androidx.drawerlayout:drawerlayout:1.1.1' implementation 'com.github.angads25:filepicker:1.1.1' @@ -82,26 +171,26 @@ dependencies { // image loading implementation 'com.github.bumptech.glide:glide:4.12.0' - implementation 'org.antlr:antlr4-runtime:4.9.2' - - - implementation files ( - 'libs/google-java-format-1.7.jar', - 'libs/language-base-0.5.0.jar', - 'libs/language-java-0.5.0.jar' - ) - - implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.preference:preference:1.2.0' implementation 'com.github.TutorialsAndroid:crashx:v6.0.19' + implementation project(path: ':eclipse-formatter') + implementation project(path: ':build-tools:builder-core') + + runtimeOnly projects.javaStubs //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' + // testing testImplementation 'junit:junit:4.13.2' testImplementation "com.google.truth:truth:1.1.3" - testImplementation "org.robolectric:robolectric:4.2.1" - testImplementation 'androidx.test:core:1.4.0' testImplementation 'org.mockito:mockito-core:1.10.19' + testImplementation "org.robolectric:robolectric:4.7.3" + debugImplementation 'androidx.test:core:1.4.0' + debugImplementation 'androidx.fragment:fragment-testing:1.4.1' + androidTestImplementation 'com.google.truth:truth:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") } diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index a069db6e2..07a75be86 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 9, - "versionName": "0.2.4.1 ALPHA", + "versionCode": 20, + "versionName": "0.2.9 ALPHA", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b603b7342..6e81fdcb3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,22 +19,25 @@ android:largeHeap="true" android:requestLegacyExternalStorage="true" android:resizeableActivity="true" - android:theme="@style/AppTheme" + android:supportsRtl="true" tools:targetApi="q"> + android:exported="false"> + + android:label="@string/title_activity_settings" + android:theme="@style/AppThemeNew" /> + android:theme="@style/AppThemeNew" + android:windowSoftInputMode="adjustResize"> @@ -42,8 +45,6 @@ - - + + + author + Martin Kühl + comment + Based on the Quiet Light theme for Espresso by Ian Beck. + name + Quiet Light + settings + + + settings + + background + #F5F5F5 + caret + #000000 + foreground + #333333 + invisibles + #AAAAAA + lineHighlight + #E4F6D4 + selection + #C9D0D9 + + + + name + Comments + scope + comment, punctuation.definition.comment + settings + + fontStyle + italic + foreground + #AAAAAA + + + + name + Comments: Preprocessor + scope + comment.block.preprocessor + settings + + fontStyle + + foreground + #AAAAAA + + + + name + Comments: Documentation + scope + comment.documentation, comment.block.documentation + settings + + foreground + #448C27 + + + + name + Invalid - Deprecated + scope + invalid.deprecated + settings + + background + #96000014 + + + + name + Invalid - Illegal + scope + invalid.illegal + settings + + background + #96000014 + foreground + #660000 + + + + name + Operators + scope + keyword.operator + settings + + foreground + #777777 + + + + name + Keywords + scope + keyword, storage + settings + + foreground + #4B83CD + + + + name + Types + scope + storage.type, support.type + settings + + foreground + #7A3E9D + + + + name + Language Constants + scope + constant.language, support.constant, variable.language + settings + + foreground + #AB6526 + + + + name + Variables + scope + variable, support.variable + settings + + foreground + #7A3E9D + + + + name + Functions + scope + entity.name.function, support.function + settings + + fontStyle + bold + foreground + #AA3731 + + + + name + Classes + scope + entity.name.type, entity.other.inherited-class, support.class + settings + + fontStyle + bold + foreground + #7A3E9D + + + + name + Exceptions + scope + entity.name.exception + settings + + foreground + #660000 + + + + name + Sections + scope + entity.name.section + settings + + fontStyle + bold + + + + name + Numbers, Characters + scope + constant.numeric, constant.character, constant + settings + + foreground + #AB6526 + + + + name + Strings + scope + string + settings + + foreground + #448C27 + + + + name + Strings: Escape Sequences + scope + constant.character.escape + settings + + foreground + #777777 + + + + name + Strings: Regular Expressions + scope + string.regexp + settings + + foreground + #4B83CD + + + + name + Strings: Symbols + scope + constant.other.symbol + settings + + foreground + #AB6526 + + + + name + Punctuation + scope + punctuation + settings + + foreground + #777777 + + + + name + Embedded Source + scope + string source, text source + settings + + background + #EAEBE6 + + + + name + ----------------------------------- + settings + + + + name + HTML: Doctype Declaration + scope + meta.tag.sgml.doctype, meta.tag.sgml.doctype string, meta.tag.sgml.doctype + entity.name.tag, meta.tag.sgml punctuation.definition.tag.html + + settings + + foreground + #AAAAAA + + + + name + HTML: Tags + scope + meta.tag, punctuation.definition.tag.html, + punctuation.definition.tag.begin.html, punctuation.definition.tag.end.html + + settings + + foreground + #91B3E0 + + + + name + HTML: Tag Names + scope + entity.name.tag + settings + + foreground + #4B83CD + + + + name + HTML: Attribute Names + scope + meta.tag entity.other.attribute-name, entity.other.attribute-name.html + + settings + + foreground + #91B3E0 + + + + name + HTML: Entities + scope + constant.character.entity, punctuation.definition.entity + settings + + foreground + #AB6526 + + + + name + ----------------------------------- + settings + + + + name + CSS: Selectors + scope + meta.selector, meta.selector entity, meta.selector entity punctuation, + entity.name.tag.css + + settings + + foreground + #7A3E9D + + + + name + CSS: Property Names + scope + meta.property-name, support.type.property-name + settings + + foreground + #AB6526 + + + + name + CSS: Property Values + scope + meta.property-value, meta.property-value constant.other, + support.constant.property-value + + settings + + foreground + #448C27 + + + + name + CSS: Important Keyword + scope + keyword.other.important + settings + + fontStyle + bold + + + + name + ----------------------------------- + settings + + + + name + Markup: Changed + scope + markup.changed + settings + + background + #FFFFDD + foreground + #000000 + + + + name + Markup: Deletion + scope + markup.deleted + settings + + background + #FFDDDD + foreground + #000000 + + + + name + Markup: Emphasis + scope + markup.italic + settings + + fontStyle + italic + + + + name + Markup: Error + scope + markup.error + settings + + background + #96000014 + foreground + #660000 + + + + name + Markup: Insertion + scope + markup.inserted + settings + + background + #DDFFDD + foreground + #000000 + + + + name + Markup: Link + scope + meta.link + settings + + foreground + #4B83CD + + + + name + Markup: Output + scope + markup.output, markup.raw + settings + + foreground + #777777 + + + + name + Markup: Prompt + scope + markup.prompt + settings + + foreground + #777777 + + + + name + Markup: Heading + scope + markup.heading + settings + + foreground + #AA3731 + + + + name + Markup: Strong + scope + markup.bold + settings + + fontStyle + bold + + + + name + Markup: Traceback + scope + markup.traceback + settings + + foreground + #660000 + + + + name + Markup: Underline + scope + markup.underline + settings + + fontStyle + underline + + + + name + Markup Quote + scope + markup.quote + settings + + foreground + #7A3E9D + + + + name + Markup Lists + scope + markup.list + settings + + foreground + #4B83CD + + + + name + Markup Styling + scope + markup.bold, markup.italic + settings + + foreground + #448C27 + + + + name + Markup Inline + scope + markup.inline.raw + settings + + fontStyle + + foreground + #AB6526 + + + + name + ----------------------------------- + settings + + + + name + Extra: Diff Range + scope + meta.diff.range, meta.diff.index, meta.separator + settings + + background + #DDDDFF + foreground + #434343 + + + + name + Extra: Diff From + scope + meta.diff.header.from-file + settings + + background + #FFDDDD + foreground + #434343 + + + + name + Extra: Diff To + scope + meta.diff.header.to-file + settings + + background + #DDFFDD + foreground + #434343 + + + + uuid + 231D6A91-5FD1-4CBE-BD2A-0F36C08693F1 + + \ No newline at end of file diff --git a/app/src/main/assets/textmate/darcula.json b/app/src/main/assets/textmate/darcula.json new file mode 100644 index 000000000..06fbcfe89 --- /dev/null +++ b/app/src/main/assets/textmate/darcula.json @@ -0,0 +1,542 @@ +{ + "name": "darcula", + "settings": [ + { + "settings": { + "background": "#1F1A1B", + "foreground": "#cccccc", + "lineHighlight": "#2B2B2B", + "blockLineColor": "#575757", + "currentBlockLineColor": "#7a7a7a", + "selection": "#214283", + "completionWindowBackground": "#1F1A1B", + "completionWindowStroke": "#555555" + } + }, + { + "name": "Package declaration", + "scope": "storage.modifier.package", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Import declaration", + "scope": "storage.modifier.import", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Class names (Identifiers starting with uppercase)", + "scope": "storage.type.java", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Annotation", + "scope": "storage.type.annotation", + "settings": { + "foreground": "#BBB529" + } + }, + { + "name": "Comment", + "scope": "comment", + "settings": { + "foreground": "#707070" + } + }, + { + "name": "Operator Keywords", + "scope": "keyword.operator,keyword.operator.logical,keyword.operator.relational,keyword.operator.assignment,keyword.operator.comparison,keyword.operator.ternary,keyword.operator.arithmetic,keyword.operator.spread", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Strings", + "scope": "string,string.character.escape,string.template.quoted,string.template.quoted.punctuation,string.template.quoted.punctuation.single,string.template.quoted.punctuation.double,string.type.declaration.annotation,string.template.quoted.punctuation.tag", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "String Interpolation Begin and End", + "scope": "punctuation.definition.template-expression.begin,punctuation.definition.template-expression.end", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "String Interpolation Body", + "scope": "expression.string,meta.template.expression", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Number", + "scope": "constant.numeric", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "Built-in constant", + "scope": "constant.language,variable.language", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "User-defined constant", + "scope": "constant.character, constant.other", + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "Keyword", + "scope": "keyword,keyword.operator.new,keyword.operator.delete,keyword.operator.static,keyword.operator.this,keyword.operator.expression", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Method return type", + "scope": "meta.method.return-type", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Method call identifier", + "scope": "meta.method-call", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Types, Class Types", + "scope": "entity.name.type,meta.return.type,meta.type.annotation,meta.type.parameters,support.type.primitive", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "Storage type", + "scope": "storage,storage.type,storage.modifier,storage.arrow", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Class constructor", + "scope": "class.instance.constructor,new.expr entity.name.type", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Function", + "scope": "support.function, entity.name.function", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Function Types", + "scope": "annotation.meta.ts, annotation.meta.tsx", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Function Argument", + "scope": "variable.parameter, operator.rest.parameters", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Variable, Property", + "scope": "variable.property,variable.other.property,variable.other.object.property,variable.object.property,support.variable.property", + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "Variable name", + "scope": "entity.name.variable", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "CONSTANT", + "scope": "variable.other.constant", + "settings": { + "foreground": "#9876AA" + } + }, + { + "name": "Module Name", + "scope": "quote.module", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "Markup Headings", + "scope": "markup.heading", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Tag name", + "scope": "punctuation.definition.tag.html, punctuation.definition.tag.begin, punctuation.definition.tag.end, entity.name.tag", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Tag attribute", + "scope": "entity.other.attribute-name", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Object Keys", + "scope": "meta.object-literal.key", + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "TypeScript Class Modifiers", + "scope": "storage.modifier.ts", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "TypeScript Type Casting", + "scope": "ts.cast.expr,ts.meta.entity.class.method.new.expr.cast,ts.meta.entity.type.name.new.expr.cast,ts.meta.entity.type.name.var-single-variable.annotation,tsx.cast.expr,tsx.meta.entity.class.method.new.expr.cast,tsx.meta.entity.type.name.new.expr.cast,tsx.meta.entity.type.name.var-single-variable.annotation", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "TypeScript Type Declaration", + "scope": "ts.meta.type.support,ts.meta.type.entity.name,ts.meta.class.inherited-class,tsx.meta.type.support,tsx.meta.type.entity.name,tsx.meta.class.inherited-class,type-declaration,enum-declaration", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "TypeScript Method Declaration", + "scope": "function-declaration,method-declaration,method-overload-declaration,type-fn-type-parameters", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Documentation Block", + "scope": "comment.block.documentation", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "Documentation Highlight (JSDoc)", + "scope": "storage.type.class.jsdoc", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Import-Export-All (*) Keyword", + "scope": "constant.language.import-export-all", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Object Key Seperator", + "scope": "objectliteral.key.separator, punctuation.separator.key-value", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex", + "scope": "regex", + "settings": { + "fontStyle": " italic" + } + }, + { + "name": "Typescript Namespace", + "scope": "ts.meta.entity.name.namespace,tsx.meta.entity.name.namespace", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex Character-class", + "scope": "regex.character-class", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Class Name", + "scope": "entity.name.type.class", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Class Inheritances", + "scope": "entity.other.inherited-class", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "Documentation Entity", + "scope": "entity.name.type.instance.jsdoc", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "YAML entity", + "scope": "yaml.entity.name,yaml.string.entity.name", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "YAML string value", + "scope": "yaml.string.out", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Ignored (Exceptions Rules)", + "scope": "meta.brace.square.ts,block.support.module,block.support.type.module,block.support.function.variable,punctuation.definition.typeparameters.begin,punctuation.definition.typeparameters.end", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex", + "scope": "string.regexp", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Regex Group/Set", + "scope": "punctuation.definition.group.regexp,punctuation.definition.character-class.regexp", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Regex Character Class", + "scope": "constant.other.character-class.regexp, constant.character.escape.ts", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex Or Operator", + "scope": "expr.regex.or.operator", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Tag string", + "scope": "string.template.tag,string.template.punctuation.tag,string.quoted.punctuation.tag,string.quoted.embedded.tag, string.quoted.double.tag", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "Tag function parenthesis", + "scope": "tag.punctuation.begin.arrow.parameters.embedded,tag.punctuation.end.arrow.parameters.embedded", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Object-literal key class", + "scope": "object-literal.object.member.key.field.other,object-literal.object.member.key.accessor,object-literal.object.member.key.array.brace.square", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "CSS Property-value", + "scope": "property-list.property-value,property-list.constant", + "settings": { + "foreground": "#A5C261" + } + }, + { + "name": "CSS Property variable", + "scope": "support.type.property-name.variable.css,support.type.property-name.variable.scss,variable.scss", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "CSS Property entity", + "scope": "entity.other.attribute-name.class.css,entity.other.attribute-name.class.scss,entity.other.attribute-name.parent-selector-suffix.css,entity.other.attribute-name.parent-selector-suffix.scss", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "CSS Property-value", + "scope": "property-list.property-value.rgb-value, keyword.other.unit.css,keyword.other.unit.scss", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "CSS Property-value function", + "scope": "property-list.property-value.function", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "CSS constant variables", + "scope": "support.constant.property-value.css,support.constant.property-value.scss", + "settings": { + "foreground": "#A5C261" + } + }, + { + "name": "CSS Tag", + "scope": "css.entity.name.tag,scss.entity.name.tag", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "CSS ID, Selector", + "scope": "meta.selector.css, entity.attribute-name.id, entity.other.attribute-name.pseudo-class.css,entity.other.attribute-name.pseudo-element.css", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "CSS Keyword", + "scope": "keyword.scss,keyword.css", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Triple-slash Directive Tag", + "scope": "triple-slash.tag", + "settings": { + "foreground": "#CCCCCC", + "fontStyle": "italic" + } + }, + { + "scope": "token.info-token", + "settings": { + "foreground": "#6796e6" + } + }, + { + "scope": "token.warn-token", + "settings": { + "foreground": "#cd9731" + } + }, + { + "scope": "token.error-token", + "settings": { + "foreground": "#f44747" + } + }, + { + "scope": "token.debug-token", + "settings": { + "foreground": "#b267e6" + } + }, + { + "name": "Python operators", + "scope": "keyword.operator.logical.python", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Dart class type", + "scope": "support.class.dart", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "PHP variables", + "scope": [ + "variable.language.php", + "variable.other.php" + ], + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "Perl specific", + "scope": [ + "variable.other.readwrite.perl" + ], + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "PHP variables", + "scope": [ + "variable.other.property.php" + ], + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "PHP variables", + "scope": [ + "support.variable.property.php" + ], + "settings": { + "foreground": "#FFC66D" + } + }, + + { + "name": "XML Namespace prefix", + "scope": "entity.name.tag.namesapce.xml", + "settings": { + "foreground": "#9876AA" + } + } + ] +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/groovy/language-configuration.json b/app/src/main/assets/textmate/groovy/language-configuration.json new file mode 100644 index 000000000..f339aa970 --- /dev/null +++ b/app/src/main/assets/textmate/groovy/language-configuration.json @@ -0,0 +1,43 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"], + ["<", ">"] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/groovy/syntaxes/groovy.tmLanguage b/app/src/main/assets/textmate/groovy/syntaxes/groovy.tmLanguage new file mode 100644 index 000000000..67d5b0861 --- /dev/null +++ b/app/src/main/assets/textmate/groovy/syntaxes/groovy.tmLanguage @@ -0,0 +1,2145 @@ + + + + + fileTypes + + groovy + gvy + + foldingStartMarker + (\{\s*$|^\s*// \{\{\{) + foldingStopMarker + ^\s*(\}|// \}\}\}$) + keyEquivalent + ^~G + name + Groovy + patterns + + + captures + + 1 + + name + punctuation.definition.comment.groovy + + + match + ^(#!).+$\n + name + comment.line.hashbang.groovy + + + captures + + 1 + + name + keyword.other.package.groovy + + 2 + + name + storage.modifier.package.groovy + + 3 + + name + punctuation.terminator.groovy + + + match + ^\s*(package)\b(?:\s*([^ ;$]+)\s*(;)?)? + name + meta.package.groovy + + + begin + (import static)\b\s* + beginCaptures + + 1 + + name + keyword.other.import.static.groovy + + + captures + + 1 + + name + keyword.other.import.groovy + + 2 + + name + storage.modifier.import.groovy + + 3 + + name + punctuation.terminator.groovy + + + contentName + storage.modifier.import.groovy + end + \s*(?:$|(?=%>)(;)) + endCaptures + + 1 + + name + punctuation.terminator.groovy + + + name + meta.import.groovy + patterns + + + match + \. + name + punctuation.separator.groovy + + + match + \s + name + invalid.illegal.character_not_allowed_here.groovy + + + + + begin + (import)\b\s* + beginCaptures + + 1 + + name + keyword.other.import.groovy + + + captures + + 1 + + name + keyword.other.import.groovy + + 2 + + name + storage.modifier.import.groovy + + 3 + + name + punctuation.terminator.groovy + + + contentName + storage.modifier.import.groovy + end + \s*(?:$|(?=%>)|(;)) + endCaptures + + 1 + + name + punctuation.terminator.groovy + + + name + meta.import.groovy + patterns + + + match + \. + name + punctuation.separator.groovy + + + match + \s + name + invalid.illegal.character_not_allowed_here.groovy + + + + + captures + + 1 + + name + keyword.other.import.groovy + + 2 + + name + keyword.other.import.static.groovy + + 3 + + name + storage.modifier.import.groovy + + 4 + + name + punctuation.terminator.groovy + + + match + ^\s*(import)(?:\s+(static)\s+)\b(?:\s*([^ ;$]+)\s*(;)?)? + name + meta.import.groovy + + + include + #groovy + + + repository + + annotations + + patterns + + + begin + (?<!\.)(@[^ (]+)(\() + beginCaptures + + 1 + + name + storage.type.annotation.groovy + + 2 + + name + punctuation.definition.annotation-arguments.begin.groovy + + + end + (\)) + endCaptures + + 1 + + name + punctuation.definition.annotation-arguments.end.groovy + + + name + meta.declaration.annotation.groovy + patterns + + + captures + + 1 + + name + constant.other.key.groovy + + 2 + + name + keyword.operator.assignment.groovy + + + match + (\w*)\s*(=) + + + include + #values + + + match + , + name + punctuation.definition.seperator.groovy + + + + + match + (?<!\.)@\S+ + name + storage.type.annotation.groovy + + + + anonymous-classes-and-new + + begin + \bnew\b + beginCaptures + + 0 + + name + keyword.control.new.groovy + + + end + (?<=\)|\])(?!\s*{)|(?<=})|(?=[;])|$ + patterns + + + begin + (\w+)\s*(?=\[) + beginCaptures + + 1 + + name + storage.type.groovy + + + end + }|(?=\s*(?:,|;|\)))|$ + patterns + + + begin + \[ + end + \] + patterns + + + include + #groovy + + + + + begin + { + end + (?=}) + patterns + + + include + #groovy + + + + + + + begin + (?=\w.*\(?) + end + (?<=\))|$ + patterns + + + include + #object-types + + + begin + \( + beginCaptures + + 1 + + name + storage.type.groovy + + + end + \) + patterns + + + include + #groovy + + + + + + + begin + { + end + } + name + meta.inner-class.groovy + patterns + + + include + #class-body + + + + + + braces + + begin + \{ + end + \} + patterns + + + include + #groovy-code + + + + class + + begin + (?=\w?[\w\s]*(?:class|(?:@)?interface|enum)\s+\w+) + end + } + endCaptures + + 0 + + name + punctuation.section.class.end.groovy + + + name + meta.definition.class.groovy + patterns + + + include + #storage-modifiers + + + include + #comments + + + captures + + 1 + + name + storage.modifier.groovy + + 2 + + name + entity.name.type.class.groovy + + + match + (class|(?:@)?interface|enum)\s+(\w+) + name + meta.class.identifier.groovy + + + begin + extends + beginCaptures + + 0 + + name + storage.modifier.extends.groovy + + + end + (?={|implements) + name + meta.definition.class.inherited.classes.groovy + patterns + + + include + #object-types-inherited + + + include + #comments + + + + + begin + (implements)\s + beginCaptures + + 1 + + name + storage.modifier.implements.groovy + + + end + (?=\s*extends|\{) + name + meta.definition.class.implemented.interfaces.groovy + patterns + + + include + #object-types-inherited + + + include + #comments + + + + + begin + { + end + (?=}) + name + meta.class.body.groovy + patterns + + + include + #class-body + + + + + + class-body + + patterns + + + include + #enum-values + + + include + #constructors + + + include + #groovy + + + + closures + + begin + \{(?=.*?->) + end + \} + patterns + + + begin + (?<=\{)(?=[^\}]*?->) + end + -> + endCaptures + + 0 + + name + keyword.operator.groovy + + + patterns + + + begin + (?!->) + end + (?=->) + name + meta.closure.parameters.groovy + patterns + + + begin + (?!,|->) + end + (?=,|->) + name + meta.closure.parameter.groovy + patterns + + + begin + = + beginCaptures + + 0 + + name + keyword.operator.assignment.groovy + + + end + (?=,|->) + name + meta.parameter.default.groovy + patterns + + + include + #groovy-code + + + + + include + #parameters + + + + + + + + + begin + (?=[^}]) + end + (?=\}) + patterns + + + include + #groovy-code + + + + + + comment-block + + begin + /\* + captures + + 0 + + name + punctuation.definition.comment.groovy + + + end + \*/ + name + comment.block.groovy + + comments + + patterns + + + captures + + 0 + + name + punctuation.definition.comment.groovy + + + match + /\*\*/ + name + comment.block.empty.groovy + + + include + text.html.javadoc + + + include + #comment-block + + + captures + + 1 + + name + punctuation.definition.comment.groovy + + + match + (//).*$\n? + name + comment.line.double-slash.groovy + + + + constants + + patterns + + + match + \b([A-Z][A-Z0-9_]+)\b + name + constant.other.groovy + + + match + \b(true|false|null)\b + name + constant.language.groovy + + + + constructors + + applyEndPatternLast + 1 + begin + (?<=;|^)(?=\s*(?:(?:private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final)\s+)*[A-Z]\w*\() + end + } + patterns + + + include + #method-content + + + + enum-values + + patterns + + + begin + (?<=;|^)\s*\b([A-Z0-9_]+)(?=\s*(?:,|;|}|\(|$)) + beginCaptures + + 1 + + name + constant.enum.name.groovy + + + end + ,|;|(?=})|^(?!\s*\w+\s*(?:,|$)) + patterns + + + begin + \( + end + \) + name + meta.enum.value.groovy + patterns + + + match + , + name + punctuation.definition.seperator.parameter.groovy + + + include + #groovy-code + + + + + + + + groovy + + patterns + + + include + #comments + + + include + #class + + + include + #variables + + + include + #methods + + + include + #annotations + + + include + #groovy-code + + + + groovy-code + + patterns + + + include + #groovy-code-minus-map-keys + + + include + #map-keys + + + + groovy-code-minus-map-keys + + comment + In some situations, maps can't be declared without enclosing []'s, + therefore we create a collection of everything but that + patterns + + + include + #comments + + + include + #annotations + + + include + #support-functions + + + include + #keyword-language + + + include + #values + + + include + #anonymous-classes-and-new + + + include + #keyword-operator + + + include + #types + + + include + #storage-modifiers + + + include + #parens + + + include + #closures + + + include + #braces + + + + keyword + + patterns + + + include + #keyword-operator + + + include + #keyword-language + + + + keyword-language + + patterns + + + match + \b(try|catch|finally|throw)\b + name + keyword.control.exception.groovy + + + match + \b((?<!\.)(?:return|break|continue|default|do|while|for|switch|if|else))\b + name + keyword.control.groovy + + + begin + \bcase\b + beginCaptures + + 0 + + name + keyword.control.groovy + + + end + : + endCaptures + + 0 + + name + punctuation.definition.case-terminator.groovy + + + name + meta.case.groovy + patterns + + + include + #groovy-code-minus-map-keys + + + + + begin + \b(assert)\s + beginCaptures + + 1 + + name + keyword.control.assert.groovy + + + end + $|;|} + name + meta.declaration.assertion.groovy + patterns + + + match + : + name + keyword.operator.assert.expression-seperator.groovy + + + include + #groovy-code-minus-map-keys + + + + + match + \b(throws)\b + name + keyword.other.throws.groovy + + + + keyword-operator + + patterns + + + match + \b(as)\b + name + keyword.operator.as.groovy + + + match + \b(in)\b + name + keyword.operator.in.groovy + + + match + \?\: + name + keyword.operator.elvis.groovy + + + match + \*\: + name + keyword.operator.spreadmap.groovy + + + match + \.\. + name + keyword.operator.range.groovy + + + match + \-> + name + keyword.operator.arrow.groovy + + + match + << + name + keyword.operator.leftshift.groovy + + + match + (?<=\S)\.(?=\S) + name + keyword.operator.navigation.groovy + + + match + (?<=\S)\?\.(?=\S) + name + keyword.operator.safe-navigation.groovy + + + begin + \? + beginCaptures + + 0 + + name + keyword.operator.ternary.groovy + + + end + (?=$|\)|}|]) + name + meta.evaluation.ternary.groovy + patterns + + + match + : + name + keyword.operator.ternary.expression-seperator.groovy + + + include + #groovy-code-minus-map-keys + + + + + match + ==~ + name + keyword.operator.match.groovy + + + match + =~ + name + keyword.operator.find.groovy + + + match + \b(instanceof)\b + name + keyword.operator.instanceof.groovy + + + match + (===|==|!=|<=|>=|<=>|<>|<|>|<<) + name + keyword.operator.comparison.groovy + + + match + = + name + keyword.operator.assignment.groovy + + + match + (\-\-|\+\+) + name + keyword.operator.increment-decrement.groovy + + + match + (\-|\+|\*|\/|%) + name + keyword.operator.arithmetic.groovy + + + match + (!|&&|\|\|) + name + keyword.operator.logical.groovy + + + + language-variables + + patterns + + + match + \b(this|super)\b + name + variable.language.groovy + + + + map-keys + + patterns + + + captures + + 1 + + name + constant.other.key.groovy + + 2 + + name + punctuation.definition.seperator.key-value.groovy + + + match + (\w+)\s*(:) + + + + method-call + + begin + ([\w$]+)(\() + beginCaptures + + 1 + + name + meta.method.groovy + + 2 + + name + punctuation.definition.method-parameters.begin.groovy + + + end + \) + endCaptures + + 0 + + name + punctuation.definition.method-parameters.end.groovy + + + name + meta.method-call.groovy + patterns + + + match + , + name + punctuation.definition.seperator.parameter.groovy + + + include + #groovy-code + + + + method-content + + patterns + + + match + \s + + + include + #annotations + + + begin + (?=(?:\w|<)[^\(]*\s+(?:[\w$]|<)+\s*\() + end + (?=[\w$]+\s*\() + name + meta.method.return-type.java + patterns + + + include + #storage-modifiers + + + include + #types + + + + + begin + ([\w$]+)\s*\( + beginCaptures + + 1 + + name + entity.name.function.java + + + end + \) + name + meta.definition.method.signature.java + patterns + + + begin + (?=[^)]) + end + (?=\)) + name + meta.method.parameters.groovy + patterns + + + begin + (?=[^,)]) + end + (?=,|\)) + name + meta.method.parameter.groovy + patterns + + + match + , + name + punctuation.definition.separator.groovy + + + begin + = + beginCaptures + + 0 + + name + keyword.operator.assignment.groovy + + + end + (?=,|\)) + name + meta.parameter.default.groovy + patterns + + + include + #groovy-code + + + + + include + #parameters + + + + + + + + + begin + (?=<) + end + (?=\s) + name + meta.method.paramerised-type.groovy + patterns + + + begin + < + end + > + name + storage.type.parameters.groovy + patterns + + + include + #types + + + match + , + name + punctuation.definition.seperator.groovy + + + + + + + begin + throws + beginCaptures + + 0 + + name + storage.modifier.groovy + + + end + (?={|;)|^(?=\s*(?:[^{\s]|$)) + name + meta.throwables.groovy + patterns + + + include + #object-types + + + + + begin + { + end + (?=}) + name + meta.method.body.java + patterns + + + include + #groovy-code + + + + + + methods + + applyEndPatternLast + 1 + begin + (?x:(?<=;|^|{)(?=\s* + (?: + (?:private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final) # visibility/modifier + | + (?:def) + | + (?: + (?: + (?:void|boolean|byte|char|short|int|float|long|double) + | + (?:@?(?:[a-zA-Z]\w*\.)*[A-Z]+\w*) # object type + ) + [\[\]]* + (?:<.*>)? + ) + + ) + \s+ + ([^=]+\s+)?\w+\s*\( + )) + end + }|(?=[^{]) + name + meta.definition.method.groovy + patterns + + + include + #method-content + + + + nest_curly + + begin + \{ + captures + + 0 + + name + punctuation.section.scope.groovy + + + end + \} + patterns + + + include + #nest_curly + + + + numbers + + patterns + + + match + ((0(x|X)[0-9a-fA-F]*)|(\+|-)?\b(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)([LlFfUuDdg]|UL|ul)?\b + name + constant.numeric.groovy + + + + object-types + + patterns + + + begin + \b((?:[a-z]\w*\.)*(?:[A-Z]+\w*[a-z]+\w*|UR[LI]))< + end + >|[^\w\s,\?<\[\]] + name + storage.type.generic.groovy + patterns + + + include + #object-types + + + begin + < + comment + This is just to support <>'s with no actual type prefix + end + >|[^\w\s,\[\]<] + name + storage.type.generic.groovy + + + + + begin + \b((?:[a-z]\w*\.)*[A-Z]+\w*[a-z]+\w*)(?=\[) + end + (?=[^\]\s]) + name + storage.type.object.array.groovy + patterns + + + begin + \[ + end + \] + patterns + + + include + #groovy + + + + + + + match + \b(?:[a-zA-Z]\w*\.)*(?:[A-Z]+\w*[a-z]+\w*|UR[LI])\b + name + storage.type.groovy + + + + object-types-inherited + + patterns + + + begin + \b((?:[a-zA-Z]\w*\.)*[A-Z]+\w*[a-z]+\w*)< + end + >|[^\w\s,\?<\[\]] + name + entity.other.inherited-class.groovy + patterns + + + include + #object-types-inherited + + + begin + < + comment + This is just to support <>'s with no actual type prefix + end + >|[^\w\s,\[\]<] + name + storage.type.generic.groovy + + + + + captures + + 1 + + name + keyword.operator.dereference.groovy + + + match + \b(?:[a-zA-Z]\w*(\.))*[A-Z]+\w*[a-z]+\w*\b + name + entity.other.inherited-class.groovy + + + + parameters + + patterns + + + include + #annotations + + + include + #storage-modifiers + + + include + #types + + + match + \w+ + name + variable.parameter.method.groovy + + + + parens + + begin + \( + end + \) + patterns + + + include + #groovy-code + + + + primitive-arrays + + patterns + + + match + \b(?:void|boolean|byte|char|short|int|float|long|double)(\[\])*\b + name + storage.type.primitive.array.groovy + + + + primitive-types + + patterns + + + match + \b(?:void|boolean|byte|char|short|int|float|long|double)\b + name + storage.type.primitive.groovy + + + + regexp + + patterns + + + begin + /(?=[^/]+/([^>]|$)) + beginCaptures + + 0 + + name + punctuation.definition.string.regexp.begin.groovy + + + end + / + endCaptures + + 0 + + name + punctuation.definition.string.regexp.end.groovy + + + name + string.regexp.groovy + patterns + + + match + \\. + name + constant.character.escape.groovy + + + + + begin + ~" + beginCaptures + + 0 + + name + punctuation.definition.string.regexp.begin.groovy + + + end + " + endCaptures + + 0 + + name + punctuation.definition.string.regexp.end.groovy + + + name + string.regexp.compiled.groovy + patterns + + + match + \\. + name + constant.character.escape.groovy + + + + + + storage-modifiers + + patterns + + + match + \b(private|protected|public)\b + name + storage.modifier.access-control.groovy + + + match + \b(static)\b + name + storage.modifier.static.groovy + + + match + \b(final)\b + name + storage.modifier.final.groovy + + + match + \b(native|synchronized|abstract|threadsafe|transient)\b + name + storage.modifier.other.groovy + + + + string-quoted-double + + begin + " + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + " + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.double.groovy + patterns + + + include + #string-quoted-double-contents + + + + string-quoted-double-contents + + patterns + + + match + \\. + name + constant.character.escape.groovy + + + applyEndPatternLast + 1 + begin + \$\w + end + (?=\W) + name + variable.other.interpolated.groovy + patterns + + + match + \w + name + variable.other.interpolated.groovy + + + match + \. + name + keyword.other.dereference.groovy + + + + + begin + \$\{ + captures + + 0 + + name + punctuation.section.embedded.groovy + + + end + \} + name + source.groovy.embedded.source + patterns + + + include + #nest_curly + + + + + + string-quoted-double-multiline + + begin + """ + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + """ + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.double.multiline.groovy + patterns + + + include + #string-quoted-double-contents + + + + string-quoted-single + + begin + ' + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + ' + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.single.groovy + patterns + + + include + #string-quoted-single-contents + + + + string-quoted-single-contents + + patterns + + + match + \\. + name + constant.character.escape.groovy + + + + string-quoted-single-multiline + + begin + ''' + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + ''' + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.single.multiline.groovy + patterns + + + include + #string-quoted-single-contents + + + + strings + + patterns + + + include + #string-quoted-double-multiline + + + include + #string-quoted-single-multiline + + + include + #string-quoted-double + + + include + #string-quoted-single + + + include + #regexp + + + + structures + + begin + \[ + beginCaptures + + 0 + + name + punctuation.definition.structure.begin.groovy + + + end + \] + endCaptures + + 0 + + name + punctuation.definition.structure.end.groovy + + + name + meta.structure.groovy + patterns + + + include + #groovy-code + + + match + , + name + punctuation.definition.separator.groovy + + + + support-functions + + patterns + + + match + (?x)\b(?:sprintf|print(?:f|ln)?)\b + name + support.function.print.groovy + + + match + (?x)\b(?:shouldFail|fail(?:NotEquals)?|ass(?:ume|ert(?:S(?:cript|ame)|N(?:ot(?:Same| + Null)|ull)|Contains|T(?:hat|oString|rue)|Inspect|Equals|False|Length| + ArrayEquals)))\b + name + support.function.testing.groovy + + + + types + + patterns + + + match + \b(def)\b + name + storage.type.def.groovy + + + include + #primitive-types + + + include + #primitive-arrays + + + include + #object-types + + + + values + + patterns + + + include + #language-variables + + + include + #strings + + + include + #numbers + + + include + #constants + + + include + #types + + + include + #structures + + + include + #method-call + + + + variables + + applyEndPatternLast + 1 + patterns + + + begin + (?x:(?= + (?: + (?:private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final) # visibility/modifier + | + (?:def) + | + (?:void|boolean|byte|char|short|int|float|long|double) + | + (?:(?:[a-z]\w*\.)*[A-Z]+\w*) # object type + ) + \s+ + [\w\d_<>\[\],\s]+ + (?:=|$) + + )) + end + ;|$ + name + meta.definition.variable.groovy + patterns + + + match + \s + + + captures + + 1 + + name + constant.variable.groovy + + + match + ([A-Z_0-9]+)\s+(?=\=) + + + captures + + 1 + + name + meta.definition.variable.name.groovy + + + match + (\w[^\s,]*)\s+(?=\=) + + + begin + = + beginCaptures + + 0 + + name + keyword.operator.assignment.groovy + + + end + $ + patterns + + + include + #groovy-code + + + + + captures + + 1 + + name + meta.definition.variable.name.groovy + + + match + (\w[^\s=]*)(?=\s*($|;)) + + + include + #groovy-code + + + + + + + scopeName + source.groovy + uuid + B3A64888-EBBB-4436-8D9E-F1169C5D7613 + + diff --git a/app/src/main/assets/textmate/java/language-configuration.json b/app/src/main/assets/textmate/java/language-configuration.json new file mode 100644 index 000000000..f339aa970 --- /dev/null +++ b/app/src/main/assets/textmate/java/language-configuration.json @@ -0,0 +1,43 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"], + ["<", ">"] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/java/syntaxes/java.tmLanguage.json b/app/src/main/assets/textmate/java/syntaxes/java.tmLanguage.json new file mode 100644 index 000000000..e80a6b5bf --- /dev/null +++ b/app/src/main/assets/textmate/java/syntaxes/java.tmLanguage.json @@ -0,0 +1,1855 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/atom/language-java/blob/master/grammars/java.cson", + "If you want to provide a fix or improvement, please create a pull request against the original repository.", + "Once accepted there, we are happy to receive an update request." + ], + "version": "https://github.com/atom/language-java/commit/29f977dc42a7e2568b39bb6fb34c4ef108eb59b3", + "name": "Java", + "scopeName": "source.java", + "patterns": [ + { + "begin": "\\b(package)\\b\\s*", + "beginCaptures": { + "1": { + "name": "keyword.other.package.java" + } + }, + "end": "\\s*(;)", + "endCaptures": { + "1": { + "name": "punctuation.terminator.java" + } + }, + "name": "meta.package.java", + "contentName": "storage.modifier.package.java", + "patterns": [ + { + "include": "#comments" + }, + { + "match": "(?<=\\.)\\s*\\.|\\.(?=\\s*;)", + "name": "invalid.illegal.character_not_allowed_here.java" + }, + { + "match": "(?", + "endCaptures": { + "0": { + "name": "punctuation.bracket.angle.java" + } + }, + "patterns": [ + { + "match": "\\b(extends|super)\\b", + "name": "storage.modifier.$1.java" + }, + { + "match": "(?>>?|~|\\^)", + "name": "keyword.operator.bitwise.java" + }, + { + "match": "((&|\\^|\\||<<|>>>?)=)", + "name": "keyword.operator.assignment.bitwise.java" + }, + { + "match": "(===?|!=|<=|>=|<>|<|>)", + "name": "keyword.operator.comparison.java" + }, + { + "match": "([+*/%-]=)", + "name": "keyword.operator.assignment.arithmetic.java" + }, + { + "match": "(=)", + "name": "keyword.operator.assignment.java" + }, + { + "match": "(\\-\\-|\\+\\+)", + "name": "keyword.operator.increment-decrement.java" + }, + { + "match": "(\\-|\\+|\\*|\\/|%)", + "name": "keyword.operator.arithmetic.java" + }, + { + "match": "(!|&&|\\|\\|)", + "name": "keyword.operator.logical.java" + }, + { + "match": "(\\||&)", + "name": "keyword.operator.bitwise.java" + }, + { + "match": "\\b(const|goto)\\b", + "name": "keyword.reserved.java" + } + ] + }, + "lambda-expression": { + "patterns": [ + { + "match": "->", + "name": "storage.type.function.arrow.java" + } + ] + }, + "member-variables": { + "begin": "(?=private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final)", + "end": "(?=\\=|;)", + "patterns": [ + { + "include": "#storage-modifiers" + }, + { + "include": "#variables" + }, + { + "include": "#primitive-arrays" + }, + { + "include": "#object-types" + } + ] + }, + "method-call": { + "begin": "(\\.)\\s*([A-Za-z_$][\\w$]*)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "punctuation.separator.period.java" + }, + "2": { + "name": "entity.name.function.java" + }, + "3": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "name": "meta.method-call.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + "methods": { + "begin": "(?!new)(?=[\\w<].*\\s+)(?=([^=/]|/(?!/))+\\()", + "end": "(})|(?=;)", + "endCaptures": { + "1": { + "name": "punctuation.section.method.end.bracket.curly.java" + } + }, + "name": "meta.method.java", + "patterns": [ + { + "include": "#storage-modifiers" + }, + { + "begin": "(\\w+)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "entity.name.function.java" + }, + "2": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "name": "meta.method.identifier.java", + "patterns": [ + { + "include": "#parameters" + }, + { + "include": "#parens" + }, + { + "include": "#comments" + } + ] + }, + { + "include": "#generics" + }, + { + "begin": "(?=\\w.*\\s+\\w+\\s*\\()", + "end": "(?=\\s+\\w+\\s*\\()", + "name": "meta.method.return-type.java", + "patterns": [ + { + "include": "#all-types" + }, + { + "include": "#parens" + }, + { + "include": "#comments" + } + ] + }, + { + "include": "#throws" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.method.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.method.body.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "include": "#comments" + } + ] + }, + "module": { + "begin": "((open)\\s)?(module)\\s+(\\w+)", + "end": "}", + "beginCaptures": { + "1": { + "name": "storage.modifier.java" + }, + "3": { + "name": "storage.modifier.java" + }, + "4": { + "name": "entity.name.type.module.java" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.module.end.bracket.curly.java" + } + }, + "name": "meta.module.java", + "patterns": [ + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.module.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.module.body.java", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#comments-javadoc" + }, + { + "match": "\\b(requires|transitive|exports|opens|to|uses|provides|with)\\b", + "name": "keyword.module.java" + } + ] + } + ] + }, + "numbers": { + "patterns": [ + { + "match": "(?x)\n\\b(?)?(\\()", + "beginCaptures": { + "1": { + "name": "storage.modifier.java" + }, + "2": { + "name": "entity.name.type.record.java" + }, + "3": { + "patterns": [ + { + "include": "#generics" + } + ] + }, + "4": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "name": "meta.record.identifier.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "begin": "(implements)\\s", + "beginCaptures": { + "1": { + "name": "storage.modifier.implements.java" + } + }, + "end": "(?=\\s*\\{)", + "name": "meta.definition.class.implemented.interfaces.java", + "patterns": [ + { + "include": "#object-types-inherited" + }, + { + "include": "#comments" + } + ] + }, + { + "include": "#record-body" + } + ] + }, + "record-body": { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.class.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "name": "meta.record.body.java", + "patterns": [ + { + "include": "#record-constructor" + }, + { + "include": "#class-body" + } + ] + }, + "record-constructor": { + "begin": "(?!new)(?=[\\w<].*\\s+)(?=([^\\(=/]|/(?!/))+(?={))", + "end": "(})|(?=;)", + "endCaptures": { + "1": { + "name": "punctuation.section.method.end.bracket.curly.java" + } + }, + "name": "meta.method.java", + "patterns": [ + { + "include": "#storage-modifiers" + }, + { + "begin": "(\\w+)", + "beginCaptures": { + "1": { + "name": "entity.name.function.java" + } + }, + "end": "(?=\\s*{)", + "name": "meta.method.identifier.java", + "patterns": [ + { + "include": "#comments" + } + ] + }, + { + "include": "#comments" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.method.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.method.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + }, + "static-initializer": { + "patterns": [ + { + "include": "#anonymous-block-and-instance-initializer" + }, + { + "match": "static", + "name": "storage.modifier.java" + } + ] + }, + "storage-modifiers": { + "match": "\\b(public|private|protected|static|final|native|synchronized|abstract|threadsafe|transient|volatile|default|strictfp|sealed|non-sealed)\\b", + "name": "storage.modifier.java" + }, + "strings": { + "patterns": [ + { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.java" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.java" + } + }, + "name": "string.quoted.double.java", + "patterns": [ + { + "match": "\\\\.", + "name": "constant.character.escape.java" + } + ] + }, + { + "begin": "'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.java" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.java" + } + }, + "name": "string.quoted.single.java", + "patterns": [ + { + "match": "\\\\.", + "name": "constant.character.escape.java" + } + ] + } + ] + }, + "throws": { + "begin": "throws", + "beginCaptures": { + "0": { + "name": "storage.modifier.java" + } + }, + "end": "(?={|;)", + "name": "meta.throwables.java", + "patterns": [ + { + "match": ",", + "name": "punctuation.separator.delimiter.java" + }, + { + "match": "[a-zA-Z$_][\\.a-zA-Z0-9$_]*", + "name": "storage.type.java" + } + ] + }, + "try-catch-finally": { + "patterns": [ + { + "begin": "\\btry\\b", + "beginCaptures": { + "0": { + "name": "keyword.control.try.java" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.try.end.bracket.curly.java" + } + }, + "name": "meta.try.java", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.section.try.resources.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.section.try.resources.end.bracket.round.java" + } + }, + "name": "meta.try.resources.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.try.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.try.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + }, + { + "begin": "\\b(catch)\\b", + "beginCaptures": { + "1": { + "name": "keyword.control.catch.java" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.catch.end.bracket.curly.java" + } + }, + "name": "meta.catch.java", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "contentName": "meta.catch.parameters.java", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#storage-modifiers" + }, + { + "begin": "[a-zA-Z$_][\\.a-zA-Z0-9$_]*", + "beginCaptures": { + "0": { + "name": "storage.type.java" + } + }, + "end": "(\\|)|(?=\\))", + "endCaptures": { + "1": { + "name": "punctuation.catch.separator.java" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "match": "\\w+", + "captures": { + "0": { + "name": "variable.parameter.java" + } + } + } + ] + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.catch.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.catch.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + }, + { + "begin": "\\bfinally\\b", + "beginCaptures": { + "0": { + "name": "keyword.control.finally.java" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.finally.end.bracket.curly.java" + } + }, + "name": "meta.finally.java", + "patterns": [ + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.finally.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.finally.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + } + ] + }, + "variables": { + "begin": "(?x)\n(?=\n \\b\n (\n (void|boolean|byte|char|short|int|float|long|double)\n |\n (?>(\\w+\\.)*[A-Z_]+\\w*) # e.g. `javax.ws.rs.Response`, or `String`\n )\n \\b\n \\s*\n (\n <[\\w<>,\\.?\\s\\[\\]]*> # e.g. `HashMap`, or `List`\n )?\n \\s*\n (\n (\\[\\])* # int[][]\n )?\n \\s+\n [A-Za-z_$][\\w$]* # At least one identifier after space\n ([\\w\\[\\],$][\\w\\[\\],\\s]*)? # possibly primitive array or additional identifiers\n \\s*(=|:|;)\n)", + "end": "(?=\\=|:|;)", + "name": "meta.definition.variable.java", + "patterns": [ + { + "match": "([A-Za-z$_][\\w$]*)(?=\\s*(\\[\\])*\\s*(;|:|=|,))", + "captures": { + "1": { + "name": "variable.other.definition.java" + } + } + }, + { + "include": "#all-types" + }, + { + "include": "#code" + } + ] + }, + "variables-local": { + "begin": "(?=\\b(var)\\b\\s+[A-Za-z_$][\\w$]*\\s*(=|:|;))", + "end": "(?=\\=|:|;)", + "name": "meta.definition.variable.local.java", + "patterns": [ + { + "match": "\\bvar\\b", + "name": "storage.type.local.java" + }, + { + "match": "([A-Za-z$_][\\w$]*)(?=\\s*(\\[\\])*\\s*(=|:|;))", + "captures": { + "1": { + "name": "variable.other.definition.java" + } + } + }, + { + "include": "#code" + } + ] + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/json/language-configuration.json b/app/src/main/assets/textmate/json/language-configuration.json new file mode 100644 index 000000000..094b22275 --- /dev/null +++ b/app/src/main/assets/textmate/json/language-configuration.json @@ -0,0 +1,38 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["\"", "\""] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/json/syntaxes/json.tmLanguage.json b/app/src/main/assets/textmate/json/syntaxes/json.tmLanguage.json new file mode 100644 index 000000000..46ee87f21 --- /dev/null +++ b/app/src/main/assets/textmate/json/syntaxes/json.tmLanguage.json @@ -0,0 +1,182 @@ +{ + "scopeName": "source.json", + "name": "JSON", + "fileTypes": [ + "avsc", + "babelrc", + "bowerrc", + "composer.lock", + "geojson", + "gltf", + "htmlhintrc", + "ipynb", + "jscsrc", + "jshintrc", + "jslintrc", + "json", + "jsonl", + "jsonld", + "languagebabel", + "ldj", + "ldjson", + "Pipfile.lock", + "schema", + "stylintrc", + "template", + "tern-config", + "tern-project", + "tfstate", + "tfstate.backup", + "topojson", + "webapp", + "webmanifest" + ], + "patterns": [ + { + "include": "#value" + } + ], + "repository": { + "array": { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.array.begin.json" + } + }, + "end": "(,)?[\\s\\n]*(\\])", + "endCaptures": { + "1": { + "name": "invalid.illegal.trailing-array-separator.json" + }, + "2": { + "name": "punctuation.definition.array.end.json" + } + }, + "name": "meta.structure.array.json", + "patterns": [ + { + "include": "#value" + }, + { + "match": ",", + "name": "punctuation.separator.array.json" + }, + { + "match": "[^\\s\\]]", + "name": "invalid.illegal.expected-array-separator.json" + } + ] + }, + "constant": { + "match": "\\b(true|false|null)\\b", + "name": "constant.language.json" + }, + "number": { + "match": "-?(?=[1-9]|0(?!\\d))\\d+(\\.\\d+)?([eE][+-]?\\d+)?", + "name": "constant.numeric.json" + }, + "object": { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.dictionary.begin.json" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.definition.dictionary.end.json" + } + }, + "name": "meta.structure.dictionary.json", + "patterns": [ + { + "begin": "(?=\")", + "end": "(?<=\")", + "name": "meta.structure.dictionary.key.json", + "patterns": [ + { + "include": "#string" + } + ] + }, + { + "begin": ":", + "beginCaptures": { + "0": { + "name": "punctuation.separator.dictionary.key-value.json" + } + }, + "end": "(,)(?=[\\s\\n]*})|(,)|(?=})", + "endCaptures": { + "1": { + "name": "invalid.illegal.trailing-dictionary-separator.json" + }, + "2": { + "name": "punctuation.separator.dictionary.pair.json" + } + }, + "name": "meta.structure.dictionary.value.json", + "patterns": [ + { + "include": "#value" + }, + { + "match": "[^\\s,]", + "name": "invalid.illegal.expected-dictionary-separator.json" + } + ] + }, + { + "match": "[^\\s}]", + "name": "invalid.illegal.expected-dictionary-separator.json" + } + ] + }, + "string": { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.json" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.json" + } + }, + "name": "string.quoted.double.json", + "patterns": [ + { + "match": "(?x)\n\\\\ # a literal backslash\n( # followed by\n [\"\\\\/bfnrt] # one of these characters\n | # or\n u[0-9a-fA-F]{4} # a u and four hex digits\n)", + "name": "constant.character.escape.json" + }, + { + "match": "\\\\.", + "name": "invalid.illegal.unrecognized-string-escape.json" + } + ] + }, + "value": { + "patterns": [ + { + "include": "#constant" + }, + { + "include": "#number" + }, + { + "include": "#string" + }, + { + "include": "#array" + }, + { + "include": "#object" + } + ] + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/kotlin/language-configuration.json b/app/src/main/assets/textmate/kotlin/language-configuration.json new file mode 100644 index 000000000..b742e9e0c --- /dev/null +++ b/app/src/main/assets/textmate/kotlin/language-configuration.json @@ -0,0 +1,35 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": [ "/*", "*/" ] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["<", ">"] + ], + "autoClosingPairs": [ + { "open": "{", "close": "}" }, + { "open": "[", "close": "]" }, + { "open": "(", "close": ")" }, + { "open": "'", "close": "'", "notIn": ["string", "comment"] }, + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "/*", "close": " */", "notIn": ["string"] } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["<", ">"], + ["'", "'"], + ["\"", "\""] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/kotlin/syntaxes/kotlin.tmLanguage b/app/src/main/assets/textmate/kotlin/syntaxes/kotlin.tmLanguage new file mode 100644 index 000000000..4a61ffeb2 --- /dev/null +++ b/app/src/main/assets/textmate/kotlin/syntaxes/kotlin.tmLanguage @@ -0,0 +1,1067 @@ + + + + + fileTypes + + kt + kts + + foldingStartMarker + (\{\s*(//.*)?$|^\s*// \{\{\{) + foldingStopMarker + ^\s*(\}|// \}\}\}$) + name + Kotlin + patterns + + + include + #comments + + + captures + + 1 + + name + keyword.other.kotlin + + 2 + + name + entity.name.package.kotlin + + + match + ^\s*(package)\b(?:\s*([^ ;$]+)\s*)? + + + captures + + 1 + + name + keyword.other.import.kotlin + + 2 + + name + storage.modifier.import.java + + 3 + + name + keyword.other.kotlin + + 4 + + name + entity.name.type + + + match + ^\s*(import)\s+([^ $.]+(?:\.(?:[`][^$`]+[`]|[^` $.]+))+)(?:\s+(as)\s+([`][^$`]+[`]|[^` $.]+))? + name + meta.import.kotlin + + + include + #code + + + repository + + annotations + + patterns + + + begin + (@[^ (]+)(\()? + beginCaptures + + 1 + + name + storage.type.annotation.kotlin + + 2 + + name + punctuation.definition.annotation-arguments.begin.kotlin + + + end + (\)|\s|$) + endCaptures + + 1 + + name + punctuation.definition.annotation-arguments.end.kotlin + + + name + meta.declaration.annotation.kotlin + patterns + + + captures + + 1 + + name + constant.other.key.kotlin + + 2 + + name + keyword.operator.assignment.kotlin + + + match + (\w*)\s*(=) + + + include + #code + + + match + , + name + punctuation.seperator.property.kotlin + + + + + match + @\w* + name + storage.type.annotation.kotlin + + + + builtin-functions + + patterns + + + match + \b(apply|also|let|takeIf|run|takeUnless|with|print|println)\b\s*(?={|\() + captures + + 1 + + name + support.function.kotlin + + + + + match + \b(mutableListOf|listOf|mutableMapOf|mapOf|mutableSetOf|setOf)\b\s*(?={|\() + captures + + 1 + + name + support.function.kotlin + + + + + + comments + + patterns + + + captures + + 0 + + name + punctuation.definition.comment.kotlin + + + match + /\*\*/ + name + comment.block.empty.kotlin + + + include + #comments-inline + + + + comments-inline + + patterns + + + begin + /\* + captures + + 0 + + name + punctuation.definition.comment.kotlin + + + end + \*/ + name + comment.block.kotlin + + + captures + + 1 + + name + comment.line.double-slash.kotlin + + 2 + + name + punctuation.definition.comment.kotlin + + + match + \s*((//).*$\n?) + + + + class-literal + + begin + (?=\b(?:class|interface|object)\s+\w+)\b + end + (?=\}|$) + name + meta.class.kotlin + patterns + + + include + #keyword-literal + + + begin + \b(class|object|interface)\b\s+(\w+) + beginCaptures + + 1 + + name + storage.modifier.kotlin + + 2 + + name + entity.name.class.kotlin + + + end + (?=\{|\(|:|$) + patterns + + + include + #keyword-literal + + + include + #annotations + + + include + #types + + + + + begin + (:)\s*(\w+) + beginCaptures + + 1 + + name + keyword.operator.declaration.kotlin + + 2 + + name + entity.other.inherited-class.kotlin + + + end + (?={|=|$) + patterns + + + include + #types + + + + + include + #braces + + + include + #parens + + + + literal-functions + + begin + (?=\b(?:fun)\b) + end + (?=$|=|\}) + patterns + + + begin + \b(fun)\b + beginCaptures + + 1 + + name + keyword.other.kotlin + + + end + (?=\() + patterns + + + captures + + 2 + + name + entity.name.function.kotlin + + + match + ([\.<\?>\w]+\.)?(\w+|(`[^`]*`)) + + + include + #types + + + + + begin + (:) + beginCaptures + + 1 + + name + keyword.operator.declaration.kotlin + + + end + (?={|=|$) + patterns + + + include + #types + + + + + include + #parens + + + include + #braces + + + + parameters + + patterns + + + begin + (:) + beginCaptures + + 1 + + name + keyword.operator.declaration.kotlin + + + end + (?=,|=|\)) + patterns + + + include + #types + + + + + match + \w+(?=:) + name + variable.parameter.function.kotlin + + + include + #keyword-literal + + + + keyword-literal + + patterns + + + match + (\!in|\!is|as\?) + name + keyword.operator.kotlin + + + match + \b(in|is|as|assert)\b + name + keyword.operator.kotlin + + + match + \b(const)\b + name + storage.modifier.kotlin + + + match + \b(val|var)\b + name + storage.type.kotlin + + + match + \b(\_)\b + name + punctuation.definition.variable.kotlin + + + match + \b(data|inline|tailrec|operator|infix|typealias|reified)\b + name + storage.type.kotlin + + + match + \b(external|public|private|protected|internal|abstract|final|sealed|enum|open|annotation|override|vararg|typealias|expect|actual|suspend|yield|out|in)\b + name + storage.modifier.kotlin + + + match + \b(try|catch|finally|throw)\b + name + keyword.control.catch-exception.kotlin + + + match + \b(if|else|when)\b + name + keyword.control.conditional.kotlin + + + match + \b(while|for|do|return|break|continue)\b + name + keyword.control.kotlin + + + match + \b(constructor|init)\b + name + entity.name.function.constructor + + + match + \b(companion|object)\b + name + storage.type.kotlin + + + + keyword-operator + + patterns + + + match + \b(and|or|not|inv)\b + name + keyword.operator.bitwise.kotlin + + + match + (==|!=|===|!==|<=|>=|<|>) + name + keyword.operator.comparison.kotlin + + + match + (=) + name + keyword.operator.assignment.kotlin + + + match + (:(?!:)) + name + keyword.operator.declaration.kotlin + + + match + (\?:) + name + keyword.operator.elvis.kotlin + + + match + (\-\-|\+\+) + name + keyword.operator.increment-decrement.kotlin + + + match + (\-|\+|\*|\/|%) + name + keyword.operator.arithmetic.kotlin + + + match + (\+\=|\-\=|\*\=|\/\=) + name + keyword.operator.arithmetic.assign.kotlin + + + match + (\!|\&\&|\|\|) + name + keyword.operator.logical.kotlin + + + match + (\.\.) + name + keyword.operator.range.kotlin + + + + keyword-punctuation + + patterns + + + match + (::) + name + punctuation.accessor.reference.kotlin + + + match + (\?\.) + name + punctuation.accessor.dot.safe.kotlin + + + match + (\.) + name + punctuation.accessor.dot.kotlin + + + match + (\,) + name + punctuation.seperator.kotlin + + + match + (\;) + name + punctuation.terminator.kotlin + + + + keyword-constant + + patterns + + + match + \b(true|false|null|class)\b + name + constant.language.kotlin + + + match + \b(this|super)\b + name + variable.language.kotlin + + + match + \b(0(x|X)[0-9A-Fa-f_]*)[L]?\b + name + constant.numeric.hex.kotlin + + + match + \b(0(b|B)[0-1_]*)[L]?\b + name + constant.numeric.binary.kotlin + + + match + \b([0-9][0-9_]*\.[0-9][0-9_]*[fFL]?)\b + name + constant.numeric.float.kotlin + + + match + \b([0-9][0-9_]*[fFL]?)\b + name + constant.numeric.integer.kotlin + + + + literal-string + + patterns + + + begin + " + beginCaptures + + 0 + + name + punctuation.definition.string.begin.kotlin + + + end + " + endCaptures + + 0 + + name + punctuation.definition.string.end.kotlin + + + name + string.quoted.double.kotlin + patterns + + + include + #string-content + + + + + + literal-raw-string + + patterns + + + begin + """ + beginCaptures + + 0 + + name + punctuation.definition.string.begin.kotlin + + + end + """ + endCaptures + + 0 + + name + punctuation.definition.string.end.kotlin + + + name + string.quoted.triple.kotlin + patterns + + + include + #string-content + + + + + + string-content + + patterns + + + name + constant.character.escape.newline.kotlin + match + \\\s*\n + + + name + constant.character.escape.kotlin + match + \\(x[\da-fA-F]{2}|u[\da-fA-F]{4}|.) + + + begin + (\$)(\{) + beginCaptures + + 1 + + name + punctuation.definition.keyword.kotlin + + 2 + + name + punctuation.section.block.begin.kotlin + + + end + \} + endCaptures + + 0 + + name + punctuation.section.block.end.kotlin + + + name + entity.string.template.element.kotlin + patterns + + + include + #code + + + + + + types + + patterns + + + match + \b(Nothing|Any|Unit|String|CharSequence|Int|Boolean|Char|Long|Double|Float|Short|Byte|Array|List|Map|Set|dynamic)\b(\?)? + name + support.class.kotlin + + + match + \b(IntArray|BooleanArray|CharArray|LongArray|DoubleArray|FloatArray|ShortArray|ByteArray)\b(\?)? + name + support.class.kotlin + + + match + ((?:[a-zA-Z]\w*\.)*[A-Z]+\w*[a-z]+\w*)(\?) + name + entity.name.type.class.kotlin + patterns + + + include + #keyword-punctuation + + + include + #types + + + + + match + \b(?:[a-z]\w*(\.))*[A-Z]+\w*\b + captures + + 1 + + name + keyword.operator.dereference.kotlin + + + name + entity.name.type.class.kotlin + + + begin + \( + beginCaptures + + 0 + + name + punctuation.section.group.begin.kotlin + + + end + \) + endCaptures + + 0 + + name + punctuation.section.group.end.kotlin + + + patterns + + + include + #types + + + + + include + #keyword-punctuation + + + include + #keyword-operator + + + + parens + + patterns + + + begin + \( + beginCaptures + + 0 + + name + punctuation.section.group.begin.kotlin + + + end + \) + endCaptures + + 0 + + name + punctuation.section.group.end.kotlin + + + name + meta.group.kotlin + patterns + + + include + #keyword-punctuation + + + include + #parameters + + + include + #code + + + + + + braces + + patterns + + + begin + \{ + beginCaptures + + 0 + + name + punctuation.section.group.begin.kotlin + + + end + \} + endCaptures + + 0 + + name + punctuation.section.group.end.kotlin + + + name + meta.block.kotlin + patterns + + + include + #code + + + + + + brackets + + patterns + + + begin + \[ + beginCaptures + + 0 + + name + punctuation.section.brackets.begin.kotlin + + + end + \] + endCaptures + + 0 + + name + punctuation.section.brackets.end.kotlin + + + name + meta.brackets.kotlin + patterns + + + include + #code + + + + + + code + + patterns + + + include + #comments + + + include + #comments-inline + + + include + #annotations + + + include + #class-literal + + + include + #parens + + + include + #braces + + + include + #brackets + + + include + #keyword-literal + + + include + #types + + + include + #keyword-operator + + + include + #keyword-constant + + + include + #keyword-punctuation + + + include + #builtin-functions + + + include + #literal-functions + + + include + #builtin-classes + + + include + #literal-raw-string + + + include + #literal-string + + + + + scopeName + source.kotlin + uuid + d9380650-5edc-447d-8dbd-98838c7d0adf + + \ No newline at end of file diff --git a/app/src/main/assets/textmate/xml/language-configuration.json b/app/src/main/assets/textmate/xml/language-configuration.json new file mode 100644 index 000000000..00f0bd9af --- /dev/null +++ b/app/src/main/assets/textmate/xml/language-configuration.json @@ -0,0 +1,42 @@ +{ + "comments": { + "lineComment": [""], + }, + "brackets": [ + ["<", "/>"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"], + ["<", ">"] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/xml/syntaxes/xml.tmLanguage.json b/app/src/main/assets/textmate/xml/syntaxes/xml.tmLanguage.json new file mode 100644 index 000000000..d493c6a3a --- /dev/null +++ b/app/src/main/assets/textmate/xml/syntaxes/xml.tmLanguage.json @@ -0,0 +1,430 @@ +{ + "scopeName": "text.xml", + "name": "XML", + "fileTypes": [ + "aiml", + "atom", + "axml", + "bpmn", + "config", + "cpt", + "csl", + "csproj", + "csproj.user", + "dae", + "dia", + "dita", + "ditamap", + "dtml", + "fodg", + "fodp", + "fods", + "fodt", + "fsproj", + "fxml", + "gir", + "glade", + "gpx", + "graphml", + "icls", + "iml", + "isml", + "jmx", + "jsp", + "kml", + "kst", + "launch", + "menu", + "mxml", + "nunit", + "nuspec", + "opml", + "owl", + "pom", + "ppj", + "proj", + "pt", + "pubxml", + "pubxml.user", + "rdf", + "rng", + "rss", + "sdf", + "shproj", + "siml", + "sld", + "storyboard", + "StyleCop", + "svg", + "targets", + "tld", + "vbox", + "vbox-prev", + "vbproj", + "vbproj.user", + "vcproj", + "vcproj.filters", + "vcxproj", + "vcxproj.filters", + "wixmsp", + "wixmst", + "wixobj", + "wixout", + "wsdl", + "wxs", + "xaml", + "xbl", + "xib", + "xlf", + "xliff", + "xml", + "xpdl", + "xsd", + "xul", + "ui" + ], + "firstLineMatch": "(?x)\n# XML declaration\n(?:\n ^ <\\? xml\n\n # VersionInfo\n \\s+ version\n \\s* = \\s*\n (['\"])\n 1 \\. [0-9]+\n \\1\n\n # EncodingDecl\n (?:\n \\s+ encoding\n \\s* = \\s*\n\n # EncName\n (['\"])\n [A-Za-z]\n [-A-Za-z0-9._]*\n \\2\n )?\n\n # SDDecl\n (?:\n \\s+ standalone\n \\s* = \\s*\n (['\"])\n (?:yes|no)\n \\3\n )?\n\n \\s* \\?>\n)\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n xml\n (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n xml\n (?=\\s|:|$)\n)", + "patterns": [ + { + "begin": "(<\\?)\\s*([-_a-zA-Z0-9]+)", + "captures": { + "1": { + "name": "punctuation.definition.tag.xml" + }, + "2": { + "name": "entity.name.tag.xml" + } + }, + "end": "(\\?>)", + "name": "meta.tag.preprocessor.xml", + "patterns": [ + { + "match": " ([a-zA-Z-]+)", + "name": "entity.other.attribute-name.xml" + }, + { + "include": "#doublequotedString" + }, + { + "include": "#singlequotedString" + } + ] + }, + { + "begin": "()", + "name": "meta.tag.sgml.doctype.xml", + "patterns": [ + { + "include": "#internalSubset" + } + ] + }, + { + "include": "#comments" + }, + { + "begin": "(<)((?:([-_a-zA-Z0-9]+)(:))?([-_a-zA-Z0-9:]+))(?=(\\s[^>]*)?>\\2>)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.tag.xml" + }, + "2": { + "name": "entity.name.tag.xml" + }, + "3": { + "name": "entity.name.tag.namespace.xml" + }, + "4": { + "name": "punctuation.separator.namespace.xml" + }, + "5": { + "name": "entity.name.tag.localname.xml" + } + }, + "end": "(>)()((?:([-_a-zA-Z0-9]+)(:))?([-_a-zA-Z0-9:]+))(>)", + "endCaptures": { + "1": { + "name": "punctuation.definition.tag.xml" + }, + "2": { + "name": "punctuation.definition.tag.xml" + }, + "3": { + "name": "entity.name.tag.xml" + }, + "4": { + "name": "entity.name.tag.namespace.xml" + }, + "5": { + "name": "punctuation.separator.namespace.xml" + }, + "6": { + "name": "entity.name.tag.localname.xml" + }, + "7": { + "name": "punctuation.definition.tag.xml" + } + }, + "name": "meta.tag.no-content.xml", + "patterns": [ + { + "include": "#tagStuff" + } + ] + }, + { + "begin": "(?)(?:([-\\w\\.]+)((:)))?([-\\w\\.:]+)", + "captures": { + "1": { + "name": "punctuation.definition.tag.xml" + }, + "2": { + "name": "entity.name.tag.namespace.xml" + }, + "3": { + "name": "entity.name.tag.xml" + }, + "4": { + "name": "punctuation.separator.namespace.xml" + }, + "5": { + "name": "entity.name.tag.localname.xml" + } + }, + "end": "(/?>)", + "name": "meta.tag.xml", + "patterns": [ + { + "include": "#tagStuff" + } + ] + }, + { + "include": "#entity" + }, + { + "include": "#bare-ampersand" + }, + { + "begin": "<%@", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.xml" + } + }, + "end": "%>", + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.xml" + } + }, + "name": "source.java-props.embedded.xml", + "patterns": [ + { + "match": "page|include|taglib", + "name": "keyword.other.page-props.xml" + } + ] + }, + { + "begin": "<%[!=]?(?!--)", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.xml" + } + }, + "end": "(?!--)%>", + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.xml" + } + }, + "name": "source.java.embedded.xml", + "patterns": [ + { + "include": "source.java" + } + ] + }, + { + "begin": "", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.xml" + } + }, + "name": "string.unquoted.cdata.xml" + } + ], + "repository": { + "EntityDecl": { + "begin": "()", + "patterns": [ + { + "include": "#doublequotedString" + }, + { + "include": "#singlequotedString" + } + ] + }, + "bare-ampersand": { + "match": "&", + "name": "invalid.illegal.bad-ampersand.xml" + }, + "doublequotedString": { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.xml" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.xml" + } + }, + "name": "string.quoted.double.xml", + "patterns": [ + { + "include": "#entity" + }, + { + "include": "#bare-ampersand" + } + ] + }, + "entity": { + "captures": { + "1": { + "name": "punctuation.definition.constant.xml" + }, + "3": { + "name": "punctuation.definition.constant.xml" + } + }, + "match": "(&)([:a-zA-Z_][:a-zA-Z0-9_.-]*|#[0-9]+|#x[0-9a-fA-F]+)(;)", + "name": "constant.character.entity.xml" + }, + "internalSubset": { + "begin": "(\\[)", + "captures": { + "1": { + "name": "punctuation.definition.constant.xml" + } + }, + "end": "(\\])", + "name": "meta.internalsubset.xml", + "patterns": [ + { + "include": "#EntityDecl" + }, + { + "include": "#parameterEntity" + }, + { + "include": "#comments" + } + ] + }, + "parameterEntity": { + "captures": { + "1": { + "name": "punctuation.definition.constant.xml" + }, + "3": { + "name": "punctuation.definition.constant.xml" + } + }, + "match": "(%)([:a-zA-Z_][:a-zA-Z0-9_.-]*)(;)", + "name": "constant.character.parameter-entity.xml" + }, + "singlequotedString": { + "begin": "'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.xml" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.xml" + } + }, + "name": "string.quoted.single.xml", + "patterns": [ + { + "include": "#entity" + }, + { + "include": "#bare-ampersand" + } + ] + }, + "tagStuff": { + "patterns": [ + { + "captures": { + "1": { + "name": "entity.other.attribute-name.namespace.xml" + }, + "2": { + "name": "entity.other.attribute-name.xml" + }, + "3": { + "name": "punctuation.separator.namespace.xml" + }, + "4": { + "name": "entity.other.attribute-name.localname.xml" + } + }, + "match": "(?:^|\\s+)(?:([-\\w.]+)((:)))?([-\\w.:]+)\\s*=" + }, + { + "include": "#doublequotedString" + }, + { + "include": "#singlequotedString" + } + ] + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ApplicationLoader.java b/app/src/main/java/com/tyron/code/ApplicationLoader.java index 7f8535b46..cecee9922 100644 --- a/app/src/main/java/com/tyron/code/ApplicationLoader.java +++ b/app/src/main/java/com/tyron/code/ApplicationLoader.java @@ -2,30 +2,91 @@ import android.app.Application; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; -import android.os.Handler; -import android.os.Looper; +import android.os.Build; import android.widget.Toast; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AppCompatDelegate; import androidx.preference.PreferenceManager; import com.developer.crashx.config.CrashConfig; +import com.tyron.actions.ActionManager; import com.tyron.builder.BuildModule; +import com.tyron.code.event.EventManager; +import com.tyron.code.service.GradleDaemonService; +import com.tyron.code.ui.editor.action.CloseAllEditorAction; +import com.tyron.code.ui.editor.action.CloseFileEditorAction; +import com.tyron.code.ui.editor.action.CloseOtherEditorAction; +import com.tyron.code.ui.editor.action.DiagnosticInfoAction; +import com.tyron.code.ui.editor.action.PreviewLayoutAction; +import com.tyron.code.ui.editor.action.text.TextActionGroup; +import com.tyron.code.ui.file.action.ImportFileActionGroup; +import com.tyron.code.ui.file.action.NewFileActionGroup; +import com.tyron.code.ui.file.action.file.DeleteFileAction; +import com.tyron.code.ui.main.action.compile.CompileActionGroup; +import com.tyron.code.ui.main.action.debug.DebugActionGroup; +import com.tyron.code.ui.main.action.other.FormatAction; +import com.tyron.code.ui.main.action.other.OpenSettingsAction; +import com.tyron.code.ui.main.action.project.ProjectActionGroup; +import com.tyron.code.ui.settings.ApplicationSettingsFragment; import com.tyron.common.ApplicationProvider; +import com.tyron.completion.CompletionProvider; +import com.tyron.completion.index.CompilerService; import com.tyron.completion.java.CompletionModule; +import com.tyron.completion.java.JavaCompilerProvider; +import com.tyron.completion.java.JavaCompletionProvider; import com.tyron.completion.xml.XmlCompletionModule; +import com.tyron.completion.xml.XmlIndexProvider; +import com.tyron.editor.selection.ExpandSelectionProvider; +import com.tyron.kotlin_completion.KotlinCompletionModule; +import com.tyron.language.fileTypes.FileTypeManager; +import com.tyron.language.java.JavaFileType; +import com.tyron.language.java.JavaLanguage; +import com.tyron.language.xml.XmlFileType; +import com.tyron.language.xml.XmlLanguage; +import com.tyron.selection.java.JavaExpandSelectionProvider; +import com.tyron.selection.xml.XmlExpandSelectionProvider; + +import org.gradle.internal.time.Time; +import org.gradle.internal.time.Timer; +import org.lsposed.hiddenapibypass.HiddenApiBypass; + +import java.io.File; +import java.io.IOException; public class ApplicationLoader extends Application { - + + private static ApplicationLoader sInstance; + + public static ApplicationLoader getInstance() { + return sInstance; + } + + private EventManager mEventManager; + + // no memory leaks since applicationContext is a singleton public static Context applicationContext; - public static Handler applicationHandler = new Handler(Looper.getMainLooper()); @Override public void onCreate() { + Timer timer = Time.startTimer(); super.onCreate(); - applicationContext = this; + System.out.println("onCreate took " + timer.getElapsed()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + HiddenApiBypass.addHiddenApiExemptions("Lsun/misc/Unsafe"); + } + + setupTheme(); + + mEventManager = new EventManager(); + sInstance = this; + applicationContext = this; ApplicationProvider.initialize(applicationContext); CompletionModule.initialize(applicationContext); @@ -40,6 +101,88 @@ public void onCreate() { .logErrorOnRestart(true) .trackActivities(true) .apply(); + + runStartup(); + + File userDir = new File(getFilesDir(), "user_dir"); + System.setProperty("codeassist.user.dir", userDir.getAbsolutePath()); + } + + /** + * Can be used to communicate within the application globally + * @return the EventManager + */ + @NonNull + public EventManager getEventManager() { + return mEventManager; + } + + private void setupTheme() { + ApplicationSettingsFragment.ThemeProvider provider = new ApplicationSettingsFragment.ThemeProvider(this); + int theme = provider.getThemeFromPreferences(); + AppCompatDelegate.setDefaultNightMode(theme); + } + + private void runStartup() { + StartupManager startupManager = new StartupManager(); + startupManager.addStartupActivity(() -> { + FileTypeManager manager = FileTypeManager.getInstance(); + manager.registerFileType(JavaFileType.INSTANCE); + manager.registerFileType(XmlFileType.INSTANCE); + }); + startupManager.addStartupActivity(() -> { + ExpandSelectionProvider.registerProvider(JavaLanguage.INSTANCE, + new JavaExpandSelectionProvider()); + ExpandSelectionProvider.registerProvider(XmlLanguage.INSTANCE, + new XmlExpandSelectionProvider()); + }); + startupManager.addStartupActivity(() -> { + CompilerService index = CompilerService.getInstance(); + if (index.isEmpty()) { + index.registerIndexProvider(JavaCompilerProvider.KEY, new JavaCompilerProvider()); + index.registerIndexProvider(XmlIndexProvider.KEY, new XmlIndexProvider()); + } + }); + startupManager.addStartupActivity(() -> { + CompletionProvider.registerCompletionProvider(JavaLanguage.INSTANCE, + new JavaCompletionProvider()); + }); + startupManager.addStartupActivity(() -> { + ActionManager manager = ActionManager.getInstance(); + // main toolbar actions + manager.registerAction(CompileActionGroup.ID, new CompileActionGroup()); + manager.registerAction(ProjectActionGroup.ID, new ProjectActionGroup()); + manager.registerAction(PreviewLayoutAction.ID, new PreviewLayoutAction()); + manager.registerAction(OpenSettingsAction.ID, new OpenSettingsAction()); + manager.registerAction(FormatAction.ID, new FormatAction()); + manager.registerAction(DebugActionGroup.ID, new DebugActionGroup()); + + // editor tab actions + manager.registerAction(CloseFileEditorAction.ID, new CloseFileEditorAction()); + manager.registerAction(CloseOtherEditorAction.ID, new CloseOtherEditorAction()); + manager.registerAction(CloseAllEditorAction.ID, new CloseAllEditorAction()); + + // editor actions + manager.registerAction(TextActionGroup.ID, new TextActionGroup()); + manager.registerAction(DiagnosticInfoAction.ID, new DiagnosticInfoAction()); + + // file manager actions + manager.registerAction(NewFileActionGroup.ID, new NewFileActionGroup()); + manager.registerAction(DeleteFileAction.ID, new DeleteFileAction()); + if(Build.VERSION.SDK_INT { - CompletionEngine engine = CompletionEngine.getInstance(); - CompilerService index = CompilerService.getInstance(); - if (index.isEmpty()) { - index.registerIndexProvider(JavaCompilerProvider.KEY, new JavaCompilerProvider()); - index.registerIndexProvider(XmlIndexProvider.KEY, new XmlIndexProvider()); - engine.registerCompletionProvider(new JavaCompletionProvider()); - engine.registerCompletionProvider(new LayoutXmlCompletionProvider()); - engine.registerCompletionProvider(new AndroidManifestCompletionProvider()); - } - }); - startupManager.addStartupActivity(() -> { - ActionManager manager = ActionManager.getInstance(); - // main toolbar actions - manager.registerAction(SelectJavaParentAction.ID, new SelectJavaParentAction()); - manager.registerAction(CompileActionGroup.ID, new CompileActionGroup()); - manager.registerAction(ProjectActionGroup.ID, new ProjectActionGroup()); - manager.registerAction(PreviewLayoutAction.ID, new PreviewLayoutAction()); - manager.registerAction(OpenSettingsAction.ID, new OpenSettingsAction()); - manager.registerAction(FormatAction.ID, new FormatAction()); - manager.registerAction(DebugActionGroup.ID, new DebugActionGroup()); - - // editor tab actions - manager.registerAction(CloseFileEditorAction.ID, new CloseFileEditorAction()); - manager.registerAction(CloseOtherEditorAction.ID, new CloseOtherEditorAction()); - manager.registerAction(CloseAllEditorAction.ID, new CloseAllEditorAction()); - - // editor actions - manager.registerAction(TextActionGroup.ID, new TextActionGroup()); - manager.registerAction(DiagnosticInfoAction.ID, new DiagnosticInfoAction()); - - // file manager actions - manager.registerAction(NewFileActionGroup.ID, new NewFileActionGroup()); - manager.registerAction(DeleteFileAction.ID, new DeleteFileAction()); - - // java actions - CompletionModule.registerActions(manager); - - // xml actions - XmlCompletionModule.registerActions(manager); - - // kotlin actions - KotlinCompletionModule.registerActions(manager); - }); - startupManager.startup(); + WindowCompat.setDecorFitsSystemWindows(getWindow(), false); if (getSupportFragmentManager().findFragmentByTag(ProjectManagerFragment.TAG) == null) { getSupportFragmentManager().beginTransaction() - .replace(R.id.fragment_container, - new ProjectManagerFragment(), - ProjectManagerFragment.TAG) + .replace(R.id.fragment_container, new ProjectManagerFragment(), + ProjectManagerFragment.TAG) .commit(); } } - - @Override + + @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); } + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) { + return super.onKeyShortcut(keyCode, event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return super.onKeyUp(keyCode, event); + } } diff --git a/app/src/main/java/com/tyron/code/analyzer/BaseTextmateAnalyzer.java b/app/src/main/java/com/tyron/code/analyzer/BaseTextmateAnalyzer.java new file mode 100644 index 000000000..eb7205c53 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/BaseTextmateAnalyzer.java @@ -0,0 +1,61 @@ +package com.tyron.code.analyzer; + +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.tyron.editor.Editor; + +import org.eclipse.tm4e.core.theme.IRawTheme; +import org.eclipse.tm4e.core.theme.Theme; + +import java.io.InputStream; +import java.io.Reader; +import java.lang.reflect.Field; + +import io.github.rosemoe.sora.langs.textmate.TextMateAnalyzer; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.widget.CodeEditor; + +/** + * A text mate analyzer which does not use a TextMateLanguage + */ +public class BaseTextmateAnalyzer extends TextMateAnalyzer { + + private static final Field THEME_FIELD; + + public BaseTextmateAnalyzer(TextMateLanguage language, Editor editor, + String grammarName, + InputStream grammarIns, + Reader languageConfiguration, + IRawTheme theme) throws Exception { + super(language, grammarName, grammarIns, + languageConfiguration, theme); + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + if (!extraArguments.getBoolean("loaded", false)) { + return; + } + super.reset(content, extraArguments); + } + + public Theme getTheme() { + try { + return (Theme) THEME_FIELD.get(this); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + static { + try { + THEME_FIELD = TextMateAnalyzer.class.getDeclaredField("theme"); + THEME_FIELD.setAccessible(true); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } +} diff --git a/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticHighlighter.java b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticHighlighter.java new file mode 100644 index 000000000..e2ea859e7 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticHighlighter.java @@ -0,0 +1,48 @@ +package com.tyron.code.analyzer.semantic; + +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class SemanticHighlighter extends TreePathScanner { + + private final AtomicBoolean mCancelFlag = new AtomicBoolean(false); + + public void cancel() { + mCancelFlag.set(true); + } + + @Override + public Void scan(Tree tree, Void unused) { + if (mCancelFlag.get()) { + return null; + } + return super.scan(tree, unused); + } + + @Override + public Void scan(Iterable extends Tree> iterable, Void unused) { + if (mCancelFlag.get()) { + return null; + } + return super.scan(iterable, unused); + } + + @Override + public Void scan(TreePath treePath, Void unused) { + if (mCancelFlag.get()) { + return null; + } + return super.scan(treePath, unused); + } + + @Override + public Void reduce(Void unused, Void r1) { + if (mCancelFlag.get()) { + return null; + } + return super.reduce(unused, r1); + } +} diff --git a/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticToken.java b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticToken.java new file mode 100644 index 000000000..9f3aeadf0 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticToken.java @@ -0,0 +1,48 @@ +package com.tyron.code.analyzer.semantic; + +import androidx.annotation.NonNull; + +public class SemanticToken { + private final TokenType tokenType; + private final int tokenModifiers; + private final int offset; + private final int length; + + public SemanticToken(int offset, int length, TokenType tokenType, int tokenModifiers) { + this.offset = offset; + this.length = length; + this.tokenType = tokenType; + this.tokenModifiers = tokenModifiers; + } + + public TokenType getTokenType() { + return tokenType; + } + + public int getTokenModifiers() { + return tokenModifiers; + } + + public int getOffset() { + return offset; + } + + public int getLength() { + return length; + } + + @NonNull + @Override + public String toString() { + return "SemanticToken{" + + "tokenType=" + + tokenType + + ", tokenModifiers=" + + tokenModifiers + + ", offset=" + + offset + + ", length=" + + length + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/analyzer/semantic/TokenType.java b/app/src/main/java/com/tyron/code/analyzer/semantic/TokenType.java new file mode 100644 index 000000000..627946160 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/semantic/TokenType.java @@ -0,0 +1,35 @@ +package com.tyron.code.analyzer.semantic; + +import androidx.annotation.NonNull; + +public class TokenType { + + public static final TokenType UNKNOWN = create("token.error-token"); + + public static TokenType create(String scope, String... fallbackScopes) { + return new TokenType(scope, fallbackScopes); + } + + private final String scope; + private final String[] fallbackScopes; + + public TokenType(@NonNull String scope, String[] fallbackScopes) { + this.scope = scope; + this.fallbackScopes = fallbackScopes; + } + + public String getScope() { + return scope; + } + + public String[] getFallbackScopes() { + return fallbackScopes; + } + + @NonNull + @Override + public String toString() { + return scope; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/event/FileCreatedEvent.java b/app/src/main/java/com/tyron/code/event/FileCreatedEvent.java new file mode 100644 index 000000000..f9d931dd5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/event/FileCreatedEvent.java @@ -0,0 +1,19 @@ +package com.tyron.code.event; + +import java.io.File; + +/** + * Called when a new file has been created through the file manager UI + */ +public class FileCreatedEvent extends Event { + + private final File file; + + public FileCreatedEvent(File file) { + this.file = file; + } + + public File getFile() { + return file; + } +} diff --git a/app/src/main/java/com/tyron/code/event/FileDeletedEvent.java b/app/src/main/java/com/tyron/code/event/FileDeletedEvent.java new file mode 100644 index 000000000..7a8127baf --- /dev/null +++ b/app/src/main/java/com/tyron/code/event/FileDeletedEvent.java @@ -0,0 +1,17 @@ +package com.tyron.code.event; + +import java.io.File; + +public class FileDeletedEvent extends Event { + + + private final File deletedFile; + + public FileDeletedEvent(File deletedFile) { + this.deletedFile = deletedFile; + } + + public File getDeletedFile() { + return deletedFile; + } +} diff --git a/app/src/main/java/com/tyron/code/event/PerformShortcutEvent.java b/app/src/main/java/com/tyron/code/event/PerformShortcutEvent.java new file mode 100644 index 000000000..4927031a1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/event/PerformShortcutEvent.java @@ -0,0 +1,23 @@ +package com.tyron.code.event; + +import com.tyron.code.ui.editor.shortcuts.ShortcutItem; +import com.tyron.fileeditor.api.FileEditor; + +public class PerformShortcutEvent extends Event { + + private final ShortcutItem item; + private final FileEditor editor; + + public PerformShortcutEvent(ShortcutItem item, FileEditor editor) { + this.item = item; + this.editor = editor; + } + + public ShortcutItem getItem() { + return item; + } + + public FileEditor getEditor() { + return editor; + } +} diff --git a/app/src/main/java/com/tyron/code/gradle/util/GradleLaunchUtil.java b/app/src/main/java/com/tyron/code/gradle/util/GradleLaunchUtil.java new file mode 100644 index 000000000..7a757ea9d --- /dev/null +++ b/app/src/main/java/com/tyron/code/gradle/util/GradleLaunchUtil.java @@ -0,0 +1,134 @@ +package com.tyron.code.gradle.util; + +import static com.tyron.builder.model.AndroidProject.MODEL_LEVEL_3_VARIANT_OUTPUT_POST_BUILD; +import static com.tyron.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_DISABLE_SRC_DOWNLOAD; +import static com.tyron.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY; +import static com.tyron.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED; +import static com.tyron.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY_VERSIONED; +import static com.tyron.builder.model.InjectedProperties.PROPERTY_INVOKED_FROM_IDE; + +import android.content.SharedPreferences; + +import androidx.annotation.NonNull; + +import com.tyron.code.ApplicationLoader; +import com.tyron.common.SharedPreferenceKeys; + +import org.apache.commons.io.FileUtils; +import org.gradle.tooling.ConfigurableLauncher; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +public class GradleLaunchUtil { + + + private static void addIdeProperties(ConfigurableLauncher> launcher) { + launcher.addArguments(createProjectProperty(PROPERTY_BUILD_MODEL_ONLY, true)); + launcher.addArguments(createProjectProperty(PROPERTY_BUILD_MODEL_ONLY_ADVANCED, true)); + launcher.addArguments(createProjectProperty(PROPERTY_INVOKED_FROM_IDE, true)); + // Sent to plugin starting with Studio 3.0 + launcher.addArguments(createProjectProperty(PROPERTY_BUILD_MODEL_ONLY_VERSIONED, MODEL_LEVEL_3_VARIANT_OUTPUT_POST_BUILD)); + + // Skip download of source and javadoc jars during Gradle sync, this flag only has effect on AGP 3.5. + //noinspection deprecation AGP 3.6 and above do not download sources at all. + launcher.addArguments(createProjectProperty(PROPERTY_BUILD_MODEL_DISABLE_SRC_DOWNLOAD, true)); + } + + /** + * Configures the given launcher to align with the preferences set in the build settings + */ + public static void configureLauncher(ConfigurableLauncher> launcher) { + addIdeProperties(launcher); + + SharedPreferences preferences = ApplicationLoader.getDefaultPreferences(); + + String logLevel = preferences.getString(SharedPreferenceKeys.GRADLE_LOG_LEVEL, "LIFECYCLE"); + switch (logLevel) { + case "QUIET": + launcher.addArguments("--quiet"); + break; + case "WARN": + launcher.addArguments("--warn"); + break; + case "INFO": + launcher.addArguments("--info"); + break; + case "DEBUG": + launcher.addArguments("--debug"); + break; + default: + case "LIFECYCLE": + // intentionally empty + } + + String stacktrace = preferences.getString(SharedPreferenceKeys.GRADLE_STACKTRACE_MODE, + "INTERNAL_EXCEPTIONS"); + switch (stacktrace) { + case "ALWAYS": + launcher.addArguments("--stacktrace"); + break; + case "ALWAYS_FULL": + launcher.addArguments("--full-stacktrace"); + break; + default: + case "INTERNAL_EXCEPTIONS": + // intentionally empty + } + } + + public static void addCodeAssistInitScript(ConfigurableLauncher> launcher) { + try { + launcher.addArguments("--init-script", getOrCreateInitScript().getAbsolutePath()); + } catch (IOException e) { + // this should not happen under normal circumstances. + // throw just in case + throw new RuntimeException(e); + } + } + + private static File getOrCreateInitScript() throws IOException { + File initScript = new File(ApplicationLoader.getInstance().getFilesDir(), "init_scripts/init_script.gradle"); + //noinspection ResultOfMethodCallIgnored + Objects.requireNonNull(initScript.getParentFile()).mkdirs(); + if (!initScript.exists() && !initScript.createNewFile()) { + throw new IOException(); + } + + //language=Groovy + String initScriptCode = "rootProject.buildscript.configurations.classpath {\n" + + " resolutionStrategy.eachDependency {\n" + + " if (it.requested.name == \"gradle\" && it.requested" + + ".group == \"com.android.tools.build\") {\n" + + " throw new GradleException(\"The Android Gradle " + + "Plugin has been applied but is not supported. CodeAssist " + + "maintains its own\" +\n" + + " \"version of the Android Gradle Plugin so " + + "you don't have to include it in the build script's\" +\n" + + " \"classpath.\")\n" + + " }\n" + + " }\n" + + "}\n"; + FileUtils.writeStringToFile(initScript, initScriptCode, StandardCharsets.UTF_8); + return initScript; + } + + private static final String PROJECT_PROPERTY_FORMAT = "-P%1$s=%2$s"; + + @NonNull + public static String createProjectProperty(@NonNull String name, boolean value) { + return createProjectProperty(name, String.valueOf(value)); + } + + @NonNull + public static String createProjectProperty(@NonNull String name, int value) { + return createProjectProperty(name, String.valueOf(value)); + } + + @NonNull + public static String createProjectProperty(@NonNull String name, @NonNull String value) { + return String.format(PROJECT_PROPERTY_FORMAT, name, value); + } +} diff --git a/app/src/main/java/com/tyron/code/language/AbstractAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/AbstractAutoCompleteProvider.java new file mode 100644 index 000000000..6df3095d9 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/AbstractAutoCompleteProvider.java @@ -0,0 +1,27 @@ +package com.tyron.code.language; + +import com.tyron.completion.model.CompletionList; + +import java.util.List; +import java.util.stream.Collectors; + +import io.github.rosemoe.sora.lang.completion.CompletionItem; + +/** + * An auto complete provider that supports cancellation as the user types + */ +public abstract class AbstractAutoCompleteProvider { + + public final List getAutoCompleteItems(String prefix, int line, int column) { + CompletionList list = getCompletionList(prefix, line, column); + if (list == null) { + return null; + } + + return list.items.stream() + .map(CompletionItemWrapper::new) + .collect(Collectors.toList()); + } + + public abstract CompletionList getCompletionList(String prefix, int line, int column); +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/AbstractCodeAnalyzer.java b/app/src/main/java/com/tyron/code/language/AbstractCodeAnalyzer.java similarity index 76% rename from app/src/main/java/com/tyron/code/ui/editor/language/AbstractCodeAnalyzer.java rename to app/src/main/java/com/tyron/code/language/AbstractCodeAnalyzer.java index aedc613a4..27c4f4e0d 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/AbstractCodeAnalyzer.java +++ b/app/src/main/java/com/tyron/code/language/AbstractCodeAnalyzer.java @@ -1,4 +1,4 @@ -package com.tyron.code.ui.editor.language; +package com.tyron.code.language; import android.os.Bundle; @@ -6,7 +6,6 @@ import androidx.annotation.Nullable; import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.code.ui.main.MainViewModel; import com.tyron.editor.Editor; import org.antlr.v4.runtime.CharStream; @@ -21,14 +20,12 @@ import java.util.List; import java.util.Map; -import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; import io.github.rosemoe.sora.lang.analysis.StyleReceiver; import io.github.rosemoe.sora.lang.styling.MappedSpans; import io.github.rosemoe.sora.lang.styling.Styles; import io.github.rosemoe.sora.text.CharPosition; import io.github.rosemoe.sora.text.ContentReference; import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; -import io.github.rosemoe.sora2.text.DiagnosticSpanMapUpdater; public abstract class AbstractCodeAnalyzer extends DiagnosticAnalyzeManager { @@ -37,14 +34,14 @@ public abstract class AbstractCodeAnalyzer extends DiagnosticAnalyzeManager mDiagnostics = new ArrayList<>(); + protected List mDiagnostics = new ArrayList<>(); public AbstractCodeAnalyzer() { setup(); } @Override - public void setReceiver(@NonNull StyleReceiver receiver) { + public void setReceiver(@Nullable StyleReceiver receiver) { super.setReceiver(receiver); mReceiver = receiver; @@ -52,46 +49,12 @@ public void setReceiver(@NonNull StyleReceiver receiver) { @Override public void insert(CharPosition start, CharPosition end, CharSequence insertedContent) { - super.insert(start, end, insertedContent); - - if (start.getLine() != end.getLine()) { - DiagnosticSpanMapUpdater.shiftDiagnosticsOnMultiLineInsert( - mDiagnostics, - start.getLine(), - start.getColumn(), - end.getLine(), - end.getColumn() - ); - } else { - DiagnosticSpanMapUpdater.shiftDiagnosticsOnSingleLineInsert( - mDiagnostics, - start.getLine(), - start.getColumn(), - end.getColumn() - ); - } + rerunWithBg(); } @Override public void delete(CharPosition start, CharPosition end, CharSequence deletedContent) { - super.delete(start, end, deletedContent); - - if (start.getLine() != end.getLine()) { - DiagnosticSpanMapUpdater.shiftDiagnosticsOnMultiLineInsert( - mDiagnostics, - start.getLine(), - start.getColumn(), - end.getLine(), - end.getColumn() - ); - } else { - DiagnosticSpanMapUpdater.shiftDiagnosticsOnSingleLineInsert( - mDiagnostics, - start.getLine(), - start.getColumn(), - end.getColumn() - ); - } + rerunWithBg(); } @Override @@ -101,8 +64,7 @@ public void reset(@NonNull ContentReference content, @NonNull Bundle extraArgume @Override public void setDiagnostics(Editor editor, List diagnostics) { - mDiagnostics.clear(); - mDiagnostics.addAll(diagnostics); + mDiagnostics = diagnostics; } public void setup() { @@ -150,9 +112,13 @@ protected void beforeAnalyze() { @Override protected Styles analyze(StringBuilder text, Delegate delegate) { + Styles styles = new Styles(); + boolean loaded = getExtraArguments().getBoolean("loaded", false); + if (!loaded) { + return styles; + } beforeAnalyze(); - Styles styles = new Styles(); MappedSpans.Builder result = new MappedSpans.Builder(1024); try { diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/CompletionItemWrapper.java b/app/src/main/java/com/tyron/code/language/CompletionItemWrapper.java similarity index 88% rename from app/src/main/java/com/tyron/code/ui/editor/language/CompletionItemWrapper.java rename to app/src/main/java/com/tyron/code/language/CompletionItemWrapper.java index a5faec1c8..37fd64d20 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/CompletionItemWrapper.java +++ b/app/src/main/java/com/tyron/code/language/CompletionItemWrapper.java @@ -1,9 +1,6 @@ -package com.tyron.code.ui.editor.language; - -import android.graphics.drawable.Drawable; +package com.tyron.code.language; import com.tyron.completion.java.drawable.CircleDrawable; -import com.tyron.completion.model.DrawableKind; import com.tyron.editor.Editor; import io.github.rosemoe.sora.lang.completion.CompletionItem; diff --git a/app/src/main/java/com/tyron/code/language/DiagnosticAnalyzeManager.java b/app/src/main/java/com/tyron/code/language/DiagnosticAnalyzeManager.java new file mode 100644 index 000000000..289981b73 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/DiagnosticAnalyzeManager.java @@ -0,0 +1,54 @@ +package com.tyron.code.language; + +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.editor.Editor; + +import java.util.List; + +import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; +import io.github.rosemoe.sora.text.ContentReference; + +public abstract class DiagnosticAnalyzeManager extends SimpleAnalyzeManager { + + protected boolean mShouldAnalyzeInBg = false; + + public abstract void setDiagnostics(Editor editor, List diagnostics); + + public void rerunWithoutBg() { + mShouldAnalyzeInBg = false; + super.rerun(); + } + + public void rerunWithBg() { + mShouldAnalyzeInBg = true; + super.rerun(); + } + + @Override + public void rerun() { + mShouldAnalyzeInBg = false; + super.rerun(); + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + if (extraArguments == null) { + extraArguments = new Bundle(); + } + mShouldAnalyzeInBg = extraArguments.getBoolean("bg", false); + super.reset(content, extraArguments); + } + + @Override + public Bundle getExtraArguments() { + Bundle extraArguments = super.getExtraArguments(); + if (extraArguments == null) { + extraArguments = new Bundle(); + } + return extraArguments; + } +} diff --git a/app/src/main/java/com/tyron/code/language/DiagnosticSpanMapUpdater.java b/app/src/main/java/com/tyron/code/language/DiagnosticSpanMapUpdater.java new file mode 100644 index 000000000..bd66061e3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/DiagnosticSpanMapUpdater.java @@ -0,0 +1,85 @@ +package com.tyron.code.language; + +import com.tyron.builder.model.DiagnosticWrapper; + +import java.util.List; + +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; + +public class DiagnosticSpanMapUpdater { + + public static void shiftDiagnosticsOnSingleLineInsert(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + // diagnostic is located before the insertion index, its not included + if (diagnostic.getEndPosition() <= end.index) { + continue; + } + diagnostic.setStartPosition(diagnostic.getStartPosition() + length); + diagnostic.setEndPosition(diagnostic.getEndPosition() + length); + } + } + + public static void shiftDiagnosticsOnSingleLineDelete(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + if (diagnostic.getStartPosition() > start.index) { + diagnostic.setStartPosition(diagnostic.getStartPosition() - length); + } + if (diagnostic.getEndPosition() > end.index) { + diagnostic.setEndPosition(diagnostic.getEndPosition() - length); + } + } + } + + public static void shiftDiagnosticsOnMultiLineDelete(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + if (diagnostic.getStartPosition() < end.index) { + continue; + } + diagnostic.setStartPosition(diagnostic.getStartPosition() - length); + diagnostic.setEndPosition(diagnostic.getEndPosition() - length); + } + } + + public static void shiftDiagnosticsOnMultiLineInsert(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + if (diagnostic.getEndPosition() < end.index) { + continue; + } + diagnostic.setStartPosition(diagnostic.getStartPosition() + length); + diagnostic.setEndPosition(diagnostic.getEndPosition() + length); + } + } + + public static boolean isValid(DiagnosticWrapper d) { + return d.getStartPosition() >= 0 && d.getEndPosition() >= 0; + } +} diff --git a/app/src/main/java/com/tyron/code/language/EditorFormatter.java b/app/src/main/java/com/tyron/code/language/EditorFormatter.java new file mode 100644 index 000000000..f7c41bc3f --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/EditorFormatter.java @@ -0,0 +1,19 @@ +package com.tyron.code.language; + +import androidx.annotation.NonNull; + +/** + * Marker interface for languages that support formatting + */ +public interface EditorFormatter { + + /** + * Formats the given CharSequence on the specified start and end indices. + * @param text The text to format. + * @param startIndex The 0-based index of where the format starts + * @param endIndex The 0-based index of where the format ends + * @return The formatted text + */ + @NonNull + CharSequence format(@NonNull CharSequence text, int startIndex, int endIndex); +} diff --git a/app/src/main/java/com/tyron/code/language/HighlightUtil.java b/app/src/main/java/com/tyron/code/language/HighlightUtil.java new file mode 100644 index 000000000..f2aa5a661 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/HighlightUtil.java @@ -0,0 +1,199 @@ +package com.tyron.code.language; + +import android.util.Log; + +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.editor.CharPosition; +import com.tyron.editor.Editor; + +import javax.tools.Diagnostic; + +import java.util.ArrayList; +import java.util.List; + +import io.github.rosemoe.sora.lang.styling.Span; +import io.github.rosemoe.sora.lang.styling.Spans; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora2.BuildConfig; + +public class HighlightUtil { + + public static void replaceSpan(Styles styles, Span newSpan, int startLine, int startColumn, int endLine, int endColumn) { + for (int line = startLine; line <= endLine; line++) { + ProgressManager.checkCanceled(); + int start = (line == startLine ? startColumn : 0); + int end = (line == endLine ? endColumn : Integer.MAX_VALUE); + Spans.Reader read = styles.getSpans().read(); + List spans = new ArrayList<>(read.getSpansOnLine(line)); + int increment; + for (int i = 0; i < spans.size(); i += increment) { + ProgressManager.checkCanceled(); + io.github.rosemoe.sora.lang.styling.Span span = spans.get(i); + increment = 1; + if (span.column >= end) { + break; + } + int spanEnd = (i + 1 >= spans.size() ? Integer.MAX_VALUE : spans.get(i + 1).column); + if (spanEnd >= start) { + int regionStartInSpan = Math.max(span.column, start); + int regionEndInSpan = Math.min(end, spanEnd); + if (regionStartInSpan == span.column) { + if (regionEndInSpan != spanEnd) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionEndInSpan; + spans.add(i + 1, nSpan); + } + span.underlineColor = newSpan.underlineColor; + span.style = newSpan.style; + span.renderer = newSpan.renderer; + } else { + //regionStartInSpan > span.column + if (regionEndInSpan == spanEnd - 1) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionStartInSpan; + spans.add(i + 1, nSpan); + span.underlineColor = newSpan.underlineColor; + span.style = newSpan.style; + span.renderer = newSpan.renderer; + } else { + increment = 3; + io.github.rosemoe.sora.lang.styling.Span span1 = span.copy(); + span1.column = regionStartInSpan; + span1.underlineColor = newSpan.underlineColor; + span1.style = newSpan.style; + span1.renderer = newSpan.renderer; + io.github.rosemoe.sora.lang.styling.Span span2 = span.copy(); + span2.column = regionEndInSpan; + spans.add(i + 1, span1); + spans.add(i + 2, span2); + } + } + } + } + + Spans.Modifier modify = styles.getSpans().modify(); + modify.setSpansOnLine(line, spans); + } + } + + public static void markProblemRegion(Styles styles, int newFlag, int startLine, int startColumn, int endLine, int endColumn) { + for (int line = startLine; line <= endLine; line++) { + ProgressManager.checkCanceled(); + int start = (line == startLine ? startColumn : 0); + int end = (line == endLine ? endColumn : Integer.MAX_VALUE); + Spans.Reader read = styles.getSpans().read(); + List spans = new ArrayList<>(read.getSpansOnLine(line)); + int increment; + for (int i = 0; i < spans.size(); i += increment) { + ProgressManager.checkCanceled(); + io.github.rosemoe.sora.lang.styling.Span span = spans.get(i); + increment = 1; + if (span.column >= end) { + break; + } + int spanEnd = (i + 1 >= spans.size() ? Integer.MAX_VALUE : spans.get(i + 1).column); + if (spanEnd >= start) { + int regionStartInSpan = Math.max(span.column, start); + int regionEndInSpan = Math.min(end, spanEnd); + if (regionStartInSpan == span.column) { + if (regionEndInSpan != spanEnd) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionEndInSpan; + spans.add(i + 1, nSpan); + } + } else { + //regionStartInSpan > span.column + if (regionEndInSpan == spanEnd) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionStartInSpan; + spans.add(i + 1, nSpan); + } else { + increment = 3; + io.github.rosemoe.sora.lang.styling.Span span1 = span.copy(); + span1.column = regionStartInSpan; + io.github.rosemoe.sora.lang.styling.Span span2 = span.copy(); + span2.column = regionEndInSpan; + spans.add(i + 1, span1); + spans.add(i + 2, span2); + } + } + } + } + + Spans.Modifier modify = styles.getSpans().modify(); + modify.setSpansOnLine(line, spans); + } + } + + + /** + * Highlights the list of given diagnostics, taking care of conversion between 1-based offsets + * to 0-based offsets. + * It also makes the Diagnostic eligible for shifting as the user types. + */ + public static void markDiagnostics(Editor editor, List diagnostics, + Styles styles) { + diagnostics.forEach(it -> { + ProgressManager.checkCanceled(); + try { + int startLine; + int startColumn; + int endLine; + int endColumn; + if (it.getPosition() != DiagnosticWrapper.USE_LINE_POS) { + if (it.getStartPosition() == -1) { + it.setStartPosition(it.getPosition()); + } + if (it.getEndPosition() == -1) { + it.setEndPosition(it.getPosition()); + } + + if (it.getStartPosition() > editor.getContent().length()) { + return; + } + if (it.getEndPosition() > editor.getContent().length()) { + return; + } + CharPosition start = editor.getCharPosition((int) it.getStartPosition()); + CharPosition end = editor.getCharPosition((int) it.getEndPosition()); + + int sLine = start.getLine(); + int sColumn = start.getColumn(); + int eLine = end.getLine(); + int eColumn = end.getColumn(); + + // the editor does not support marking underline spans for the same start and end + // index + // to work around this, we just subtract one to the start index + if (sLine == eLine && eColumn == sColumn) { + sColumn--; + eColumn++; + } + + it.setStartLine(sLine); + it.setEndLine(eLine); + it.setStartColumn(sColumn); + it.setEndColumn(eColumn); + } + startLine = it.getStartLine(); + startColumn = it.getStartColumn(); + endLine = it.getEndLine(); + endColumn = it.getEndColumn(); + + } catch (IllegalArgumentException | IndexOutOfBoundsException e) { + if (BuildConfig.DEBUG) { + Log.d("HighlightUtil", "Failed to mark diagnostics", e); + } + } + }); + } + + public static void clearDiagnostics(Styles styles) { + + } +} diff --git a/app/src/main/java/com/tyron/code/language/Language.java b/app/src/main/java/com/tyron/code/language/Language.java new file mode 100644 index 000000000..222c893dd --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/Language.java @@ -0,0 +1,32 @@ +package com.tyron.code.language; + +import com.tyron.editor.Editor; + +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystem; +import org.apache.commons.vfs2.provider.local.LocalFile; +import org.apache.commons.vfs2.provider.local.LocalFileSystem; + +import java.io.File; + +public interface Language { + + /** + * Subclasses return whether they support this file extension + */ + boolean isApplicable(File ext); + + default boolean isApplicable(FileObject fileObject) { + if (fileObject instanceof LocalFile) { + return isApplicable(new File(fileObject.getURI())); + } + return false; + } + + /** + * + * @param editor the editor instance + * @return The specific language instance for this editor + */ + io.github.rosemoe.sora.lang.Language get(Editor editor); +} diff --git a/app/src/main/java/com/tyron/code/language/LanguageManager.java b/app/src/main/java/com/tyron/code/language/LanguageManager.java new file mode 100644 index 000000000..c7ba41aa5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/LanguageManager.java @@ -0,0 +1,93 @@ +package com.tyron.code.language; + +import android.content.res.AssetManager; + +import com.tyron.code.ApplicationLoader; +import com.tyron.code.language.groovy.Groovy; +import com.tyron.code.language.java.Java; +import com.tyron.code.language.json.Json; +import com.tyron.code.language.kotlin.Kotlin; +import com.tyron.code.language.xml.Xml; +import com.tyron.editor.Editor; + +import org.apache.commons.vfs2.FileObject; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Supplier; + +import io.github.rosemoe.sora.langs.textmate.TextMateColorScheme; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +import io.github.rosemoe.sora.widget.CodeEditor; + +public class LanguageManager { + + private static LanguageManager Instance = null; + + public static LanguageManager getInstance() { + if (Instance == null) { + Instance = new LanguageManager(); + } + return Instance; + } + + private final Set mLanguages = new HashSet<>(); + + private LanguageManager() { + initLanguages(); + } + + private void initLanguages() { + mLanguages.addAll( + Arrays.asList( + new Xml(), + new Java(), + new Kotlin(), + new Groovy(), + new Json())); + } + + public boolean supports(File file) { + for (Language language : mLanguages) { + if (language.isApplicable(file)) { + return true; + } + } + return false; + } + + public io.github.rosemoe.sora.lang.Language get(Editor editor, FileObject file) { + for (Language lang : mLanguages) { + if (lang.isApplicable(file)) { + return lang.get(editor); + } + } + return null; + } + + public io.github.rosemoe.sora.lang.Language get(Editor editor, File file) { + for (Language lang : mLanguages) { + if (lang.isApplicable(file)) { + return lang.get(editor); + } + } + return null; + } + + public static TextMateLanguage createTextMateLanguage(String grammarName, String grammarPath, String configurationPath, Editor editor) { + AssetManager assets = ApplicationLoader.getInstance().getAssets(); + try { + return TextMateLanguage.createNoCompletion( + grammarName, + assets.open(grammarPath), + new InputStreamReader(assets.open(configurationPath)), + ((TextMateColorScheme) ((CodeEditor) editor).getColorScheme()).getRawTheme()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/groovy/AndroidDelegate.java b/app/src/main/java/com/tyron/code/language/groovy/AndroidDelegate.java new file mode 100644 index 000000000..1e59b5beb --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/AndroidDelegate.java @@ -0,0 +1,18 @@ +package com.tyron.code.language.groovy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AndroidDelegate { + + // Here to support different versions of delegate types. The values of delegate + // types are sorted by priority + private static final Map> delegateMap; + + static { + delegateMap = new HashMap<>(); + + //plugins + } +} diff --git a/app/src/main/java/com/tyron/code/language/groovy/CompletionHandler.java b/app/src/main/java/com/tyron/code/language/groovy/CompletionHandler.java new file mode 100644 index 000000000..63c7aa697 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/CompletionHandler.java @@ -0,0 +1,181 @@ +package com.tyron.code.language.groovy; + +import com.tyron.code.language.java.Java; +import com.tyron.completion.java.provider.JavaSortCategory; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.DrawableKind; + +import org.codehaus.groovy.ast.expr.MethodCallExpression; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class CompletionHandler { + + private static final String BUILD_GRADLE = "build.gradle"; + private static final String SETTING_GRADLE = "settings.gradle"; + private static final String DEPENDENCYHANDLER_CLASS = "org.gradle.api.artifacts.dsl.DependencyHandler"; + private static final List CONFIGURATIONS = List.of("implementation", "compileOnly", "runtimeOnly", "classpath", "api"); + + public List getCompletionItems(MethodCallExpression containingCall, String fileName, String projectPath, Set plugins) { + Set resultSet = new HashSet<>(); + List results = new ArrayList<>(); + List delegateClassNames = new ArrayList<>(); + + if (containingCall == null) { + if (BUILD_GRADLE.equals(fileName)) { + delegateClassNames.add(GradleDelegate.getDefault()); + } else if (SETTING_GRADLE.equals(fileName)) { + delegateClassNames.add(GradleDelegate.getSettings()); + } + results.addAll(getCompletionItemsFromExtClosures(projectPath, resultSet)); + } else { + String methodName = containingCall.getMethodAsString(); + List delegates = GradleDelegate.getDelegateMap().get(methodName); + if (delegates == null) { + return results; + } + delegateClassNames.addAll(delegates); + } + if (delegateClassNames.isEmpty()) { + return Collections.emptyList(); + } + for (String delegateClassName : delegateClassNames) { + Class> delegateClass; + try { + delegateClass = Class.forName(delegateClassName); + } catch (ClassNotFoundException e) { + delegateClass = null; + } + if (delegateClass == null) { + continue; + } + results.addAll(getCompletionItemsFromClass(delegateClass, plugins, resultSet)); + break; + } + + + return results; + } + + private List getCompletionItemsFromClass(Class> javaClass, Set plugins, Set resultSet) { + List results = new ArrayList<>(); + for (Class> superInterface : javaClass.getInterfaces()) { + results.addAll(getCompletionItemsFromClass(superInterface, plugins, resultSet)); + } + Class> superclass = javaClass.getSuperclass(); + if (superclass != null) { + results.addAll(getCompletionItemsFromClass(superclass, plugins, resultSet)); + } + + Method[] methods = javaClass.getMethods(); + for (Method method : methods) { + boolean isMethodDeprecated = isDeprecated(method); + String methodName = method.getName(); + + // When parsing a abstract class, we'll get a "" method which can't be + // called directly, + // So we filter it here. + if (methodName.equals("")) { + continue; + } + List arguments = new ArrayList<>(); + Arrays.asList(method.getParameterTypes()).forEach(type -> + arguments.add(type.getName())); + CompletionItem item = generateCompletionItemForMethod(methodName, arguments, isMethodDeprecated); + if (resultSet.add(item.getLabel())) { + results.add(item); + } + int modifiers = method.getModifiers(); + // See: + // https://docs.gradle.org/current/userguide/custom_gradle_types.html#managed_properties + // we offer managed properties for an abstract getter method; + if (methodName.startsWith("get") && methodName.length() > 3 && Modifier.isPublic(modifiers) + && Modifier.isAbstract(modifiers)) { + String propertyName = methodName.substring(3, 4).toLowerCase() + methodName.substring(4); + CompletionItem property = new CompletionItem(propertyName); + property.commitText = propertyName; + property.detail = "Property"; + property.iconKind = DrawableKind.Attribute; + property.setSortText(JavaSortCategory.ACCESSIBLE_SYMBOL.toString()); + property.addFilterText(propertyName); + if (resultSet.add(propertyName)) { + results.add(property); + } + } + } + boolean shouldAddConfigurations = !Collections.disjoint( + plugins, + List.of("com.android.application", "application", "java", "java-library") + ); + if (shouldAddConfigurations && javaClass.getName().equals(DEPENDENCYHANDLER_CLASS)) { + for (String configuration : CONFIGURATIONS) { + String builder = configuration + "(Object... o)"; + String insertBuilder = configuration + "()"; + CompletionItem item = new CompletionItem(builder); + item.iconKind = DrawableKind.Method; + item.commitText = insertBuilder; + item.addFilterText(configuration); + item.setSortText(JavaSortCategory.ACCESSIBLE_SYMBOL.toString()); + results.add(item); + } + } + return results; + } + + private List getCompletionItemsFromExtClosures(String projectPath, + Set resultSet) { + return Collections.emptyList(); + } + + private static CompletionItem generateCompletionItemForMethod(String name, List arguments, + boolean deprecated) { + StringBuilder labelBuilder = new StringBuilder(); + labelBuilder.append(name); + labelBuilder.append("("); + for (int i = 0; i < arguments.size(); i++) { + String type = arguments.get(i); + String[] classNameSplits = type.split("\\."); + String className = classNameSplits[classNameSplits.length - 1]; + String variableName = className.substring(0, 1).toLowerCase(); + labelBuilder.append(className); + labelBuilder.append(" "); + labelBuilder.append(variableName); + if (i != arguments.size() - 1) { + labelBuilder.append(", "); + } + } + labelBuilder.append(")"); + String label = labelBuilder.toString(); + CompletionItem item = new CompletionItem(label); + item.addFilterText(name); + item.setSortText(JavaSortCategory.ACCESSIBLE_SYMBOL.toString()); + item.iconKind = DrawableKind.Method; + + StringBuilder builder = new StringBuilder(); + builder.append(name); + if (label.endsWith("(Closure c)")) { + // for single closure, we offer curly brackets + builder.append(" {}"); + } else { + builder.append("()"); + } + item.commitText = builder.toString(); + return item; + } + + private static boolean isDeprecated(Method object) { + try { + Deprecated annotation = object.getAnnotation(Deprecated.class); + return annotation != null; + } catch (Throwable t) { + return false; + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/groovy/CompletionVisitor.java b/app/src/main/java/com/tyron/code/language/groovy/CompletionVisitor.java new file mode 100644 index 000000000..729459cbf --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/CompletionVisitor.java @@ -0,0 +1,247 @@ +package com.tyron.code.language.groovy; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.codehaus.groovy.ast.ClassCodeVisitorSupport; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.GStringExpression; +import org.codehaus.groovy.ast.expr.MapEntryExpression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.NamedArgumentListExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; +import org.eclipse.lsp4j.Range; + +public class CompletionVisitor extends ClassCodeVisitorSupport { + + @Override + protected SourceUnit getSourceUnit() { + return sourceUnit; + } + + public class DependencyItem { + private String text; + private Range range; + + public DependencyItem(String text, Range range) { + this.text = text; + this.range = range; + } + + public String getText() { + return this.text; + } + + public Range getRange() { + return this.range; + } + } + + private URI currentUri; + private final Map> dependencies = new HashMap<>(); + private final Map> methodCalls = new HashMap<>(); + private final Map> statements = new HashMap<>(); + private final Map> constants = new HashMap<>(); + private final Map> plugins = new HashMap<>(); + + public List getDependencies(URI uri) { + return this.dependencies.get(uri); + } + + public Set getMethodCalls(URI uri) { + return this.methodCalls.get(uri); + } + + public List getStatements(URI uri) { + return this.statements.get(uri); + } + + public List getConstants(URI uri) { + return this.constants.get(uri); + } + + public Set getPlugins(URI uri) { + return this.plugins.get(uri); + } + + public void visitCompilationUnit(URI uri, CompilationUnit compilationUnit) { + this.currentUri = uri; + compilationUnit.iterator().forEachRemaining(this::visitSourceUnit); + } + + SourceUnit sourceUnit; + + public void visitSourceUnit(SourceUnit unit) { + this.sourceUnit = unit; + ModuleNode moduleNode = unit.getAST(); + if (moduleNode != null) { + this.dependencies.put(this.currentUri, new ArrayList<>()); + this.methodCalls.put(this.currentUri, new HashSet<>()); + this.statements.put(this.currentUri, new ArrayList<>()); + this.constants.put(this.currentUri, new ArrayList<>()); + this.plugins.put(this.currentUri, new HashSet<>()); + visitModule(moduleNode); + } + } + + public void visitModule(ModuleNode node) { + BlockStatement blockStatement = node.getStatementBlock(); + this.statements.put(currentUri, blockStatement.getStatements()); + node.getClasses().forEach(super::visitClass); + } + + @Override + public void visitMethodCallExpression(MethodCallExpression node) { + this.methodCalls.get(this.currentUri).add(node); + if (node.getMethodAsString().equals("dependencies")) { + this.dependencies.get(this.currentUri).addAll(getDependencies(node)); + } else if (node.getMethodAsString().equals("plugins")) { + // match plugins { id: ${id} } + List plugins = getPluginFromPlugins(node); + this.plugins.get(this.currentUri).addAll(plugins); + } else if (node.getMethodAsString().equals("apply")) { + // match apply plugins: '${id}' + String plugin = getPluginFromApply(node); + if (plugin != null) { + this.plugins.get(this.currentUri).add(plugin); + } + } + super.visitMethodCallExpression(node); + } + + private List getDependencies(MethodCallExpression expression) { + Expression argument = expression.getArguments(); + if (argument instanceof ArgumentListExpression) { + return getDependencies((ArgumentListExpression) argument); + } + return Collections.emptyList(); + } + + private List getDependencies(ArgumentListExpression argumentListExpression) { + List expressions = argumentListExpression.getExpressions(); + List symbols = new ArrayList<>(); + for (Expression expression : expressions) { + if (expression instanceof ClosureExpression) { + symbols.addAll(getDependencies((ClosureExpression) expression)); + } else if (expression instanceof GStringExpression || expression instanceof ConstantExpression) { + // GStringExp: implementation + // "org.gradle:gradle-tooling-api:${gradleToolingApi}" + // ConstantExp: implementation "org.gradle:gradle-tooling-api:6.8.0" + symbols.add(new DependencyItem(expression.getText(), GroovyUtils.toDependencyRange(expression))); + } else if (expression instanceof MethodCallExpression) { + symbols.addAll(getDependencies((MethodCallExpression) expression)); + } + } + return symbols; + } + + private List getDependencies(ClosureExpression expression) { + Statement code = expression.getCode(); + if (code instanceof BlockStatement) { + return getDependencies((BlockStatement) code); + } + return Collections.emptyList(); + } + + private List getDependencies(BlockStatement blockStatement) { + List statements = blockStatement.getStatements(); + List results = new ArrayList<>(); + for (Statement statement : statements) { + if (statement instanceof ExpressionStatement) { + results.addAll(getDependencies((ExpressionStatement) statement)); + } + } + return results; + } + + private List getDependencies(ExpressionStatement expressionStatement) { + Expression expression = expressionStatement.getExpression(); + if (expression instanceof MethodCallExpression) { + return getDependencies((MethodCallExpression) expression); + } + return Collections.emptyList(); + } + + private String getPluginFromApply(MethodCallExpression node) { + Expression argument = node.getArguments(); + if (argument instanceof TupleExpression) { + List expressions = ((TupleExpression) argument).getExpressions(); + for (Expression expression : expressions) { + if (expression instanceof NamedArgumentListExpression) { + List mapEntryExpressions = ((NamedArgumentListExpression) expression) + .getMapEntryExpressions(); + for (MapEntryExpression mapEntryExp : mapEntryExpressions) { + Expression keyExpression = mapEntryExp.getKeyExpression(); + if (keyExpression instanceof ConstantExpression && keyExpression.getText().equals("plugin")) { + return mapEntryExp.getValueExpression().getText(); + } + } + } + } + } + return null; + } + + private List getPluginFromPlugins(MethodCallExpression node) { + Expression objectExpression = node.getObjectExpression(); + if (objectExpression instanceof MethodCallExpression) { + return getPluginFromPlugins((MethodCallExpression) objectExpression); + } + List results = new ArrayList<>(); + Expression argument = node.getArguments(); + if (argument instanceof ArgumentListExpression) { + List expressions = ((ArgumentListExpression) argument).getExpressions(); + for (Expression expression : expressions) { + if (expression instanceof ConstantExpression && node.getMethodAsString().equals("id")) { + results.add(expression.getText()); + } else if (expression instanceof ClosureExpression) { + Statement code = ((ClosureExpression) expression).getCode(); + if (code instanceof BlockStatement) { + results.addAll(getPluginFromPlugins((BlockStatement) code)); + } + } + } + } + return results; + } + + private List getPluginFromPlugins(BlockStatement code) { + List results = new ArrayList<>(); + List statements = code.getStatements(); + for (Statement statement : statements) { + if (statement instanceof ExpressionStatement) { + Expression expression = ((ExpressionStatement) statement).getExpression(); + if (expression instanceof MethodCallExpression) { + results.addAll(getPluginFromPlugins((MethodCallExpression) expression)); + } + } + } + return results; + } + + @Override + public void visitConstantExpression(ConstantExpression expression) { + this.constants.get(currentUri).add(expression); + super.visitConstantExpression(expression); + } + + @Override + public void visitGStringExpression(GStringExpression expression) { + this.constants.get(currentUri).add(expression); + super.visitGStringExpression(expression); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/groovy/GradleDelegate.java b/app/src/main/java/com/tyron/code/language/groovy/GradleDelegate.java new file mode 100644 index 000000000..57e98f0be --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/GradleDelegate.java @@ -0,0 +1,57 @@ +package com.tyron.code.language.groovy; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GradleDelegate { + private static final String PROJECT = "org.gradle.api.Project"; + private static final String SETTINGS = "org.gradle.api.initialization.Settings"; + // Here to support different versions of delegate types. The values of delegate + // types are sorted by priority + private static final Map> delegateMap; + + static { + delegateMap = new HashMap<>(); + // plugins + delegateMap.put("application", List.of("org.gradle.api.plugins.JavaApplication", + "org.gradle.api.plugins.ApplicationPluginConvention")); + delegateMap.put("base", List.of("org.gradle.api.plugins.BasePluginExtension", + "org.gradle.api.plugins.BasePluginConvention")); + delegateMap.put("java", List.of("org.gradle.api.plugins.JavaPluginExtension", + "org.gradle.api.plugins.JavaPluginConvention")); + delegateMap.put("war", List.of("org.gradle.api.plugins.WarPluginConvention")); + // basic closures + delegateMap.put("plugins", List.of("org.gradle.plugin.use.PluginDependenciesSpec")); + delegateMap.put("configurations", List.of("org.gradle.api.artifacts.Configuration")); + delegateMap.put("dependencySubstitution", List.of("org.gradle.api.artifacts.DependencySubstitutions")); + delegateMap.put("resolutionStrategy", List.of("org.gradle.api.artifacts.ResolutionStrategy")); + delegateMap.put("artifacts", List.of("org.gradle.api.artifacts.dsl.ArtifactHandler")); + delegateMap.put("components", List.of("org.gradle.api.artifacts.dsl.ComponentMetadataHandler")); + delegateMap.put("modules", List.of("org.gradle.api.artifacts.dsl.ComponentModuleMetadataHandler")); + delegateMap.put("dependencies", List.of("org.gradle.api.artifacts.dsl.DependencyHandler")); + delegateMap.put("repositories", List.of("org.gradle.api.artifacts.dsl.RepositoryHandler")); + delegateMap.put("publishing", List.of("org.gradle.api.publish.PublishingExtension")); + delegateMap.put("publications", List.of("org.gradle.api.publish.PublicationContainer")); + delegateMap.put("sourceSets", List.of("org.gradle.api.tasks.SourceSet")); + delegateMap.put("distributions", List.of("org.gradle.api.distribution.Distribution")); + delegateMap.put("fileTree", List.of("org.gradle.api.file.ConfigurableFileTree")); + delegateMap.put("copySpec", List.of("org.gradle.api.file.CopySpec")); + delegateMap.put("exec", List.of("org.gradle.process.ExecSpec")); + delegateMap.put("files", List.of("org.gradle.api.file.ConfigurableFileCollection")); + delegateMap.put("task", List.of("org.gradle.api.Task")); + } + + public static Map> getDelegateMap() { + return delegateMap; + } + + public static String getDefault() { + return PROJECT; + } + + public static String getSettings() { + return SETTINGS; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/groovy/Groovy.java b/app/src/main/java/com/tyron/code/language/groovy/Groovy.java new file mode 100644 index 000000000..8583866ed --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/Groovy.java @@ -0,0 +1,31 @@ +package com.tyron.code.language.groovy; + +import com.tyron.code.language.Language; +import com.tyron.code.language.LanguageManager; +import com.tyron.editor.Editor; + +import org.apache.commons.vfs2.FileObject; + +import java.io.File; + +public class Groovy implements Language { + + + + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".groovy") || ext.getName().endsWith(".gradle"); + } + + @Override + public boolean isApplicable(FileObject fileObject) { + String extension = fileObject.getName().getExtension(); + return "groovy".equals(extension) || "gradle".equals(extension); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new GroovyLanguage(editor); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/groovy/GroovyLanguage.java b/app/src/main/java/com/tyron/code/language/groovy/GroovyLanguage.java new file mode 100644 index 000000000..200f598ec --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/GroovyLanguage.java @@ -0,0 +1,278 @@ +package com.tyron.code.language.groovy; + +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.code.language.CompletionItemWrapper; +import com.tyron.code.language.LanguageManager; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Editor; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.Phases; +import org.codehaus.groovy.control.SourceUnit; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.util.Ranges; + +import java.io.File; +import java.net.URI; +import java.util.List; +import java.util.Set; + +import groovy.lang.GroovyClassLoader; +import groovy.lang.GroovyCodeSource; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionHelper; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.format.Formatter; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +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.util.MyCharacter; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class GroovyLanguage implements Language { + + private static final String GRAMMAR_NAME = "groovy.tmLanguage"; + private static final String LANGUAGE_PATH = "textmate/groovy/syntaxes/groovy.tmLanguage"; + private static final String CONFIG_PATH = "textmate/groovy/language-configuration.json"; + + private final Editor editor; + private final TextMateLanguage delegate; + + + public GroovyLanguage(Editor editor) { + this.editor = editor; + delegate = LanguageManager.createTextMateLanguage(GRAMMAR_NAME, LANGUAGE_PATH, CONFIG_PATH, editor); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return delegate.getAnalyzeManager(); + } + + @Override + public int getInterruptionLevel() { + return delegate.getInterruptionLevel(); + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) throws CompletionCancelledException { + String prefix = CompletionHelper.computePrefix(content, position, MyCharacter::isJavaIdentifierPart); + + if (true) { + return; + } + try { + File currentFile = editor.getCurrentFile(); + URI uri = currentFile.toURI(); + Position pos = new Position(position.getLine(), position.getColumn()); + + CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); + GroovyClassLoader classLoader = new GroovyClassLoader(getClass().getClassLoader(), compilerConfiguration); + CompilationUnit unit = new CompilationUnit(compilerConfiguration, null, classLoader); + SourceUnit source = new SourceUnit( + currentFile.getName(), + content.getReference().toString(), + unit.getConfiguration(), + classLoader, + unit.getErrorCollector() + ); + unit.addSource(source); + + unit.compile(Phases.CANONICALIZATION); + + CompletionVisitor completionVisitor = new CompletionVisitor(); + completionVisitor.visitCompilationUnit(uri, + unit + ); + + Set methodCalls = completionVisitor.getMethodCalls(uri); + MethodCallExpression containingCall = null; + for (MethodCallExpression call : methodCalls) { + Expression expression = call.getArguments(); + Range range = GroovyUtils.toRange(expression); + if (Ranges.containsPosition(range, pos) + && (containingCall == null || Ranges.containsRange(GroovyUtils.toRange(containingCall.getArguments()), + GroovyUtils.toRange(call.getArguments())))) { + // find inner containing call + containingCall = call; + } + } + + if (containingCall != null) { + CompletionHandler completionHandler = new CompletionHandler(); + List completionItems = completionHandler.getCompletionItems( + containingCall, + currentFile.getName(), + editor.getProject().getRootFile().getAbsolutePath(), + completionVisitor.getPlugins(uri)); + CompletionList.Builder builder = new CompletionList.Builder(prefix); + completionItems.forEach(builder::addItem); + builder.build().getItems().forEach(it -> { + publisher.addItem(new CompletionItemWrapper(it)); + }); + } + } catch (Exception e) { + System.out.println(e); + } + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + return delegate.getIndentAdvance(content, line, column); + } + + @Override + public boolean useTab() { + return false; + } + + @NonNull + @Override + public Formatter getFormatter() { + return delegate.getFormatter(); + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return delegate.getSymbolPairs(); + } + + @Nullable + @Override + public NewlineHandler[] getNewlineHandlers() { + return new NewlineHandler[]{ + new BraceHandler(), + new JavaDocHandler(), + new TwoIndentHandler() + }; + } + + @Override + public void destroy() { + delegate.destroy(); + } + + public int getIndentAdvance(String p1) { + GroovyLexer groovyLexer = new GroovyLexer(CharStreams.fromString(p1)); + Token token; + int advance = 0; + while ((token = groovyLexer.nextToken()).getType() != Token.EOF) { + if (token.getType() == GroovyLexer.LBRACK) { + advance++; + } + } + return (advance * delegate.getTabSize()); + } + + class TwoIndentHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + Log.d("BeforeText", beforeText); + if (beforeText.replace("\r", "").trim().startsWith(".")) { + return false; + } + return beforeText.endsWith(")") && !afterText.startsWith(";"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText) + (4 * 2); + String text; + StringBuilder sb = new StringBuilder().append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = 0; + return new NewlineHandleResult(sb, shiftLeft); + } + + + } + + class BraceHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.endsWith("{") && afterText.startsWith("}"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceBefore = getIndentAdvance(beforeText); + int advanceAfter = getIndentAdvance(afterText); + String text; + StringBuilder sb = new StringBuilder("\n").append( + TextUtils.createIndent(count + advanceBefore, tabSize, useTab())).append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = text.length() + 1; + return new NewlineHandleResult(sb, shiftLeft); + } + } + + class JavaDocStartHandler implements NewlineHandler { + + private boolean shouldCreateEnd = true; + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().startsWith("/**"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText); + String text = ""; + StringBuilder sb = new StringBuilder().append("\n") + .append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())) + .append(" * "); + if (shouldCreateEnd) { + sb.append("\n").append( + text = TextUtils.createIndent(count + advanceAfter, tabSize, + useTab())) + .append(" */"); + } + return new NewlineHandleResult(sb, text.length() + 4); + } + } + + class JavaDocHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().startsWith("*") && !beforeText.trim().startsWith("*/"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText); + StringBuilder sb = new StringBuilder().append("\n") + .append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())) + .append("* "); + return new NewlineHandleResult(sb, 0); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLexer.g4 b/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.g4 similarity index 100% rename from app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLexer.g4 rename to app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.g4 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLexer.java b/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.java similarity index 99% rename from app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLexer.java rename to app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.java index 3f489ba59..bd90d8258 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLexer.java +++ b/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.java @@ -1,5 +1,5 @@ // Generated from C:/Users/bounc/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/groovy\GroovyLexer.g4 by ANTLR 4.9.1 -package com.tyron.code.ui.editor.language.groovy; +package com.tyron.code.language.groovy; import java.util.ArrayDeque; import java.util.Arrays; @@ -10,11 +10,9 @@ import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.*; + import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class GroovyLexer extends Lexer { diff --git a/app/src/main/java/com/tyron/code/language/groovy/GroovyUtils.java b/app/src/main/java/com/tyron/code/language/groovy/GroovyUtils.java new file mode 100644 index 000000000..cc804df4d --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/GroovyUtils.java @@ -0,0 +1,34 @@ +package com.tyron.code.language.groovy; + +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.syntax.SyntaxException; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; + +public class GroovyUtils { + + public static Range toRange(SyntaxException exp) { + // LSP Range start from 0, while groovy classes start from 1 + return new Range(new Position(exp.getStartLine() - 1, exp.getStartColumn() - 1), + new Position(exp.getEndLine() - 1, exp.getEndColumn() - 1)); + } + + public static Range toRange(Expression expression) { + // LSP Range start from 0, while groovy expressions start from 1 + return new Range(new Position(expression.getLineNumber() - 1, expression.getColumnNumber() - 1), + new Position(expression.getLastLineNumber() - 1, expression.getLastColumnNumber() - 1)); + } + + public static Range toRange(Statement statement) { + // LSP Range start from 0, while groovy expressions start from 1 + return new Range(new Position(statement.getLineNumber() - 1, statement.getColumnNumber() - 1), + new Position(statement.getLastLineNumber() - 1, statement.getLastColumnNumber() - 1)); + } + + public static Range toDependencyRange(Expression expression) { + // For dependency, the string includes open/close quotes should be excluded + return new Range(new Position(expression.getLineNumber() - 1, expression.getColumnNumber()), + new Position(expression.getLastLineNumber() - 1, expression.getLastColumnNumber() - 2)); + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/Java.java b/app/src/main/java/com/tyron/code/language/java/Java.java new file mode 100644 index 000000000..eddc2a7ff --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/Java.java @@ -0,0 +1,27 @@ +package com.tyron.code.language.java; + +import com.tyron.code.language.Language; +import com.tyron.editor.Editor; + +import org.apache.commons.vfs2.FileObject; + +import java.io.File; + +public class Java implements Language { + + + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".java"); + } + + @Override + public boolean isApplicable(FileObject fileObject) { + return fileObject.getName().getExtension().equals("java"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new JavaLanguage(editor); + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/JavaAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/java/JavaAutoCompleteProvider.java new file mode 100644 index 000000000..fa0d3d384 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/JavaAutoCompleteProvider.java @@ -0,0 +1,62 @@ +package com.tyron.code.language.java; + +import android.content.SharedPreferences; + +import androidx.annotation.Nullable; + +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.JavaModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.language.AbstractAutoCompleteProvider; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.completion.main.CompletionEngine; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Content; +import com.tyron.editor.Editor; + +import java.util.Optional; + +public class JavaAutoCompleteProvider extends AbstractAutoCompleteProvider { + + private final Editor mEditor; + private final SharedPreferences mPreferences; + + public JavaAutoCompleteProvider(Editor editor) { + mEditor = editor; + mPreferences = ApplicationLoader.getDefaultPreferences(); + } + + + @Nullable + @Override + public CompletionList getCompletionList(String prefix, int line, int column) { + if (!mPreferences.getBoolean(SharedPreferenceKeys.JAVA_CODE_COMPLETION, true)) { + return null; + } + + Project project = ProjectManager.getInstance().getCurrentProject(); + + if (project == null) { + return null; + } + + Module currentModule = project.getModule(mEditor.getCurrentFile()); + + if (currentModule instanceof JavaModule) { + Content content = mEditor.getContent(); + return CompletionEngine.getInstance() + .complete(project, + currentModule, + mEditor, + mEditor.getCurrentFile(), + content.toString(), + prefix, + line, + column, + mEditor.getCaret().getStart()); + } + return null; + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/JavaLanguage.java b/app/src/main/java/com/tyron/code/language/java/JavaLanguage.java new file mode 100644 index 000000000..d53a47773 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/JavaLanguage.java @@ -0,0 +1,332 @@ +package com.tyron.code.language.java; + +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.sun.tools.javac.util.JCDiagnostic; +import com.tyron.builder.project.Project; +import com.tyron.code.language.CompletionItemWrapper; +import com.tyron.code.language.EditorFormatter; +import com.tyron.code.language.LanguageManager; +import com.tyron.completion.CompletionParameters; +import com.tyron.completion.java.JavaCompletionProvider; +import com.tyron.completion.java.compiler.services.NBLog; +import com.tyron.completion.java.parse.CompilationInfo; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Editor; +import com.tyron.language.api.CodeAssistLanguage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; + +import io.github.rosemoe.editor.langs.java.JavaTextTokenizer; +import io.github.rosemoe.editor.langs.java.Tokens; +import io.github.rosemoe.sora.lang.EmptyLanguage; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionHelper; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.diagnostic.DiagnosticRegion; +import io.github.rosemoe.sora.lang.format.AsyncFormatter; +import io.github.rosemoe.sora.lang.format.Formatter; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.Content; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.text.TextRange; +import io.github.rosemoe.sora.text.TextUtils; +import io.github.rosemoe.sora.util.MyCharacter; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class JavaLanguage implements Language, EditorFormatter, CodeAssistLanguage { + + private static final Logger LOGGER = LoggerFactory.getLogger(JavaLanguage.class); + + private static final String GRAMMAR_NAME = "java.tmLanguage.json"; + private static final String LANGUAGE_PATH = "textmate/java/syntaxes/java.tmLanguage.json"; + private static final String CONFIG_PATH = "textmate/java/language-configuration.json"; + + private final Editor editor; + private final TextMateLanguage delegate; + private final Formatter formatter = new AsyncFormatter() { + @Nullable + @Override + public TextRange formatAsync(@NonNull Content text, @NonNull TextRange cursorRange) { + String format = com.tyron.eclipse.formatter.Formatter.format(text.toString(), + cursorRange.getStartIndex(), + cursorRange.getEndIndex() - cursorRange.getStartIndex()); + if (!text.toString().equals(format)) { + text.delete(0, text.getLineCount() - 1); + text.insert(0, 0, format); + } + return cursorRange; + } + + @Nullable + @Override + public TextRange formatRegionAsync(@NonNull Content text, + @NonNull TextRange rangeToFormat, + @NonNull TextRange cursorRange) { + return null; + } + }; + + + public JavaLanguage(Editor editor) { + this.editor = editor; + delegate = LanguageManager.createTextMateLanguage(GRAMMAR_NAME, LANGUAGE_PATH, CONFIG_PATH, editor); + } + + + public boolean isAutoCompleteChar(char p1) { + return p1 == '.' || MyCharacter.isJavaIdentifierPart(p1); + } + + public int getIndentAdvance(String p1) { + JavaTextTokenizer tokenizer = new JavaTextTokenizer(p1); + Tokens token; + int advance = 0; + while ((token = tokenizer.directNextToken()) != Tokens.EOF) { + switch (token) { + case LBRACE: + advance++; + break; + } + } + return (advance * getTabWidth()); + } + + public int getFormatIndent(String line) { + JavaTextTokenizer tokenizer = new JavaTextTokenizer(line); + Tokens token; + int advance = 0; + while ((token = tokenizer.directNextToken()) != Tokens.EOF) { + switch (token) { + case LBRACE: + advance++; + break; + case RBRACE: + advance--; + } + } + return (advance * getTabWidth()); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return delegate.getAnalyzeManager(); + } + + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_SLIGHT; + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) throws CompletionCancelledException { + String prefix = CompletionHelper.computePrefix(content, position, MyCharacter::isJavaIdentifierPart); + CompletionParameters parameters = CompletionParameters.builder() + .setColumn(position.getColumn()) + .setLine(position.getLine()) + .setIndex(position.getIndex()) + .setEditor(editor) + .setFile(editor.getCurrentFile()) + .setProject(editor.getProject()) + .setModule(editor.getProject().getMainModule()) + .setContents(content.getReference().toString()) + .setPrefix(prefix) + .build(); + JavaCompletionProvider provider = new JavaCompletionProvider(); + CompletionList list = provider.complete(parameters); + + publisher.setUpdateThreshold(0); + publisher.addItems(list.getItems().stream().map(CompletionItemWrapper::new) + .collect(Collectors.toList())); + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + String text = content.getLine(line).substring(0, column); + return getIndentAdvance(text); + } + + @Override + public boolean useTab() { + return true; + } + + @NonNull + @Override + public Formatter getFormatter() { + return formatter; + } + + public int getTabWidth() { + return 4; + } + + public CharSequence format(CharSequence p1) { + return format(p1, 0, p1.length()); + } + + @NonNull + @Override + public CharSequence format(@NonNull CharSequence contents, int start, int end) { + return com.tyron.eclipse.formatter.Formatter.format(contents.toString(), start, + end - start); + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return delegate.getSymbolPairs(); + } + + private final NewlineHandler[] newLineHandlers = + new NewlineHandler[]{new BraceHandler(), new TwoIndentHandler(), + new JavaDocStartHandler(), new JavaDocHandler()}; + + @Override + public NewlineHandler[] getNewlineHandlers() { + return newLineHandlers; + } + + @Override + public void destroy() { + delegate.destroy(); + } + + @Override + public void onContentChange(File file, CharSequence content) { + Project project = editor.getProject(); + if (project == null) { + return; + } + CompilationInfo compilationInfo = CompilationInfo.get(project, editor.getCurrentFile()); + if (compilationInfo == null) { + return; + } + JavaFileObject fileObject = new SimpleJavaFileObject(editor.getCurrentFile().toURI(), + JavaFileObject.Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return content; + } + }; + try { + compilationInfo.update(fileObject); + } catch (Throwable t) { + LOGGER.error("Failed to update compilation unit", t); + } + } + + class TwoIndentHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + Log.d("BeforeText", beforeText); + if (beforeText.replace("\r", "").trim().startsWith(".")) { + return false; + } + return beforeText.endsWith(")") && !afterText.startsWith(";"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText) + (4 * 2); + String text; + StringBuilder sb = new StringBuilder().append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = 0; + return new NewlineHandleResult(sb, shiftLeft); + } + + + } + + class BraceHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.endsWith("{") && afterText.startsWith("}"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceBefore = getIndentAdvance(beforeText); + int advanceAfter = getIndentAdvance(afterText); + String text; + StringBuilder sb = new StringBuilder("\n").append( + TextUtils.createIndent(count + advanceBefore, tabSize, useTab())).append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = text.length() + 1; + return new NewlineHandleResult(sb, shiftLeft); + } + } + + class JavaDocStartHandler implements NewlineHandler { + + private boolean shouldCreateEnd = true; + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().startsWith("/**"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText); + String text = ""; + StringBuilder sb = new StringBuilder().append("\n") + .append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())) + .append(" * "); + if (shouldCreateEnd) { + sb.append("\n").append( + text = TextUtils.createIndent(count + advanceAfter, tabSize, + useTab())) + .append(" */"); + } + return new NewlineHandleResult(sb, text.length() + 4); + } + } + + class JavaDocHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().startsWith("*") && !beforeText.trim().startsWith("*/"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText); + StringBuilder sb = new StringBuilder().append("\n") + .append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())) + .append("* "); + return new NewlineHandleResult(sb, 0); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/JavaTokenTypes.java b/app/src/main/java/com/tyron/code/language/java/JavaTokenTypes.java new file mode 100644 index 000000000..20daea507 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/JavaTokenTypes.java @@ -0,0 +1,58 @@ +package com.tyron.code.language.java; + +import com.tyron.code.analyzer.semantic.TokenType; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; +import com.sun.tools.javac.code.Symbol; + +public class JavaTokenTypes { + + public static final TokenType FIELD = TokenType.create("variable.other.object.property.java"); + public static final TokenType CONSTANT = TokenType.create("variable.other.constant"); + public static final TokenType PARAMETER = TokenType.create("variable.parameter"); + public static final TokenType CLASS = TokenType.create("entity.name.type.class"); + public static final TokenType METHOD_CALL = TokenType.create("meta.method-call"); + public static final TokenType METHOD_DECLARATION = TokenType.create("entity.name.function.member"); + public static final TokenType VARIABLE = TokenType.create("entity.name.variable"); + public static final TokenType CONSTRUCTOR = TokenType.create("class.instance.constructor"); + public static final TokenType ANNOTATION = TokenType.create("storage.type.annotation"); + + public static TokenType getApplicableType(Element element) { + if (element == null) { + return null; + } + + switch (element.getKind()) { + case LOCAL_VARIABLE: + Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) element; + if (varSymbol.getModifiers().contains(Modifier.FINAL)) { + return CONSTANT; + } + return VARIABLE; + case METHOD: + Symbol.MethodSymbol methodSymbol = ((Symbol.MethodSymbol) element); + if (methodSymbol.isConstructor()) { + return getApplicableType(methodSymbol.getEnclosingElement()); + } + return METHOD_DECLARATION; + case FIELD: + VariableElement variableElement = ((VariableElement) element); + if (variableElement.getModifiers().contains(Modifier.FINAL)) { + return CONSTANT; + } + return FIELD; + case CLASS: + return CLASS; + case CONSTRUCTOR: + return CONSTRUCTOR; + case PARAMETER: + return PARAMETER; + case ANNOTATION_TYPE: + return ANNOTATION; + default: + return null; + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 b/app/src/main/java/com/tyron/code/language/json/JSON.g4 similarity index 86% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 rename to app/src/main/java/com/tyron/code/language/json/JSON.g4 index ae970bec3..2f11b8b3f 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 +++ b/app/src/main/java/com/tyron/code/language/json/JSON.g4 @@ -8,8 +8,8 @@ json ; obj - : LBRACKET pair (',' pair)* RBRACKET - | LBRACKET RBRACKET + : LBRACE pair (',' pair)* RBRACE + | LBRACE RBRACE ; pair @@ -17,8 +17,8 @@ pair ; arr - : '[' value (COMMA value)* ']' - | '[' ']' + : LBRACKET value (COMMA value)* RBRACKET + | LBRACKET RBRACKET ; value @@ -50,14 +50,23 @@ STRING : '"' (ESC | SAFECODEPOINT)* '"' ; -LBRACKET +LBRACE : '{' ; -RBRACKET +RBRACE : '}' ; + +LBRACKET + : '[' + ; + +RBRACKET + : ']' + ; + COMMA : ',' ; diff --git a/app/src/main/java/com/tyron/code/language/json/JSON.interp b/app/src/main/java/com/tyron/code/language/json/JSON.interp new file mode 100644 index 000000000..2b38429fe --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSON.interp @@ -0,0 +1,40 @@ +token literal names: +null +'true' +'false' +'null' +':' +null +'{' +'}' +'[' +']' +',' +null +null + +token symbolic names: +null +TRUE +FALSE +NULL +COLON +STRING +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +NUMBER +WS + +rule names: +json +obj +pair +arr +value + + +atn: +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 14, 58, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 19, 10, 3, 12, 3, 14, 3, 22, 11, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 28, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 7, 5, 38, 10, 5, 12, 5, 14, 5, 41, 11, 5, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 47, 10, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 56, 10, 6, 3, 6, 2, 2, 7, 2, 4, 6, 8, 10, 2, 2, 2, 62, 2, 12, 3, 2, 2, 2, 4, 27, 3, 2, 2, 2, 6, 29, 3, 2, 2, 2, 8, 46, 3, 2, 2, 2, 10, 55, 3, 2, 2, 2, 12, 13, 5, 10, 6, 2, 13, 3, 3, 2, 2, 2, 14, 15, 7, 8, 2, 2, 15, 20, 5, 6, 4, 2, 16, 17, 7, 12, 2, 2, 17, 19, 5, 6, 4, 2, 18, 16, 3, 2, 2, 2, 19, 22, 3, 2, 2, 2, 20, 18, 3, 2, 2, 2, 20, 21, 3, 2, 2, 2, 21, 23, 3, 2, 2, 2, 22, 20, 3, 2, 2, 2, 23, 24, 7, 9, 2, 2, 24, 28, 3, 2, 2, 2, 25, 26, 7, 8, 2, 2, 26, 28, 7, 9, 2, 2, 27, 14, 3, 2, 2, 2, 27, 25, 3, 2, 2, 2, 28, 5, 3, 2, 2, 2, 29, 30, 7, 7, 2, 2, 30, 31, 7, 6, 2, 2, 31, 32, 5, 10, 6, 2, 32, 7, 3, 2, 2, 2, 33, 34, 7, 10, 2, 2, 34, 39, 5, 10, 6, 2, 35, 36, 7, 12, 2, 2, 36, 38, 5, 10, 6, 2, 37, 35, 3, 2, 2, 2, 38, 41, 3, 2, 2, 2, 39, 37, 3, 2, 2, 2, 39, 40, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 39, 3, 2, 2, 2, 42, 43, 7, 11, 2, 2, 43, 47, 3, 2, 2, 2, 44, 45, 7, 10, 2, 2, 45, 47, 7, 11, 2, 2, 46, 33, 3, 2, 2, 2, 46, 44, 3, 2, 2, 2, 47, 9, 3, 2, 2, 2, 48, 56, 7, 7, 2, 2, 49, 56, 7, 13, 2, 2, 50, 56, 5, 4, 3, 2, 51, 56, 5, 8, 5, 2, 52, 56, 7, 3, 2, 2, 53, 56, 7, 4, 2, 2, 54, 56, 7, 5, 2, 2, 55, 48, 3, 2, 2, 2, 55, 49, 3, 2, 2, 2, 55, 50, 3, 2, 2, 2, 55, 51, 3, 2, 2, 2, 55, 52, 3, 2, 2, 2, 55, 53, 3, 2, 2, 2, 55, 54, 3, 2, 2, 2, 56, 11, 3, 2, 2, 2, 7, 20, 27, 39, 46, 55] \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSON.tokens b/app/src/main/java/com/tyron/code/language/json/JSON.tokens new file mode 100644 index 000000000..44a91db05 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSON.tokens @@ -0,0 +1,21 @@ +TRUE=1 +FALSE=2 +NULL=3 +COLON=4 +STRING=5 +LBRACE=6 +RBRACE=7 +LBRACKET=8 +RBRACKET=9 +COMMA=10 +NUMBER=11 +WS=12 +'true'=1 +'false'=2 +'null'=3 +':'=4 +'{'=6 +'}'=7 +'['=8 +']'=9 +','=10 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseListener.java b/app/src/main/java/com/tyron/code/language/json/JSONBaseListener.java similarity index 98% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseListener.java rename to app/src/main/java/com/tyron/code/language/json/JSONBaseListener.java index f1525f738..0f2740e35 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseListener.java +++ b/app/src/main/java/com/tyron/code/language/json/JSONBaseListener.java @@ -1,5 +1,5 @@ // Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ErrorNode; diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseVisitor.java b/app/src/main/java/com/tyron/code/language/json/JSONBaseVisitor.java similarity index 97% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseVisitor.java rename to app/src/main/java/com/tyron/code/language/json/JSONBaseVisitor.java index 82553f681..38842f071 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONBaseVisitor.java +++ b/app/src/main/java/com/tyron/code/language/json/JSONBaseVisitor.java @@ -1,5 +1,5 @@ // Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; /** diff --git a/app/src/main/java/com/tyron/code/language/json/JSONLexer.interp b/app/src/main/java/com/tyron/code/language/json/JSONLexer.interp new file mode 100644 index 000000000..d8aa9bf91 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONLexer.interp @@ -0,0 +1,59 @@ +token literal names: +null +'true' +'false' +'null' +':' +null +'{' +'}' +'[' +']' +',' +null +null + +token symbolic names: +null +TRUE +FALSE +NULL +COLON +STRING +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +NUMBER +WS + +rule names: +TRUE +FALSE +NULL +COLON +STRING +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +ESC +UNICODE +HEX +SAFECODEPOINT +NUMBER +INT +EXP +WS + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 14, 130, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 7, 6, 61, 10, 6, 12, 6, 14, 6, 64, 11, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 5, 12, 81, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 3, 16, 5, 16, 94, 10, 16, 3, 16, 3, 16, 3, 16, 6, 16, 99, 10, 16, 13, 16, 14, 16, 100, 5, 16, 103, 10, 16, 3, 16, 5, 16, 106, 10, 16, 3, 17, 3, 17, 3, 17, 7, 17, 111, 10, 17, 12, 17, 14, 17, 114, 11, 17, 5, 17, 116, 10, 17, 3, 18, 3, 18, 5, 18, 120, 10, 18, 3, 18, 3, 18, 3, 19, 6, 19, 125, 10, 19, 13, 19, 14, 19, 126, 3, 19, 3, 19, 2, 2, 20, 3, 3, 5, 4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 10, 19, 11, 21, 12, 23, 2, 25, 2, 27, 2, 29, 2, 31, 13, 33, 2, 35, 2, 37, 14, 3, 2, 10, 10, 2, 36, 36, 49, 49, 94, 94, 100, 100, 104, 104, 112, 112, 116, 116, 118, 118, 5, 2, 50, 59, 67, 72, 99, 104, 5, 2, 2, 33, 36, 36, 94, 94, 3, 2, 50, 59, 3, 2, 51, 59, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47, 5, 2, 11, 12, 15, 15, 34, 34, 2, 134, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 3, 39, 3, 2, 2, 2, 5, 44, 3, 2, 2, 2, 7, 50, 3, 2, 2, 2, 9, 55, 3, 2, 2, 2, 11, 57, 3, 2, 2, 2, 13, 67, 3, 2, 2, 2, 15, 69, 3, 2, 2, 2, 17, 71, 3, 2, 2, 2, 19, 73, 3, 2, 2, 2, 21, 75, 3, 2, 2, 2, 23, 77, 3, 2, 2, 2, 25, 82, 3, 2, 2, 2, 27, 88, 3, 2, 2, 2, 29, 90, 3, 2, 2, 2, 31, 93, 3, 2, 2, 2, 33, 115, 3, 2, 2, 2, 35, 117, 3, 2, 2, 2, 37, 124, 3, 2, 2, 2, 39, 40, 7, 118, 2, 2, 40, 41, 7, 116, 2, 2, 41, 42, 7, 119, 2, 2, 42, 43, 7, 103, 2, 2, 43, 4, 3, 2, 2, 2, 44, 45, 7, 104, 2, 2, 45, 46, 7, 99, 2, 2, 46, 47, 7, 110, 2, 2, 47, 48, 7, 117, 2, 2, 48, 49, 7, 103, 2, 2, 49, 6, 3, 2, 2, 2, 50, 51, 7, 112, 2, 2, 51, 52, 7, 119, 2, 2, 52, 53, 7, 110, 2, 2, 53, 54, 7, 110, 2, 2, 54, 8, 3, 2, 2, 2, 55, 56, 7, 60, 2, 2, 56, 10, 3, 2, 2, 2, 57, 62, 7, 36, 2, 2, 58, 61, 5, 23, 12, 2, 59, 61, 5, 29, 15, 2, 60, 58, 3, 2, 2, 2, 60, 59, 3, 2, 2, 2, 61, 64, 3, 2, 2, 2, 62, 60, 3, 2, 2, 2, 62, 63, 3, 2, 2, 2, 63, 65, 3, 2, 2, 2, 64, 62, 3, 2, 2, 2, 65, 66, 7, 36, 2, 2, 66, 12, 3, 2, 2, 2, 67, 68, 7, 125, 2, 2, 68, 14, 3, 2, 2, 2, 69, 70, 7, 127, 2, 2, 70, 16, 3, 2, 2, 2, 71, 72, 7, 93, 2, 2, 72, 18, 3, 2, 2, 2, 73, 74, 7, 95, 2, 2, 74, 20, 3, 2, 2, 2, 75, 76, 7, 46, 2, 2, 76, 22, 3, 2, 2, 2, 77, 80, 7, 94, 2, 2, 78, 81, 9, 2, 2, 2, 79, 81, 5, 25, 13, 2, 80, 78, 3, 2, 2, 2, 80, 79, 3, 2, 2, 2, 81, 24, 3, 2, 2, 2, 82, 83, 7, 119, 2, 2, 83, 84, 5, 27, 14, 2, 84, 85, 5, 27, 14, 2, 85, 86, 5, 27, 14, 2, 86, 87, 5, 27, 14, 2, 87, 26, 3, 2, 2, 2, 88, 89, 9, 3, 2, 2, 89, 28, 3, 2, 2, 2, 90, 91, 10, 4, 2, 2, 91, 30, 3, 2, 2, 2, 92, 94, 7, 47, 2, 2, 93, 92, 3, 2, 2, 2, 93, 94, 3, 2, 2, 2, 94, 95, 3, 2, 2, 2, 95, 102, 5, 33, 17, 2, 96, 98, 7, 48, 2, 2, 97, 99, 9, 5, 2, 2, 98, 97, 3, 2, 2, 2, 99, 100, 3, 2, 2, 2, 100, 98, 3, 2, 2, 2, 100, 101, 3, 2, 2, 2, 101, 103, 3, 2, 2, 2, 102, 96, 3, 2, 2, 2, 102, 103, 3, 2, 2, 2, 103, 105, 3, 2, 2, 2, 104, 106, 5, 35, 18, 2, 105, 104, 3, 2, 2, 2, 105, 106, 3, 2, 2, 2, 106, 32, 3, 2, 2, 2, 107, 116, 7, 50, 2, 2, 108, 112, 9, 6, 2, 2, 109, 111, 9, 5, 2, 2, 110, 109, 3, 2, 2, 2, 111, 114, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 116, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 115, 107, 3, 2, 2, 2, 115, 108, 3, 2, 2, 2, 116, 34, 3, 2, 2, 2, 117, 119, 9, 7, 2, 2, 118, 120, 9, 8, 2, 2, 119, 118, 3, 2, 2, 2, 119, 120, 3, 2, 2, 2, 120, 121, 3, 2, 2, 2, 121, 122, 5, 33, 17, 2, 122, 36, 3, 2, 2, 2, 123, 125, 9, 9, 2, 2, 124, 123, 3, 2, 2, 2, 125, 126, 3, 2, 2, 2, 126, 124, 3, 2, 2, 2, 126, 127, 3, 2, 2, 2, 127, 128, 3, 2, 2, 2, 128, 129, 8, 19, 2, 2, 129, 38, 3, 2, 2, 2, 14, 2, 60, 62, 80, 93, 100, 102, 105, 112, 115, 119, 126, 3, 8, 2, 2] \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSONLexer.java b/app/src/main/java/com/tyron/code/language/json/JSONLexer.java new file mode 100644 index 000000000..1bfa98663 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONLexer.java @@ -0,0 +1,150 @@ +// Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 +package com.tyron.code.language.json; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) +public class JSONLexer extends Lexer { + static { RuntimeMetaData.checkVersion("4.9.2", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + TRUE=1, FALSE=2, NULL=3, COLON=4, STRING=5, LBRACE=6, RBRACE=7, LBRACKET=8, + RBRACKET=9, COMMA=10, NUMBER=11, WS=12; + public static String[] channelNames = { + "DEFAULT_TOKEN_CHANNEL", "HIDDEN" + }; + + public static String[] modeNames = { + "DEFAULT_MODE" + }; + + private static String[] makeRuleNames() { + return new String[] { + "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACE", "RBRACE", "LBRACKET", + "RBRACKET", "COMMA", "ESC", "UNICODE", "HEX", "SAFECODEPOINT", "NUMBER", + "INT", "EXP", "WS" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, "'true'", "'false'", "'null'", "':'", null, "'{'", "'}'", "'['", + "']'", "','" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACE", "RBRACE", + "LBRACKET", "RBRACKET", "COMMA", "NUMBER", "WS" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + + public JSONLexer(CharStream input) { + super(input); + _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @Override + public String getGrammarFileName() { return "JSON.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public String[] getChannelNames() { return channelNames; } + + @Override + public String[] getModeNames() { return modeNames; } + + @Override + public ATN getATN() { return _ATN; } + + public static final String _serializedATN = + "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2\16\u0082\b\1\4\2"+ + "\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4"+ + "\13\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22"+ + "\t\22\4\23\t\23\3\2\3\2\3\2\3\2\3\2\3\3\3\3\3\3\3\3\3\3\3\3\3\4\3\4\3"+ + "\4\3\4\3\4\3\5\3\5\3\6\3\6\3\6\7\6=\n\6\f\6\16\6@\13\6\3\6\3\6\3\7\3\7"+ + "\3\b\3\b\3\t\3\t\3\n\3\n\3\13\3\13\3\f\3\f\3\f\5\fQ\n\f\3\r\3\r\3\r\3"+ + "\r\3\r\3\r\3\16\3\16\3\17\3\17\3\20\5\20^\n\20\3\20\3\20\3\20\6\20c\n"+ + "\20\r\20\16\20d\5\20g\n\20\3\20\5\20j\n\20\3\21\3\21\3\21\7\21o\n\21\f"+ + "\21\16\21r\13\21\5\21t\n\21\3\22\3\22\5\22x\n\22\3\22\3\22\3\23\6\23}"+ + "\n\23\r\23\16\23~\3\23\3\23\2\2\24\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n"+ + "\23\13\25\f\27\2\31\2\33\2\35\2\37\r!\2#\2%\16\3\2\n\n\2$$\61\61^^ddh"+ + "hppttvv\5\2\62;CHch\5\2\2!$$^^\3\2\62;\3\2\63;\4\2GGgg\4\2--//\5\2\13"+ + "\f\17\17\"\"\2\u0086\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2"+ + "\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3"+ + "\2\2\2\2\37\3\2\2\2\2%\3\2\2\2\3\'\3\2\2\2\5,\3\2\2\2\7\62\3\2\2\2\t\67"+ + "\3\2\2\2\139\3\2\2\2\rC\3\2\2\2\17E\3\2\2\2\21G\3\2\2\2\23I\3\2\2\2\25"+ + "K\3\2\2\2\27M\3\2\2\2\31R\3\2\2\2\33X\3\2\2\2\35Z\3\2\2\2\37]\3\2\2\2"+ + "!s\3\2\2\2#u\3\2\2\2%|\3\2\2\2\'(\7v\2\2()\7t\2\2)*\7w\2\2*+\7g\2\2+\4"+ + "\3\2\2\2,-\7h\2\2-.\7c\2\2./\7n\2\2/\60\7u\2\2\60\61\7g\2\2\61\6\3\2\2"+ + "\2\62\63\7p\2\2\63\64\7w\2\2\64\65\7n\2\2\65\66\7n\2\2\66\b\3\2\2\2\67"+ + "8\7<\2\28\n\3\2\2\29>\7$\2\2:=\5\27\f\2;=\5\35\17\2<:\3\2\2\2<;\3\2\2"+ + "\2=@\3\2\2\2><\3\2\2\2>?\3\2\2\2?A\3\2\2\2@>\3\2\2\2AB\7$\2\2B\f\3\2\2"+ + "\2CD\7}\2\2D\16\3\2\2\2EF\7\177\2\2F\20\3\2\2\2GH\7]\2\2H\22\3\2\2\2I"+ + "J\7_\2\2J\24\3\2\2\2KL\7.\2\2L\26\3\2\2\2MP\7^\2\2NQ\t\2\2\2OQ\5\31\r"+ + "\2PN\3\2\2\2PO\3\2\2\2Q\30\3\2\2\2RS\7w\2\2ST\5\33\16\2TU\5\33\16\2UV"+ + "\5\33\16\2VW\5\33\16\2W\32\3\2\2\2XY\t\3\2\2Y\34\3\2\2\2Z[\n\4\2\2[\36"+ + "\3\2\2\2\\^\7/\2\2]\\\3\2\2\2]^\3\2\2\2^_\3\2\2\2_f\5!\21\2`b\7\60\2\2"+ + "ac\t\5\2\2ba\3\2\2\2cd\3\2\2\2db\3\2\2\2de\3\2\2\2eg\3\2\2\2f`\3\2\2\2"+ + "fg\3\2\2\2gi\3\2\2\2hj\5#\22\2ih\3\2\2\2ij\3\2\2\2j \3\2\2\2kt\7\62\2"+ + "\2lp\t\6\2\2mo\t\5\2\2nm\3\2\2\2or\3\2\2\2pn\3\2\2\2pq\3\2\2\2qt\3\2\2"+ + "\2rp\3\2\2\2sk\3\2\2\2sl\3\2\2\2t\"\3\2\2\2uw\t\7\2\2vx\t\b\2\2wv\3\2"+ + "\2\2wx\3\2\2\2xy\3\2\2\2yz\5!\21\2z$\3\2\2\2{}\t\t\2\2|{\3\2\2\2}~\3\2"+ + "\2\2~|\3\2\2\2~\177\3\2\2\2\177\u0080\3\2\2\2\u0080\u0081\b\23\2\2\u0081"+ + "&\3\2\2\2\16\2<>P]dfipsw~\3\b\2\2"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSONLexer.tokens b/app/src/main/java/com/tyron/code/language/json/JSONLexer.tokens new file mode 100644 index 000000000..44a91db05 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONLexer.tokens @@ -0,0 +1,21 @@ +TRUE=1 +FALSE=2 +NULL=3 +COLON=4 +STRING=5 +LBRACE=6 +RBRACE=7 +LBRACKET=8 +RBRACKET=9 +COMMA=10 +NUMBER=11 +WS=12 +'true'=1 +'false'=2 +'null'=3 +':'=4 +'{'=6 +'}'=7 +'['=8 +']'=9 +','=10 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONListener.java b/app/src/main/java/com/tyron/code/language/json/JSONListener.java similarity index 97% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSONListener.java rename to app/src/main/java/com/tyron/code/language/json/JSONListener.java index 97e99d964..6336e40b7 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONListener.java +++ b/app/src/main/java/com/tyron/code/language/json/JSONListener.java @@ -1,5 +1,5 @@ // Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; import org.antlr.v4.runtime.tree.ParseTreeListener; /** diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONParser.java b/app/src/main/java/com/tyron/code/language/json/JSONParser.java similarity index 92% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSONParser.java rename to app/src/main/java/com/tyron/code/language/json/JSONParser.java index c76017e58..425748e39 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONParser.java +++ b/app/src/main/java/com/tyron/code/language/json/JSONParser.java @@ -1,13 +1,10 @@ // Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.misc.*; import org.antlr.v4.runtime.tree.*; import java.util.List; -import java.util.Iterator; -import java.util.ArrayList; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class JSONParser extends Parser { @@ -17,7 +14,7 @@ public class JSONParser extends Parser { protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache(); public static final int - T__0=1, T__1=2, TRUE=3, FALSE=4, NULL=5, COLON=6, STRING=7, LBRACKET=8, + TRUE=1, FALSE=2, NULL=3, COLON=4, STRING=5, LBRACE=6, RBRACE=7, LBRACKET=8, RBRACKET=9, COMMA=10, NUMBER=11, WS=12; public static final int RULE_json = 0, RULE_obj = 1, RULE_pair = 2, RULE_arr = 3, RULE_value = 4; @@ -30,15 +27,15 @@ private static String[] makeRuleNames() { private static String[] makeLiteralNames() { return new String[] { - null, "'['", "']'", "'true'", "'false'", "'null'", "':'", null, "'{'", - "'}'", "','" + null, "'true'", "'false'", "'null'", "':'", null, "'{'", "'}'", "'['", + "']'", "','" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { return new String[] { - null, null, null, "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACKET", - "RBRACKET", "COMMA", "NUMBER", "WS" + null, "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACE", "RBRACE", + "LBRACKET", "RBRACKET", "COMMA", "NUMBER", "WS" }; } private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); @@ -137,14 +134,14 @@ public final JsonContext json() throws RecognitionException { } public static class ObjContext extends ParserRuleContext { - public TerminalNode LBRACKET() { return getToken(JSONParser.LBRACKET, 0); } + public TerminalNode LBRACE() { return getToken(JSONParser.LBRACE, 0); } public List pair() { return getRuleContexts(PairContext.class); } public PairContext pair(int i) { return getRuleContext(PairContext.class,i); } - public TerminalNode RBRACKET() { return getToken(JSONParser.RBRACKET, 0); } + public TerminalNode RBRACE() { return getToken(JSONParser.RBRACE, 0); } public List COMMA() { return getTokens(JSONParser.COMMA); } public TerminalNode COMMA(int i) { return getToken(JSONParser.COMMA, i); @@ -180,7 +177,7 @@ public final ObjContext obj() throws RecognitionException { enterOuterAlt(_localctx, 1); { setState(12); - match(LBRACKET); + match(LBRACE); setState(13); pair(); setState(18); @@ -200,16 +197,16 @@ public final ObjContext obj() throws RecognitionException { _la = _input.LA(1); } setState(21); - match(RBRACKET); + match(RBRACE); } break; case 2: enterOuterAlt(_localctx, 2); { setState(23); - match(LBRACKET); + match(LBRACE); setState(24); - match(RBRACKET); + match(RBRACE); } break; } @@ -276,12 +273,14 @@ public final PairContext pair() throws RecognitionException { } public static class ArrContext extends ParserRuleContext { + public TerminalNode LBRACKET() { return getToken(JSONParser.LBRACKET, 0); } public List value() { return getRuleContexts(ValueContext.class); } public ValueContext value(int i) { return getRuleContext(ValueContext.class,i); } + public TerminalNode RBRACKET() { return getToken(JSONParser.RBRACKET, 0); } public List COMMA() { return getTokens(JSONParser.COMMA); } public TerminalNode COMMA(int i) { return getToken(JSONParser.COMMA, i); @@ -317,7 +316,7 @@ public final ArrContext arr() throws RecognitionException { enterOuterAlt(_localctx, 1); { setState(31); - match(T__0); + match(LBRACKET); setState(32); value(); setState(37); @@ -337,16 +336,16 @@ public final ArrContext arr() throws RecognitionException { _la = _input.LA(1); } setState(40); - match(T__1); + match(RBRACKET); } break; case 2: enterOuterAlt(_localctx, 2); { setState(42); - match(T__0); + match(LBRACKET); setState(43); - match(T__1); + match(RBRACKET); } break; } @@ -414,14 +413,14 @@ public final ValueContext value() throws RecognitionException { match(NUMBER); } break; - case LBRACKET: + case LBRACE: enterOuterAlt(_localctx, 3); { setState(48); obj(); } break; - case T__0: + case LBRACKET: enterOuterAlt(_localctx, 4); { setState(49); @@ -470,15 +469,15 @@ public final ValueContext value() throws RecognitionException { "\26\13\3\3\3\3\3\3\3\3\3\5\3\34\n\3\3\4\3\4\3\4\3\4\3\5\3\5\3\5\3\5\7"+ "\5&\n\5\f\5\16\5)\13\5\3\5\3\5\3\5\3\5\5\5/\n\5\3\6\3\6\3\6\3\6\3\6\3"+ "\6\3\6\5\68\n\6\3\6\2\2\7\2\4\6\b\n\2\2\2>\2\f\3\2\2\2\4\33\3\2\2\2\6"+ - "\35\3\2\2\2\b.\3\2\2\2\n\67\3\2\2\2\f\r\5\n\6\2\r\3\3\2\2\2\16\17\7\n"+ + "\35\3\2\2\2\b.\3\2\2\2\n\67\3\2\2\2\f\r\5\n\6\2\r\3\3\2\2\2\16\17\7\b"+ "\2\2\17\24\5\6\4\2\20\21\7\f\2\2\21\23\5\6\4\2\22\20\3\2\2\2\23\26\3\2"+ - "\2\2\24\22\3\2\2\2\24\25\3\2\2\2\25\27\3\2\2\2\26\24\3\2\2\2\27\30\7\13"+ - "\2\2\30\34\3\2\2\2\31\32\7\n\2\2\32\34\7\13\2\2\33\16\3\2\2\2\33\31\3"+ - "\2\2\2\34\5\3\2\2\2\35\36\7\t\2\2\36\37\7\b\2\2\37 \5\n\6\2 \7\3\2\2\2"+ - "!\"\7\3\2\2\"\'\5\n\6\2#$\7\f\2\2$&\5\n\6\2%#\3\2\2\2&)\3\2\2\2\'%\3\2"+ - "\2\2\'(\3\2\2\2(*\3\2\2\2)\'\3\2\2\2*+\7\4\2\2+/\3\2\2\2,-\7\3\2\2-/\7"+ - "\4\2\2.!\3\2\2\2.,\3\2\2\2/\t\3\2\2\2\608\7\t\2\2\618\7\r\2\2\628\5\4"+ - "\3\2\638\5\b\5\2\648\7\5\2\2\658\7\6\2\2\668\7\7\2\2\67\60\3\2\2\2\67"+ + "\2\2\24\22\3\2\2\2\24\25\3\2\2\2\25\27\3\2\2\2\26\24\3\2\2\2\27\30\7\t"+ + "\2\2\30\34\3\2\2\2\31\32\7\b\2\2\32\34\7\t\2\2\33\16\3\2\2\2\33\31\3\2"+ + "\2\2\34\5\3\2\2\2\35\36\7\7\2\2\36\37\7\6\2\2\37 \5\n\6\2 \7\3\2\2\2!"+ + "\"\7\n\2\2\"\'\5\n\6\2#$\7\f\2\2$&\5\n\6\2%#\3\2\2\2&)\3\2\2\2\'%\3\2"+ + "\2\2\'(\3\2\2\2(*\3\2\2\2)\'\3\2\2\2*+\7\13\2\2+/\3\2\2\2,-\7\n\2\2-/"+ + "\7\13\2\2.!\3\2\2\2.,\3\2\2\2/\t\3\2\2\2\608\7\7\2\2\618\7\r\2\2\628\5"+ + "\4\3\2\638\5\b\5\2\648\7\3\2\2\658\7\4\2\2\668\7\5\2\2\67\60\3\2\2\2\67"+ "\61\3\2\2\2\67\62\3\2\2\2\67\63\3\2\2\2\67\64\3\2\2\2\67\65\3\2\2\2\67"+ "\66\3\2\2\28\13\3\2\2\2\7\24\33\'.\67"; public static final ATN _ATN = diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONVisitor.java b/app/src/main/java/com/tyron/code/language/json/JSONVisitor.java similarity index 96% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JSONVisitor.java rename to app/src/main/java/com/tyron/code/language/json/JSONVisitor.java index e273f38d0..6cf9add5b 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONVisitor.java +++ b/app/src/main/java/com/tyron/code/language/json/JSONVisitor.java @@ -1,5 +1,5 @@ // Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; import org.antlr.v4.runtime.tree.ParseTreeVisitor; /** diff --git a/app/src/main/java/com/tyron/code/language/json/Json.java b/app/src/main/java/com/tyron/code/language/json/Json.java new file mode 100644 index 000000000..ada6c514a --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/Json.java @@ -0,0 +1,22 @@ +package com.tyron.code.language.json; + +import com.tyron.code.language.Language; +import com.tyron.code.language.LanguageManager; +import com.tyron.editor.Editor; + +import java.io.File; + +public class Json implements Language { + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".json"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return LanguageManager.createTextMateLanguage("json.tmLanguage.json", + "textmate/json" + "/syntaxes/json" + ".tmLanguage.json", + "textmate/json/language-configuration.json", editor); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JsonAnalyzer.java b/app/src/main/java/com/tyron/code/language/json/JsonAnalyzer.java similarity index 89% rename from app/src/main/java/com/tyron/code/ui/editor/language/json/JsonAnalyzer.java rename to app/src/main/java/com/tyron/code/language/json/JsonAnalyzer.java index fab008a25..a0aa49fa0 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JsonAnalyzer.java +++ b/app/src/main/java/com/tyron/code/language/json/JsonAnalyzer.java @@ -1,6 +1,6 @@ -package com.tyron.code.ui.editor.language.json; +package com.tyron.code.language.json; -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; +import com.tyron.code.language.AbstractCodeAnalyzer; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Lexer; @@ -11,9 +11,7 @@ import io.github.rosemoe.sora.lang.styling.CodeBlock; import io.github.rosemoe.sora.lang.styling.MappedSpans; import io.github.rosemoe.sora.lang.styling.Styles; -import io.github.rosemoe.sora2.data.BlockLine; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.widget.EditorColorScheme; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; public class JsonAnalyzer extends AbstractCodeAnalyzer { @@ -29,7 +27,7 @@ public Lexer getLexer(CharStream input) { @Override public void setup() { putColor(EditorColorScheme.TEXT_NORMAL, JSONLexer.LBRACKET, - JSONLexer.RBRACKET); + JSONLexer.RBRACKET, JSONLexer.LBRACE, JSONLexer.RBRACE); putColor(EditorColorScheme.KEYWORD, JSONLexer.TRUE, JSONLexer.FALSE, JSONLexer.NULL, JSONLexer.COLON, JSONLexer.COMMA); @@ -65,7 +63,7 @@ public boolean onNextToken(Token currentToken, Styles styles, MappedSpans.Builde } } break; - case JSONLexer.RBRACKET: + case JSONLexer.RBRACE: if (!mBlockLines.isEmpty()) { CodeBlock b = mBlockLines.pop(); b.endLine = line; @@ -75,7 +73,7 @@ public boolean onNextToken(Token currentToken, Styles styles, MappedSpans.Builde } } return false; - case JSONLexer.LBRACKET: + case JSONLexer.LBRACE: if (mBlockLines.isEmpty()) { if (mCurrSwitch > mMaxSwitch) { mMaxSwitch = mCurrSwitch; diff --git a/app/src/main/java/com/tyron/code/language/kotlin/Kotlin.java b/app/src/main/java/com/tyron/code/language/kotlin/Kotlin.java new file mode 100644 index 000000000..882cb8a39 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/Kotlin.java @@ -0,0 +1,19 @@ +package com.tyron.code.language.kotlin; + +import com.tyron.code.language.Language; +import com.tyron.editor.Editor; + +import java.io.File; + +public class Kotlin implements Language { + + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".kt"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new KotlinLanguage(editor); + } +} diff --git a/app/src/main/java/com/tyron/code/language/kotlin/KotlinAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/kotlin/KotlinAutoCompleteProvider.java new file mode 100644 index 000000000..b4d28abc2 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/KotlinAutoCompleteProvider.java @@ -0,0 +1,85 @@ +package com.tyron.code.language.kotlin; + +import android.content.SharedPreferences; + +import androidx.annotation.Nullable; + +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.language.AbstractAutoCompleteProvider; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.completion.java.provider.JavaSortCategory; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.CompletionList; +import com.tyron.completion.util.CompletionUtils; +import com.tyron.editor.Editor; +import com.tyron.kotlin.completion.KotlinEnvironment; +import com.tyron.kotlin.completion.KotlinFile; + +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; + +import java.util.List; + +public class KotlinAutoCompleteProvider extends AbstractAutoCompleteProvider { + + private static final String TAG = KotlinAutoCompleteProvider.class.getSimpleName(); + + private final Editor mEditor; + private final SharedPreferences mPreferences; + + private KotlinCoreEnvironment environment; + + public KotlinAutoCompleteProvider(Editor editor) { + mEditor = editor; + mPreferences = ApplicationLoader.getDefaultPreferences(); + } + + @Nullable + @Override + public CompletionList getCompletionList(String prefix, int line, int column) { + if (!mPreferences.getBoolean(SharedPreferenceKeys.KOTLIN_COMPLETIONS, false)) { + return null; + } + + if (com.tyron.completion.java.provider.CompletionEngine.isIndexing()) { + return null; + } + + if (!mPreferences.getBoolean(SharedPreferenceKeys.KOTLIN_COMPLETIONS, false)) { + return null; + } + + Project project = ProjectManager.getInstance().getCurrentProject(); + if (project == null) { + return null; + } + + Module currentModule = project.getModule(mEditor.getCurrentFile()); + + if (!(currentModule instanceof AndroidModule)) { + return null; + } + + KotlinEnvironment kotlinEnvironment = KotlinEnvironment.Companion.get(currentModule); + if (kotlinEnvironment == null) { + return null; + } + + KotlinFile updatedFile = + kotlinEnvironment.updateKotlinFile(mEditor.getCurrentFile().getAbsolutePath(), + mEditor.getContent().toString()); + List itemList = kotlinEnvironment.complete(updatedFile, + line, + column - 1); + + for (CompletionItem completionItem : itemList) { + completionItem.addFilterText(completionItem.commitText); + completionItem.setSortText(JavaSortCategory.DIRECT_MEMBER.toString()); + } + + return CompletionList.builder(prefix).addItems(itemList).build(); + } +} diff --git a/app/src/main/java/com/tyron/code/language/kotlin/KotlinLanguage.java b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLanguage.java new file mode 100644 index 000000000..6ba4ab5bc --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLanguage.java @@ -0,0 +1,123 @@ +package com.tyron.code.language.kotlin; + +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.builder.BuildModule; +import com.tyron.code.language.CompletionItemWrapper; +import com.tyron.code.language.LanguageManager; +import com.tyron.completion.DefaultInsertHandler; +import com.tyron.completion.java.provider.JavaSortCategory; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.CompletionList; +import com.tyron.completion.util.CompletionUtils; +import com.tyron.editor.Editor; +import com.tyron.kotlin.completion.KotlinEnvironment; +import com.tyron.kotlin.completion.KotlinFile; + +import java.io.File; +import java.util.List; +import java.util.Objects; + +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionHelper; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.format.Formatter; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class KotlinLanguage implements Language { + + private static final String GRAMMAR_NAME = "kotlin.tmLanguage"; + private static final String LANGUAGE_PATH = "textmate/kotlin/syntaxes/kotlin.tmLanguage"; + private static final String CONFIG_PATH = "textmate/kotlin/language-configuration.json"; + + private final TextMateLanguage delegate; + private final Editor editor; + + private KotlinEnvironment kotlinEnvironment; + + public KotlinLanguage(Editor editor) { + this.editor = editor; + delegate = LanguageManager.createTextMateLanguage(GRAMMAR_NAME, + LANGUAGE_PATH, + CONFIG_PATH, + editor); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return delegate.getAnalyzeManager(); + } + + @Override + public int getInterruptionLevel() { + return delegate.getInterruptionLevel(); + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) throws CompletionCancelledException { + String identifierPart = CompletionHelper.computePrefix(content, position, CompletionUtils.JAVA_PREDICATE::test); + KotlinAutoCompleteProvider provider = + new KotlinAutoCompleteProvider(editor); + CompletionList completionList = provider.getCompletionList(identifierPart, + position.getLine(), + position.getColumn()); + if (completionList == null) { + return; + } + completionList.getItems().stream().map(CompletionItemWrapper::new).forEach(publisher::addItem); + } + + private KotlinEnvironment getOrCreateKotlinEnvironment() { + if (kotlinEnvironment == null) { + kotlinEnvironment = + KotlinEnvironment.Companion.with(List.of(Objects.requireNonNull(BuildModule.getAndroidJar()), + BuildModule.getLambdaStubs())); + } + return kotlinEnvironment; + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + return delegate.getIndentAdvance(content, line, column); + } + + @Override + public boolean useTab() { + return delegate.useTab(); + } + + @NonNull + @Override + public Formatter getFormatter() { + return delegate.getFormatter(); + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return delegate.getSymbolPairs(); + } + + @Nullable + @Override + public NewlineHandler[] getNewlineHandlers() { + return new NewlineHandler[0]; + } + + @Override + public void destroy() { + delegate.destroy(); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLexer.g4 b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.g4 similarity index 100% rename from app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLexer.g4 rename to app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.g4 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLexer.java b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.java similarity index 99% rename from app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLexer.java rename to app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.java index baf89eff4..b5dfbab93 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLexer.java +++ b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.java @@ -1,9 +1,7 @@ // Generated from C:/Users/admin/StudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/kotlin\KotlinLexer.g4 by ANTLR 4.9.1 -package com.tyron.code.ui.editor.language.kotlin; +package com.tyron.code.language.kotlin; import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/UnicodeClasses.g4 b/app/src/main/java/com/tyron/code/language/kotlin/UnicodeClasses.g4 similarity index 100% rename from app/src/main/java/com/tyron/code/ui/editor/language/kotlin/UnicodeClasses.g4 rename to app/src/main/java/com/tyron/code/language/kotlin/UnicodeClasses.g4 diff --git a/app/src/main/java/com/tyron/code/language/textmate/BaseIncrementalAnalyzeManager.java b/app/src/main/java/com/tyron/code/language/textmate/BaseIncrementalAnalyzeManager.java new file mode 100644 index 000000000..995144871 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/textmate/BaseIncrementalAnalyzeManager.java @@ -0,0 +1,547 @@ +package com.tyron.code.language.textmate; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import io.github.rosemoe.sora.lang.analysis.IncrementalAnalyzeManager; +import io.github.rosemoe.sora.lang.analysis.StyleReceiver; +import io.github.rosemoe.sora.lang.styling.CodeBlock; +import io.github.rosemoe.sora.lang.styling.Span; +import io.github.rosemoe.sora.lang.styling.Spans; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.Content; +import io.github.rosemoe.sora.text.ContentLine; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.util.IntPair; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +public abstract class BaseIncrementalAnalyzeManager implements IncrementalAnalyzeManager { + + private StyleReceiver receiver; + private ContentReference ref; + private Bundle extraArguments; + private LooperThread thread; + private volatile long runCount; + private static int sThreadId = 0; + private final static int MSG_BASE = 11451400; + private final static int MSG_INIT = MSG_BASE + 1; + private final static int MSG_MOD = MSG_BASE + 2; + private final static int MSG_EXIT = MSG_BASE + 3; + + @Override + public void setReceiver(StyleReceiver receiver) { + this.receiver = receiver; + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + this.ref = content; + this.extraArguments = extraArguments; + rerun(); + } + + @Override + public void insert(CharPosition start, CharPosition end, CharSequence insertedText) { + if (thread != null) { + increaseRunCount(); + thread.handler.sendMessage(thread.handler.obtainMessage(MSG_MOD, new TextModification(IntPair.pack(start.line, start.column), IntPair.pack(end.line, end.column), insertedText))); + sendUpdate(thread.styles); + } + } + + @Override + public void delete(CharPosition start, CharPosition end, CharSequence deletedText) { + if (thread != null) { + increaseRunCount(); + thread.handler.sendMessage(thread.handler.obtainMessage(MSG_MOD, new TextModification(IntPair.pack(start.line, start.column), IntPair.pack(end.line, end.column), null))); + sendUpdate(thread.styles); + } + } + + @Override + public void rerun() { + if (thread != null) { + thread.callback = () -> { throw new CancelledException(); }; + if (thread.isAlive()) { + final Handler handler = thread.handler; + if (handler != null) { + handler.sendMessage(Message.obtain(thread.handler, MSG_EXIT)); + } + thread.abort = true; + } + } + final Content text = ref.getReference().copyText(false); + text.setUndoEnabled(false); + thread = new LooperThread(() -> thread.handler.sendMessage(thread.handler.obtainMessage(MSG_INIT, text))); + thread.setName("AsyncAnalyzer-" + nextThreadId()); + increaseRunCount(); + thread.start(); + sendUpdate(null); + } + + public abstract Result tokenizeLine(CharSequence line, S state); + + @Override + public Result getState(int line) { + final LooperThread thread = this.thread; + if (thread == Thread.currentThread()) { + return thread.states.get(line); + } + throw new SecurityException("Can not get state from non-analytical or abandoned thread"); + } + + private synchronized void increaseRunCount() { + runCount ++; + } + + private synchronized static int nextThreadId() { + sThreadId++; + return sThreadId; + } + + @Override + public void destroy() { + if (thread != null) { + thread.callback = () -> { throw new CancelledException(); }; + if (thread.isAlive()) { + thread.handler.sendMessage(Message.obtain(thread.handler, MSG_EXIT)); + thread.abort = true; + } + } + receiver = null; + ref = null; + extraArguments = null; + thread = null; + } + + private void sendUpdate(Styles styles) { + final StyleReceiver r = receiver; + if (r != null) { + r.setStyles(this, styles); + } + } + + /** + * Compute code blocks + * @param text The text. can be safely accessed. + */ + public abstract List computeBlocks(Content text, CodeBlockAnalyzeDelegate delegate); + + public Bundle getExtraArguments() { + return extraArguments; + } + + /** + * Helper class for analyzing code block + */ + public class CodeBlockAnalyzeDelegate { + + private final LooperThread + thread; + int suppressSwitch; + + CodeBlockAnalyzeDelegate(@NonNull LooperThread lp) { + thread = lp; + } + + public void setSuppressSwitch(int suppressSwitch) { + this.suppressSwitch = suppressSwitch; + } + + void reset() { + suppressSwitch = Integer.MAX_VALUE; + } + + public boolean isCancelled() { + return thread.myRunCount != runCount; + } + + public boolean isNotCancelled() { + return thread.myRunCount == runCount; + } + + } + + private class LooperThread extends Thread { + + volatile boolean abort; + Looper looper; + Handler handler; + Content shadowed; + long myRunCount; + + List> states = new ArrayList<>(); + Styles styles; + LockedSpans spans; + Runnable callback; + CodeBlockAnalyzeDelegate + delegate = new CodeBlockAnalyzeDelegate(this); + + public LooperThread(Runnable callback) { + this.callback = callback; + } + + private void tryUpdate() { + if (!abort) + sendUpdate(styles); + } + + private void initialize() { + styles = new Styles(spans = new LockedSpans()); + S state = getInitialState(); + Spans.Modifier mdf = spans.modify(); + for (int i = 0;i < shadowed.getLineCount();i++) { + ContentLine line = shadowed.getLine(i); + Result result = tokenizeLine(line, state); + state = result.state; + List spans = result.spans != null ? result. spans :generateSpansForLine(result); + states.add(result.clearSpans()); + mdf.addLineAt(i, spans); + } + styles.blocks = computeBlocks(shadowed, delegate); + styles.setSuppressSwitch(delegate.suppressSwitch); + tryUpdate(); + } + + @Override + public void run() { + Looper.prepare(); + looper = Looper.myLooper(); + handler = new Handler(looper) { + + @Override + public void handleMessage(@NonNull Message msg) { + super.handleMessage(msg); + try { + myRunCount = runCount; + delegate.reset(); + switch (msg.what) { + case MSG_INIT: + shadowed = (Content) msg.obj; + if (!abort) { + initialize(); + } + break; + case MSG_MOD: + if (!abort) { + TextModification mod = (TextModification) msg.obj; + int startLine = IntPair.getFirst(mod.start); + int endLine = IntPair.getFirst(mod.end); + if (mod.changedText == null) { + shadowed.delete(IntPair.getFirst(mod.start), IntPair.getSecond(mod.start), + IntPair.getFirst(mod.end), IntPair.getSecond(mod.end)); + S state = startLine == 0 ? getInitialState() : states.get(startLine - 1).state; + // Remove states + if (endLine >= startLine + 1) { + states.subList(startLine + 1, endLine + 1).clear(); + } + Spans.Modifier mdf = spans.modify(); + for (int i = startLine + 1;i <= endLine;i++) { + mdf.deleteLineAt(startLine + 1); + } + int line = startLine; + while (line < shadowed.getLineCount()){ + Result res = tokenizeLine(shadowed.getLine(line), state); + mdf.setSpansOnLine(line, res.spans != null ? res.spans : generateSpansForLine(res)); + Result old = states.set(line, res.clearSpans()); + if (stateEquals(old.state, res.state)) { + break; + } + state = res.state; + line ++; + } + } else { + shadowed.insert(IntPair.getFirst(mod.start), IntPair.getSecond(mod.start), mod.changedText); + S state = startLine == 0 ? getInitialState() : states.get(startLine - 1).state; + int line = startLine; + Spans.Modifier spans = styles.spans.modify(); + // Add Lines + while (line <= endLine) { + Result res = tokenizeLine(shadowed.getLine(line), state); + if (line == startLine) { + spans.setSpansOnLine(line, res.spans != null ? res.spans : generateSpansForLine(res)); + states.set(line, res.clearSpans()); + } else { + spans.addLineAt(line, res.spans != null ? res.spans : generateSpansForLine(res)); + states.add(line, res.clearSpans()); + } + state = res.state; + line++; + } + // line = end.line + 1, check whether the state equals + while (line < shadowed.getLineCount()) { + Result res = tokenizeLine(shadowed.getLine(line), state); + if (stateEquals(res.state, states.get(line).state)) { + break; + } else { + spans.setSpansOnLine(line, res.spans != null ? res.spans : generateSpansForLine(res)); + states.set(line, res.clearSpans()); + } + line ++; + } + } + } + styles.blocks = computeBlocks(shadowed, delegate); + styles.setSuppressSwitch(delegate.suppressSwitch); + tryUpdate(); + break; + case MSG_EXIT: + looper.quit(); + break; + } + } catch (Exception e) { + Log.w("AsyncAnalysis", "Thread " + Thread.currentThread().getName() + " failed", e); + } + } + + }; + + try { + callback.run(); + Looper.loop(); + } catch (CancelledException e) { + //ignored + } + } + } + + private static class LockedSpans implements Spans { + + private final Lock lock; + private final List lines; + + public LockedSpans() { + lines = new ArrayList<>(128); + lock = new ReentrantLock(); + } + + @Override + public void adjustOnDelete(CharPosition start, CharPosition end) { + + } + + @Override + public void adjustOnInsert(CharPosition start, CharPosition end) { + + } + + @Override + public int getLineCount() { + return lines.size(); + } + + @Override + public Reader read() { + return new LockedSpans.ReaderImpl(); + } + + @Override + public Modifier modify() { + return new LockedSpans.ModifierImpl(); + } + + @Override + public boolean supportsModify() { + return true; + } + + private static class Line { + + public Lock lock = new ReentrantLock(); + + public List spans; + + public Line() { + this(null); + } + + public Line(List s) { + spans = s; + } + + } + + private class ReaderImpl implements Spans.Reader { + + private LockedSpans.Line + line; + + public void moveToLine(int line) { + if (line < 0) { + if (this.line != null) { + this.line.lock.unlock(); + } + this.line = null; + } else if (line >= lines.size()) { + if (this.line != null) { + this.line.lock.unlock(); + } + this.line = null; + } else { + if (this.line != null) { + this.line.lock.unlock(); + } + boolean locked = false; + try { + locked = lock.tryLock(1, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (locked) { + try { + Line obj = lines.get(line); + if (obj.lock.tryLock()) { + this.line = obj; + } else { + this.line = null; + } + } finally { + lock.unlock(); + } + } else { + this.line = null; + } + } + } + + @Override + public int getSpanCount() { + return line == null ? 1 : line.spans.size(); + } + + @Override + public Span getSpanAt(int index) { + return line == null ? Span.obtain(0, EditorColorScheme.TEXT_NORMAL) : line.spans.get(index); + } + + @Override + public List getSpansOnLine(int line) { + ArrayList spans = new ArrayList<>(); + boolean locked = false; + try { + locked = lock.tryLock(1, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (locked) { + LockedSpans.Line + obj = null; + try { + if (line < lines.size()) { + obj = lines.get(line); + } + } finally { + lock.unlock(); + } + if (obj != null && obj.lock.tryLock()) { + try { + return Collections.unmodifiableList(obj.spans); + } finally { + obj.lock.unlock(); + } + } else { + spans.add(getSpanAt(0)); + } + } else { + spans.add(getSpanAt(0)); + } + return spans; + } + } + + private class ModifierImpl implements Modifier { + + @Override + public void setSpansOnLine(int line, List spans) { + lock.lock(); + try { + while (lines.size() <= line) { + ArrayList list = new ArrayList<>(); + list.add(Span.obtain(0, EditorColorScheme.TEXT_NORMAL)); + lines.add(new LockedSpans.Line(list)); + } + lines.get(line).spans = spans; + } finally { + lock.unlock(); + } + } + + @Override + public void addLineAt(int line, List spans) { + lock.lock(); + try { + lines.add(line, new LockedSpans.Line(spans)); + } finally { + lock.unlock(); + } + } + + @Override + public void deleteLineAt(int line) { + lock.lock(); + try { + Line obj = lines.get(line); + obj.lock.lock(); + try { + lines.remove(line); + } finally { + obj.lock.unlock(); + } + } finally { + lock.unlock(); + } + } + } + + } + + private static class TextModification { + + private final long start; + private final long end; + /** + * null for deletion + */ + private final CharSequence changedText; + + TextModification(long start, long end, CharSequence text) { + this.start = start; + this.end = end; + changedText = text; + } + } + + public static class Result extends LineTokenizeResult { + + public Result(@NonNull S_ state, @Nullable List tokens) { + super(state, tokens); + } + + public Result(@NonNull S_ state, @Nullable List tokens, @Nullable List spans) { + super(state, tokens, spans); + } + + @Override + public Result clearSpans() { + super.clearSpans(); + return this; + } + } + + private static class CancelledException extends RuntimeException {} +} + diff --git a/app/src/main/java/com/tyron/code/language/textmate/CodeBlockUtils.java b/app/src/main/java/com/tyron/code/language/textmate/CodeBlockUtils.java new file mode 100644 index 000000000..2fae12175 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/textmate/CodeBlockUtils.java @@ -0,0 +1,98 @@ +package com.tyron.code.language.textmate; + +import org.eclipse.tm4e.core.internal.oniguruma.OnigRegExp; +import org.eclipse.tm4e.core.internal.oniguruma.OnigResult; +import org.eclipse.tm4e.core.internal.oniguruma.OnigString; +import org.eclipse.tm4e.languageconfiguration.internal.supports.Folding; + +import java.util.ArrayList; +import java.util.List; + +import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; +import io.github.rosemoe.sora.langs.textmate.folding.FoldingRegions; +import io.github.rosemoe.sora.langs.textmate.folding.IndentRange; +import io.github.rosemoe.sora.langs.textmate.folding.PreviousRegion; +import io.github.rosemoe.sora.langs.textmate.folding.RangesCollector; +import io.github.rosemoe.sora.text.Content; + +public class CodeBlockUtils { + + @SuppressWarnings("rawtype") + public static FoldingRegions computeRanges(Content model, int tabSize , boolean offSide, Folding markers, int foldingRangesLimit, BaseIncrementalAnalyzeManager.CodeBlockAnalyzeDelegate delegate) throws Exception { + + RangesCollector result = new RangesCollector(); + + OnigRegExp pattern = null; + if (markers != null) { + pattern = new OnigRegExp("(" + markers.getMarkersStart() + ")|(?:" + markers.getMarkersEnd() + ")"); + } + + List previousRegions = new ArrayList<>(); + int line = model.getLineCount() + 1; + // sentinel, to make sure there's at least one entry + previousRegions.add(new PreviousRegion(-1, line, line)); + + for (line = model.getLineCount() - 1; line >= 0 && !delegate.isCancelled(); line--) { + String lineContent = model.getLineString(line); + int indent = IndentRange + .computeIndentLevel(model.getLine(line).getRawData(), model.getColumnCount(line), tabSize); + PreviousRegion previous = previousRegions.get(previousRegions.size() - 1); + if (indent == -1) { + if (offSide) { + // for offSide languages, empty lines are associated to the previous block + // note: the next block is already written to the results, so this only + // impacts the end position of the block before + previous.endAbove = line; + } + continue; // only whitespace + } + OnigResult m; + if (pattern != null && (m = pattern.search(new OnigString(lineContent), 0)) != null) { + // folding pattern match + if (m.count() >= 2) { // start pattern match + // discard all regions until the folding pattern + int i = previousRegions.size() - 1; + while (i > 0 && previousRegions.get(i).indent != -2) { + i--; + } + if (i > 0) { + //??? previousRegions.length = i + 1; + previous = previousRegions.get(i); + + // new folding range from pattern, includes the end line + result.insertFirst(line, previous.line, indent); + previous.line = line; + previous.indent = indent; + previous.endAbove = line; + continue; + } else { + // no end marker found, treat line as a regular line + } + } else { // end pattern match + previousRegions.add(new PreviousRegion(-2, line, line)); + continue; + } + } + if (previous.indent > indent) { + // discard all regions with larger indent + do { + previousRegions.remove(previousRegions.size() - 1); + previous = previousRegions.get(previousRegions.size() - 1); + } while (previous.indent > indent); + + // new folding range + int endLineNumber = previous.endAbove - 1; + if (endLineNumber - line >= 1) { // needs at east size 1 + result.insertFirst(line, endLineNumber, indent); + } + } + if (previous.indent == indent) { + previous.endAbove = line; + } else { // previous.indent < indent + // new region with a bigger indent + previousRegions.add(new PreviousRegion(indent, line, line)); + } + } + return result.toIndentRanges(model); + } +} diff --git a/app/src/main/java/com/tyron/code/language/textmate/EmptyTextMateLanguage.java b/app/src/main/java/com/tyron/code/language/textmate/EmptyTextMateLanguage.java new file mode 100644 index 000000000..3cb232831 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/textmate/EmptyTextMateLanguage.java @@ -0,0 +1,7 @@ +package com.tyron.code.language.textmate; + +import io.github.rosemoe.sora.lang.EmptyLanguage; + +public class EmptyTextMateLanguage extends EmptyLanguage { + +} diff --git a/app/src/main/java/com/tyron/code/language/xml/LanguageXML.java b/app/src/main/java/com/tyron/code/language/xml/LanguageXML.java new file mode 100644 index 000000000..9db132700 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/xml/LanguageXML.java @@ -0,0 +1,276 @@ +package com.tyron.code.language.xml; + +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.tyron.builder.compiler.manifest.xml.XmlFormatPreferences; +import com.tyron.builder.compiler.manifest.xml.XmlFormatStyle; +import com.tyron.builder.compiler.manifest.xml.XmlPrettyPrinter; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.event.EventManager; +import com.tyron.code.language.CompletionItemWrapper; +import com.tyron.code.language.LanguageManager; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.util.ProjectUtils; +import com.tyron.completion.CompletionParameters; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.CompletionList; +import com.tyron.completion.xml.lexer.XMLLexer; +import com.tyron.completion.xml.task.InjectResourcesTask; +import com.tyron.completion.xml.v2.AndroidXmlCompletionProvider; +import com.tyron.completion.xml.v2.events.XmlResourceChangeEvent; +import com.tyron.editor.Editor; +import com.tyron.language.api.CodeAssistLanguage; +import com.tyron.viewbinding.task.InjectViewBindingTask; + +import java.io.File; +import java.util.List; + +import io.github.rosemoe.sora.lang.EmptyLanguage; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionHelper; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.format.Formatter; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.langs.textmate.TextMateLanguage; +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.util.MyCharacter; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class LanguageXML implements Language, CodeAssistLanguage { + + private final Editor mEditor; + private final TextMateLanguage delegate; + + + public LanguageXML(Editor editor) { + mEditor = editor; + + delegate = LanguageManager.createTextMateLanguage("xml.tmLanguage.json", + "textmate/xml/syntaxes/xml.tmLanguage.json", + "textmate/java/language-configuration.json", editor); + } + + public boolean isAutoCompleteChar(char ch) { + return MyCharacter.isJavaIdentifierPart(ch) || + ch == '<' || + ch == '/' || + ch == ':' || + ch == '.'; + } + + @Override + public boolean useTab() { + return true; + } + + @NonNull + @Override + public Formatter getFormatter() { + return EmptyLanguage.EmptyFormatter.INSTANCE; + } + + public CharSequence format(CharSequence text) { + XmlFormatPreferences preferences = XmlFormatPreferences.defaults(); + File file = mEditor.getCurrentFile(); + CharSequence formatted = null; + if ("AndroidManifest.xml".equals(file.getName())) { + formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), preferences, + XmlFormatStyle.MANIFEST, "\n"); + } else { + if (ProjectUtils.isLayoutXMLFile(file)) { + formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), preferences, + XmlFormatStyle.LAYOUT, "\n"); + } else if (ProjectUtils.isResourceXMLFile(file)) { + formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), preferences, + XmlFormatStyle.RESOURCE, "\n"); + } + } + if (formatted == null) { + formatted = text; + } + return formatted; + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return delegate.getSymbolPairs(); + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return new NewlineHandler[]{new StartTagHandler(), new EndTagHandler(), + new EndTagAttributeHandler()}; + } + + @Override + public void destroy() { + delegate.destroy(); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return delegate.getAnalyzeManager(); + } + + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_SLIGHT; + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) throws CompletionCancelledException { + if (mEditor.getProject() == null) { + return; + } + Module module = mEditor.getProject().getModule(mEditor.getCurrentFile()); + if (!(module instanceof AndroidModule)) { + return; + } + String prefix = CompletionHelper.computePrefix(content, position, this::isAutoCompleteChar); + CompletionParameters parameters = CompletionParameters.builder() + .setPrefix(prefix) + .setModule(module) + .setProject(mEditor.getProject()) + .setFile(mEditor.getCurrentFile()) + .setIndex(position.getIndex()) + .setLine(position.getLine()) + .setColumn(position.getColumn()) + .setContents(content.getReference().toString()) + .build(); + CompletionList items = + new AndroidXmlCompletionProvider().complete(parameters); + if (items == null) { + return; + } + for (CompletionItem item : items.getItems()) { + publisher.addItem(new CompletionItemWrapper(item)); + } + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + String text = content.getLine(line).substring(0, column); + return getIndentAdvance(text); + } + + public int getIndentAdvance(String content) { + return getIndentAdvance(content, XMLLexer.DEFAULT_MODE, true); + } + + public int getIndentAdvance(String content, int mode, boolean ignore) { + return 0; +// XMLLexer lexer = new XMLLexer(CharStreams.fromString(content)); +// lexer.pushMode(mode); +// +// int advance = 0; +// while (lexer.nextToken() +// .getType() != Lexer.EOF) { +// switch (lexer.getToken() +// .getType()) { +// case XMLLexer.OPEN: +// advance++; +// break; +// case XMLLexer.CLOSE: +// case XMLLexer.SLASH_CLOSE: +// advance--; +// break; +// } +// } +// +// if (advance == 0 && mode != XMLLexer.INSIDE) { +// return getIndentAdvance(content, XMLLexer.INSIDE, ignore); +// } +// +// return advance * mEditor.getTabCount(); + } + + public int getFormatIndent(String line) { + return getIndentAdvance(line, XMLLexer.DEFAULT_MODE, false); + } + + @Override + public void onContentChange(File file, CharSequence contents) { + if (mEditor.getProject() == null) { + return; + } + EventManager eventManager = mEditor.getProject().getEventManager(); + eventManager.dispatchEvent(new XmlResourceChangeEvent(mEditor.getCurrentFile(), mEditor.getContent())); + } + + private class EndTagHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + String trim = beforeText.trim(); + if (!trim.startsWith("<")) { + return false; + } + if (!trim.endsWith(">")) { + return false; + } + return afterText.trim().startsWith(""); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + String middle; + StringBuilder sb = new StringBuilder(); + sb.append('\n'); + sb.append(TextUtils.createIndent(count + tabSize, tabSize, useTab())); + sb.append('\n'); + sb.append(middle = TextUtils.createIndent(count, tabSize, useTab())); + return new NewlineHandleResult(sb, middle.length() + 1); + } + } + + private class EndTagAttributeHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().endsWith(">") && afterText.trim().startsWith(""); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + String middle; + StringBuilder sb = new StringBuilder(); + sb.append('\n'); + sb.append(TextUtils.createIndent(count, tabSize, useTab())); + sb.append('\n'); + sb.append(middle = TextUtils.createIndent(count - tabSize, tabSize, useTab())); + return new NewlineHandleResult(sb, middle.length() + 1); + } + } + + private class StartTagHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + String trim = beforeText.trim(); + return trim.startsWith("<") && !trim.endsWith(">"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + String text; + StringBuilder sb = new StringBuilder().append("\n") + .append(TextUtils.createIndent(count + tabSize, tabSize, useTab())); + return new NewlineHandleResult(sb, 0); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/xml/XMLAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/xml/XMLAutoCompleteProvider.java new file mode 100644 index 000000000..04716cf40 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/xml/XMLAutoCompleteProvider.java @@ -0,0 +1,41 @@ +package com.tyron.code.language.xml; + +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.language.AbstractAutoCompleteProvider; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.completion.main.CompletionEngine; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Editor; + +import java.io.File; + +public class XMLAutoCompleteProvider extends AbstractAutoCompleteProvider { + + private final Editor mEditor; + + public XMLAutoCompleteProvider(Editor editor) { + mEditor = editor; + } + + @Override + public CompletionList getCompletionList(String prefix, int line, int column) { + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + if (currentProject == null) { + return null; + } + Module module = currentProject.getModule(mEditor.getCurrentFile()); + if (!(module instanceof AndroidModule)) { + return null; + } + + File currentFile = mEditor.getCurrentFile(); + if (currentFile == null) { + return null; + } + return CompletionEngine.getInstance().complete(currentProject, module, mEditor, + currentFile, mEditor.getContent().toString(), prefix, line, column, + mEditor.getCaret().getStart()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLColorScheme.java b/app/src/main/java/com/tyron/code/language/xml/XMLColorScheme.java similarity index 77% rename from app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLColorScheme.java rename to app/src/main/java/com/tyron/code/language/xml/XMLColorScheme.java index 082c82d18..d62a6d3c7 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLColorScheme.java +++ b/app/src/main/java/com/tyron/code/language/xml/XMLColorScheme.java @@ -1,6 +1,6 @@ -package com.tyron.code.ui.editor.language.xml; +package com.tyron.code.language.xml; -import io.github.rosemoe.sora2.widget.EditorColorScheme; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; public class XMLColorScheme extends EditorColorScheme { diff --git a/app/src/main/java/com/tyron/code/language/xml/Xml.java b/app/src/main/java/com/tyron/code/language/xml/Xml.java new file mode 100644 index 000000000..9d2a35a0f --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/xml/Xml.java @@ -0,0 +1,28 @@ +package com.tyron.code.language.xml; + +import com.tyron.code.language.Language; +import com.tyron.code.language.LanguageManager; +import com.tyron.editor.Editor; + +import org.apache.commons.vfs2.FileObject; + +import java.io.File; + + +public class Xml implements Language { + + @Override + public boolean isApplicable(File file) { + return file.getName().endsWith(".xml"); + } + + @Override + public boolean isApplicable(FileObject fileObject) { + return fileObject.getName() .getExtension().equals("xml"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new LanguageXML(editor); + } +} diff --git a/app/src/main/java/com/tyron/code/lint/DefaultLintClient.java b/app/src/main/java/com/tyron/code/lint/DefaultLintClient.java deleted file mode 100644 index 384295dee..000000000 --- a/app/src/main/java/com/tyron/code/lint/DefaultLintClient.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.tyron.code.lint; - -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.tyron.builder.project.api.JavaModule; -import com.tyron.completion.index.CompilerService; -import com.tyron.completion.java.JavaCompilerProvider; -import com.tyron.completion.java.compiler.JavaCompilerService; -import com.tyron.lint.api.Context; -import com.tyron.lint.api.Issue; -import com.tyron.lint.api.Lint; -import com.tyron.lint.api.Location; -import com.tyron.lint.api.Severity; -import com.tyron.lint.api.TextFormat; -import com.tyron.lint.client.LintClient; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -public class DefaultLintClient extends LintClient { - - private final List mIssues = new ArrayList<>(); - private final Lint mLint; - private final JavaCompilerService mCompiler; - - public DefaultLintClient(JavaModule project) { - mCompiler = CompilerService.getInstance() - .getIndex(JavaCompilerProvider.KEY); - mLint = new Lint(mCompiler, project, this); - } - - public void scan(File file) { - mIssues.clear(); - mLint.scanFile(file); - } - - @Override - public void report(@NonNull Context context, @NonNull Issue issue, @NonNull Severity severity, @Nullable Location location, @NonNull String message, @NonNull TextFormat format) { - if (location != null) { - Log.d("default lint client", "adding issue: " + issue.getId()); - mIssues.add(new LintIssue(issue, severity, location)); - } - } - - public List getReportedIssues() { - return mIssues; - } -} diff --git a/app/src/main/java/com/tyron/code/lint/LintIssue.java b/app/src/main/java/com/tyron/code/lint/LintIssue.java deleted file mode 100644 index 492bcc197..000000000 --- a/app/src/main/java/com/tyron/code/lint/LintIssue.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.tyron.code.lint; - -import com.tyron.lint.api.Issue; -import com.tyron.lint.api.Location; -import com.tyron.lint.api.Severity; - -public class LintIssue { - - private final Issue mIssue; - - private final Severity mSeverity; - - private final Location mLocation; - - public LintIssue(Issue mIssue, Severity mSeverity, Location mLocation) { - this.mIssue = mIssue; - this.mSeverity = mSeverity; - this.mLocation = mLocation; - } - - public Issue getIssue() { - return mIssue; - } - - public Severity getSeverity() { - return mSeverity; - } - - public Location getLocation() { - return mLocation; - } -} diff --git a/app/src/main/java/com/tyron/code/service/CompilerService.java b/app/src/main/java/com/tyron/code/service/CompilerService.java deleted file mode 100644 index 7ba559d05..000000000 --- a/app/src/main/java/com/tyron/code/service/CompilerService.java +++ /dev/null @@ -1,279 +0,0 @@ -package com.tyron.code.service; - -import android.app.Notification; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.core.app.NotificationChannelCompat; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; - -import com.tyron.builder.compiler.AndroidAppBuilder; -import com.tyron.builder.compiler.AndroidAppBundleBuilder; -import com.tyron.builder.compiler.ApkBuilder; -import com.tyron.builder.compiler.BuildType; -import com.tyron.builder.compiler.Builder; -import com.tyron.builder.compiler.ProjectBuilder; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.AndroidModule; -import com.tyron.builder.project.api.Module; -import com.tyron.code.BuildConfig; -import com.tyron.code.R; -import com.tyron.code.util.ApkInstaller; -import com.tyron.completion.progress.ProgressIndicator; -import com.tyron.completion.progress.ProgressManager; - -import java.io.File; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.concurrent.Executors; - -public class CompilerService extends Service { - - private final Handler mMainHandler = new Handler(Looper.getMainLooper()); - private final CompilerBinder mBinder = new CompilerBinder(this); - - public static class CompilerBinder extends Binder { - - private final WeakReference mServiceReference; - - public CompilerBinder(CompilerService service) { - mServiceReference = new WeakReference<>(service); - } - - public CompilerService getCompilerService() { - return mServiceReference.get(); - } - } - - private Project mProject; - private ApkBuilder.OnResultListener onResultListener; - private ILogger external; - /** - * Logger that delegates logs to the external logger set - */ - private final ILogger logger = new ILogger() { - - @Override - public void info(DiagnosticWrapper wrapper) { - if (external != null) { - external.info(wrapper); - } - } - - @Override - public void debug(DiagnosticWrapper wrapper) { - if (external != null) { - external.debug(wrapper); - } - } - - @Override - public void warning(DiagnosticWrapper wrapper) { - if (external != null) { - external.warning(wrapper); - } - } - - @Override - public void error(DiagnosticWrapper wrapper) { - if (external != null) { - external.error(wrapper); - } - } - }; - - private boolean shouldShowNotification = true; - - public void setShouldShowNotification(boolean val) { - shouldShowNotification = val; - } - - public void setLogger(ILogger logger) { - this.external = logger; - } - - public void setOnResultListener(ApkBuilder.OnResultListener listener) { - onResultListener = listener; - } - - @Override - public void onCreate() { - super.onCreate(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - - Notification notification = setupNotification(); - startForeground(201, notification); - - return START_STICKY; - } - - private Notification setupNotification() { - return new NotificationCompat.Builder(this, createNotificationChannel()).setContentTitle(getString(R.string.app_name)).setSmallIcon(R.drawable.ic_launcher).setContentText("Preparing").setPriority(NotificationCompat.PRIORITY_HIGH).setOngoing(true).setProgress(100, 0, true).build(); - } - - private void updateNotification(String title, String message, int progress) { - updateNotification(title, message, progress, NotificationCompat.PRIORITY_LOW); - } - - private void updateNotification(String title, String message, int progress, int priority) { - new Handler(Looper.getMainLooper()).post(() -> { - NotificationCompat.Builder builder = - new NotificationCompat.Builder(this, "Compiler").setContentTitle(title).setContentText(message).setSmallIcon(R.drawable.ic_launcher).setPriority(priority); - if (progress != -1) { - builder.setProgress(100, progress, false); - } - NotificationManagerCompat.from(this).notify(201, builder.build()); - }); - } - - private String createNotificationChannel() { - NotificationChannelCompat channel = new NotificationChannelCompat.Builder("Compiler", - NotificationManagerCompat.IMPORTANCE_HIGH).setName("Compiler service").setDescription("Foreground notification for the compiler").build(); - - NotificationManagerCompat.from(this).createNotificationChannel(channel); - - return "Compiler"; - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - public void compile(Project project, BuildType type) { - mProject = project; - - - if (mProject == null) { - if (onResultListener != null) { - mMainHandler.post(() -> onResultListener.onComplete(false, "Failed to open " + - "project (Have you opened a project?)")); - } - - if (shouldShowNotification) { - updateNotification("Compilation failed", "Unable to open project", -1, - NotificationCompat.PRIORITY_HIGH); - } - return; - } - - ProgressIndicator indicator = new ProgressIndicator(); - ProgressManager.getInstance().runAsync(() -> { - if (true) { - buildProject(project, type); - } else { - buildMainModule(project, type); - } - }, i -> { - - }, indicator); - } - - private void buildProject(Project project, BuildType type) { - boolean success = true; - - try { - ProjectBuilder projectBuilder = new ProjectBuilder(project, logger); - projectBuilder.build(type); - } catch (Throwable e) { - String message; - if (BuildConfig.DEBUG) { - message = Log.getStackTraceString(e); - } else { - message = e.getMessage(); - } - mMainHandler.post(() -> onResultListener.onComplete(false, message)); - success = false; - } - - report(success, type, project.getMainModule()); - } - - private void buildMainModule(Project project, BuildType type) { - Module module = project.getMainModule(); - Builder extends Module> projectBuilder = getBuilderForProject(module, type); - - module.clear(); - module.index(); - - boolean success = true; - - projectBuilder.setTaskListener(this::updateNotification); - - try { - projectBuilder.build(type); - } catch (Exception e) { - String message; - if (BuildConfig.DEBUG) { - message = Log.getStackTraceString(e); - } else { - message = e.getMessage(); - } - mMainHandler.post(() -> onResultListener.onComplete(false, message)); - success = false; - } - - report(success, type, module); - } - - private void report(boolean success, BuildType type, Module module) { - if (success) { - mMainHandler.post(() -> onResultListener.onComplete(true, "Success")); - } - - - String projectName = "Project"; - if (!success) { - updateNotification(projectName, getString(R.string.compilation_result_failed), -1 - , NotificationCompat.PRIORITY_HIGH); - } else { - if (shouldShowNotification) { - mMainHandler.post(() -> { - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, - "Compiler").setSmallIcon(R.drawable.ic_launcher).setContentTitle(projectName).setContentText(getString(R.string.compilation_result_success)); - - if (type != BuildType.AAB) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(ApkInstaller.uriFromFile(this, - new File(module.getBuildDirectory(), "bin/signed.apk")), - "application/vnd.android.package-archive"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - PendingIntent pending = PendingIntent.getActivity(this, 0, intent, - PendingIntent.FLAG_IMMUTABLE); - builder.addAction(new NotificationCompat.Action(0, - getString(R.string.compilation_button_install), pending)); - } - NotificationManagerCompat.from(this).notify(201, builder.build()); - }); - } - } - - stopSelf(); - stopForeground(true); - } - - private Builder extends Module> getBuilderForProject(Module module, BuildType type) { - if (module instanceof AndroidModule) { - if (type == BuildType.AAB) { - return new AndroidAppBundleBuilder((AndroidModule) module, logger); - } - return new AndroidAppBuilder((AndroidModule) module, logger); - } - return null; - } -} diff --git a/app/src/main/java/com/tyron/code/service/CompilerServiceConnection.java b/app/src/main/java/com/tyron/code/service/CompilerServiceConnection.java deleted file mode 100644 index b20241360..000000000 --- a/app/src/main/java/com/tyron/code/service/CompilerServiceConnection.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.tyron.code.service; - -import android.content.ComponentName; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.content.SharedPreferences; - -import androidx.preference.PreferenceManager; - -import com.tyron.code.ApplicationLoader; -import com.tyron.common.SharedPreferenceKeys; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.builder.compiler.BuildType; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.log.LogViewModel; -import com.tyron.code.ui.main.MainViewModel; -import com.tyron.code.util.ApkInstaller; - -import org.openjdk.javax.tools.Diagnostic; - -import java.io.File; -import java.util.Objects; - -public class CompilerServiceConnection implements ServiceConnection { - - private final MainViewModel mMainViewModel; - private final LogViewModel mLogViewModel; - - private CompilerService mService; - private BuildType mBuildType; - private boolean mCompiling; - - public CompilerServiceConnection(MainViewModel mainViewModel, LogViewModel logViewModel) { - mMainViewModel = mainViewModel; - mLogViewModel = logViewModel; - } - - public void setBuildType(BuildType type) { - mBuildType = type; - } - - public boolean isCompiling() { - return mCompiling; - } - - public void setShouldShowNotification(boolean val) { - if (mService != null) { - mService.setShouldShowNotification(val); - } - } - - @Override - public void onServiceConnected(ComponentName name, IBinder binder) { - mService = ((CompilerService.CompilerBinder) binder).getCompilerService(); - if (mService == null) { - mLogViewModel.e(LogViewModel.BUILD_LOG, "CompilerService is null!"); - return; - } - mService.setLogger(ILogger.wrap(mLogViewModel)); - mService.setShouldShowNotification(false); - mService.setOnResultListener((success, message) -> { - mMainViewModel.setCurrentState(null); - mMainViewModel.setIndexing(false); - - if (success) { - mLogViewModel.d(LogViewModel.BUILD_LOG, message); - mLogViewModel.clear(LogViewModel.APP_LOG); - - File file = new File(ProjectManager.getInstance().getCurrentProject() - .getMainModule().getBuildDirectory(), "bin/signed.apk"); - if (file.exists() && mBuildType != BuildType.AAB) { - SharedPreferences preference = ApplicationLoader.getDefaultPreferences(); - if (preference.getBoolean(SharedPreferenceKeys.INSTALL_APK_DIRECTLY, true)) { - ApkInstaller.installApplication(mService,file.getAbsolutePath()); - } else { - mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_HALF_EXPANDED); - } - DiagnosticWrapper wrapper = new DiagnosticWrapper(); - wrapper.setKind(Diagnostic.Kind.NOTE); - wrapper.setMessage("Generated APK has been saved to " + file.getAbsolutePath()); - wrapper.setExtra("INSTALL"); - wrapper.setSource(file); - wrapper.setCode(""); - wrapper.setOnClickListener((view) -> { - if (view == null || view.getContext() == null) { - return; - } - ApkInstaller.installApplication(view.getContext(), file.getAbsolutePath()); - }); - mLogViewModel.d(LogViewModel.BUILD_LOG, wrapper); - } - } else { - mLogViewModel.e(LogViewModel.BUILD_LOG, message); - if (BottomSheetBehavior.STATE_COLLAPSED == - Objects.requireNonNull(mMainViewModel.getBottomSheetState().getValue())) { - mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_HALF_EXPANDED); - } - } - }); - - if (mBuildType != null) { - mCompiling = true; - mService.compile(ProjectManager.getInstance().getCurrentProject(), mBuildType); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mService = null; - mMainViewModel.setCurrentState(null); - mMainViewModel.setIndexing(false); - mCompiling = false; - } -} diff --git a/app/src/main/java/com/tyron/code/service/GradleDaemonService.java b/app/src/main/java/com/tyron/code/service/GradleDaemonService.java new file mode 100644 index 000000000..6f0b09ab1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/service/GradleDaemonService.java @@ -0,0 +1,125 @@ +package com.tyron.code.service; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +import androidx.core.app.NotificationChannelCompat; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import com.google.common.base.Throwables; +import com.tyron.code.R; +import com.tyron.common.util.FileUtilsEx; + +import org.apache.commons.io.FileUtils; +import org.gradle.launcher.daemon.bootstrap.DaemonMain; +import org.gradle.util.GradleVersion; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; + +/** + * A service that runs the {@link org.gradle.launcher.daemon.bootstrap.GradleDaemon} in a + * separate process + */ +public class GradleDaemonService extends Service { + private static final String ACTION_STOP_DAEMON = "stopDaemon"; + private static final String EXTRA_NOTIFICATION_ID = "notificationId"; + + private boolean daemonStarted = false; + + public GradleDaemonService() { + + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + Notification notification = setupNotification(); + startForeground(201, notification); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Notification notification = setupNotification(); + startForeground(201, notification); + + if (ACTION_STOP_DAEMON.equals(intent.getAction())) { + System.out.println("User has requested to stop the daemon service."); + System.exit(0); + } + + InputStream originalIn = System.in; + PrintStream originalOut = System.out; + + try { + String dir = intent.getStringExtra("dir"); + File currentDir = new File(dir); + + File daemonInput = new File(currentDir, "daemonInput"); + FileUtilsEx.createFile(daemonInput); + + File daemonOutput = new File(currentDir, "daemonOutput"); + FileUtilsEx.createFile(daemonOutput); + + System.setIn(FileUtils.openInputStream(daemonInput)); + System.setOut(new PrintStream(FileUtils.openOutputStream(daemonOutput, true))); + } catch (IOException e) { + System.out.println("Failed to connect to DaemonClient" + + Throwables.getStackTraceAsString(e)); + System.exit(1); + } + + if (!daemonStarted) { + new Thread(() -> { + daemonStarted = true; + + DaemonMain daemonMain = new DaemonMain(); + daemonMain.run(new String[]{GradleVersion.current().getVersion()});; + }, "GradleDaemonThread").start(); + } + + return START_NOT_STICKY; + } + + private Notification setupNotification() { + Intent stopIntent = new Intent(this, GradleDaemonService.class); + stopIntent.setAction(ACTION_STOP_DAEMON); + stopIntent.putExtra(EXTRA_NOTIFICATION_ID, 201); + PendingIntent snoozePendingIntent = PendingIntent.getBroadcast( + this, + 0, + stopIntent, + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE + ); + return new NotificationCompat.Builder(this, createNotificationChannel()) + .setContentTitle("CodeAssist Gradle Daemon") + .setSmallIcon(R.drawable.ic_stat_code) + .setContentText("Running") + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setOngoing(true) + .addAction(R.drawable.crash_ic_close, "STOP", snoozePendingIntent) + .build(); + } + + private String createNotificationChannel() { + NotificationChannelCompat channel = new NotificationChannelCompat.Builder("GradleDaemon", + NotificationManagerCompat.IMPORTANCE_HIGH).setName("Gradle Daemon Service") + .setDescription("Foreground service to keep the Gradle Daemon alive").build(); + + NotificationManagerCompat.from(this).createNotificationChannel(channel); + + return "GradleDaemon"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/service/IndexService.java b/app/src/main/java/com/tyron/code/service/IndexService.java deleted file mode 100644 index 786c2a04d..000000000 --- a/app/src/main/java/com/tyron/code/service/IndexService.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.tyron.code.service; - -import android.app.Notification; -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; - -import androidx.core.app.NotificationChannelCompat; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; - -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.project.Project; -import com.tyron.code.R; - -import java.lang.ref.WeakReference; - -public class IndexService extends Service { - - private static final int NOTIFICATION_ID = 23; - - private final Handler mMainHandler = new Handler(Looper.getMainLooper()); - private final IndexBinder mBinder = new IndexBinder(this); - - public IndexService() { - } - - public static class IndexBinder extends Binder { - - public final WeakReference mIndexServiceReference; - - public IndexBinder(IndexService service) { - mIndexServiceReference = new WeakReference<>(service); - } - - public void index(Project project, ProjectManager.TaskListener listener, ILogger logger) { - IndexService service = mIndexServiceReference.get(); - if (service == null) { - listener.onComplete(project, false, "Index service is null!"); - } else { - service.index(project, listener, logger); - } - } - } - - - public IBinder onBind(Intent intent) { - return mBinder; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Notification notification = new NotificationCompat.Builder(this, createNotificationChannel()) - .setProgress(100, 0, true) - .setSmallIcon(R.drawable.ic_launcher) - .setContentTitle("Indexing") - .setContentText("Preparing") - .build(); - updateNotification(notification); - startForeground(NOTIFICATION_ID, notification); - return START_STICKY; - } - - @Override - public void onDestroy() { - super.onDestroy(); - } - - private void index(Project project, ProjectManager.TaskListener listener, ILogger logger) { - ProjectManager.TaskListener delegate = new ProjectManager.TaskListener() { - @Override - public void onTaskStarted(String message) { - Notification notification = new NotificationCompat.Builder(IndexService.this, "Index") - .setProgress(100, 0, true) - .setSmallIcon(R.drawable.ic_launcher) - .setContentTitle("Indexing") - .setContentText(message) - .build(); - updateNotification(notification); - mMainHandler.post(() -> listener.onTaskStarted(message)); - } - - @Override - public void onComplete(Project project, boolean success, String message) { - mMainHandler.post(() -> listener.onComplete(project, success, message)); - stopForeground(true); - stopSelf(); - } - }; - - try { - ProjectManager.getInstance() - .openProject(project, true, delegate, logger); - } catch (Throwable e) { - stopForeground(true); - Notification notification = new NotificationCompat.Builder(IndexService.this, "Index") - .setProgress(100, 0, true) - .setSmallIcon(R.drawable.ic_launcher) - .setContentTitle("Indexing error") - .setContentText("Unknown error: " + e.getMessage()) - .build(); - updateNotification(notification); - stopSelf(); - throw e; - } - } - - private String createNotificationChannel() { - NotificationChannelCompat channel = new NotificationChannelCompat.Builder("Index", - NotificationManagerCompat.IMPORTANCE_NONE) - .setName("Index Service") - .setDescription("Service that downloads libraries in the foreground") - .build(); - NotificationManagerCompat.from(this) - .createNotificationChannel(channel); - return "Index"; - } - - private void updateNotification(Notification notification) { - NotificationManagerCompat.from(this) - .notify(NOTIFICATION_ID, notification); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/service/IndexServiceConnection.java b/app/src/main/java/com/tyron/code/service/IndexServiceConnection.java deleted file mode 100644 index b1e730bc8..000000000 --- a/app/src/main/java/com/tyron/code/service/IndexServiceConnection.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.tyron.code.service; - -import android.content.ComponentName; -import android.content.ServiceConnection; -import android.os.IBinder; - -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import com.tyron.builder.model.ProjectSettings; -import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; -import com.tyron.fileeditor.api.FileEditor; -import com.tyron.fileeditor.api.FileEditorSavedState; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.log.LogViewModel; -import com.tyron.builder.project.Project; -import com.tyron.code.ui.main.MainViewModel; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Handles the communication between the Index service and the main fragment - */ -public class IndexServiceConnection implements ServiceConnection { - - private final MainViewModel mMainViewModel; - private final LogViewModel mLogViewModel; - private final ILogger mLogger; - private Project mProject; - - public IndexServiceConnection(MainViewModel mainViewModel, LogViewModel logViewModel) { - mMainViewModel = mainViewModel; - mLogViewModel = logViewModel; - mLogger = ILogger.wrap(logViewModel); - } - - public void setProject(Project project) { - mProject = project; - } - - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - IndexService.IndexBinder binder = (IndexService.IndexBinder) iBinder; - binder.index(mProject, new TaskListener(), mLogger); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - mMainViewModel.setIndexing(false); - mMainViewModel.setCurrentState(null); - } - - private List getOpenedFiles(ProjectSettings settings) { - String openedFilesString = settings.getString(ProjectSettings.SAVED_EDITOR_FILES, null); - if (openedFilesString != null) { - try { - Type type = new TypeToken>(){}.getType(); - List savedStates = new Gson().fromJson(openedFilesString, type); - return savedStates.stream().filter(it -> it.getFile().exists()).map(FileEditorManagerImpl.getInstance()::openFile).collect(Collectors.toList()); - } catch (Throwable e) { - // ignored, users may have edited the file manually and is corrupt - // just return an empty editor list - } - } - return new ArrayList<>(); - } - - private class TaskListener implements ProjectManager.TaskListener { - - @Override - public void onTaskStarted(String message) { - mMainViewModel.setCurrentState(message); - } - - @SuppressWarnings("ConstantConditions") - @Override - public void onComplete(Project project, boolean success, String message) { - mMainViewModel.setIndexing(false); - mMainViewModel.setCurrentState(null); - if (success) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - if (project.equals(currentProject)) { - mMainViewModel.setToolbarTitle(project.getRootFile().getName()); - mMainViewModel.setFiles(getOpenedFiles(currentProject.getSettings())); - } - } else { - if (mMainViewModel.getBottomSheetState().getValue() - != BottomSheetBehavior.STATE_EXPANDED) { - mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_HALF_EXPANDED); - } - mLogViewModel.e(LogViewModel.BUILD_LOG, message); - } - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/BottomEditorFragment.java b/app/src/main/java/com/tyron/code/ui/editor/BottomEditorFragment.java index ad385e5bb..50e2f0dd8 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/BottomEditorFragment.java +++ b/app/src/main/java/com/tyron/code/ui/editor/BottomEditorFragment.java @@ -16,9 +16,12 @@ import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; +import com.google.common.base.Strings; import com.tyron.builder.log.LogViewModel; +import com.tyron.code.ApplicationLoader; import com.tyron.code.R; -import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; +import com.tyron.code.event.EventManager; +import com.tyron.code.event.PerformShortcutEvent; import com.tyron.code.ui.editor.log.AppLogFragment; import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; @@ -29,6 +32,8 @@ import com.tyron.code.ui.editor.shortcuts.action.UndoAction; import com.tyron.code.ui.main.MainViewModel; import com.tyron.common.util.AndroidUtilities; +import com.tyron.editor.Caret; +import com.tyron.editor.Editor; import com.tyron.fileeditor.api.FileEditor; import java.util.ArrayList; @@ -37,9 +42,6 @@ import java.util.List; import java.util.stream.Collectors; -import io.github.rosemoe.sora2.text.Cursor; -import io.github.rosemoe.sora2.widget.CodeEditor; - @SuppressWarnings("FieldCanBeLocal") public class BottomEditorFragment extends Fragment { @@ -65,15 +67,16 @@ public BottomEditorFragment() { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, + ViewGroup container, + Bundle savedInstanceState) { mRoot = inflater.inflate(R.layout.bottom_editor_fragment, container, false); mRowLayout = mRoot.findViewById(R.id.row_layout); mShortcutsRecyclerView = mRoot.findViewById(R.id.recyclerview_shortcuts); mPager = mRoot.findViewById(R.id.viewpager); mTabLayout = mRoot.findViewById(R.id.tablayout); - mFilesViewModel = new ViewModelProvider(requireActivity()) - .get(MainViewModel.class); + mFilesViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); return mRoot; } @@ -87,36 +90,38 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { new TabLayoutMediator(mTabLayout, mPager, (tab, position) -> { switch (position) { case 0: - tab.setText("Build Logs"); + tab.setText(R.string.tab_build_logs_title); break; default: case 1: - tab.setText("App Logs"); + tab.setText(R.string.tab_app_logs_title); break; case 2: - tab.setText("Debug"); + tab.setText(R.string.tab_diagnostics_title); + break; + case 3: + tab.setText(R.string.tab_ide_logs_title); + break; } }).attach(); ShortcutsAdapter adapter = new ShortcutsAdapter(getShortcuts()); - LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext(), - LinearLayoutManager.HORIZONTAL, false); + LinearLayoutManager layoutManager = + new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false); mShortcutsRecyclerView.setLayoutManager(layoutManager); mShortcutsRecyclerView.setAdapter(adapter); adapter.setOnShortcutSelectedListener((item, pos) -> { FileEditor currentFile = mFilesViewModel.getCurrentFileEditor(); if (currentFile != null) { - if (currentFile.getFragment() instanceof CodeEditorFragment) { - ((CodeEditorFragment) currentFile.getFragment()).performShortcut(item); - } + EventManager eventManager = ApplicationLoader.getInstance().getEventManager(); + eventManager.dispatchEvent(new PerformShortcutEvent(item, currentFile)); } }); - getParentFragmentManager().setFragmentResultListener(OFFSET_KEY, getViewLifecycleOwner(), - ((requestKey, result) -> { - setOffset(result.getFloat("offset", 0f)); - })); + getParentFragmentManager().setFragmentResultListener(OFFSET_KEY, + getViewLifecycleOwner(), + ((requestKey, result) -> setOffset(result.getFloat("offset", 0f)))); } private void setOffset(float offset) { @@ -135,8 +140,7 @@ private void setOffset(float offset) { } private void setRowOffset(float offset) { - mRowLayout.getLayoutParams() - .height = Math.round(AndroidUtilities.dp(38) * offset); + mRowLayout.getLayoutParams().height = Math.round(AndroidUtilities.dp(38) * offset); mRowLayout.requestLayout(); } @@ -151,32 +155,39 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { - Cursor cursor = editor.getCursor(); - editor.getText().insert(cursor.getLeftLine(), cursor.getLeftColumn(), "\t"); + public void apply(Editor editor, ShortcutItem item) { + Caret cursor = editor.getCaret(); + if (editor.useTab()) { + editor.insert(cursor.getStartLine(), cursor.getStartColumn(), "\t"); + } else { + editor.insert(cursor.getStartLine(), + cursor.getStartColumn(), + Strings.repeat(" ", editor.getTabCount())); + } } }), "->", "tab")); - items.addAll(strings.stream() - .map(item -> { - ShortcutItem it = new ShortcutItem(); - it.label = item; - it.kind = TextInsertAction.KIND; - it.actions = Collections.singletonList(new TextInsertAction()); - return it; - }).collect(Collectors.toList())); + items.addAll(strings.stream().map(item -> { + ShortcutItem it = new ShortcutItem(); + it.label = item; + it.kind = TextInsertAction.KIND; + it.actions = Collections.singletonList(new TextInsertAction()); + return it; + }).collect(Collectors.toList())); Collections.addAll(items, new ShortcutItem(Collections.singletonList(new UndoAction()), "⬿", UndoAction.KIND), new ShortcutItem(Collections.singletonList(new RedoAction()), "⤳", RedoAction.KIND), - new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.UP, 1)), "↑", CursorMoveAction.KIND), - new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.DOWN, 1)), "↓", CursorMoveAction.KIND), - new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.LEFT, 1)), "←", CursorMoveAction.KIND), - new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.RIGHT, 1)), "→", CursorMoveAction.KIND) - ); + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.UP, + 1)), "↑", CursorMoveAction.KIND), + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.DOWN, + 1)), "↓", CursorMoveAction.KIND), + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.LEFT, + 1)), "←", CursorMoveAction.KIND), + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.RIGHT, + 1)), "→", CursorMoveAction.KIND)); return items; } - @SuppressWarnings("deprecation") private static class PageAdapter extends FragmentStateAdapter { public PageAdapter(@NonNull Fragment fragment) { @@ -194,12 +205,14 @@ public Fragment createFragment(int position) { return AppLogFragment.newInstance(LogViewModel.APP_LOG); case 2: return AppLogFragment.newInstance(LogViewModel.DEBUG); + case 3: + return AppLogFragment.newInstance(LogViewModel.IDE); } } @Override public int getItemCount() { - return 3; + return 4; } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/EditorContainerFragment.java b/app/src/main/java/com/tyron/code/ui/editor/EditorContainerFragment.java index 275d96212..8dd855d57 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/EditorContainerFragment.java +++ b/app/src/main/java/com/tyron/code/ui/editor/EditorContainerFragment.java @@ -1,52 +1,81 @@ package com.tyron.code.ui.editor; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.PopupMenu; +import android.widget.FrameLayout; import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.fragment.app.Fragment; +import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; -import androidx.viewpager2.widget.ViewPager2; +import androidx.lifecycle.ViewModelProviderKt; +import androidx.lifecycle.ViewModelStoreOwner; +import androidx.transition.TransitionManager; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.tabs.TabLayout; -import com.google.android.material.tabs.TabLayoutMediator; -import com.tyron.actions.ActionManager; +import com.google.android.material.transition.MaterialFadeThrough; import com.tyron.actions.ActionPlaces; import com.tyron.actions.CommonDataKeys; import com.tyron.actions.DataContext; -import com.tyron.actions.util.DataContextUtils; +import com.tyron.actions.menu.ActionPopupMenu; +import com.tyron.builder.project.Project; +import com.tyron.code.ApplicationLoader; import com.tyron.code.R; -import com.tyron.code.ui.editor.adapter.PageAdapter; -import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; -import com.tyron.code.ui.editor.impl.xml.LayoutTextEditorFragment; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; import com.tyron.code.ui.main.MainFragment; import com.tyron.code.ui.main.MainViewModel; +import com.tyron.code.ui.main.action.project.SaveEvent; import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.util.EventManagerUtilsKt; +import com.tyron.code.util.Listeners; +import com.tyron.code.util.UiUtilsKt; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.editor.Content; +import com.tyron.editor.event.ContentEvent; +import com.tyron.editor.event.ContentListener; +import com.tyron.editor.util.EditorUtil; +import com.tyron.fileeditor.api.FileDocumentManager; +import com.tyron.fileeditor.api.FileEditor; import com.tyron.fileeditor.api.FileEditorManager; +import com.tyron.fileeditor.api.TextEditor; + +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.VFS; import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; -public class EditorContainerFragment extends Fragment { +public class EditorContainerFragment extends Fragment implements + ProjectManager.OnProjectOpenListener, SharedPreferences.OnSharedPreferenceChangeListener { public static final String SAVE_ALL_KEY = "saveAllEditors"; public static final String PREVIEW_KEY = "previewEditor"; public static final String FORMAT_KEY = "formatEditor"; private TabLayout mTabLayout; - private ViewPager2 mPager; - private PageAdapter mAdapter; + private FrameLayout mContainer; private BottomSheetBehavior mBehavior; private MainViewModel mMainViewModel; + private EditorContainerViewModel mEditorContainerViewModel; private FileEditorManager mFileEditorManager; + private SharedPreferences pref; + private final List mEditors = new ArrayList<>(); private final OnBackPressedCallback mOnBackPressedCallback = new OnBackPressedCallback(false) { @Override @@ -55,13 +84,16 @@ public void handleOnBackPressed() { } }; + private DataContext mDataContext; + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + pref = ApplicationLoader.getDefaultPreferences(); mMainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); - requireActivity().getOnBackPressedDispatcher().addCallback(this, - mOnBackPressedCallback); + mEditorContainerViewModel = new ViewModelProvider((ViewModelStoreOwner) this).get(EditorContainerViewModel.class); + requireActivity().getOnBackPressedDispatcher().addCallback((LifecycleOwner) this, mOnBackPressedCallback); } @Override @@ -76,56 +108,54 @@ public void onSaveInstanceState(@NonNull Bundle outState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.editor_container_fragment, container, false); - - mAdapter = new PageAdapter(getChildFragmentManager(), getLifecycle()); - mPager = root.findViewById(R.id.viewpager); - mPager.setAdapter(mAdapter); - mPager.setUserInputEnabled(false); + // safe cast, getContext() ensures it returns a DataContext + DataContext dataContext = (DataContext) requireContext(); + dataContext.putData(CommonDataKeys.PROJECT, + ProjectManager.getInstance().getCurrentProject()); + dataContext.putData(CommonDataKeys.FRAGMENT, EditorContainerFragment.this); + dataContext.putData(MainFragment.MAIN_VIEW_MODEL_KEY, mMainViewModel); + CoordinatorLayout root = (CoordinatorLayout) inflater.inflate( + R.layout.editor_container_fragment, + container, + false + ); + mContainer = root.findViewById(R.id.viewpager); + ((FileEditorManagerImpl) FileEditorManagerImpl.getInstance()) + .attach(mMainViewModel, getChildFragmentManager()); mTabLayout = root.findViewById(R.id.tablayout); mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabUnselected(TabLayout.Tab p1) { - Fragment fragment = getChildFragmentManager() - .findFragmentByTag("f" + mAdapter.getItemId(p1.getPosition())); - if (fragment instanceof CodeEditorFragment) { - ((CodeEditorFragment) fragment).save(); + FileEditor currentFileEditor = mMainViewModel.getCurrentFileEditor(); + if (currentFileEditor instanceof TextEditor) { + FileDocumentManager instance = FileDocumentManager.getInstance(); + instance.saveContent(((TextEditor) currentFileEditor).getContent()); } } @Override public void onTabReselected(TabLayout.Tab p1) { - PopupMenu popup = new PopupMenu(requireActivity(), p1.view); - - DataContext dataContext = DataContextUtils.getDataContext(mTabLayout); - dataContext.putData(CommonDataKeys.PROJECT, ProjectManager.getInstance().getCurrentProject()); - dataContext.putData(CommonDataKeys.FRAGMENT, EditorContainerFragment.this); - dataContext.putData(MainFragment.MAIN_VIEW_MODEL_KEY, mMainViewModel); - dataContext.putData(CommonDataKeys.FILE_EDITOR_KEY, mMainViewModel.getCurrentFileEditor()); - - ActionManager.getInstance().fillMenu(dataContext, - popup.getMenu(), ActionPlaces.EDITOR_TAB, - true, - false); - popup.show(); + ActionPopupMenu.createAndShow( + p1.view, + (DataContext) requireContext(), + ActionPlaces.EDITOR_TAB + ); } @Override public void onTabSelected(TabLayout.Tab p1) { - mMainViewModel.setCurrentPosition(p1.getPosition(), false); - getParentFragmentManager() - .setFragmentResult(MainFragment.REFRESH_TOOLBAR_KEY, Bundle.EMPTY); + updateTab(p1.getPosition()); + mMainViewModel.setCurrentPosition(p1.getPosition(), true); + + ProgressManager.getInstance().runLater(() -> getParentFragmentManager() + .setFragmentResult(MainFragment.REFRESH_TOOLBAR_KEY, Bundle.EMPTY), 300); } }); + View persistentSheet = root.findViewById(R.id.persistent_sheet); + mBehavior = BottomSheetBehavior.from(persistentSheet); + mBehavior.setGestureInsetBottomIgnored(true); - new TabLayoutMediator(mTabLayout, mPager, true, true, (tab, pos) -> { - File current = Objects.requireNonNull(mMainViewModel.getFiles().getValue()).get(pos) - .getFile(); - tab.setText(current != null ? current.getName() : "Unknown"); - }).attach(); - - mBehavior = BottomSheetBehavior.from(root.findViewById(R.id.persistent_sheet)); mBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull View p1, int state) { @@ -137,12 +167,14 @@ public void onSlide(@NonNull View bottomSheet, float slideOffset) { if (isAdded()) { Bundle bundle = new Bundle(); bundle.putFloat("offset", slideOffset); - getChildFragmentManager().setFragmentResult(BottomEditorFragment.OFFSET_KEY, - bundle); + getChildFragmentManager().setFragmentResult(BottomEditorFragment.OFFSET_KEY, bundle); } } }); mBehavior.setHalfExpandedRatio(0.3f); + mBehavior.setFitToContents(false); + + ProjectManager.getInstance().addOnProjectOpenListener(this); if (savedInstanceState != null) { restoreViewState(savedInstanceState); @@ -150,67 +182,160 @@ public void onSlide(@NonNull View bottomSheet, float slideOffset) { return root; } + private void updateTabs() { + FileEditor fileEditor = mMainViewModel.getCurrentFileEditor(); + int index = mEditors.indexOf(fileEditor); + if (index != -1) { + updateTab(index); + } + } + + private void updateTab(int pos) { + TabLayout.Tab tab = mTabLayout.getTabAt(pos); + if (tab == null) { + return; + } + + List fileEditors = mMainViewModel.getFiles().getValue(); + if (fileEditors == null) { + fileEditors = Collections.emptyList(); + } + List files = fileEditors.stream().map(FileEditor::getFile).collect(Collectors.toList()); + FileEditor currentEditor = + Objects.requireNonNull(fileEditors).get(pos); + File current = currentEditor.getFile(); + + String text = current != null ? + EditorUtil.getUniqueTabTitle(current, files) + : "Unknown"; + if (currentEditor.isModified()) { + text = "*" + text; + } + + tab.setText(text); + } + + @Override + public void onProjectOpen(Project project) { + + } + @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - mMainViewModel.getCurrentPosition().observe(getViewLifecycleOwner(), pos -> { - mPager.setCurrentItem(pos, false); - }); + ApplicationLoader.getDefaultPreferences().registerOnSharedPreferenceChangeListener(this); + mMainViewModel.getFiles().observe(getViewLifecycleOwner(), files -> { - mAdapter.submitList(files); - mTabLayout.setVisibility(files.isEmpty() ? View.GONE : View.VISIBLE); + List oldList = new ArrayList<>(mEditors); + mEditors.clear(); + mEditors.addAll(files); + + TransitionManager.beginDelayedTransition(mContainer, new MaterialFadeThrough()); + if (files.isEmpty()) { + mContainer.removeAllViews(); + mTabLayout.removeAllTabs(); + mTabLayout.setVisibility(View.GONE); + mMainViewModel.setCurrentPosition(-1); + } else { + mTabLayout.setVisibility(View.VISIBLE); + EditorTabUtil.updateTabLayout(mTabLayout, oldList, files); + } }); - mMainViewModel.getCurrentPosition().observe(getViewLifecycleOwner(), pos -> { - mPager.setCurrentItem(pos, false); - if (mBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { - mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED); + + mMainViewModel.getCurrentPosition().observe(getViewLifecycleOwner(), position -> { + mContainer.removeAllViews(); + + FileEditor currentFileEditor = mMainViewModel.getCurrentFileEditor(); + if (position == -1 || currentFileEditor == null) { + return; + } + + if (mTabLayout.getSelectedTabPosition() != position) { + mTabLayout.selectTab(mTabLayout.getTabAt(position), true); + } + MaterialFadeThrough transition = new MaterialFadeThrough(); + transition.setDuration(150L); + TransitionManager.beginDelayedTransition(mContainer, transition); + + UiUtilsKt.removeFromParent(currentFileEditor.getView()); + mContainer.addView(currentFileEditor.getView()); + + try { + File file = currentFileEditor.getFile(); + FileObject fileObject = VFS.getManager().toFileObject(file); + Content content = FileDocumentManager.getInstance().getContent(fileObject); + + if (content != null) { + Listeners.registerListener(new ContentListener() { + @Override + public void contentChanged(@NonNull ContentEvent event) { + updateTabs(); + } + }, getViewLifecycleOwner(), content::addContentListener, content::removeContentListener); + } + } catch (FileSystemException e) { + // safe to ignore here, just don't register the listener then } }); + + + EventManagerUtilsKt.subscribeEvent( + ApplicationLoader.getInstance().getEventManager(), + getViewLifecycleOwner(), + SaveEvent.class, + (event, unsubscribe) -> updateTabs() + ); + mMainViewModel.getBottomSheetState().observe(getViewLifecycleOwner(), state -> { + if (state == BottomSheetBehavior.STATE_DRAGGING || state == BottomSheetBehavior.STATE_SETTLING) { + return; + } mBehavior.setState(state); mOnBackPressedCallback.setEnabled(state == BottomSheetBehavior.STATE_EXPANDED); }); - - getParentFragmentManager().setFragmentResultListener(SAVE_ALL_KEY, - getViewLifecycleOwner(), (requestKey, result) -> saveAll()); - getParentFragmentManager().setFragmentResultListener(PREVIEW_KEY, - getViewLifecycleOwner(), ((requestKey, result) -> previewCurrent())); - getParentFragmentManager().setFragmentResultListener(FORMAT_KEY, - getViewLifecycleOwner(), (((requestKey, result) -> formatCurrent()))); } private void restoreViewState(@NonNull Bundle state) { int behaviorState = state.getInt("bottom_sheet_state", BottomSheetBehavior.STATE_COLLAPSED); mMainViewModel.setBottomSheetState(behaviorState); Bundle floatOffset = new Bundle(); - floatOffset.putFloat("offset", behaviorState == BottomSheetBehavior.STATE_EXPANDED - ? 1 - : 0f); + floatOffset.putFloat("offset", behaviorState == BottomSheetBehavior.STATE_EXPANDED ? 1 : 0f); getChildFragmentManager().setFragmentResult(BottomEditorFragment.OFFSET_KEY, floatOffset); } - private void formatCurrent() { - String tag = "f" + mAdapter.getItemId(mPager.getCurrentItem()); - Fragment fragment = getChildFragmentManager().findFragmentByTag(tag); - if (fragment instanceof CodeEditorFragment) { - ((CodeEditorFragment) fragment).format(); - } + @Override + public void onDestroyView() { + super.onDestroyView(); } - private void previewCurrent() { - String tag = "f" + mAdapter.getItemId(mPager.getCurrentItem()); - Fragment fragment = getChildFragmentManager().findFragmentByTag(tag); - if (fragment instanceof LayoutTextEditorFragment) { - ((LayoutTextEditorFragment) fragment).preview(); + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + switch (key) { + case SharedPreferenceKeys.EDITOR_TAB_UNIQUE_FILE_NAME: + for (int i = 0; i < mTabLayout.getTabCount(); i++) { + updateTab(i); + } + break; } } - private void saveAll() { - for (int i = 0; i < mAdapter.getItemCount(); i++) { - String tag = "f" + mAdapter.getItemId(i); - Fragment fragment = getChildFragmentManager().findFragmentByTag(tag); - if (fragment instanceof Savable) { - ((Savable) fragment).save(); - } + @Override + public void onDestroy() { + super.onDestroy(); + mDataContext = null; + ApplicationLoader.getDefaultPreferences().unregisterOnSharedPreferenceChangeListener(this); + } + + @Nullable + @Override + public Context getContext() { + Context originalContext = super.getContext(); + if (originalContext == null) { + return null; + } + + if (mDataContext == null) { + mDataContext = new DataContext(originalContext); } + return mDataContext; } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/EditorContainerViewModel.kt b/app/src/main/java/com/tyron/code/ui/editor/EditorContainerViewModel.kt new file mode 100644 index 000000000..f6a1c1775 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/EditorContainerViewModel.kt @@ -0,0 +1,19 @@ +package com.tyron.code.ui.editor + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.tyron.code.ApplicationLoader +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme +import io.github.rosemoe.sora2.text.EditorUtil + +class EditorContainerViewModel : ViewModel() { + + private val _editorTheme = MutableLiveData() + val editorTheme: LiveData + get() = _editorTheme + + init { + _editorTheme.value = EditorUtil.getDefaultColorScheme(ApplicationLoader.applicationContext) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/EditorTabUtil.java b/app/src/main/java/com/tyron/code/ui/editor/EditorTabUtil.java new file mode 100644 index 000000000..0cd7894c4 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/EditorTabUtil.java @@ -0,0 +1,51 @@ +package com.tyron.code.ui.editor; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.ListUpdateCallback; + +import com.google.android.material.tabs.TabLayout; +import com.tyron.code.ui.editor.adapter.PageAdapter; +import com.tyron.fileeditor.api.FileEditor; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class EditorTabUtil { + + public static void updateTabLayout(@NonNull TabLayout mTabLayout, List oldList, List files) { + PageAdapter.getDiff(oldList, files, new ListUpdateCallback() { + @Override + public void onInserted(int position, int count) { + FileEditor editor = files.get(position); + TabLayout.Tab tab = getTabLayout(editor); + mTabLayout.addTab(tab, position, false); + mTabLayout.selectTab(tab, true); + } + + @Override + public void onRemoved(int position, int count) { + for (int i = 0; i < count; i++) { + mTabLayout.removeTabAt(position); + } + } + + @Override + public void onMoved(int fromPosition, int toPosition) { + + } + + @Override + public void onChanged(int position, int count, @Nullable Object payload) { + + } + + private TabLayout.Tab getTabLayout(FileEditor editor) { + TabLayout.Tab tab = mTabLayout.newTab(); + tab.setText(editor.getFile().getName()); + return tab; + } + }); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/EditorViewModel.kt b/app/src/main/java/com/tyron/code/ui/editor/EditorViewModel.kt new file mode 100644 index 000000000..9ef0ba314 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/EditorViewModel.kt @@ -0,0 +1,24 @@ +package com.tyron.code.ui.editor + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.tyron.completion.java.JavaCompletionProvider +import com.tyron.completion.model.CompletionList +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch + +class EditorViewModel : ViewModel() { + private val mAnalyzeState = MutableLiveData(false) + + fun setAnalyzeState(analyzing: Boolean) { + mAnalyzeState.value = analyzing + } + + val analyzeState: LiveData + get() = mAnalyzeState + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/Savable.java b/app/src/main/java/com/tyron/code/ui/editor/Savable.java index fe6ff97c4..fdbfec01c 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/Savable.java +++ b/app/src/main/java/com/tyron/code/ui/editor/Savable.java @@ -6,5 +6,14 @@ */ public interface Savable { - void save(); + /** + * @return Whether the content can be saved + */ + boolean canSave(); + + /** + * Saves the contents of this class + * @param toDisk whether the contents will be written to file or stored in memory + */ + void save(boolean toDisk); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/CloseAllEditorAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/CloseAllEditorAction.java index 7d4b18c66..158f5a7fd 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/CloseAllEditorAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/CloseAllEditorAction.java @@ -32,6 +32,8 @@ public void update(@NonNull AnActionEvent event) { @Override public void actionPerformed(@NonNull AnActionEvent e) { MainViewModel mainViewModel = e.getData(MainFragment.MAIN_VIEW_MODEL_KEY); - mainViewModel.clear(); + if (mainViewModel != null) { + mainViewModel.clear(); + } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/CloseFileEditorAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/CloseFileEditorAction.java index 7ca64cd8b..429e134ea 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/CloseFileEditorAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/CloseFileEditorAction.java @@ -17,19 +17,19 @@ public class CloseFileEditorAction extends AnAction { @Override public void update(@NonNull AnActionEvent event) { - MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); - FileEditor fileEditor = event.getData(CommonDataKeys.FILE_EDITOR_KEY); - event.getPresentation().setVisible(false); - if (!ActionPlaces.EDITOR_TAB.equals(event.getPlace())) { + + MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); + if (mainViewModel == null) { return; } - if (fileEditor == null) { + if (!ActionPlaces.EDITOR_TAB.equals(event.getPlace())) { return; } - if (mainViewModel == null) { + FileEditor fileEditor = mainViewModel.getCurrentFileEditor(); + if (fileEditor == null) { return; } @@ -40,7 +40,9 @@ public void update(@NonNull AnActionEvent event) { @Override public void actionPerformed(@NonNull AnActionEvent e) { MainViewModel mainViewModel = e.getRequiredData(MainFragment.MAIN_VIEW_MODEL_KEY); - FileEditor fileEditor = e.getRequiredData(CommonDataKeys.FILE_EDITOR_KEY); - mainViewModel.removeFile(fileEditor.getFile()); + FileEditor fileEditor = mainViewModel.getCurrentFileEditor(); + if (fileEditor != null) { + mainViewModel.removeFile(fileEditor.getFile()); + } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/CloseOtherEditorAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/CloseOtherEditorAction.java index c99501c2d..38f125f95 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/CloseOtherEditorAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/CloseOtherEditorAction.java @@ -17,19 +17,17 @@ public class CloseOtherEditorAction extends AnAction { @Override public void update(@NonNull AnActionEvent event) { - MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); - FileEditor fileEditor = event.getData(CommonDataKeys.FILE_EDITOR_KEY); - event.getPresentation().setVisible(false); if (!ActionPlaces.EDITOR_TAB.equals(event.getPlace())) { return; } - if (fileEditor == null) { + MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); + if (mainViewModel == null) { return; } - - if (mainViewModel == null) { + FileEditor fileEditor = mainViewModel.getCurrentFileEditor(); + if (fileEditor == null) { return; } @@ -39,8 +37,8 @@ public void update(@NonNull AnActionEvent event) { @Override public void actionPerformed(@NonNull AnActionEvent e) { - MainViewModel mainViewModel = e.getData(MainFragment.MAIN_VIEW_MODEL_KEY); - FileEditor fileEditor = e.getData(CommonDataKeys.FILE_EDITOR_KEY); + MainViewModel mainViewModel = e.getRequiredData(MainFragment.MAIN_VIEW_MODEL_KEY); + FileEditor fileEditor = e.getRequiredData(CommonDataKeys.FILE_EDITOR_KEY); mainViewModel.removeOthers(fileEditor.getFile()); } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/DiagnosticInfoAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/DiagnosticInfoAction.java index c13cfa7e7..f3d05b142 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/DiagnosticInfoAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/DiagnosticInfoAction.java @@ -10,7 +10,7 @@ import com.tyron.actions.CommonDataKeys; import com.tyron.actions.Presentation; -import org.openjdk.javax.tools.Diagnostic; +import javax.tools.Diagnostic; import java.util.Locale; diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/ExpandSelectionAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/ExpandSelectionAction.java new file mode 100644 index 000000000..1f2048e0d --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/ExpandSelectionAction.java @@ -0,0 +1,89 @@ +package com.tyron.code.ui.editor.action; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; + +import com.google.common.collect.Range; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.Presentation; +import com.tyron.builder.model.SourceFileObject; +import com.tyron.builder.project.Project; +import com.tyron.code.R; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.completion.java.action.FindCurrentPath; +import com.tyron.completion.java.compiler.Parser; +import com.tyron.editor.CharPosition; +import com.tyron.editor.Editor; +import com.tyron.editor.selection.ExpandSelectionProvider; + +import com.sun.source.tree.ClassTree; +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.io.File; +import java.time.Instant; + +public class ExpandSelectionAction extends AnAction { + + public static final String ID = "expandSelection"; + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + if (!ActionPlaces.EDITOR.equals(event.getPlace())) { + return; + } + + File file = event.getData(CommonDataKeys.FILE); + if (file == null) { + return; + } + + Editor editor = event.getData(CommonDataKeys.EDITOR); + if (editor == null) { + return; + } + + ExpandSelectionProvider provider = ExpandSelectionProvider.forEditor(editor); + if (provider == null) { + return; + } + + if (editor.getProject() == null) { + return; + } + + DataContext context = event.getDataContext(); + presentation.setVisible(true); + presentation.setText(context.getString(R.string.expand_selection)); + presentation.setIcon(ResourcesCompat.getDrawable(context.getResources(), + R.drawable.ic_baseline_code_24, null)); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); + ExpandSelectionProvider provider = ExpandSelectionProvider.forEditor(editor); + if (provider == null) { + AndroidUtilities.showSimpleAlert(e.getDataContext(), "No provider", + "No expand selection provider found."); + return; + } + Range range = provider.expandSelection(editor); + if (range == null) { + AndroidUtilities.showSimpleAlert(e.getDataContext(), + "Error", + "Cannot expand selection"); + return; + } + editor.setSelectionRegion(range.lowerEndpoint(), range.upperEndpoint()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/PreviewLayoutAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/PreviewLayoutAction.java index f9ab7a475..96ab30520 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/PreviewLayoutAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/PreviewLayoutAction.java @@ -41,20 +41,20 @@ public void update(@NonNull AnActionEvent event) { @Override public void actionPerformed(@NonNull AnActionEvent e) { - FileEditor fileEditor = e.getData(CommonDataKeys.FILE_EDITOR_KEY); - Fragment fragment = fileEditor.getFragment(); - if (fragment == null || fragment.isDetached()) { - return; - } - FragmentActivity activity = fragment.requireActivity(); - View currentFocus = activity.getCurrentFocus(); - if (currentFocus == null) { - currentFocus = new View(activity); - } - AndroidUtilities.hideKeyboard(currentFocus); - - if (fragment instanceof LayoutTextEditorFragment) { - ((LayoutTextEditorFragment) fragment).preview(); - } +// FileEditor fileEditor = e.getRequiredData(CommonDataKeys.FILE_EDITOR_KEY); +// Fragment fragment = fileEditor.getFragment(); +// if (fragment == null || fragment.isDetached() || fragment.getActivity() == null) { +// return; +// } +// FragmentActivity activity = fragment.requireActivity(); +// View currentFocus = activity.getCurrentFocus(); +// if (currentFocus == null) { +// currentFocus = new View(activity); +// } +// AndroidUtilities.hideKeyboard(currentFocus); +// +// if (fragment instanceof LayoutTextEditorFragment) { +// ((LayoutTextEditorFragment) fragment).preview(); +// } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/SelectJavaParentAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/SelectJavaParentAction.java deleted file mode 100644 index c788077ee..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/action/SelectJavaParentAction.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.tyron.code.ui.editor.action; - -import androidx.annotation.NonNull; - -import com.tyron.actions.ActionPlaces; -import com.tyron.actions.AnAction; -import com.tyron.actions.AnActionEvent; -import com.tyron.actions.CommonDataKeys; -import com.tyron.actions.Presentation; -import com.tyron.builder.project.Project; -import com.tyron.code.R; -import com.tyron.completion.java.action.FindCurrentPath; -import com.tyron.completion.java.compiler.Parser; -import com.tyron.editor.CharPosition; -import com.tyron.editor.Editor; - -import org.openjdk.source.tree.ClassTree; -import org.openjdk.source.tree.Tree; -import org.openjdk.source.util.SourcePositions; -import org.openjdk.source.util.TreePath; -import org.openjdk.source.util.Trees; - -import java.io.File; - -public class SelectJavaParentAction extends AnAction { - - public static final String ID = "selectScopeParent"; - - @Override - public void update(@NonNull AnActionEvent event) { - Presentation presentation = event.getPresentation(); - presentation.setVisible(false); - - if (!ActionPlaces.EDITOR.equals(event.getPlace())) { - return; - } - - File file = event.getData(CommonDataKeys.FILE); - if (file == null || !file.getName().endsWith(".java")) { - return; - } - - if (event.getData(CommonDataKeys.PROJECT) == null) { - return; - } - - Editor editor = event.getData(CommonDataKeys.EDITOR); - if (editor == null) { - return; - } - - presentation.setVisible(true); - presentation.setText("Select all"); - presentation.setIcon(event.getDataContext().getDrawable(R.drawable.round_select_all_20)); - } - - @Override - public void actionPerformed(@NonNull AnActionEvent e) { - Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); - Project project = e.getRequiredData(CommonDataKeys.PROJECT); - File file = e.getRequiredData(CommonDataKeys.FILE); - Parser parser = Parser.parseFile(project, file.toPath()); - - FindCurrentPath findCurrentPath = new FindCurrentPath(parser.task); - - int cursorStart = editor.getCaret().getStart(); - int cursorEnd = editor.getCaret().getEnd(); - - SourcePositions positions = Trees.instance(parser.task).getSourcePositions(); - TreePath path = findCurrentPath.scan(parser.root, cursorStart, cursorEnd); - if (path != null) { - path = modifyTreePath(path); - - long afterStart; - long afterEnd; - - long currentStart = positions.getStartPosition(parser.root, path.getLeaf()); - long currentEnd = positions.getEndPosition(parser.root, path.getLeaf()); - if (currentStart == cursorStart && currentEnd == cursorEnd) { - TreePath parentPath = path.getParentPath(); - afterStart = positions.getStartPosition(parser.root, parentPath.getLeaf()); - afterEnd = positions.getEndPosition(parser.root, parentPath.getLeaf()); - } else { - afterStart = currentStart; - afterEnd = currentEnd; - } - CharPosition start = editor.getCharPosition((int) afterStart); - CharPosition end = editor.getCharPosition((int) afterEnd); - editor.setSelectionRegion(start.getLine(), start.getColumn(), - end.getLine(), end.getColumn()); - } - } - - @NonNull - private TreePath modifyTreePath(TreePath treePath) { - TreePath parent = treePath.getParentPath(); - - if (treePath.getLeaf().getKind() == Tree.Kind.BLOCK) { - // select the parent of { } - return parent; - } - - if (treePath.getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) { - return modifyTreePath(parent); - } - - if (treePath.getLeaf() instanceof ClassTree - && parent.getLeaf().getKind() == Tree.Kind.NEW_CLASS) { - if (parent.getParentPath().getLeaf().getKind() == Tree.Kind.EXPRESSION_STATEMENT) { - return parent.getParentPath(); - } - - return parent; - } - - if (treePath.getLeaf().getKind() == Tree.Kind.IDENTIFIER) { - if (parent.getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) { - return modifyTreePath(parent); - } - if (parent.getLeaf() .getKind() == Tree.Kind.METHOD_INVOCATION) { - // identifier -> method call -> expression - return parent.getParentPath(); - } - } - - if (treePath.getLeaf().getKind() == Tree.Kind.METHOD_INVOCATION) { - return parent; - } - return treePath; - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/CopyAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/CopyAction.java index b8bba7a89..0faef1937 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/text/CopyAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/CopyAction.java @@ -1,12 +1,15 @@ package com.tyron.code.ui.editor.action.text; import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; import com.tyron.actions.ActionPlaces; import com.tyron.actions.AnAction; import com.tyron.actions.AnActionEvent; import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; import com.tyron.actions.Presentation; +import com.tyron.code.R; import com.tyron.common.util.AndroidUtilities; import com.tyron.editor.Caret; import com.tyron.editor.Editor; @@ -33,8 +36,11 @@ public void update(@NonNull AnActionEvent event) { return; } + DataContext context = event.getDataContext(); presentation.setVisible(true); - presentation.setText(event.getDataContext().getString(io.github.rosemoe.sora2.R.string.copy)); + presentation.setText(context.getString(io.github.rosemoe.sora2.R.string.copy)); + presentation.setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_content_copy_20, context.getTheme())); } @Override @@ -43,6 +49,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { Caret caret = editor.getCaret(); CharSequence textToCopy = editor.getContent().subSequence(caret.getStart(), caret.getEnd()); - AndroidUtilities.copyToClipboard(textToCopy.toString(), true); + AndroidUtilities.copyToClipboard(textToCopy.toString(), false); + AndroidUtilities.showToast(R.string.copied_to_clipoard); } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/CutAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/CutAction.java index 9d9149f94..fef5eaf42 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/text/CutAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/CutAction.java @@ -1,9 +1,11 @@ package com.tyron.code.ui.editor.action.text; import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; import com.tyron.actions.AnActionEvent; import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; import com.tyron.editor.Caret; import com.tyron.editor.Editor; @@ -14,7 +16,10 @@ public void update(@NonNull AnActionEvent event) { super.update(event); if (event.getPresentation().isVisible()) { - event.getPresentation().setText("Cut"); + DataContext context = event.getDataContext(); + event.getPresentation().setText(context.getString(io.github.rosemoe.sora2.R.string.cut)); + event.getPresentation().setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_content_cut_20, context.getTheme())); } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/PasteAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/PasteAction.java new file mode 100644 index 000000000..349c185e5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/PasteAction.java @@ -0,0 +1,78 @@ +package com.tyron.code.ui.editor.action.text; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; + +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.Presentation; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.editor.Caret; +import com.tyron.editor.Editor; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import io.github.rosemoe.sora.text.TextUtils; +import io.github.rosemoe.sora2.text.EditorUtil; + +public class PasteAction extends CopyAction { + + @Override + public void update(@NonNull AnActionEvent event) { + super.update(event); + + Presentation presentation = event.getPresentation(); + if (!presentation.isVisible() && AndroidUtilities.getPrimaryClip() != null) { + presentation.setVisible(true); + } + + if (presentation.isVisible()) { + DataContext context = event.getDataContext(); + presentation.setText(context.getString(io.github.rosemoe.sora2.R.string.paste)); + presentation.setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_content_paste_20, context.getTheme())); + } + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); + Caret caret = editor.getCaret(); + + if (caret.isSelected()) { + editor.delete(caret.getStart(), caret.getEnd()); + } + + String clip = String.valueOf(AndroidUtilities.getPrimaryClip()); + String[] lines = clip.split("\n"); + if (lines.length == 0) { + lines = new String[]{clip}; + } + + int count = TextUtils.countLeadingSpaceCount(lines[0], editor.getTabCount()); + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + if (count < line.length()) { + String whitespace = line.substring(0, count); + if (EditorUtil.isWhitespace(whitespace)) { + line = line.substring(count); + } else { + line = line.trim(); + } + } else { + line = line.trim(); + } + lines[i] = line; + } + + String textToCopy = String.join("\n", lines); + editor.insertMultilineString( + caret.getStartLine(), + caret.getStartColumn(), + textToCopy + ); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/SelectAllAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/SelectAllAction.java new file mode 100644 index 000000000..4824d8122 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/SelectAllAction.java @@ -0,0 +1,30 @@ +package com.tyron.code.ui.editor.action.text; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; + +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.editor.Editor; + +public class SelectAllAction extends CopyAction { + + @Override + public void update(@NonNull AnActionEvent event) { + super.update(event); + + if (event.getPresentation().isVisible()) { + DataContext context = event.getDataContext(); + event.getPresentation().setText(context.getString(io.github.rosemoe.sora2.R.string.selectAll)); + event.getPresentation().setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_select_all_20, context.getTheme())); + } + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); + editor.setSelectionRegion(0, editor.getContent().length()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/TextActionGroup.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/TextActionGroup.java index 752e3306f..77d2ca774 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/action/text/TextActionGroup.java +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/TextActionGroup.java @@ -8,6 +8,8 @@ import com.tyron.actions.AnAction; import com.tyron.actions.AnActionEvent; import com.tyron.actions.Presentation; +import com.tyron.code.R; +import com.tyron.code.ui.editor.action.ExpandSelectionAction; public class TextActionGroup extends ActionGroup { @@ -23,19 +25,22 @@ public void update(@NonNull AnActionEvent event) { } presentation.setVisible(true); - presentation.setText("Text Actions"); + presentation.setText(event.getDataContext().getString(R.string.text_actions)); } @Override public boolean isPopup() { - return false; + return true; } @Override public AnAction[] getChildren(@Nullable AnActionEvent e) { return new AnAction[] { + new ExpandSelectionAction(), + new SelectAllAction(), + new CutAction(), new CopyAction(), - new CutAction() + new PasteAction() }; } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/adapter/PageAdapter.java b/app/src/main/java/com/tyron/code/ui/editor/adapter/PageAdapter.java index f20741a2e..f137544ca 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/adapter/PageAdapter.java +++ b/app/src/main/java/com/tyron/code/ui/editor/adapter/PageAdapter.java @@ -1,10 +1,17 @@ package com.tyron.code.ui.editor.adapter; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.Lifecycle; import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListUpdateCallback; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager2.adapter.FragmentStateAdapter; import com.tyron.fileeditor.api.FileEditor; @@ -13,67 +20,59 @@ import java.util.List; import java.util.Objects; -public class PageAdapter extends FragmentStateAdapter { - - private final List data = new ArrayList<>(); - - public PageAdapter(FragmentManager fm, Lifecycle lifecycle) { - super(fm, lifecycle); - } +public class PageAdapter extends RecyclerView.Adapter { - public void submitList(List files) { + public static void getDiff(List oldFiles, List newFiles, ListUpdateCallback callback) { DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { @Override public int getOldListSize() { - return data.size(); + return oldFiles.size(); } @Override public int getNewListSize() { - return files.size(); + return newFiles.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return Objects.equals(data.get(oldItemPosition), files.get(newItemPosition)); + return Objects.equals(oldFiles.get(oldItemPosition), newFiles.get(newItemPosition)); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - return Objects.equals(data.get(oldItemPosition), files.get(newItemPosition)); + return Objects.equals(oldFiles.get(oldItemPosition), newFiles.get(newItemPosition)); } }); - data.clear(); - data.addAll(files); - result.dispatchUpdatesTo(this); + oldFiles.clear(); + oldFiles.addAll(newFiles); + result.dispatchUpdatesTo(callback); } - @Override - public int getItemCount() { - return data.size(); + private final List data = new ArrayList<>(); + + public void submitList(List files) { } + @NonNull @Override - public long getItemId(int position) { - if (data.isEmpty()) { - return -1; - } - return data.get(position).hashCode(); + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(new FrameLayout(parent.getContext())); } @Override - public boolean containsItem(long itemId) { - for (FileEditor d : data) { - if (d.hashCode() == itemId) { - return true; - } - } - return false; + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + } - @NonNull @Override - public Fragment createFragment(int p1) { - return data.get(p1).getFragment(); + public int getItemCount() { + return data.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + public ViewHolder(@NonNull View itemView) { + super(itemView); + } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/completion/CodeCompletionState.kt b/app/src/main/java/com/tyron/code/ui/editor/completion/CodeCompletionState.kt new file mode 100644 index 000000000..0823ff6c8 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/completion/CodeCompletionState.kt @@ -0,0 +1,4 @@ +package com.tyron.code.ui.editor.completion + +class CodeCompletionState { +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/DefaultFileDocumentContentProvider.java b/app/src/main/java/com/tyron/code/ui/editor/impl/DefaultFileDocumentContentProvider.java new file mode 100644 index 000000000..c8be4785b --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/DefaultFileDocumentContentProvider.java @@ -0,0 +1,14 @@ +package com.tyron.code.ui.editor.impl; + +import com.tyron.code.ui.editor.impl.text.rosemoe.ContentWrapper; +import com.tyron.editor.Content; +import com.tyron.fileeditor.api.impl.FileDocumentContentProvider; + +import org.apache.commons.vfs2.FileObject; + +public class DefaultFileDocumentContentProvider implements FileDocumentContentProvider { + @Override + public Content createContent(CharSequence text, FileObject file) { + return new ContentWrapper(text); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorManagerImpl.java b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorManagerImpl.java index 30b5d3c6c..ed734b864 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorManagerImpl.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorManagerImpl.java @@ -3,9 +3,12 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentManager; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.tyron.code.R; +import com.tyron.code.ui.main.MainViewModel; +import com.tyron.common.ApplicationProvider; import com.tyron.fileeditor.api.FileEditor; import com.tyron.fileeditor.api.FileEditorManager; import com.tyron.fileeditor.api.FileEditorProvider; @@ -25,9 +28,60 @@ public static synchronized FileEditorManager getInstance() { return sInstance; } + private MainViewModel mViewModel; + private FragmentManager mFragmentManager; + + FileEditorManagerImpl() { + + } + + public void attach(MainViewModel mainViewModel, FragmentManager fragmentManager) { + mViewModel = mainViewModel; + mFragmentManager = fragmentManager; + } + @Override public void openFile(@NonNull Context context, File file, Consumer callback) { - FileEditor[] fileEditors = openFile(file, true); + checkAttached(); + + FileEditor[] fileEditors = getFileEditors(context, file); + openChooser(context, fileEditors, callback); + } + + @NonNull + @Override + public FileEditor[] openFile(@NonNull Context context, @NonNull File file, boolean focus) { + checkAttached(); + + FileEditor[] editors = getFileEditors(context, file); + openChooser(context, editors, this::openFileEditor); + return editors; + } + + @Override + public FileEditor[] getFileEditors(Context context, @NonNull File file) { + FileEditor[] editors; + FileEditorProvider[] providers = FileEditorProviderManagerImpl.getInstance().getProviders(file); + editors = new FileEditor[providers.length]; + for (int i = 0; i < providers.length; i++) { + FileEditor editor = providers[i].createEditor(context, file); + editors[i] = editor; + } + return editors; + } + + @Override + public void openFileEditor(@NonNull FileEditor fileEditor) { + mViewModel.openFile(fileEditor); + } + + @Override + public void closeFile(@NonNull File file) { + mViewModel.removeFile(file); + } + + @Override + public void openChooser(Context context, FileEditor[] fileEditors, Consumer callback) { if (fileEditors.length == 0) { return; } @@ -46,25 +100,14 @@ public void openFile(@NonNull Context context, File file, Consumer c } } - public FileEditorManagerImpl() { - + public FragmentManager getFragmentManager() { + checkAttached(); + return this.mFragmentManager; } - @NonNull - @Override - public FileEditor[] openFile(@NonNull File file, boolean focus) { - FileEditor[] editors; - FileEditorProvider[] providers = FileEditorProviderManagerImpl.getInstance().getProviders(file); - editors = new FileEditor[providers.length]; - for (int i = 0; i < providers.length; i++) { - FileEditor editor = providers[i].createEditor(file); - editors[i] = editor; + private void checkAttached() { + if (mViewModel == null || mFragmentManager == null) { + throw new IllegalStateException("File editor manager is not yet attached to a ViewModel"); } - return editors; - } - - @Override - public void closeFile(@NonNull File file) { - } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorProviderManagerImpl.java b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorProviderManagerImpl.java index 053af5e2c..44de150d6 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorProviderManagerImpl.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorProviderManagerImpl.java @@ -4,6 +4,7 @@ import com.tyron.code.ui.editor.impl.image.ImageEditorProvider; import com.tyron.code.ui.editor.impl.text.rosemoe.RosemoeEditorProvider; +import com.tyron.code.ui.editor.impl.text.squircle.SquircleEditorProvider; import com.tyron.code.ui.editor.impl.xml.LayoutTextEditorProvider; import com.tyron.fileeditor.api.FileEditorProvider; import com.tyron.fileeditor.api.FileEditorProviderManager; @@ -34,7 +35,6 @@ public FileEditorProviderManagerImpl() { private void registerBuiltInProviders() { registerProvider(new RosemoeEditorProvider()); - registerProvider(new LayoutTextEditorProvider()); registerProvider(new ImageEditorProvider()); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditor.java b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditor.java index bc45120bb..30c131a06 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditor.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditor.java @@ -26,9 +26,14 @@ protected ImageEditorFragment createFragment(@NonNull File file) { return ImageEditorFragment.newInstance(file); } +// @Override +// public Fragment getFragment() { +// return mFragment; +// } + @Override - public Fragment getFragment() { - return mFragment; + public View getView() { + return null; } @Override diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorProvider.java b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorProvider.java index db00b3e37..bb5d443ec 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorProvider.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorProvider.java @@ -1,5 +1,7 @@ package com.tyron.code.ui.editor.impl.image; +import android.content.Context; + import androidx.annotation.NonNull; import com.tyron.fileeditor.api.FileEditor; @@ -32,7 +34,7 @@ public boolean accept(@NonNull File file) { @NonNull @Override - public FileEditor createEditor(@NonNull File file) { + public FileEditor createEditor(@NonNull Context context, @NonNull File file) { return new ImageEditor(file, this); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorFragment.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorFragment.java index 36d85e1b6..81f2c1abc 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorFragment.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorFragment.java @@ -1,136 +1,170 @@ package com.tyron.code.ui.editor.impl.text.rosemoe; + +import static io.github.rosemoe.sora2.text.EditorUtil.getDefaultColorScheme; + +import android.annotation.SuppressLint; import android.content.SharedPreferences; import android.graphics.Color; import android.os.Bundle; -import android.view.LayoutInflater; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.ForwardingListener; +import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; -import androidx.preference.PreferenceManager; +import androidx.lifecycle.ViewModelStoreOwner; -import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.snackbar.Snackbar; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import com.tyron.actions.ActionManager; import com.tyron.actions.ActionPlaces; import com.tyron.actions.CommonDataKeys; import com.tyron.actions.DataContext; import com.tyron.actions.util.DataContextUtils; -import com.tyron.builder.compiler.manifest.xml.XmlFormatPreferences; -import com.tyron.builder.compiler.manifest.xml.XmlFormatStyle; -import com.tyron.builder.compiler.manifest.xml.XmlPrettyPrinter; import com.tyron.builder.log.LogViewModel; import com.tyron.builder.model.DiagnosticWrapper; import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.JavaModule; +import com.tyron.builder.project.api.FileManager; import com.tyron.builder.project.api.Module; +import com.tyron.builder.project.listener.FileListener; import com.tyron.code.ApplicationLoader; import com.tyron.code.R; +import com.tyron.code.analyzer.BaseTextmateAnalyzer; +import com.tyron.code.language.LanguageManager; +import com.tyron.code.language.java.JavaLanguage; +import com.tyron.code.language.textmate.EmptyTextMateLanguage; import com.tyron.code.ui.editor.CodeAssistCompletionAdapter; import com.tyron.code.ui.editor.CodeAssistCompletionLayout; +import com.tyron.code.ui.editor.EditorViewModel; import com.tyron.code.ui.editor.Savable; -import com.tyron.code.ui.editor.language.LanguageManager; -import com.tyron.code.ui.editor.language.java.JavaLanguage; -import com.tyron.code.ui.editor.language.kotlin.KotlinLanguage; -import com.tyron.code.ui.editor.language.xml.LanguageXML; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.editor.scheme.CompiledEditorScheme; import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; -import com.tyron.code.ui.layoutEditor.LayoutEditorFragment; import com.tyron.code.ui.main.MainViewModel; import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.ui.settings.EditorSettingsFragment; +import com.tyron.code.ui.theme.ThemeRepository; +import com.tyron.code.util.CoordinatePopupMenu; +import com.tyron.code.util.PopupMenuHelper; import com.tyron.common.SharedPreferenceKeys; -import com.tyron.completion.index.CompilerService; -import com.tyron.completion.java.JavaCompilerProvider; -import com.tyron.completion.java.compiler.JavaCompilerService; -import com.tyron.completion.java.compiler.ParseTask; -import com.tyron.completion.java.compiler.Parser; -import com.tyron.completion.java.action.CommonJavaContextKeys; -import com.tyron.completion.java.action.FindCurrentPath; -import com.tyron.completion.java.provider.CompletionEngine; -import com.tyron.completion.java.rewrite.AddImport; -import com.tyron.completion.java.util.ActionUtil; +import com.tyron.common.logging.IdeLog; +import com.tyron.common.util.AndroidUtilities; import com.tyron.completion.java.util.DiagnosticUtil; -import com.tyron.completion.model.CompletionItem; -import com.tyron.completion.model.TextEdit; +import com.tyron.completion.java.util.JavaDataContextUtil; import com.tyron.completion.progress.ProgressManager; -import com.tyron.editor.Editor; +import com.tyron.editor.CharPosition; import org.apache.commons.io.FileUtils; -import org.openjdk.source.util.TreePath; +import org.apache.commons.vfs2.FileContent; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.commons.vfs2.VFS; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Map; import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.logging.Logger; +import io.github.rosemoe.sora.event.ClickEvent; import io.github.rosemoe.sora.event.ContentChangeEvent; -import io.github.rosemoe.sora.event.EventReceiver; import io.github.rosemoe.sora.event.LongPressEvent; -import io.github.rosemoe.sora.event.Unsubscribe; import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.langs.textmate.TextMateColorScheme; +import io.github.rosemoe.sora.text.Content; import io.github.rosemoe.sora.text.Cursor; -import io.github.rosemoe.sora.widget.CodeEditor; import io.github.rosemoe.sora.widget.DirectAccessProps; -import io.github.rosemoe.sora.widget.component.DefaultCompletionLayout; import io.github.rosemoe.sora.widget.component.EditorAutoCompletion; import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; -import io.github.rosemoe.sora.widget.schemes.SchemeDarcula; +import io.github.rosemoe.sora2.text.EditorUtil; @SuppressWarnings("FieldCanBeLocal") public class CodeEditorFragment extends Fragment implements Savable, - SharedPreferences.OnSharedPreferenceChangeListener { + SharedPreferences.OnSharedPreferenceChangeListener, FileListener, + ProjectManager.OnProjectOpenListener { + + private static final Logger LOG = IdeLog.getCurrentLogger(CodeEditorFragment.class); + + public static final String KEY_LINE = "line"; + public static final String KEY_COLUMN = "column"; + public static final String KEY_PATH = "path"; + + public static CodeEditorFragment newInstance(File file) { + CodeEditorFragment fragment = new CodeEditorFragment(); + Bundle args = new Bundle(); + args.putString(KEY_PATH, file.getAbsolutePath()); + fragment.setArguments(args); + return fragment; + } + + /** + * Creates a new instance of the editor with the the cursor positioned at the given + * line and column + * + * @param file The file to be r ead + * @param line The 0-based line + * @param column The 0-based column + * @return The editor instance + */ + public static CodeEditorFragment newInstance(File file, int line, int column) { + CodeEditorFragment fragment = new CodeEditorFragment(); + Bundle args = new Bundle(); + args.putInt(KEY_LINE, line); + args.putInt(KEY_COLUMN, column); + args.putString(KEY_PATH, file.getAbsolutePath()); + fragment.setArguments(args); + return fragment; + } + /** + * Keys for saved states + */ private static final String EDITOR_LEFT_LINE_KEY = "line"; private static final String EDITOR_LEFT_COLUMN_KEY = "column"; private static final String EDITOR_RIGHT_LINE_KEY = "rightLine"; private static final String EDITOR_RIGHT_COLUMN_KEY = "rightColumn"; private CodeEditorView mEditor; -// private CodeEditorEventListener mEditorEventListener; private Language mLanguage; private File mCurrentFile = new File(""); private MainViewModel mMainViewModel; - private SharedPreferences mPreferences; - private boolean mCanSave; + private Bundle mSavedInstanceState; - public static CodeEditorFragment newInstance(File file) { - CodeEditorFragment fragment = new CodeEditorFragment(); - Bundle args = new Bundle(); - args.putString("path", file.getAbsolutePath()); - fragment.setArguments(args); - return fragment; + private boolean mCanSave = false; + private boolean mReading = false; + + private View.OnTouchListener mDragToOpenListener; + + public CodeEditorFragment() { + super(R.layout.code_editor_fragment); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - mCurrentFile = new File(requireArguments().getString("path", "")); + mCurrentFile = new File(requireArguments().getString(KEY_PATH, "")); mMainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); + mSavedInstanceState = savedInstanceState; } @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); - if (mCanSave) { - if (ProjectManager.getInstance().getCurrentProject() != null) { - ProjectManager.getInstance().getCurrentProject() - .getModule(mCurrentFile) - .getFileManager() - .setSnapshotContent(mCurrentFile, mEditor.getText().toString()); - } - } - Cursor cursor = mEditor.getCursor(); outState.putInt(EDITOR_LEFT_LINE_KEY, cursor.getLeftLine()); outState.putInt(EDITOR_LEFT_COLUMN_KEY, cursor.getLeftColumn()); @@ -141,94 +175,213 @@ public void onSaveInstanceState(@NonNull Bundle outState) { @Override public void onResume() { super.onResume(); - - if (!CompletionEngine.isIndexing()) { - analyze(); - } - - if (BottomSheetBehavior.STATE_HIDDEN == mMainViewModel.getBottomSheetState().getValue()) { - mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED); - } - - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - if (currentProject != null) { - Module module = currentProject.getModule(mCurrentFile); - Optional fileContent = - module.getFileManager().getFileContent(mCurrentFile); - if (fileContent.isPresent()) { - CharSequence content = fileContent.get(); - if (!content.equals(mEditor.getText())) { - int line = mEditor.getCursor().getLeftLine(); - int column = mEditor.getCursor().getLeftColumn(); - - int targetX = mEditor.getOffsetX(); - int targetY = mEditor.getOffsetY(); - - mEditor.setText(content); - - mEditor.setSelection(line, column, false); - - mEditor.getScroller().startScroll(mEditor.getOffsetX(), mEditor.getOffsetY(), - targetX - mEditor.getOffsetX(), targetY - mEditor.getOffsetY(), 0); - } - } - } } - public void hideEditorWindows() { -// mEditor.getTextActionPresenter().onExit(); mEditor.hideAutoCompleteWindow(); } - @Override - public void onStart() { - super.onStart(); + /** + * Return the {@link EditorColorScheme} from the specified path. + * If the path is null or does not exist, the default color scheme is returned + * depending on the state of the device's theme + * + * @param path The file path to color scheme json file + * @return The color scheme instance + */ + @NonNull + private ListenableFuture getScheme(@Nullable String path) { + if (path != null && new File(path).exists()) { + return EditorSettingsFragment.getColorScheme(new File(path)); + } else { + return Futures.immediateFailedFuture(new Throwable()); + } } + @SuppressLint("RestrictedApi") @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); - - View root = inflater.inflate(R.layout.code_editor_fragment, container, false); - - mEditor = root.findViewById(R.id.code_editor); - configure(mEditor.getProps()); - mEditor.setEditorLanguage(mLanguage = LanguageManager.getInstance().get(mEditor, mCurrentFile)); - SchemeDarcula scheme = new SchemeDarcula(); - scheme.setColor(EditorColorScheme.HTML_TAG, 0xFFF0C56C); - scheme.setColor(EditorColorScheme.ATTRIBUTE_NAME, 0xff9876AA); - scheme.setColor(EditorColorScheme.AUTO_COMP_PANEL_BG, 0xff2b2b2b); - scheme.setColor(EditorColorScheme.AUTO_COMP_PANEL_CORNER, 0xff575757); - mEditor.setColorScheme(scheme); - mEditor.setTextSize(Integer.parseInt(mPreferences.getString(SharedPreferenceKeys.FONT_SIZE, "12"))); - mEditor.openFile(mCurrentFile); - mEditor.getComponent(EditorAutoCompletion.class).setLayout(new CodeAssistCompletionLayout()); - mEditor.setAutoCompletionItemAdapter(new CodeAssistCompletionAdapter()); -// mEditor.setText(CodeEditor.TextActionMode.POPUP_WINDOW); - mEditor.setTypefaceText(ResourcesCompat.getFont(requireContext(), - R.font.jetbrains_mono_regular)); - mEditor.setLigatureEnabled(true); - mEditor.setHighlightCurrentBlock(true); - mEditor.setEdgeEffectColor(Color.TRANSPARENT); - mEditor.setWordwrap(mPreferences.getBoolean(SharedPreferenceKeys.EDITOR_WORDWRAP, false)); - mEditor.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); - if (mPreferences.getBoolean(SharedPreferenceKeys.KEYBOARD_ENABLE_SUGGESTIONS, false)) { - mEditor.setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mCanSave = false; + + mEditor = view.findViewById(R.id.code_editor); + mEditor.setEditable(false); + configureEditor(mEditor); + + View topView = view.findViewById(R.id.top_view); + EditorViewModel viewModel = + new ViewModelProvider((ViewModelStoreOwner) requireParentFragment()).get( + EditorViewModel.class); + viewModel.getAnalyzeState().observe(getViewLifecycleOwner(), analyzing -> { + if (analyzing) { + topView.setVisibility(View.VISIBLE); + } else { + topView.setVisibility(View.GONE); + } + }); + mEditor.setViewModel(viewModel); + ApplicationLoader.getDefaultPreferences().registerOnSharedPreferenceChangeListener(this); + + postConfigureEditor(); + + String schemeValue = ApplicationLoader.getDefaultPreferences() + .getString(SharedPreferenceKeys.SCHEME, null); + if (schemeValue != null && + new File(schemeValue).exists() && + ThemeRepository.getColorScheme(schemeValue) != null) { + TextMateColorScheme scheme = ThemeRepository.getColorScheme(schemeValue); + if (scheme != null) { + mEditor.setColorScheme(scheme); + initializeLanguage(); + mEditor.openFile(mCurrentFile); + readOrWait(); + } } else { - mEditor.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + ListenableFuture scheme = getScheme(schemeValue); + Futures.addCallback(scheme, new FutureCallback() { + @Override + public void onSuccess(@Nullable TextMateColorScheme result) { + if (getContext() == null) { + return; + } + assert result != null; + ThemeRepository.putColorScheme(schemeValue, result); + mEditor.setColorScheme(result); + + initializeLanguage(); + mEditor.openFile(mCurrentFile); + readOrWait(); + } + + @Override + public void onFailure(@NonNull Throwable t) { + if (getContext() == null) { + return; + } + String key = EditorUtil.isDarkMode( + requireContext()) ? ThemeRepository.DEFAULT_NIGHT : + ThemeRepository.DEFAULT_LIGHT; + TextMateColorScheme scheme = ThemeRepository.getColorScheme(key); + if (scheme == null) { + scheme = getDefaultColorScheme(requireContext()); + ThemeRepository.putColorScheme(key, scheme); + } + mEditor.setColorScheme(scheme); + initializeLanguage(); + mEditor.openFile(mCurrentFile); + readOrWait(); + } + }, ContextCompat.getMainExecutor(requireContext())); + } + } + + private void initializeLanguage() { + mLanguage = LanguageManager.getInstance().get(mEditor, mCurrentFile); + if (mLanguage == null) { + mLanguage = new EmptyTextMateLanguage(); } - mPreferences.registerOnSharedPreferenceChangeListener(this); - return root; + mEditor.setEditorLanguage(mLanguage); } - private void configure(DirectAccessProps props) { + private void configureEditor(@NonNull CodeEditorView editor) { + // do not allow the user to edit, since at the time this is called + // the contents may still be loading. + editor.setEditable(false); + editor.setColorScheme(new CompiledEditorScheme(requireContext())); + editor.setBackgroundAnalysisEnabled(false); + editor.setTypefaceText( + ResourcesCompat.getFont(requireContext(), R.font.jetbrains_mono_regular)); + editor.getComponent(EditorAutoCompletion.class).setLayout(new CodeAssistCompletionLayout()); + editor.setLigatureEnabled(true); + editor.setHighlightCurrentBlock(true); + editor.setEdgeEffectColor(Color.TRANSPARENT); + editor.openFile(mCurrentFile); + editor.setAutoCompletionItemAdapter(new CodeAssistCompletionAdapter()); + editor.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); + editor.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | + EditorInfo.TYPE_CLASS_TEXT | + EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | + EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + + SharedPreferences pref = ApplicationLoader.getDefaultPreferences(); + editor.setWordwrap(pref.getBoolean(SharedPreferenceKeys.EDITOR_WORDWRAP, false)); + editor.setTextSize(Integer.parseInt(pref.getString(SharedPreferenceKeys.FONT_SIZE, "12"))); + + DirectAccessProps props = editor.getProps(); props.overScrollEnabled = false; props.allowFullscreen = false; - props.deleteEmptyLineFast = false; + props.deleteEmptyLineFast = pref.getBoolean(SharedPreferenceKeys.DELETE_WHITESPACES, false); } + private void postConfigureEditor() { + // noinspection ClickableViewAccessibility + mEditor.setOnTouchListener((view12, motionEvent) -> { + if (mDragToOpenListener instanceof ForwardingListener) { + PopupMenuHelper.setForwarding((ForwardingListener) mDragToOpenListener); + // noinspection RestrictedApi + mDragToOpenListener.onTouch(view12, motionEvent); + } + return false; + }); + mEditor.subscribeEvent(LongPressEvent.class, (event, unsubscribe) -> { + event.intercept(); + + updateFile(mEditor.getText()); + Cursor cursor = mEditor.getCursor(); + if (cursor.isSelected()) { + int index = mEditor.getCharIndex(event.getLine(), event.getColumn()); + int cursorLeft = cursor.getLeft(); + int cursorRight = cursor.getRight(); + char c = mEditor.getText().charAt(index); + if (Character.isWhitespace(c)) { + mEditor.setSelection(event.getLine(), event.getColumn()); + } else if (index < cursorLeft || index > cursorRight) { + EditorUtil.selectWord(mEditor, event.getLine(), event.getColumn()); + } + } else { + char c = mEditor.getText().charAt(event.getIndex()); + if (!Character.isWhitespace(c)) { + EditorUtil.selectWord(mEditor, event.getLine(), event.getColumn()); + } else { + mEditor.setSelection(event.getLine(), event.getColumn()); + } + } + + ProgressManager.getInstance().runLater(() -> { + showPopupMenu(event); + }); + }); + mEditor.subscribeEvent(ClickEvent.class, (event, unsubscribe) -> { + Cursor cursor = mEditor.getCursor(); + if (mEditor.getCursor().isSelected()) { + int index = mEditor.getCharIndex(event.getLine(), event.getColumn()); + int cursorLeft = cursor.getLeft(); + int cursorRight = cursor.getRight(); + if (!EditorUtil.isWhitespace(mEditor.getText().charAt(index) + "") && + index >= cursorLeft && + index <= cursorRight) { + mEditor.showSoftInput(); + event.intercept(); + } + } + }); + mEditor.subscribeEvent(ContentChangeEvent.class, (event, unsubscribe) -> { + if (event.getAction() == ContentChangeEvent.ACTION_SET_NEW_TEXT) { + return; + } + updateFile(event.getEditor().getText()); + }); + + LogViewModel logViewModel = + new ViewModelProvider(requireActivity()).get(LogViewModel.class); + mEditor.setDiagnosticsListener(diagnostics -> { + for (DiagnosticWrapper diagnostic : diagnostics) { + DiagnosticUtil.setLineAndColumn(diagnostic, mEditor); + } + ProgressManager.getInstance() + .runLater(() -> logViewModel.updateLogs(LogViewModel.DEBUG, diagnostics)); + }); + } @Override public void onSharedPreferenceChanged(SharedPreferences pref, String key) { @@ -239,273 +392,301 @@ public void onSharedPreferenceChanged(SharedPreferences pref, String key) { case SharedPreferenceKeys.FONT_SIZE: mEditor.setTextSize(Integer.parseInt(pref.getString(key, "14"))); break; - case SharedPreferenceKeys.KEYBOARD_ENABLE_SUGGESTIONS: - if (pref.getBoolean(key, false)) { - mEditor.setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); - } else { - mEditor.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); - } - break; case SharedPreferenceKeys.EDITOR_WORDWRAP: mEditor.setWordwrap(pref.getBoolean(key, false)); break; + case SharedPreferenceKeys.DELETE_WHITESPACES: + mEditor.getProps().deleteEmptyLineFast = + pref.getBoolean(SharedPreferenceKeys.DELETE_WHITESPACES, false); + break; + case SharedPreferenceKeys.SCHEME: + ListenableFuture scheme = + getScheme(pref.getString(SharedPreferenceKeys.SCHEME, null)); + Futures.addCallback(scheme, new FutureCallback() { + @Override + public void onSuccess(@Nullable TextMateColorScheme result) { + if (getContext() == null) { + return; + } + assert result != null; + mEditor.setColorScheme(result); + if (mLanguage.getAnalyzeManager() instanceof BaseTextmateAnalyzer) { + ((BaseTextmateAnalyzer) mLanguage.getAnalyzeManager()).updateTheme( + result.getRawTheme()); + mLanguage.getAnalyzeManager().rerun(); + } + } + + @Override + public void onFailure(@NonNull Throwable t) { + if (getContext() == null) { + return; + } + mEditor.setColorScheme(getDefaultColorScheme(requireContext())); + mLanguage.getAnalyzeManager().rerun(); + } + }, ContextCompat.getMainExecutor(requireContext())); + break; } } + /** + * Show the popup menu with the actions api + */ + private void showPopupMenu(LongPressEvent event) { + MotionEvent e = event.getCausingEvent(); + CoordinatePopupMenu popupMenu = + new CoordinatePopupMenu(requireContext(), mEditor, Gravity.BOTTOM); + DataContext dataContext = createDataContext(); + ActionManager.getInstance() + .fillMenu(dataContext, popupMenu.getMenu(), ActionPlaces.EDITOR, true, false); + popupMenu.show((int) e.getX(), ((int) e.getY()) - AndroidUtilities.dp(24)); + + // we don't want to enable the drag to open listener right away, + // this may cause the buttons to be clicked right away + // so wait for a few ms + ProgressManager.getInstance().runLater(() -> { + popupMenu.setOnDismissListener(d -> mDragToOpenListener = null); + mDragToOpenListener = popupMenu.getDragToOpenListener(); + }, 300); + } + @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + public void onDestroyView() { + super.onDestroyView(); - Module module; Project currentProject = ProjectManager.getInstance().getCurrentProject(); if (currentProject != null) { - module = currentProject.getModule(mCurrentFile); - } else { - module = null; - } - if (mCurrentFile.exists()) { - String text; - try { - text = FileUtils.readFileToString(mCurrentFile, StandardCharsets.UTF_8); - mCanSave = true; - } catch (IOException e) { - text = "File does not exist: " + e.getMessage(); - mCanSave = false; - } + Module module = currentProject.getModule(mCurrentFile); if (module != null) { - module.getFileManager().openFileForSnapshot(mCurrentFile, text); + module.getFileManager().removeSnapshotListener(this); } - mEditor.setText(text); - } else { - mCanSave = false; } + ProjectManager.getInstance().removeOnProjectOpenListener(this); + } -// mEditorEventListener = new CodeEditorEventListener(module, mCurrentFile); -// mEditor.setEventListener(mEditorEventListener); -// mEditor.setOnCompletionItemSelectedListener((window, item) -> { -// Cursor cursor = mEditor.getCursor(); -// if (!cursor.isSelected()) { -// window.setCancelShowUp(true); -// -// int length = window.getLastPrefix().length(); -// if (mLanguage instanceof JavaLanguage || mLanguage instanceof KotlinLanguage) { -// if (window.getLastPrefix().contains(".")) { -// length -= window.getLastPrefix().lastIndexOf(".") + 1; -// } -// } - -// window.setSelectedItem(item.commit); -// cursor.onCommitMultilineText(item.commit); -// -// if (item.commit != null && item.cursorOffset != item.commit.length()) { -// int delta = (item.commit.length() - item.cursorOffset); -// int newSel = Math.max(mEditor.getCursor().getLeft() - delta, 0); -// CharPosition charPosition = -// mEditor.getCursor().getIndexer().getCharPosition(newSel); -// mEditor.setSelection(charPosition.line, charPosition.column); -// } -// -// if (item.item == null) { -// return; -// } -// -// if (item.item.additionalTextEdits != null) { -// for (TextEdit edit : item.item.additionalTextEdits) { -// window.applyTextEdit(edit); -// } -// } -// -// if (item.item.action == CompletionItem.Kind.IMPORT) { -// if (module instanceof JavaModule) { -// Parser parser = Parser.parseFile(currentProject, -// mEditor.getCurrentFile().toPath()); -// ParseTask task = new ParseTask(parser.task, parser.root); -// -// boolean samePackage = false; -// String packageName = task.root.getPackageName() == null ? "" : -// task.root.getPackageName().toString(); -// //it's either in the same class or it's already imported -// if (!item.item.data.contains(".") || packageName.equals(item.item.data.substring(0, item.item.data.lastIndexOf(".")))) { -// samePackage = true; -// } -// -// if (!samePackage && !ActionUtil.hasImport(task.root, item.item.data)) { -// AddImport imp = new AddImport(new File(""), item.item.data); -// Map edits = imp.getText(task); -// TextEdit edit = edits.values().iterator().next(); -// window.applyTextEdit(edit); -// } -// } -// } -// window.setCancelShowUp(false); -// } -// mEditor.postHideCompletionWindow(); -// }); - - mEditor.setOnCreateContextMenuListener((menu, view1, contextMenuInfo) -> { - menu.clear(); - - DataContext dataContext = DataContextUtils.getDataContext(view1); - dataContext.putData(CommonDataKeys.PROJECT, currentProject); - dataContext.putData(CommonDataKeys.FILE_EDITOR_KEY, - mMainViewModel.getCurrentFileEditor()); - dataContext.putData(CommonDataKeys.FILE, mCurrentFile); - dataContext.putData(CommonDataKeys.EDITOR, mEditor); - - if (currentProject != null) { - Module currentModule = currentProject.getModule(mCurrentFile); - if ((mLanguage instanceof JavaLanguage) && (currentModule instanceof JavaModule)) { - JavaCompilerProvider service = - CompilerService.getInstance().getIndex(JavaCompilerProvider.KEY); - JavaCompilerService compiler = service.getCompiler(currentProject, - (JavaModule) currentModule); - - compiler.compile(mCurrentFile.toPath()).run(task -> { - if (task != null) { - FindCurrentPath findCurrentPath = new FindCurrentPath(task.task); - TreePath currentPath = findCurrentPath.scan(task.root(), - (long) mEditor.getCursor().getLeft()); - dataContext.putData(CommonJavaContextKeys.CURRENT_PATH, currentPath); - } - }); - dataContext.putData(CommonJavaContextKeys.COMPILER, compiler); - } - } + @Override + public void onDestroy() { + super.onDestroy(); + if (ProjectManager.getInstance().getCurrentProject() != null && mCanSave) { + ProgressManager.getInstance().runNonCancelableAsync( + () -> ProjectManager.getInstance().getCurrentProject().getModule(mCurrentFile) + .getFileManager().closeFileForSnapshot(mCurrentFile)); + } + ApplicationLoader.getDefaultPreferences().unregisterOnSharedPreferenceChangeListener(this); + } - DiagnosticWrapper diagnosticWrapper = - DiagnosticUtil.getDiagnosticWrapper(mEditor.getDiagnostics(), - mEditor.getCursor().getLeft()); - if (diagnosticWrapper == null && mLanguage instanceof LanguageXML) { - diagnosticWrapper = - DiagnosticUtil.getXmlDiagnosticWrapper(mEditor.getDiagnostics(), - mEditor.getCursor().getLeftLine()); - } - dataContext.putData(CommonDataKeys.DIAGNOSTIC, diagnosticWrapper); + @Override + public void onPause() { + super.onPause(); - ActionManager.getInstance().fillMenu(dataContext, menu, ActionPlaces.EDITOR, true, - false); - }); - mEditor.subscribeEvent(LongPressEvent.class, (event, unsubscribe) -> { - MotionEvent e = event.getCausingEvent(); - // wait for the cursor to move - ProgressManager.getInstance().runLater(() -> { - event.getEditor().showContextMenu(e.getX(), e.getY()); - }); - }); - mEditor.subscribeEvent(ContentChangeEvent.class, (event, unsubscibe) -> { - updateFile(event.getEditor().getText()); - }); + hideEditorWindows(); - LogViewModel logViewModel = - new ViewModelProvider(requireActivity()).get(LogViewModel.class); + save(true); + } - mEditor.setDiagnosticsListener(diagnostics -> { - ProgressManager.getInstance().runLater(() -> { - logViewModel.updateLogs(LogViewModel.DEBUG, diagnostics); - }); - }); + @Override + public void onLowMemory() { + super.onLowMemory(); - getChildFragmentManager().setFragmentResultListener(LayoutEditorFragment.KEY_SAVE, - getViewLifecycleOwner(), ((requestKey, result) -> { - String xml = result.getString("text", mEditor.getText().toString()); - xml = XmlPrettyPrinter.prettyPrint(xml, XmlFormatPreferences.defaults(), - XmlFormatStyle.LAYOUT, "\n"); - mEditor.setText(xml); - })); + mEditor.setBackgroundAnalysisEnabled(false); + } - if (savedInstanceState != null) { - restoreState(savedInstanceState); + @Override + public void onSnapshotChanged(File file, CharSequence contents) { + if (mCurrentFile.equals(file)) { + if (mEditor != null) { + if (!mEditor.getText().toString().contentEquals(contents)) { + Cursor cursor = mEditor.getCursor(); + int left = cursor.getLeft(); + mEditor.setText(contents); + + if (left > contents.length()) { + left = contents.length(); + } + CharPosition position = mEditor.getCharPosition(left); + mEditor.setSelection(position.getLine(), position.getColumn()); + } + } } } - private void restoreState(@NonNull Bundle savedInstanceState) { - int leftLine = savedInstanceState.getInt(EDITOR_LEFT_LINE_KEY, 0); - int leftColumn = savedInstanceState.getInt(EDITOR_LEFT_COLUMN_KEY, 0); - int rightLine = savedInstanceState.getInt(EDITOR_RIGHT_LINE_KEY, 0); - int rightColumn = savedInstanceState.getInt(EDITOR_RIGHT_COLUMN_KEY, 0); + @Override + public boolean canSave() { + return mCanSave && !mReading; + } - if (leftLine != rightLine && leftColumn != rightColumn) { - mEditor.setSelectionRegion(leftLine, leftColumn, rightLine, rightColumn, true); + @Override + public void save(boolean toDisk) { + if (!mCanSave || mReading) { + return; + } + + // don't save if the file has been deleted externally but its still opened in the editor, + if (!mCurrentFile.exists()) { + return; + } + + if (ProjectManager.getInstance().getCurrentProject() != null && !toDisk) { + ProjectManager.getInstance().getCurrentProject().getModule(mCurrentFile) + .getFileManager() + .setSnapshotContent(mCurrentFile, mEditor.getText().toString(), false); } else { - mEditor.setSelection(leftLine, leftColumn); + ProgressManager.getInstance().runNonCancelableAsync(() -> { + try { + FileUtils.writeStringToFile(mCurrentFile, mEditor.getText().toString(), + StandardCharsets.UTF_8); + } catch (IOException e) { + LOG.severe("Unable to save file: " + + mCurrentFile.getAbsolutePath() + + "\n" + + "Reason: " + + e.getMessage()); + } + }); } } @Override - public void onDestroyView() { - super.onDestroyView(); - -// mEditorEventListener = null; - mEditor.setEditorLanguage(null); + public void onProjectOpen(Project project) { + ProgressManager.getInstance().runLater(() -> readFile(project, mSavedInstanceState)); } - @Override - public void onDestroy() { - super.onDestroy(); + /** + * Read the file immediately if there is a project open. If not, wait for the project + * to be opened first. + */ + private void readOrWait() { if (ProjectManager.getInstance().getCurrentProject() != null) { - ProjectManager.getInstance().getCurrentProject().getModule(mCurrentFile).getFileManager().closeFileForSnapshot(mCurrentFile); + readFile(ProjectManager.getInstance().getCurrentProject(), mSavedInstanceState); + } else { + ProjectManager.getInstance().addOnProjectOpenListener(this); } - mPreferences.unregisterOnSharedPreferenceChangeListener(this); } - @Override - public void onPause() { - super.onPause(); + private ListenableFuture readFile() { + return Futures.submitAsync(() -> { + FileSystemManager manager = VFS.getManager(); + FileObject fileObject = manager.resolveFile(mCurrentFile.toURI()); + FileContent content = fileObject.getContent(); + return Futures.immediateFuture(content.getString(StandardCharsets.UTF_8)); + }, Executors.newSingleThreadExecutor()); + } - hideEditorWindows(); + private void readFile(@NonNull Project currentProject, @Nullable Bundle savedInstanceState) { + mCanSave = false; + Module module = currentProject.getModule(mCurrentFile); + FileManager fileManager = module.getFileManager(); + fileManager.addSnapshotListener(this); - if (mCanSave) { - if (ProjectManager.getInstance().getCurrentProject() != null) { - ProjectManager.getInstance().getCurrentProject().getModule(mCurrentFile).getFileManager().setSnapshotContent(mCurrentFile, mEditor.getText().toString()); - } else { - try { - FileUtils.writeStringToFile(mCurrentFile, mEditor.getText().toString(), - StandardCharsets.UTF_8); - } catch (IOException e) { - // ignored + + // the file is already opened, so no need to load it. + if (fileManager.isOpened(mCurrentFile)) { + Optional contents = fileManager.getFileContent(mCurrentFile); + if (contents.isPresent()) { + mEditor.setText(contents.get()); + return; + } + } + + mReading = true; + mEditor.setBackgroundAnalysisEnabled(false); + ListenableFuture future = readFile(); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable String result) { + mReading = false; + if (getContext() == null) { + mCanSave = false; + return; } + if (mLanguage == null) { + return; + } + mCanSave = true; + mEditor.setBackgroundAnalysisEnabled(true); + mEditor.setEditable(true); + fileManager.openFileForSnapshot(mCurrentFile, result); + + Bundle bundle = new Bundle(); + bundle.putBoolean("loaded", true); + bundle.putBoolean("bg", true); + mEditor.setText(result, bundle); + + if (savedInstanceState != null) { + restoreState(savedInstanceState); + } else { + int line = requireArguments().getInt(KEY_LINE, 0); + int column = requireArguments().getInt(KEY_COLUMN, 0); + Content text = mEditor.getText(); + if (line < text.getLineCount() && column < text.getColumnCount(line)) { + setCursorPosition(line, column); + } + } + checkCanSave(); + } + + @Override + public void onFailure(@NonNull Throwable t) { + mCanSave = false; + mReading = false; + if (getContext() != null) { + checkCanSave(); + } + + LOG.severe("Unable to read current file: " + + mCurrentFile + + "\n" + + "Reason: " + + t.getMessage()); } + }, ContextCompat.getMainExecutor(requireContext())); + } + + private void checkCanSave() { + if (!mCanSave) { + Snackbar snackbar = + Snackbar.make(mEditor, R.string.editor_error_file, Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.menu_close, v -> FileEditorManagerImpl.getInstance() + .closeFile(mCurrentFile)); + ViewGroup snackbarView = (ViewGroup) snackbar.getView(); + AndroidUtilities.setMargins(snackbarView, 0, 0, 0, 50); + snackbar.show(); } } - private void updateFile(CharSequence contents) { - Project project = ProjectManager.getInstance().getCurrentProject(); - if (project == null) { + private void restoreState(@NonNull Bundle savedInstanceState) { + int leftLine = savedInstanceState.getInt(EDITOR_LEFT_LINE_KEY, 0); + int leftColumn = savedInstanceState.getInt(EDITOR_LEFT_COLUMN_KEY, 0); + int rightLine = savedInstanceState.getInt(EDITOR_RIGHT_LINE_KEY, 0); + int rightColumn = savedInstanceState.getInt(EDITOR_RIGHT_COLUMN_KEY, 0); + + Content text = mEditor.getText(); + if (leftLine > text.getLineCount() || rightLine > text.getLineCount()) { return; } - Module module = project.getModule(mCurrentFile); - if (module != null) { - module.getFileManager().setSnapshotContent(mCurrentFile, contents.toString()); + if (leftLine != rightLine && leftColumn != rightColumn) { + mEditor.setSelectionRegion(leftLine, leftColumn, rightLine, rightColumn, true); + } else { + mEditor.setSelection(leftLine, leftColumn); } } - @Override - public void save() { - if (!mCanSave) { + private void updateFile(CharSequence contents) { + Project project = ProjectManager.getInstance().getCurrentProject(); + if (project == null) { return; } - if (mCurrentFile.exists()) { - ProgressManager.getInstance().runNonCancelableAsync(() -> { - String oldContents = ""; - try { - oldContents = FileUtils.readFileToString(mCurrentFile, StandardCharsets.UTF_8); - } catch (IOException e) { - e.printStackTrace(); - } - if (oldContents.equals(mEditor.getText().toString())) { - return; - } - - try { - FileUtils.writeStringToFile(mCurrentFile, mEditor.getText().toString(), StandardCharsets.UTF_8); - } catch (IOException e) { - // ignored - } - }); + Module module = project.getModule(mCurrentFile); + if (module != null) { + if (!module.getFileManager().isOpened(mCurrentFile)) { + return; + } + module.getFileManager().setSnapshotContent(mCurrentFile, contents.toString(), this); } } - public Editor getEditor() { + public CodeEditorView getEditor() { return mEditor; } @@ -555,9 +736,7 @@ public void performShortcut(ShortcutItem item) { return; } for (ShortcutAction action : item.actions) { - if (action.isApplicable(item.kind)) { - // action.apply(mEditor, item); - } + action.apply(mEditor, item); } } @@ -580,71 +759,30 @@ public void format() { * Notifies the editor to analyze and highlight the current text */ public void analyze() { - if (mEditor != null) { + if (mEditor != null && !mReading) { mEditor.rerunAnalysis(); } } - @Override - public void onLowMemory() { - super.onLowMemory(); + /** + * Create the data context specific to this fragment for use with the actions API. + * + * @return the data context. + */ + private DataContext createDataContext() { + Project currentProject = ProjectManager.getInstance().getCurrentProject(); - mEditor.setBackgroundAnalysisEnabled(false); - } + DataContext dataContext = DataContextUtils.getDataContext(mEditor); + dataContext.putData(CommonDataKeys.PROJECT, currentProject); + dataContext.putData(CommonDataKeys.ACTIVITY, requireActivity()); + dataContext.putData(CommonDataKeys.FILE_EDITOR_KEY, mMainViewModel.getCurrentFileEditor()); + dataContext.putData(CommonDataKeys.FILE, mCurrentFile); + dataContext.putData(CommonDataKeys.EDITOR, mEditor); -// private static final class CodeEditorEventListener implements EditorEventListener { -// -// private final Module mModule; -// private final File mCurrentFile; -// -// public CodeEditorEventListener(Module module, File currentFile) { -// mModule = module; -// mCurrentFile = currentFile; -// } -// -// @Override -// public boolean onRequestFormat(@NonNull CodeEditor editor) { -// return false; -// } -// -// @Override -// public boolean onFormatFail(@NonNull CodeEditor editor, Throwable cause) { -// ApplicationLoader.showToast("Unable to format: " + cause.getMessage()); -// return false; -// } -// -// @Override -// public void onFormatSucceed(@NonNull CodeEditor editor) { -// -// } -// -// @Override -// public void onNewTextSet(@NonNull CodeEditor editor) { -// updateFile(editor.getText().toString()); -// } -// -// @Override -// public void afterDelete(@NonNull CodeEditor editor, @NonNull CharSequence content, -// int startLine, int startColumn, int endLine, int endColumn, -// CharSequence deletedContent) { -// updateFile(content); -// } -// -// @Override -// public void afterInsert(@NonNull CodeEditor editor, @NonNull CharSequence content, -// int startLine, int startColumn, int endLine, int endColumn, -// CharSequence insertedContent) { -// updateFile(content); -// } -// -// @Override -// public void beforeReplace(@NonNull CodeEditor editor, @NonNull CharSequence content) { -// updateFile(content); -// } -// -// @Override -// public void onSelectionChanged(@NonNull CodeEditor editor, @NonNull Cursor cursor) { -// -// } -// } + if (currentProject != null && mLanguage instanceof JavaLanguage) { + JavaDataContextUtil.addEditorKeys(dataContext, currentProject, mCurrentFile, + mEditor.getCursor().getLeft()); + } + return dataContext; + } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorView.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorView.java index 0d6f58ff8..d033c5cd5 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorView.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorView.java @@ -2,85 +2,140 @@ import android.content.Context; import android.graphics.Canvas; +import android.graphics.Paint; import android.util.AttributeSet; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.common.collect.ImmutableSet; import com.tyron.actions.DataContext; import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.code.ui.editor.CodeAssistCompletionAdapter; +import com.tyron.builder.project.Project; +import com.tyron.code.language.xml.LanguageXML; import com.tyron.code.ui.editor.CodeAssistCompletionWindow; +import com.tyron.code.ui.editor.EditorViewModel; import com.tyron.code.ui.editor.NoOpTextActionWindow; -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; -import com.tyron.code.ui.editor.language.DiagnosticAnalyzeManager; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.completion.xml.model.XmlCompletionType; +import com.tyron.completion.xml.util.XmlUtils; import com.tyron.editor.Caret; import com.tyron.editor.CharPosition; import com.tyron.editor.Content; import com.tyron.editor.Editor; +import com.tyron.xml.completion.util.DOMUtils; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMNode; +import org.eclipse.lemminx.dom.DOMParser; +import org.jetbrains.kotlin.com.intellij.util.ReflectionUtil; import java.io.File; -import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.List; +import java.util.Set; import java.util.function.Consumer; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.text.Cursor; +import io.github.rosemoe.sora.text.TextUtils; import io.github.rosemoe.sora.widget.CodeEditor; +import io.github.rosemoe.sora.widget.SymbolPairMatch; import io.github.rosemoe.sora.widget.component.EditorAutoCompletion; import io.github.rosemoe.sora.widget.component.EditorTextActionWindow; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; +import io.github.rosemoe.sora2.text.EditorUtil; public class CodeEditorView extends CodeEditor implements Editor { + private final Set IGNORED_PAIR_ENDS = ImmutableSet.builder() + .add(')') + .add(']') + .add('"') + .add('>') + .add('\'') + .add(';') + .build(); + private boolean mIsBackgroundAnalysisEnabled; private List mDiagnostics; private Consumer> mDiagnosticsListener; private File mCurrentFile; + private EditorViewModel mViewModel; + + private final Paint mDiagnosticPaint; + private CodeAssistCompletionWindow mCompletionWindow; public CodeEditorView(Context context) { - super(DataContext.wrap(context)); + this(DataContext.wrap(context), null); } public CodeEditorView(Context context, AttributeSet attrs) { - super(DataContext.wrap(context), attrs); - - init(); + this(DataContext.wrap(context), attrs, 0); } public CodeEditorView(Context context, AttributeSet attrs, int defStyleAttr) { - super(DataContext.wrap(context), attrs, defStyleAttr); - - init(); + this(DataContext.wrap(context), attrs, defStyleAttr, 0); } public CodeEditorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(DataContext.wrap(context), attrs, defStyleAttr, defStyleRes); + mDiagnosticPaint = new Paint(); + mDiagnosticPaint.setStrokeWidth(getDpUnit() * 2); + init(); } + @Nullable + @Override + public Project getProject() { + return ProjectManager.getInstance().getCurrentProject(); + } + + @Override + public void setEditorLanguage(@Nullable Language lang) { + super.setEditorLanguage(lang); + + if (lang != null) { + // languages should have an option to declare their own tab width + try { + Class extends Language> aClass = lang.getClass(); + Method method = ReflectionUtil.getDeclaredMethod(aClass, "getTabWidth"); + if (method != null) { + Object invoke = method.invoke(getEditorLanguage()); + if (invoke instanceof Integer) { + setTabWidth((Integer) invoke); + } + } + } catch (Throwable e) { + // use default + } + } + } + private void init() { - CodeAssistCompletionWindow window = - new CodeAssistCompletionWindow(this); - window.setAdapter(new CodeAssistCompletionAdapter()); - replaceComponent(EditorAutoCompletion.class, window); + setColorScheme(EditorUtil.getDefaultColorScheme(getContext())); replaceComponent(EditorTextActionWindow.class, new NoOpTextActionWindow(this)); } @Override - public List getDiagnostics() { - return mDiagnostics; + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + hideEditorWindows(); } + @Override + public void setColorScheme(@NonNull EditorColorScheme colors) { + super.setColorScheme(colors); + } + + @Override public void setDiagnostics(List diagnostics) { mDiagnostics = diagnostics; - - AnalyzeManager manager = getEditorLanguage().getAnalyzeManager(); - if (manager instanceof DiagnosticAnalyzeManager) { - ((DiagnosticAnalyzeManager>) manager).setDiagnostics(this, mDiagnostics); - ((DiagnosticAnalyzeManager>) manager).rerunWithoutBg(); - } - if (mDiagnosticsListener != null) { - mDiagnosticsListener.accept(mDiagnostics); - } } public void setDiagnosticsListener(Consumer> listener) { @@ -109,11 +164,137 @@ public int getCharIndex(int line, int column) { return getText().getCharIndex(line, column); } + @Override + public boolean useTab() { + //noinspection ConstantConditions, editor language can be null + if (getEditorLanguage() == null) { + // enabled by default + return true; + } + + return getEditorLanguage().useTab(); + } + + @Override + public int getTabCount() { + return getTabWidth(); + } + @Override public void insert(int line, int column, String string) { getText().insert(line, column, string); } + @Override + public void commitText(CharSequence text) { + super.commitText(text); + } + + @Override + public void commitText(CharSequence text, boolean applyAutoIndent) { + if (text.length() == 1) { + char currentChar = getText().charAt(getCursor().getLeft()); + char c = text.charAt(0); + if (IGNORED_PAIR_ENDS.contains(c) && c == currentChar) { + // ignored pair end, just move the cursor over the character + setSelection(getCursor().getLeftLine(), getCursor().getLeftColumn() + 1); + return; + } + } + super.commitText(text, applyAutoIndent); + + if (text.length() == 1) { + char c = text.charAt(0); + handleAutoInsert(c); + } + } + + private void handleAutoInsert(char c) { + if (getEditorLanguage() instanceof LanguageXML) { + try { + if (c != '>' && c != '/') { + return; + } + boolean full = c == '>'; + + DOMDocument document = DOMParser.getInstance().parse(getText().toString(), "", null); + DOMNode nodeAt = document.findNodeAt(getCursor().getLeft()); + if (!DOMUtils.isClosed(nodeAt) && nodeAt.getNodeName() != null) { + if (XmlUtils.getCompletionType(document, getCursor().getLeft()) == + XmlCompletionType.ATTRIBUTE_VALUE) { + return; + } + String insertText = full ? "" + nodeAt.getNodeName() + ">" : ">"; + commitText(insertText); + setSelection(getCursor().getLeftLine(), + getCursor().getLeftColumn() - (full ? insertText.length() : 0)); + } + } catch (Exception e) { + // ignored, just dont auto insert + } + } + } + + @Override + public void deleteText() { + Cursor cursor = getCursor(); + if (!cursor.isSelected()) { + io.github.rosemoe.sora.text.Content text = getText(); + int startIndex = cursor.getLeft(); + if (startIndex - 1 >= 0) { + char deleteChar = text.charAt(startIndex - 1); + char afterChar = text.charAt(startIndex); + SymbolPairMatch.Replacement replacement = null; + + SymbolPairMatch pairs = getEditorLanguage().getSymbolPairs(); + if (pairs != null) { + replacement = pairs.getCompletion(deleteChar); + } + if (replacement != null) { + if (("" + deleteChar + afterChar + "").equals(replacement.text)) { + text.delete(startIndex - 1, startIndex + 1); + return; + } + } + } + } + super.deleteText(); + } + + @Override + public void insertMultilineString(int line, int column, String string) { + String currentLine = getText().getLineString(line); + + String[] lines = string.split("\\n"); + if (lines.length == 0) { + return; + } + int count = TextUtils.countLeadingSpaceCount(currentLine, getTabWidth()); + for (int i = 0; i < lines.length; i++) { + String trimmed = lines[i].trim(); + + int advance = EditorUtil.getFormatIndent(getEditorLanguage(), trimmed); + + if (advance < 0) { + count += advance; + } + + if (i != 0) { + String indent = TextUtils.createIndent(count, getTabWidth(), useTab()); + trimmed = indent + trimmed; + } + + lines[i] = trimmed; + + if (advance > 0) { + count += advance; + } + } + + String textToInsert = String.join("\n", lines); + getText().insert(line, column, textToInsert); + } + @Override public void delete(int startLine, int startColumn, int endLine, int endColumn) { getText().delete(startLine, startColumn, endLine, endColumn); @@ -139,6 +320,16 @@ public void setSelectionRegion(int lineLeft, int columnLeft, int lineRight, int CodeEditorView.super.setSelectionRegion(lineLeft, columnLeft, lineRight, columnRight); } + @Override + public void setSelectionRegion(int startIndex, int endIndex) { + CharPosition start = getCharPosition(startIndex); + CharPosition end = getCharPosition(endIndex); + CodeEditorView.super.setSelectionRegion(start.getLine(), + start.getColumn(), + end.getLine(), + end.getColumn()); + } + @Override public void beginBatchEdit() { getText().beginBatchEdit(); @@ -155,9 +346,7 @@ public synchronized boolean formatCodeAsync() { } @Override - public synchronized boolean formatCodeAsync(int start, int end) { -// CodeEditorView.super.formatCodeAsync(); -// return CodeEditorView.super.formatCodeAsync(start, end); + public boolean formatCodeAsync(int startIndex, int endIndex) { return false; } @@ -168,7 +357,7 @@ public Caret getCaret() { @Override public Content getContent() { - return new ContentWrapper(CodeEditorView.this.getText()); + return (Content) getText(); } /** @@ -178,4 +367,29 @@ public Content getContent() { public void setBackgroundAnalysisEnabled(boolean enabled) { mIsBackgroundAnalysisEnabled = enabled; } + + @Override + public boolean isBackgroundAnalysisEnabled() { + return mIsBackgroundAnalysisEnabled; + } + + public void setAnalyzing(boolean analyzing) { + if (mViewModel != null) { + mViewModel.setAnalyzeState(analyzing); + } + } + + @Override + public void requireCompletion() { + getComponent(EditorAutoCompletion.class).requireCompletion(); + } + + public void setViewModel(EditorViewModel editorViewModel) { + mViewModel = editorViewModel; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/ContentWrapper.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/ContentWrapper.java index 6b62e4fbb..018ab8259 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/ContentWrapper.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/ContentWrapper.java @@ -3,65 +3,160 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.tyron.editor.Content; +import com.google.common.collect.Maps; +import com.tyron.editor.AbstractContent; +import com.tyron.editor.event.ContentEvent; +import com.tyron.editor.event.ContentListener; +import com.tyron.editor.event.impl.ContentEventImpl; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; -public class ContentWrapper implements Content { +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.Content; - private final io.github.rosemoe.sora.text.Content mContent; +public class ContentWrapper extends Content implements com.tyron.editor.Content { - public ContentWrapper(io.github.rosemoe.sora.text.Content content) { - mContent = content; + private AtomicInteger sequence; + + private boolean hasCalledSuper = false; + + public ContentWrapper() { + + } + + public ContentWrapper(CharSequence text) { + super(text, true); + + hasCalledSuper = true; + } + + private long modificationStamp = 0; + private final Map dataMap = Maps.newConcurrentMap(); + private final List contentListeners = new CopyOnWriteArrayList<>(); + + @Override + public void insert(int index, CharSequence text) { + CharPosition pos = getIndexer().getCharPosition(index); + insert(pos.line, pos.column, text); + } + + @Override + public void insert(int line, int column, CharSequence text) { + super.insert(line, column, text); + + if (!hasCalledSuper) { + return; + } + int offset = getCharIndex(line, column); + Content newText = this; + CharSequence newString = newText.subSequence(offset, offset + text.length()); + updateText(newText, offset, "", newString, false, System.currentTimeMillis(), offset, 0, + offset); } @Override - public int length() { - return mContent.length(); + public void replace(int start, int end, CharSequence text) { + CharPosition startPos = getIndexer().getCharPosition(start); + CharPosition endPos = getIndexer().getCharPosition(end); + replace(startPos.line, startPos.column, endPos.line, endPos.column, text); } @Override - public char charAt(int index) { - return mContent.charAt(index); + public void delete(int start, int end) { + CharPosition startPos = getIndexer().getCharPosition(start); + CharPosition endPos = getIndexer().getCharPosition(end); + delete(startPos.line, startPos.column, endPos.line, endPos.column); } - @NonNull @Override - public CharSequence subSequence(int start, int end) { - return mContent.subSequence(start, end); + public void delete(int startLine, int columnOnStartLine, int endLine, int columnOnEndLine) { + // need to get the offset before deleting since the end offset will be invalid + // if it has been deleted before + int startOffset = getCharIndex(startLine, columnOnStartLine); + int endOffset = getCharIndex(endLine, columnOnEndLine); + CharSequence oldString = subSequence(startOffset, endOffset); + + super.delete(startLine, columnOnStartLine, endLine, columnOnEndLine); + + if (!hasCalledSuper) { + return; + } + + Content newText = this; + updateText(newText, startOffset, oldString, "", false, System.currentTimeMillis(), + startOffset, endOffset - startOffset, startOffset); + } + + private AtomicInteger getSequence() { + if (sequence == null) { + sequence = new AtomicInteger(0); + } + return sequence; + } + + protected void updateText(@NonNull CharSequence text, + int offset, + @NonNull CharSequence oldString, + @NonNull CharSequence newString, + boolean wholeTextReplaced, + long newModificationStamp, + int initialStartOffset, + int initialOldLength, + int moveOffset) { + assert moveOffset >= 0 && moveOffset <= length() : "Invalid moveOffset: " + moveOffset; + ContentEvent event = + new ContentEventImpl(this, offset, oldString, newString, modificationStamp, + wholeTextReplaced, initialStartOffset, initialOldLength, moveOffset); + getSequence().incrementAndGet(); + + CharSequence prevText = this; + changedUpdate(event, newModificationStamp, prevText); + } + + protected void changedUpdate(@NonNull ContentEvent event, + long newModificationStamp, + @NonNull CharSequence prevText) { +// assert event.getOldFragment().length() == event.getOldLength(); +// assert event.getNewFragment().length() == event.getNewLength(); + if (contentListeners == null) { + return; + } + for (ContentListener contentListener : contentListeners) { + contentListener.contentChanged(event); + } } - @NonNull @Override - public IntStream chars() { - return mContent.chars(); + public void setData(String key, Object object) { + dataMap.put(key, object); } - @NonNull @Override - public IntStream codePoints() { - return mContent.codePoints(); + public Object getData(String key) { + return dataMap.get(key); } @Override - public int hashCode() { - return mContent.hashCode(); + public void addContentListener(ContentListener listener) { + contentListeners.add(listener); } - @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override - public boolean equals(@Nullable Object obj) { - return mContent.equals(obj); + public void removeContentListener(ContentListener listener) { + contentListeners.remove(listener); } - @NonNull @Override - public String toString() { - return mContent.toString(); + public void setModificationStamp(long stamp) { + modificationStamp = stamp; } @Override - public String getLineString(int line) { - return mContent.getLineString(line); + public long getModificationStamp() { + return modificationStamp; } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CursorWrapper.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CursorWrapper.java index 4f95d1864..5ad87a578 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CursorWrapper.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CursorWrapper.java @@ -42,4 +42,9 @@ public int getEndLine() { public int getEndColumn() { return mCursor.getRightColumn(); } + + @Override + public boolean isSelected() { + return mCursor.isSelected(); + } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeCodeEditor.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeCodeEditor.java index 833478e0a..ef7a2c731 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeCodeEditor.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeCodeEditor.java @@ -1,12 +1,20 @@ package com.tyron.code.ui.editor.impl.text.rosemoe; +import android.content.Context; import android.view.View; import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelStore; +import androidx.lifecycle.ViewModelStoreOwner; +import com.tyron.editor.Content; +import com.tyron.fileeditor.api.FileDocumentManager; import com.tyron.fileeditor.api.TextEditor; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.VFS; + import java.io.File; import java.util.Objects; @@ -14,26 +22,32 @@ public class RosemoeCodeEditor implements TextEditor { private final File mFile; private final RosemoeEditorProvider mProvider; - private final CodeEditorFragment mFragment; + private final RosemoeEditorFacade mEditor; - public RosemoeCodeEditor(File file, RosemoeEditorProvider provider) { + public RosemoeCodeEditor(Context context, File file, RosemoeEditorProvider provider) { mFile = file; mProvider = provider; - mFragment = createFragment(file); + try { + mEditor = createEditor(context, VFS.getManager().resolveFile(file.toURI())); + } catch (FileSystemException e) { + throw new RuntimeException(e); + } } - protected CodeEditorFragment createFragment(File file) { - return CodeEditorFragment.newInstance(file); + @Override + public Content getContent() { + return mEditor.getContent(); } + @Override - public Fragment getFragment() { - return mFragment; + public View getView() { + return mEditor.getView(); } @Override public View getPreferredFocusedView() { - return mFragment.getView(); + return mEditor.getView(); } @NonNull @@ -44,12 +58,13 @@ public String getName() { @Override public boolean isModified() { - return false; + FileDocumentManager instance = FileDocumentManager.getInstance(); + return instance.isContentUnsaved(getContent()); } @Override public boolean isValid() { - return true; + return mFile.exists(); } @Override @@ -69,4 +84,13 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(mFile); } + + private RosemoeEditorFacade createEditor(Context context, FileObject file) { + try { + Content content = FileDocumentManager.getInstance().getContent(file); + return new RosemoeEditorFacade(this, context, content, file); + } catch (FileSystemException e) { + throw new RuntimeException(e); + } + } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorFacade.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorFacade.java new file mode 100644 index 000000000..aa623e662 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorFacade.java @@ -0,0 +1,314 @@ +package com.tyron.code.ui.editor.impl.text.rosemoe; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Bundle; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.FrameLayout; + +import androidx.appcompat.widget.ForwardingListener; +import androidx.core.content.res.ResourcesCompat; + +import com.tyron.actions.ActionManager; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.util.DataContextUtils; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.R; +import com.tyron.code.event.EventManager; +import com.tyron.code.event.PerformShortcutEvent; +import com.tyron.code.language.LanguageManager; +import com.tyron.code.language.java.JavaLanguage; +import com.tyron.code.ui.editor.CodeAssistCompletionAdapter; +import com.tyron.code.ui.editor.CodeAssistCompletionWindow; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.util.CoordinatePopupMenu; +import com.tyron.code.util.PopupMenuHelper; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.common.util.DebouncerStore; +import com.tyron.completion.java.util.JavaDataContextUtil; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.diagnostics.DiagnosticProvider; +import com.tyron.editor.Content; +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.language.api.CodeAssistLanguage; + +import org.apache.commons.vfs2.FileObject; +import org.jetbrains.kotlin.com.intellij.util.ReflectionUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.function.Function; + +import javax.tools.Diagnostic; + +import io.github.rosemoe.sora.event.ClickEvent; +import io.github.rosemoe.sora.event.ContentChangeEvent; +import io.github.rosemoe.sora.event.EditorKeyEvent; +import io.github.rosemoe.sora.event.Event; +import io.github.rosemoe.sora.event.InterceptTarget; +import io.github.rosemoe.sora.event.LongPressEvent; +import io.github.rosemoe.sora.lang.EmptyLanguage; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.diagnostic.DiagnosticRegion; +import io.github.rosemoe.sora.lang.diagnostic.DiagnosticsContainer; +import io.github.rosemoe.sora.text.Cursor; +import io.github.rosemoe.sora.widget.component.EditorAutoCompletion; +import io.github.rosemoe.sora2.text.EditorUtil; + +public class RosemoeEditorFacade { + + private static final Logger LOGGER = LoggerFactory.getLogger(RosemoeEditorFacade.class); + + private final FileEditor fileEditor; + private final Content content; + private final FrameLayout container; + private final CodeEditorView editor; + + private View.OnTouchListener dragToOpenListener; + + RosemoeEditorFacade(RosemoeCodeEditor rosemoeCodeEditor, + Context context, + Content content, + FileObject file) { + this.fileEditor = rosemoeCodeEditor; + this.content = content; + + container = new FrameLayout(context); + + editor = new CodeEditorView(context); + configureEditor(editor, file); + container.addView(editor); + + + EventManager eventManager = ApplicationLoader.getInstance().getEventManager(); + eventManager.subscribeEvent(PerformShortcutEvent.class, (event, unsubscribe) -> { + if (event.getEditor() == rosemoeCodeEditor) { + event.getItem().actions.forEach(it -> it.apply(editor, event.getItem())); + } + }); + } + + /** + * Background updates + * + * @param content the current content when the update is called + */ + private void onContentChange(Content content) { + Language language = editor.getEditorLanguage(); + File currentFile = editor.getCurrentFile(); + Project project = editor.getProject(); + if (project == null) { + return; + } + Module module = project.getModule(currentFile); + if (module == null) { + return; + } + + if (language instanceof CodeAssistLanguage) { + ((CodeAssistLanguage) language).onContentChange(currentFile, content); + } + + Objects.requireNonNull(editor.getDiagnostics()).reset(); + + ServiceLoader providers = ServiceLoader.load(DiagnosticProvider.class); + for (DiagnosticProvider provider : providers) { + List extends Diagnostic>> diagnostics = + provider.getDiagnostics(module, currentFile); + Function severitySupplier = it -> { + switch (it) { + case ERROR: + return DiagnosticRegion.SEVERITY_ERROR; + case MANDATORY_WARNING: + case WARNING: + return DiagnosticRegion.SEVERITY_WARNING; + default: + case OTHER: + case NOTE: + return DiagnosticRegion.SEVERITY_NONE; + } + }; + diagnostics.stream() + .map(it -> new DiagnosticRegion((int) it.getStartPosition(), + (int) it.getEndPosition(), + severitySupplier.apply(it.getKind()))) + .forEach(Objects.requireNonNull(editor.getDiagnostics())::addDiagnostic); + } + } + + @SuppressLint("ClickableViewAccessibility") + private void configureEditor(CodeEditorView editor, FileObject file) { + Language language = LanguageManager.getInstance().get(editor, file); + if (language == null) { + language = new EmptyLanguage(); + } + editor.setEditorLanguage(language); + + // only used for checking the extension + editor.openFile(file.getPath().toFile()); + Bundle bundle = new Bundle(); + bundle.putBoolean("loaded", true); + bundle.putBoolean("bg", true); + editor.setText(content, bundle); + editor.setHighlightBracketPair(false); + editor.setDiagnostics(new DiagnosticsContainer()); + editor.setTypefaceText(ResourcesCompat.getFont(editor.getContext(), + R.font.jetbrains_mono_regular)); + + editor.replaceComponent(EditorAutoCompletion.class, new CodeAssistCompletionWindow(editor)); + editor.setAutoCompletionItemAdapter(new CodeAssistCompletionAdapter()); + editor.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); + editor.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | + EditorInfo.TYPE_CLASS_TEXT | + EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | + EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + + editor.setOnTouchListener((v, motionEvent) -> { + if (dragToOpenListener instanceof ForwardingListener) { + PopupMenuHelper.setForwarding((ForwardingListener) dragToOpenListener); + // noinspection RestrictedApi + dragToOpenListener.onTouch(v, motionEvent); + } + return false; + }); + editor.subscribeEvent(LongPressEvent.class, (event, unsubscribe) -> { + event.intercept(); + + Cursor cursor = editor.getCursor(); + if (cursor.isSelected()) { + int index = editor.getCharIndex(event.getLine(), event.getColumn()); + int cursorLeft = cursor.getLeft(); + int cursorRight = cursor.getRight(); + char c = editor.getText().charAt(index); + if (Character.isWhitespace(c)) { + editor.setSelection(event.getLine(), event.getColumn()); + } else if (index < cursorLeft || index > cursorRight) { + EditorUtil.selectWord(editor, event.getLine(), event.getColumn()); + } + } else { + char c = editor.getText().charAt(event.getIndex()); + if (!Character.isWhitespace(c)) { + EditorUtil.selectWord(editor, event.getLine(), event.getColumn()); + } else { + editor.setSelection(event.getLine(), event.getColumn()); + } + } + + ProgressManager.getInstance().runLater(() -> showPopupMenu(event)); + }); + editor.subscribeEvent(ClickEvent.class, (event, unsubscribe) -> { + Cursor cursor = editor.getCursor(); + if (editor.getCursor().isSelected()) { + int index = editor.getCharIndex(event.getLine(), event.getColumn()); + int cursorLeft = cursor.getLeft(); + int cursorRight = cursor.getRight(); + if (!EditorUtil.isWhitespace(editor.getText().charAt(index) + "") && + index >= cursorLeft && + index <= cursorRight) { + editor.showSoftInput(); + event.intercept(); + } + } + }); + + editor.subscribeEvent(EditorKeyEvent.class, (event, unsubscribe) -> { + CodeAssistCompletionWindow window = + (CodeAssistCompletionWindow) editor.getComponent(EditorAutoCompletion.class); + if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER || + event.getKeyCode() == KeyEvent.KEYCODE_TAB) { + if (window.isShowing() && window.trySelect()) { + event.setResult(true); + + // KeyEvent cannot be intercepted??? + // workaround + Field mInterceptTargets = + ReflectionUtil.getDeclaredField(Event.class, "mInterceptTargets"); + mInterceptTargets.setAccessible(true); + try { + mInterceptTargets.set(event, InterceptTarget.TARGET_EDITOR); + } catch (IllegalAccessException e) { + throw new RuntimeException("REFLECTION FAILED"); + } + + editor.requestFocus(); + } + } + }); + editor.subscribeEvent(ContentChangeEvent.class, + (event, unsubscribe) -> ProgressManager.getInstance() + .runNonCancelableAsync(() -> DebouncerStore.DEFAULT.registerOrGetDebouncer( + "contentChange").debounce(300, () -> { + try { + onContentChange(editor.getContent()); + } catch (Throwable t) { + LOGGER.error("Error in onContentChange", t); + } + }))); + } + + /** + * Show the popup menu with the actions api + */ + private void showPopupMenu(LongPressEvent event) { + MotionEvent e = event.getCausingEvent(); + CoordinatePopupMenu popupMenu = + new CoordinatePopupMenu(editor.getContext(), editor, Gravity.BOTTOM); + DataContext dataContext = createDataContext(); + ActionManager.getInstance() + .fillMenu(dataContext, popupMenu.getMenu(), ActionPlaces.EDITOR, true, false); + popupMenu.show((int) e.getX(), ((int) e.getY()) - AndroidUtilities.dp(24)); + + // we don't want to enable the drag to open listener right away, + // this may cause the buttons to be clicked right away + // so wait for a few ms + ProgressManager.getInstance().runLater(() -> { + popupMenu.setOnDismissListener(d -> dragToOpenListener = null); + dragToOpenListener = popupMenu.getDragToOpenListener(); + }, 300); + } + + /** + * Create the data context specific to this fragment for use with the actions API. + * + * @return the data context. + */ + private DataContext createDataContext() { + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + + DataContext dataContext = DataContextUtils.getDataContext(editor); + dataContext.putData(CommonDataKeys.PROJECT, currentProject); + dataContext.putData(CommonDataKeys.FILE_EDITOR_KEY, fileEditor); + dataContext.putData(CommonDataKeys.FILE, editor.getCurrentFile()); + dataContext.putData(CommonDataKeys.EDITOR, editor); + + if (currentProject != null && editor.getEditorLanguage() instanceof JavaLanguage) { + JavaDataContextUtil.addEditorKeys(dataContext, + currentProject, + editor.getCurrentFile(), + editor.getCursor().getLeft()); + } + return dataContext; + } + + public View getView() { + return container; + } + + public Content getContent() { + return content; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorProvider.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorProvider.java index 828064eec..8946403d9 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorProvider.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorProvider.java @@ -1,25 +1,41 @@ package com.tyron.code.ui.editor.impl.text.rosemoe; +import android.content.Context; + import androidx.annotation.NonNull; +import com.google.common.collect.ImmutableSet; import com.tyron.fileeditor.api.FileEditor; import com.tyron.fileeditor.api.FileEditorProvider; import java.io.File; +import java.util.Set; + +import kotlin.io.FilesKt; public class RosemoeEditorProvider implements FileEditorProvider { + private static final Set NON_TEXT_FILES = ImmutableSet.builder() + .add("jar", "zip", "png", "jpg") + .add("jpeg", "mp4", "mp3", "ogg") + .add("7zip", "tar") + .build(); private static final String TYPE_ID = "rosemoe-code-editor"; @Override public boolean accept(@NonNull File file) { + boolean nonText = NON_TEXT_FILES.stream() + .anyMatch(it -> FilesKt.getExtension(file).endsWith(it)); + if (nonText) { + return false; + } return file.exists() && !file.isDirectory(); } @NonNull @Override - public FileEditor createEditor(@NonNull File file) { - return new RosemoeCodeEditor(file, this); + public FileEditor createEditor(@NonNull Context context, @NonNull File file) { + return new RosemoeCodeEditor(context, file, this); } @NonNull diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/window/ActionsWindow.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/window/ActionsWindow.java new file mode 100644 index 000000000..c98d5160f --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/window/ActionsWindow.java @@ -0,0 +1,43 @@ +package com.tyron.code.ui.editor.impl.text.rosemoe.window; + +import android.annotation.SuppressLint; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; +import android.widget.PopupWindow; + +import androidx.appcompat.view.menu.ListMenuPresenter; +import androidx.appcompat.view.menu.MenuBuilder; +import androidx.appcompat.view.menu.MenuPopupHelper; +import androidx.appcompat.view.menu.MenuView; +import androidx.appcompat.widget.ActionMenuView; +import androidx.appcompat.widget.ListPopupWindow; +import androidx.appcompat.widget.MenuPopupWindow; + +import com.tyron.actions.ActionManager; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.util.DataContextUtils; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; + +import io.github.rosemoe.sora.widget.CodeEditor; +import io.github.rosemoe.sora.widget.component.EditorTextActionWindow; + +@SuppressLint("RestrictedApi") +public class ActionsWindow extends EditorTextActionWindow { + + /** + * Create a panel for the given editor + * + * @param editor Target editor + */ + public ActionsWindow(CodeEditor editor) { + super(editor); + } + + @Override + public void unregister() { + super.unregister(); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/ContentImpl.kt b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/ContentImpl.kt new file mode 100644 index 000000000..20efc543c --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/ContentImpl.kt @@ -0,0 +1,108 @@ +package com.tyron.code.ui.editor.impl.text.squircle + +import android.text.Editable +import android.text.InputFilter +import android.text.SpannableStringBuilder +import com.blacksquircle.ui.editorkit.plugin.base.EditorPlugin +import com.tyron.editor.Content +import com.tyron.editor.event.ContentListener + +class ContentImpl( + private val baseContent: Content +) : EditorPlugin("content"), Editable { + + val editable = SpannableStringBuilder() + + override fun get(index: Int): Char { + return editable[index] + } + + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { + return editable.subSequence(startIndex, endIndex) + } + + override fun getChars(p0: Int, p1: Int, p2: CharArray?, p3: Int) { + return editable.getChars(p0, p1, p2, p3) + } + + override fun getSpans(p0: Int, p1: Int, p2: Class?): Array { + return editable.getSpans(p0, p1, p2) + } + + override fun getSpanStart(p0: Any?): Int { + return editable.getSpanStart(p0) + } + + override fun getSpanEnd(p0: Any?): Int { + return editable.getSpanEnd(p0) + } + + override fun getSpanFlags(p0: Any?): Int { + return editable.getSpanFlags(p0) + } + + override fun nextSpanTransition(p0: Int, p1: Int, p2: Class<*>?): Int { + return editable.nextSpanTransition(p0, p1, p2) + } + + override fun setSpan(p0: Any?, p1: Int, p2: Int, p3: Int) { + editable.setSpan(p0, p1, p2, p3) + } + + override fun removeSpan(p0: Any?) { + editable.removeSpan(p0) + } + + override fun append(p0: CharSequence?): Editable { + return editable.append(p0) + } + + override fun append(p0: CharSequence?, p1: Int, p2: Int): Editable { + return editable.append(p0, p1, p2) + } + + override fun append(p0: Char): Editable { + return editable.append(p0) + } + + override fun replace(p0: Int, p1: Int, p2: CharSequence?, p3: Int, p4: Int): Editable { + return this + } + + override fun replace(p0: Int, p1: Int, p2: CharSequence?): Editable { + TODO("Not yet implemented") + } + + override fun insert(p0: Int, p1: CharSequence?, p2: Int, p3: Int): Editable { + TODO("Not yet implemented") + } + + override fun insert(p0: Int, p1: CharSequence?): Editable { + TODO("Not yet implemented") + } + + override fun delete(p0: Int, p1: Int): Editable { + return editable.delete(p0, p1) + } + + override fun clear() { + editable.clear() + } + + override fun clearSpans() { + editable.clearSpans() + } + + override fun setFilters(p0: Array?) { + editable.filters = p0 + } + + override fun getFilters(): Array { + return editable.filters + } + + override val length: Int + get() = editable.length + + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditor.kt b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditor.kt new file mode 100644 index 000000000..f4f39aa92 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditor.kt @@ -0,0 +1,46 @@ +package com.tyron.code.ui.editor.impl.text.squircle + +import android.content.Context +import android.view.View +import com.blacksquircle.ui.editorkit.widget.TextProcessor +import com.tyron.editor.Content +import com.tyron.fileeditor.api.FileDocumentManager +import com.tyron.fileeditor.api.FileEditor +import org.apache.commons.vfs2.VFS +import java.io.File + +class SquircleEditor( + val context: Context, + private val ioFile: File, + val provider: SquircleEditorProvider +) : FileEditor { + + private val editorView = TextProcessor(context) + + init { + val fileObject = VFS.getManager().toFileObject(ioFile) + val content = FileDocumentManager.getInstance().getContent(fileObject) + val contentPlugin = ContentImpl(content!!) + + editorView.text = contentPlugin + editorView.installPlugin(contentPlugin) + } + + override fun getView() = editorView + + override fun getPreferredFocusedView() = view + + override fun getName() = "Squircle Editor" + + override fun isModified(): Boolean { + return false + } + + override fun isValid(): Boolean { + return true + } + + override fun getFile(): File { + return ioFile + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditorProvider.kt b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditorProvider.kt new file mode 100644 index 000000000..ecc346bbf --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/squircle/SquircleEditorProvider.kt @@ -0,0 +1,21 @@ +package com.tyron.code.ui.editor.impl.text.squircle + +import android.content.Context +import com.tyron.fileeditor.api.FileEditor +import com.tyron.fileeditor.api.FileEditorProvider +import java.io.File + +class SquircleEditorProvider : FileEditorProvider { + + override fun accept(file: File): Boolean { + return true + } + + override fun createEditor(context: Context, file: File): FileEditor { + return SquircleEditor(context, file, this) + } + + override fun getEditorTypeId(): String { + return "squircle-editor" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutEditor.java b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutEditor.java index 504d5f5a6..44a7be5d0 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutEditor.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutEditor.java @@ -1,5 +1,7 @@ package com.tyron.code.ui.editor.impl.xml; +import android.content.Context; + import androidx.annotation.NonNull; import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; @@ -10,11 +12,10 @@ public class LayoutEditor extends RosemoeCodeEditor { - public LayoutEditor(File file, RosemoeEditorProvider provider) { - super(file, provider); + public LayoutEditor(Context context, File file, RosemoeEditorProvider provider) { + super(context, file, provider); } - @Override protected CodeEditorFragment createFragment(File file) { return LayoutTextEditorFragment.newInstance(file); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorFragment.java b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorFragment.java index 7301b97ba..417e07c1c 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorFragment.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorFragment.java @@ -1,7 +1,18 @@ package com.tyron.code.ui.editor.impl.xml; import android.os.Bundle; +import android.util.Pair; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.os.BundleKt; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentResultListener; + +import com.tyron.builder.compiler.manifest.xml.XmlFormatPreferences; +import com.tyron.builder.compiler.manifest.xml.XmlFormatStyle; +import com.tyron.builder.compiler.manifest.xml.XmlPrettyPrinter; import com.tyron.code.R; import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; import com.tyron.code.ui.layoutEditor.LayoutEditorFragment; @@ -22,6 +33,25 @@ public static LayoutTextEditorFragment newInstance(File file) { return fragment; } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + FragmentManager fragmentManager = getChildFragmentManager(); + FragmentResultListener listener = ((requestKey, result) -> { + String xml = result.getString("text", getEditor().getText() + .toString()); + xml = XmlPrettyPrinter.prettyPrint(xml, XmlFormatPreferences.defaults(), + XmlFormatStyle.LAYOUT, "\n"); + Bundle bundle = new Bundle(); + bundle.putBoolean("loaded", true); + bundle.putBoolean("bg", true); + getEditor().setText(xml, bundle); + }); + fragmentManager.setFragmentResultListener(LayoutEditorFragment.KEY_SAVE, + getViewLifecycleOwner(), listener); + } + public void preview() { File currentFile = getEditor().getCurrentFile(); diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorProvider.java b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorProvider.java index 4d30d0267..7bb9da4f5 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorProvider.java +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorProvider.java @@ -1,5 +1,7 @@ package com.tyron.code.ui.editor.impl.xml; +import android.content.Context; + import androidx.annotation.NonNull; import com.tyron.fileeditor.api.FileEditor; @@ -22,8 +24,8 @@ public boolean accept(@NonNull File file) { @NonNull @Override - public FileEditor createEditor(@NonNull File file) { - return new LayoutEditor(file, this); + public FileEditor createEditor(@NonNull Context context, @NonNull File file) { + return new LayoutEditor(context, file, this); } @NonNull diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/AbstractAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/ui/editor/language/AbstractAutoCompleteProvider.java deleted file mode 100644 index 4923f46ac..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/AbstractAutoCompleteProvider.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.tyron.code.ui.editor.language; - -import androidx.annotation.Nullable; - -import com.tyron.completion.model.CompletionList; - -import java.util.List; -import java.util.stream.Collectors; - -import io.github.rosemoe.sora2.data.CompletionItem; -import io.github.rosemoe.sora2.interfaces.AutoCompleteProvider; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; - -/** - * An auto complete provider that supports cancellation as the user types - */ -public abstract class AbstractAutoCompleteProvider implements AutoCompleteProvider { - - @Override - public final List getAutoCompleteItems(String prefix, TextAnalyzeResult colors, int line, int column) { - CompletionList list = getCompletionList(prefix, colors, - line, column); - if (list == null) { - return null; - } - - return list.items.stream() - .map(CompletionItem::new) - .collect(Collectors.toList()); - } - - @Nullable - public abstract CompletionList getCompletionList(String prefix, TextAnalyzeResult colors, int line, int column); -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/DiagnosticAnalyzeManager.java b/app/src/main/java/com/tyron/code/ui/editor/language/DiagnosticAnalyzeManager.java deleted file mode 100644 index ee1badd50..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/DiagnosticAnalyzeManager.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.tyron.code.ui.editor.language; - -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.editor.Editor; - -import java.util.List; - -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; - -public abstract class DiagnosticAnalyzeManager extends SimpleAnalyzeManager { - - protected boolean mShouldAnalyzeInBg; - - public abstract void setDiagnostics(Editor editor, List diagnostics); - - public void rerunWithoutBg() { - mShouldAnalyzeInBg = false; - super.rerun(); - } - - @Override - public void rerun() { - mShouldAnalyzeInBg = true; - super.rerun(); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/HighlightUtil.java b/app/src/main/java/com/tyron/code/ui/editor/language/HighlightUtil.java deleted file mode 100644 index d1dd60f08..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/HighlightUtil.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.tyron.code.ui.editor.language; - -import android.util.Log; - -import com.android.tools.r8.naming.T; -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.editor.CharPosition; -import com.tyron.editor.Editor; - -import org.openjdk.javax.tools.Diagnostic; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import io.github.rosemoe.sora.lang.styling.MappedSpans; -import io.github.rosemoe.sora.lang.styling.Spans; -import io.github.rosemoe.sora.lang.styling.Styles; -import io.github.rosemoe.sora2.BuildConfig; -import io.github.rosemoe.sora2.data.Span; -import io.github.rosemoe.sora2.text.Indexer; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.widget.CodeEditor; -import io.github.rosemoe.sora2.widget.EditorColorScheme; - -public class HighlightUtil { - - public static void markProblemRegion(Styles styles, int newFlag, int startLine, int startColumn, int endLine, int endColumn) { - for (int line = startLine; line <= endLine; line++) { - int start = (line == startLine ? startColumn : 0); - int end = (line == endLine ? endColumn : Integer.MAX_VALUE); - Spans.Reader read = styles.getSpans().read(); - List spans = new ArrayList<>(read.getSpansOnLine(line)); - int increment; - for (int i = 0; i < spans.size(); i += increment) { - io.github.rosemoe.sora.lang.styling.Span span = spans.get(i); - increment = 1; - if (span.column >= end) { - break; - } - int spanEnd = (i + 1 >= spans.size() ? Integer.MAX_VALUE : spans.get(i + 1).column); - if (spanEnd >= start) { - int regionStartInSpan = Math.max(span.column, start); - int regionEndInSpan = Math.min(end, spanEnd); - if (regionStartInSpan == span.column) { - if (regionEndInSpan != spanEnd) { - increment = 2; - io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); - nSpan.column = regionEndInSpan; - spans.add(i + 1, nSpan); - } - span.problemFlags |= newFlag; - } else { - //regionStartInSpan > span.column - if (regionEndInSpan == spanEnd) { - increment = 2; - io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); - nSpan.column = regionStartInSpan; - spans.add(i + 1, nSpan); - nSpan.problemFlags |= newFlag; - } else { - increment = 3; - io.github.rosemoe.sora.lang.styling.Span span1 = span.copy(); - span1.column = regionStartInSpan; - span1.problemFlags |= newFlag; - io.github.rosemoe.sora.lang.styling.Span span2 = span.copy(); - span2.column = regionEndInSpan; - spans.add(i + 1, span1); - spans.add(i + 2, span2); - } - } - } - } - - Spans.Modifier modify = styles.getSpans().modify(); - modify.setSpansOnLine(line, spans); - } - } - - - /** - * Highlights the list of given diagnostics, taking care of conversion between 1-based offsets - * to 0-based offsets. - * It also makes the Diagnostic eligible for shifting as the user types. - */ - public static void markDiagnostics(Editor editor, List diagnostics, - Styles styles) { - diagnostics.forEach(it -> { - try { - int startLine; - int startColumn; - int endLine; - int endColumn; - if (it.getPosition() != DiagnosticWrapper.USE_LINE_POS) { - if (it.getStartPosition() == -1) { - it.setStartPosition(it.getPosition()); - } - if (it.getEndPosition() == -1) { - it.setEndPosition(it.getPosition()); - } - CharPosition start = editor.getCharPosition((int) it.getStartPosition()); - CharPosition end = editor.getCharPosition((int) it.getEndPosition()); - - int sLine = start.getLine(); - int sColumn = start.getColumn(); - int eLine = end.getLine(); - int eColumn = end.getColumn(); - - // the editor does not support marking underline spans for the same start and end - // index - // to work around this, we just subtract one to the start index - if (sLine == eLine && eColumn == sColumn) { - sColumn--; - eColumn++; - } - - it.setStartLine(sLine); - it.setEndLine(eLine); - it.setStartColumn(sColumn); - it.setEndColumn(eColumn); - } - startLine = it.getStartLine(); - startColumn = it.getStartColumn(); - endLine = it.getEndLine(); - endColumn = it.getEndColumn(); - - int flag = it.getKind() == Diagnostic.Kind.ERROR ? Span.FLAG_ERROR : - Span.FLAG_WARNING; - markProblemRegion(styles, flag, startLine, startColumn, endLine, endColumn); - } catch (IllegalArgumentException | IndexOutOfBoundsException e) { - if (BuildConfig.DEBUG) { - Log.d("HighlightUtil", "Failed to mark diagnostics", e); - } - } - }); - } - - public static int[] setErrorSpan(TextAnalyzeResult colors, int line, int column) { - int lineCount = colors.getSpanMap().size(); - int realLine = line - 1; - List spans = colors.getSpanMap().get(Math.min(realLine, lineCount - 1)); - - int[] end = new int[2]; - end[0] = Math.min(realLine, lineCount - 1); - - if (realLine >= lineCount) { - Span span = Span.obtain(0, EditorColorScheme.PROBLEM_ERROR); - span.problemFlags = Span.FLAG_ERROR; - colors.add(realLine, span); - end[0]++; - } else { - Span last = null; - for (int i = 0; i < spans.size(); i++) { - Span span = spans.get(i); - if (last != null) { - if (last.column <= column - 1 && span.column >= column - 1) { - span.problemFlags = Span.FLAG_ERROR; - last.problemFlags = Span.FLAG_ERROR; - end[1] = last.column; - break; - } - } - if (i == spans.size() - 1 && span.column <= column - 1) { - span.problemFlags = Span.FLAG_ERROR; - end[1] = span.column; - break; - } - last = span; - } - } - return end; - } - - /** - * Used in xml diagnostics where line is only given - */ - public static void setErrorSpan(TextAnalyzeResult colors, int line) { - int lineCount = colors.getSpanMap().size(); - int realLine = line - 1; - List spans = colors.getSpanMap().get(Math.min(realLine, lineCount - 1)); - - for (Span span : spans) { - span.problemFlags = Span.FLAG_ERROR; - } - } - - /** - * Used in xml diagnostics where line is only given - */ - public static void setErrorSpan(Styles colors, int line) { - try { - Spans.Reader reader = colors.getSpans().read(); - int realLine = line - 1; - List spans = reader.getSpansOnLine(realLine); - - for (io.github.rosemoe.sora.lang.styling.Span span : spans) { - span.problemFlags = Span.FLAG_ERROR; - } - } catch (IndexOutOfBoundsException e) { - // ignored - } - } - -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/Language.java b/app/src/main/java/com/tyron/code/ui/editor/language/Language.java deleted file mode 100644 index 7907d336f..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/Language.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.tyron.code.ui.editor.language; - -import com.tyron.editor.Editor; - -import io.github.rosemoe.sora.widget.CodeEditor; -import java.io.File; - -public interface Language { - - /** - * Subclasses return whether they support this file extension - */ - boolean isApplicable(File ext); - - /** - * - * @param editor the editor instance - * @return The specific language instance for this editor - */ - io.github.rosemoe.sora.lang.Language get(Editor editor); -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/LanguageManager.java b/app/src/main/java/com/tyron/code/ui/editor/language/LanguageManager.java deleted file mode 100644 index 740e40843..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/LanguageManager.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.tyron.code.ui.editor.language; - -import com.tyron.code.ui.editor.language.groovy.Groovy; -import com.tyron.code.ui.editor.language.java.Java; -import com.tyron.code.ui.editor.language.json.Json; -import com.tyron.code.ui.editor.language.kotlin.Kotlin; -import com.tyron.code.ui.editor.language.xml.Xml; -import com.tyron.editor.Editor; - -import java.io.File; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -public class LanguageManager { - - private static LanguageManager Instance = null; - - public static LanguageManager getInstance() { - if (Instance == null) { - Instance = new LanguageManager(); - } - return Instance; - } - - private final Set mLanguages = new HashSet<>(); - - private LanguageManager() { - initLanguages(); - } - - private void initLanguages() { - mLanguages.addAll( - Arrays.asList( - new Xml(), - new Java(), - new Kotlin(), - new Groovy(), - new Json())); - } - - public boolean supports(File file) { - for (Language language : mLanguages) { - if (language.isApplicable(file)) { - return true; - } - } - return false; - } - - public io.github.rosemoe.sora.lang.Language get(Editor editor, File file) { - for (Language lang : mLanguages) { - if (lang.isApplicable(file)) { - return lang.get(editor); - } - } - return null; - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/Groovy.java b/app/src/main/java/com/tyron/code/ui/editor/language/groovy/Groovy.java deleted file mode 100644 index 2bfc9cd4b..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/Groovy.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.tyron.code.ui.editor.language.groovy; - -import com.tyron.code.ui.editor.language.Language; -import com.tyron.editor.Editor; - -import java.io.File; - -import io.github.rosemoe.sora.widget.CodeEditor; - -public class Groovy implements Language { - @Override - public boolean isApplicable(File ext) { - return ext.getName().endsWith(".groovy") || ext.getName().endsWith(".gradle"); - } - - @Override - public io.github.rosemoe.sora.lang.Language get(Editor editor) { - return new GroovyLanguage(editor); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyAnalyzer.java b/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyAnalyzer.java deleted file mode 100644 index bb5a1a848..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyAnalyzer.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.tyron.code.ui.editor.language.groovy; - -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.Token; - -import java.util.Stack; - -import io.github.rosemoe.sora.lang.styling.CodeBlock; -import io.github.rosemoe.sora.lang.styling.MappedSpans; -import io.github.rosemoe.sora.lang.styling.Styles; -import io.github.rosemoe.sora.widget.CodeEditor; -import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; - -public class GroovyAnalyzer extends AbstractCodeAnalyzer { - - private final Editor mEditor; - - int maxSwitch = 1; - int currSwitch; - private final Stack mBlockLines = new Stack<>(); - - public GroovyAnalyzer(Editor editor) { - mEditor = editor; - } - - @Override - public Lexer getLexer(CharStream input) { - return new GroovyLexer(input); - } - - @Override - public void setup() { - putColor(EditorColorScheme.KEYWORD, GroovyLexer.KW_DO, - GroovyLexer.KW_ABSTRACT, GroovyLexer.KW_FALSE, - GroovyLexer.KW_TRUE, GroovyLexer.KW_CASE, - GroovyLexer.KW_CATCH, GroovyLexer.KW_AS, - GroovyLexer.KW_WHILE, GroovyLexer.KW_TRY, - GroovyLexer.KW_BREAK, GroovyLexer.KW_THIS, - GroovyLexer.KW_ASSERT, GroovyLexer.KW_VOLATILE, - GroovyLexer.KW_NULL, GroovyLexer.KW_NEW, - GroovyLexer.KW_RETURN, GroovyLexer.KW_PACKAGE, - GroovyLexer.KW_FOR, GroovyLexer.KW_IF, - GroovyLexer.SEMICOLON); - putColor(EditorColorScheme.LITERAL, GroovyLexer.STRING, - GroovyLexer.INTEGER); - putColor(EditorColorScheme.OPERATOR, GroovyLexer.PLUS, - GroovyLexer.MINUS, GroovyLexer.MULT, GroovyLexer.DIV, - GroovyLexer.PLUS_ASSIGN, GroovyLexer.MINUS_ASSIGN, - GroovyLexer.MULT_ASSIGN, GroovyLexer.DIV_ASSIGN, - GroovyLexer.MOD, GroovyLexer.MOD_ASSIGN, - GroovyLexer.OR, GroovyLexer.AND, GroovyLexer.BAND, - GroovyLexer.BAND_ASSIGN, GroovyLexer.LSHIFT, - GroovyLexer.LSHIFT_ASSIGN, GroovyLexer.RSHIFT_ASSIGN, - GroovyLexer.XOR_ASSIGN, GroovyLexer.XOR); - putColor(EditorColorScheme.IDENTIFIER_NAME, GroovyLexer.IDENTIFIER); - } - - @Override - public void analyzeInBackground(CharSequence contents) { - - } - - @Override - protected void beforeAnalyze() { - mBlockLines.clear(); - maxSwitch = 1; - currSwitch = 0; - } - - @Override - public boolean onNextToken(Token currentToken, Styles styles, MappedSpans.Builder colors) { - int line = currentToken.getLine() - 1; - int column = currentToken.getCharPositionInLine(); - - switch (currentToken.getType()) { - case GroovyLexer.RCURVE: - if (!mBlockLines.isEmpty()) { - CodeBlock b = mBlockLines.pop(); - b.endLine = line; - b.endColumn = column; - if (b.startLine != b.endLine) { - styles.addCodeBlock(b); - } - } - return true; - case GroovyLexer.LCURVE: - if (mBlockLines.isEmpty()) { - if (currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - currSwitch = 0; - } - currSwitch++; - CodeBlock block = styles.obtainNewBlock(); - block.startLine = line; - block.startColumn = column; - mBlockLines.push(block); - return true; - } - return false; - } - - @Override - protected void afterAnalyze(CharSequence content, Styles styles, MappedSpans.Builder colors) { - if (mBlockLines.isEmpty()) { - if (currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - } - styles.setSuppressSwitch(maxSwitch + 10); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLanguage.java b/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLanguage.java deleted file mode 100644 index 97fe5438e..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/groovy/GroovyLanguage.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.tyron.code.ui.editor.language.groovy; - -import android.os.Bundle; - -import androidx.annotation.NonNull; - -import com.tyron.editor.Editor; - -import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; -import io.github.rosemoe.sora.lang.completion.CompletionPublisher; -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.CodeEditor; -import io.github.rosemoe.sora.widget.SymbolPairMatch; - -public class GroovyLanguage implements Language { - - private final Editor mEditor; - private final GroovyAnalyzer mAnalyzer; - - public GroovyLanguage(Editor editor) { - mEditor = editor; - mAnalyzer = new GroovyAnalyzer(editor); - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return mAnalyzer; - } - - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_STRONG; - } - - @Override - public void requireAutoComplete(@NonNull ContentReference content, @NonNull CharPosition position, @NonNull CompletionPublisher publisher, @NonNull Bundle extraArguments) throws CompletionCancelledException { - - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return 0; - } - - @Override - public boolean useTab() { - return true; - } - - @Override - public CharSequence format(CharSequence text) { - return text; - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return null; - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return new NewlineHandler[0]; - } - - @Override - public void destroy() { - - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/java/IncrementalJavaAnalyzeManager.java b/app/src/main/java/com/tyron/code/ui/editor/language/java/IncrementalJavaAnalyzeManager.java deleted file mode 100644 index 8956c1e8a..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/java/IncrementalJavaAnalyzeManager.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.tyron.code.ui.editor.language.java; - -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.jetbrains.kotlin.com.intellij.lang.java.lexer.JavaLexer; -import org.jetbrains.kotlin.com.intellij.lexer.Lexer; -import org.jetbrains.kotlin.com.intellij.lexer.LexerPosition; -import org.jetbrains.kotlin.com.intellij.pom.java.LanguageLevel; - -import java.util.List; - -import io.github.rosemoe.sora.lang.analysis.IncrementalAnalyzeManager; -import io.github.rosemoe.sora.lang.analysis.StyleReceiver; -import io.github.rosemoe.sora.lang.styling.Span; -import io.github.rosemoe.sora.text.CharPosition; -import io.github.rosemoe.sora.text.ContentReference; - -public class IncrementalJavaAnalyzeManager - implements IncrementalAnalyzeManager { - - private final Lexer mLexer; - - public IncrementalJavaAnalyzeManager() { - mLexer = new JavaLexer(LanguageLevel.HIGHEST); - } - - @Override - public LexerPosition getInitialState() { - return mLexer.getCurrentPosition(); - } - - @Override - public boolean stateEquals(LexerPosition state, LexerPosition another) { - return state.equals(another); - } - - @Override - public LineTokenizeResult tokenizeLine(CharSequence line, LexerPosition state) { - return null; - } - - @Override - public List generateSpansForLine(LineTokenizeResult tokens) { - return null; - } - - @Override - public void setReceiver(@Nullable StyleReceiver receiver) { - - } - - @Override - public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { - mLexer.start(content.getReference().toString()); - } - - @Override - public void insert(CharPosition start, CharPosition end, CharSequence insertedContent) { - - } - - @Override - public void delete(CharPosition start, CharPosition end, CharSequence deletedContent) { - - } - - @Override - public void rerun() { - - } - - @Override - public void destroy() { - - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/java/Java.java b/app/src/main/java/com/tyron/code/ui/editor/language/java/Java.java deleted file mode 100644 index fe069b727..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/java/Java.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.tyron.code.ui.editor.language.java; - -import com.tyron.code.ui.editor.language.Language; -import com.tyron.editor.Editor; - -import io.github.rosemoe.sora.lang.EmptyLanguage; -import io.github.rosemoe.sora2.interfaces.EditorLanguage; -import io.github.rosemoe.sora2.widget.CodeEditor; -import java.io.File; - -public class Java implements Language { - - @Override - public boolean isApplicable(File ext) { - return ext.getName().endsWith(".java"); - } - - @Override - public io.github.rosemoe.sora.lang.Language get(Editor editor) { - return new JavaLanguage(editor); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAnalyzer.java b/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAnalyzer.java deleted file mode 100644 index 2143e958f..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAnalyzer.java +++ /dev/null @@ -1,402 +0,0 @@ -package com.tyron.code.ui.editor.language.java; - -import android.content.SharedPreferences; -import android.util.Log; - -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.builder.model.SourceFileObject; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.JavaModule; -import com.tyron.builder.project.api.Module; -import com.tyron.code.ApplicationLoader; -import com.tyron.code.BuildConfig; -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; -import com.tyron.code.ui.editor.language.HighlightUtil; -import com.tyron.code.ui.editor.language.kotlin.KotlinLexer; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.common.util.Debouncer; -import com.tyron.completion.index.CompilerService; -import com.tyron.completion.java.JavaCompilerProvider; -import com.tyron.completion.java.compiler.CompileTask; -import com.tyron.completion.java.compiler.CompilerContainer; -import com.tyron.completion.java.compiler.JavaCompilerService; -import com.tyron.completion.java.provider.CompletionEngine; -import com.tyron.completion.java.util.ErrorCodes; -import com.tyron.completion.java.util.TreeUtil; -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Lexer; -import org.openjdk.javax.tools.Diagnostic; -import org.openjdk.javax.tools.JavaFileObject; -import org.openjdk.source.tree.BlockTree; -import org.openjdk.source.tree.ClassTree; -import org.openjdk.source.tree.CompilationUnitTree; -import org.openjdk.source.tree.MethodTree; -import org.openjdk.source.tree.Tree; -import org.openjdk.source.util.SourcePositions; -import org.openjdk.source.util.TreePath; -import org.openjdk.source.util.Trees; -import org.openjdk.tools.javac.api.ClientCodeWrapper; -import org.openjdk.tools.javac.tree.JCTree; -import org.openjdk.tools.javac.util.JCDiagnostic; - -import java.lang.ref.WeakReference; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Stack; -import java.util.stream.Collectors; - -import io.github.rosemoe.sora.lang.styling.CodeBlock; -import io.github.rosemoe.sora.lang.styling.MappedSpans; -import io.github.rosemoe.sora.lang.styling.Styles; -import io.github.rosemoe.sora2.data.BlockLine; -import io.github.rosemoe.sora2.data.NavigationItem; -import io.github.rosemoe.sora2.langs.java.JavaTextTokenizer; -import io.github.rosemoe.sora2.langs.java.Tokens; -import io.github.rosemoe.sora2.text.LineNumberCalculator; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.text.TextAnalyzer; -import io.github.rosemoe.sora2.widget.EditorColorScheme; -import kotlin.Unit; -import kotlin.jvm.functions.Function0; - -public class JavaAnalyzer extends AbstractCodeAnalyzer { - - private static final Debouncer sDebouncer = new Debouncer(Duration.ofMillis(700)); - private static final String TAG = JavaAnalyzer.class.getSimpleName(); - /** - * These are tokens that cannot exist before a valid function identifier - */ - private static final Tokens[] sKeywordsBeforeFunctionName = new Tokens[]{Tokens.RETURN, - Tokens.BREAK, Tokens.IF, Tokens.AND, Tokens.OR, Tokens.OREQ, Tokens.OROR, - Tokens.ANDAND, Tokens.ANDEQ, Tokens.RPAREN, Tokens.LPAREN, Tokens.LBRACE, Tokens.NEW, - Tokens.DOT, Tokens.SEMICOLON, Tokens.EQ, Tokens.NOTEQ, Tokens.NOT, Tokens.RBRACE, - Tokens.COMMA, Tokens.PLUS, Tokens.PLUSEQ, Tokens.MINUS, Tokens.MINUSEQ, Tokens.MULT, - Tokens.MULTEQ, Tokens.DIV, Tokens.DIVEQ}; - - private final WeakReference mEditorReference; - private List mDiagnostics; - private final List mPreviousDiagnostics = new ArrayList<>(); - private final SharedPreferences mPreferences; - - public JavaAnalyzer(Editor editor) { - mEditorReference = new WeakReference<>(editor); - mPreferences = ApplicationLoader.getDefaultPreferences(); - mDiagnostics = new ArrayList<>(); - } - - @Override - public void setDiagnostics(Editor editor, List diagnostics) { - mDiagnostics = diagnostics; - } - - @Override - public Lexer getLexer(CharStream input) { - return new KotlinLexer(input); - } - - @Override - public void analyzeInBackground(CharSequence contents) { - sDebouncer.schedule(cancel -> { - doAnalyzeInBackground(cancel, contents); - return Unit.INSTANCE; - }); - } - - private JavaCompilerService getCompiler(Editor editor) { - Project project = ProjectManager.getInstance().getCurrentProject(); - if (project == null) { - return null; - } - Module module = project.getModule(editor.getCurrentFile()); - if (module instanceof JavaModule) { - JavaCompilerProvider provider = - CompilerService.getInstance().getIndex(JavaCompilerProvider.KEY); - if (provider != null) { - return provider.getCompiler(project, (JavaModule) module); - } - } - return null; - } - - private void doAnalyzeInBackground(Function0 cancel, CharSequence contents) { - Editor editor = mEditorReference.get(); - if (editor == null) { - return; - } - if (cancel.invoke()) { - return; - } - // do not compile the file if it not yet closed as it will cause issues when - // compiling multiple files at the same time - if (mPreferences.getBoolean("code_editor_error_highlight", true) && !CompletionEngine.isIndexing()) { - JavaCompilerService service = getCompiler(editor); - if (service != null) { - try { - SourceFileObject sourceFileObject = - new SourceFileObject(editor.getCurrentFile().toPath(), - contents.toString(), Instant.now()); - CompilerContainer container = - service.compile(Collections.singletonList(sourceFileObject)); - container.run(task -> { - if (!cancel.invoke()) { - List collect = - task.diagnostics.stream() - .map(d -> modifyDiagnostic(task, d)) - .collect(Collectors.toList()); - editor.setDiagnostics(collect); - } - }); - } catch (Throwable e) { - if (BuildConfig.DEBUG) { - Log.e(TAG, "Unable to get diagnostics", e); - } - service.close(); - } - } - } - } - - private DiagnosticWrapper modifyDiagnostic(CompileTask task, Diagnostic extends JavaFileObject> diagnostic) { - DiagnosticWrapper wrapped = new DiagnosticWrapper(diagnostic); - - if (diagnostic instanceof ClientCodeWrapper.DiagnosticSourceUnwrapper) { - Trees trees = Trees.instance(task.task); - SourcePositions positions = trees.getSourcePositions(); - - JCDiagnostic jcDiagnostic = ((ClientCodeWrapper.DiagnosticSourceUnwrapper) diagnostic).d; - JCDiagnostic.DiagnosticPosition diagnosticPosition = - jcDiagnostic.getDiagnosticPosition(); - JCTree tree = diagnosticPosition.getTree(); - - if (tree != null) { - TreePath treePath = trees.getPath(task.root(), tree); - String code = jcDiagnostic.getCode(); - - long start = diagnostic.getStartPosition(); - long end = diagnostic.getEndPosition(); - switch (code) { - case ErrorCodes.MISSING_RETURN_STATEMENT: - TreePath block = TreeUtil.findParentOfType(treePath, - BlockTree.class); - if (block != null) { - // show error span only at the end parenthesis - end = positions.getEndPosition(task.root(), block.getLeaf()) + 1; - start = end - 2; - } - break; - } - - wrapped.setStartPosition(start); - wrapped.setEndPosition(end); - } - } - return wrapped; - } - - @Override - protected Styles analyze(StringBuilder text, Delegate delegate) { - Styles styles = new Styles(); - MappedSpans.Builder colors = new MappedSpans.Builder(); - - Editor editor = mEditorReference.get(); - if (editor == null) { - return styles; - } - JavaTextTokenizer tokenizer = new JavaTextTokenizer(text); - tokenizer.setCalculateLineColumn(false); - Tokens token, previous = Tokens.UNKNOWN; - int line = 0, column = 0; - LineNumberCalculator helper = new LineNumberCalculator(text); - - Stack stack = new Stack<>(); - List labels = new ArrayList<>(); - int maxSwitch = 1, currSwitch = 0; - - boolean first = true; - - while (!delegate.isCancelled()) { - try { - // directNextToken() does not skip any token - token = tokenizer.directNextToken(); - } catch (RuntimeException e) { - //When a spelling input is in process, this will happen because of format mismatch - token = Tokens.CHARACTER_LITERAL; - } - if (token == Tokens.EOF) { - break; - } - // Backup values because looking ahead in function name match will change them - int thisIndex = tokenizer.getIndex(); - int thisLength = tokenizer.getTokenLength(); - - switch (token) { - case WHITESPACE: - case NEWLINE: - if (first) { - colors.addNormalIfNull(); - } - break; - case IDENTIFIER: - //Add a identifier to auto complete - - //The previous so this will be the annotation's type name - if (previous == Tokens.AT) { - colors.addIfNeeded(line, column, EditorColorScheme.ANNOTATION); - break; - } - //Here we have to get next token to see if it is function - //We can only get the next token in stream. - //If more tokens required, we have to use a stack in tokenizer - Tokens next = tokenizer.directNextToken(); - //The next is LPAREN,so this is function name or type name - if (next == Tokens.LPAREN) { - boolean found = false; - for (Tokens before : sKeywordsBeforeFunctionName) { - if (before == previous) { - found = true; - break; - } - } - if (!found) { - colors.addIfNeeded(line, column, EditorColorScheme.FUNCTION_NAME); - tokenizer.pushBack(tokenizer.getTokenLength()); - break; - } - } - //Push back the next token - tokenizer.pushBack(tokenizer.getTokenLength()); - //This is a class definition - - colors.addIfNeeded(line, column, EditorColorScheme.TEXT_NORMAL); - break; - case CHARACTER_LITERAL: - case STRING: - case FLOATING_POINT_LITERAL: - case INTEGER_LITERAL: - colors.addIfNeeded(line, column, EditorColorScheme.LITERAL); - break; - case INT: - case LONG: - case BOOLEAN: - case BYTE: - case CHAR: - case FLOAT: - case DOUBLE: - case SHORT: - case VOID: - case ABSTRACT: - case ASSERT: - case CLASS: - case DO: - case FINAL: - case FOR: - case IF: - case NEW: - case PUBLIC: - case PRIVATE: - case PROTECTED: - case PACKAGE: - case RETURN: - case STATIC: - case SUPER: - case SWITCH: - case ELSE: - case VOLATILE: - case SYNCHRONIZED: - case STRICTFP: - case GOTO: - case CONTINUE: - case BREAK: - case TRANSIENT: - case TRY: - case CATCH: - case FINALLY: - case WHILE: - case CASE: - case DEFAULT: - case CONST: - case ENUM: - case EXTENDS: - case IMPLEMENTS: - case IMPORT: - case INSTANCEOF: - case INTERFACE: - case NATIVE: - case THIS: - case THROW: - case THROWS: - case TRUE: - case FALSE: - case NULL: - case SEMICOLON: - colors.addIfNeeded(line, column, EditorColorScheme.KEYWORD); - break; - case LBRACE: { - colors.addIfNeeded(line, column, EditorColorScheme.OPERATOR); - if (stack.isEmpty()) { - if (currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - currSwitch = 0; - } - currSwitch++; - CodeBlock block = styles.obtainNewBlock(); - block.startLine = line; - block.startColumn = column; - stack.push(block); - break; - } - case RBRACE: { - colors.addIfNeeded(line, column, EditorColorScheme.OPERATOR); - if (!stack.isEmpty()) { - CodeBlock block = stack.pop(); - block.endLine = line; - block.endColumn = column; - if (block.startLine != block.endLine) { - styles.addCodeBlock(block); - } - } - break; - } - case LINE_COMMENT: - case LONG_COMMENT: - colors.addIfNeeded(line, column, EditorColorScheme.COMMENT); - break; - default: - if (token == Tokens.LBRACK || (token == Tokens.RBRACK && previous == Tokens.LBRACK)) { - colors.addIfNeeded(line, column, EditorColorScheme.OPERATOR); - break; - } - colors.addIfNeeded(line, column, EditorColorScheme.OPERATOR); - } - - - first = false; - helper.update(thisLength); - line = helper.getLine(); - column = helper.getColumn(); - if (token != Tokens.WHITESPACE && token != Tokens.NEWLINE) { - previous = token; - } - } - if (stack.isEmpty()) { - if (currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - } - colors.determine(line); - styles.setSuppressSwitch(maxSwitch + 10); - styles.spans = colors.build(); - - if (mShouldAnalyzeInBg) { - analyzeInBackground(text); - } - HighlightUtil.markDiagnostics(editor, mDiagnostics, styles); - return styles; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAutoCompleteProvider.java deleted file mode 100644 index 4fd3312d6..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaAutoCompleteProvider.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.tyron.code.ui.editor.language.java; - -import android.content.SharedPreferences; - -import androidx.annotation.Nullable; -import androidx.preference.PreferenceManager; - -import com.tyron.code.ApplicationLoader; -import com.tyron.code.ui.editor.language.AbstractAutoCompleteProvider; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.JavaModule; -import com.tyron.builder.project.api.Module; -import com.tyron.completion.main.CompletionEngine; -import com.tyron.completion.model.CompletionList; -import com.tyron.editor.Editor; - -import java.util.Optional; - -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.widget.CodeEditor; - -public class JavaAutoCompleteProvider extends AbstractAutoCompleteProvider { - - private final Editor mEditor; - private final SharedPreferences mPreferences; - - public JavaAutoCompleteProvider(Editor editor) { - mEditor = editor; - mPreferences = ApplicationLoader.getDefaultPreferences(); - } - - - @Nullable - @Override - public CompletionList getCompletionList( - String prefix, TextAnalyzeResult colors, int line, int column) { - if (!mPreferences.getBoolean("code_editor_completion", true)) { - return null; - } - - Project project = ProjectManager.getInstance().getCurrentProject(); - - if (project == null) { - return null; - } - - Module currentModule = project.getModule(mEditor.getCurrentFile()); - - if (currentModule instanceof JavaModule) { - Optional content = currentModule.getFileManager() - .getFileContent(mEditor.getCurrentFile()); - if (content.isPresent()) { - return CompletionEngine.getInstance() - .complete(project, - currentModule, - mEditor.getCurrentFile(), - content.get().toString(), - prefix, - line, - column, - mEditor.getCaret().getStart()); - } - } - return null; - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaLanguage.java b/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaLanguage.java deleted file mode 100644 index 1e0cac0e7..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/java/JavaLanguage.java +++ /dev/null @@ -1,244 +0,0 @@ -package com.tyron.code.ui.editor.language.java; - -import android.os.Bundle; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.google.common.collect.Range; -import com.google.googlejavaformat.java.Formatter; -import com.google.googlejavaformat.java.FormatterException; -import com.google.googlejavaformat.java.JavaFormatterOptions; -import com.tyron.code.ui.editor.JavaCompletionItem; -import com.tyron.code.ui.editor.language.CompletionItemWrapper; -import com.tyron.completion.model.CompletionItem; -import com.tyron.completion.model.CompletionList; -import com.tyron.editor.Editor; - -import java.util.ArrayList; -import java.util.Collection; - -import io.github.rosemoe.editor.langs.java.JavaTextTokenizer; -import io.github.rosemoe.editor.langs.java.Tokens; -import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; -import io.github.rosemoe.sora.lang.completion.CompletionHelper; -import io.github.rosemoe.sora.lang.completion.CompletionPublisher; -import io.github.rosemoe.sora.lang.completion.SimpleCompletionItem; -import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; -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.text.TextUtils; -import io.github.rosemoe.sora.util.MyCharacter; -import io.github.rosemoe.sora.widget.SymbolPairMatch; - -public class JavaLanguage implements Language { - - private Editor mEditor; - - private final JavaAnalyzer mAnalyzer; - - public JavaLanguage(Editor editor) { - mEditor = editor; - mAnalyzer = new JavaAnalyzer(editor); - } - - public boolean isAutoCompleteChar(char p1) { - return p1 == '.' || MyCharacter.isJavaIdentifierPart(p1); - } - - public int getIndentAdvance(String p1) { - JavaTextTokenizer tokenizer = new JavaTextTokenizer(p1); - Tokens token; - int advance = 0; - while ((token = tokenizer.directNextToken()) != Tokens.EOF) { - switch (token) { - case LBRACE: - advance++; - break; -// case RBRACE: -// advance--; -// break; - } - } - advance = Math.max(0, advance); - return advance * 4; - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return mAnalyzer; - } - - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_SLIGHT; - } - - @Override - public void requireAutoComplete(@NonNull ContentReference content, - @NonNull CharPosition position, - @NonNull CompletionPublisher publisher, - @NonNull Bundle extraArguments) throws CompletionCancelledException { - char c = content.charAt(position.getIndex() - 1); - if (!isAutoCompleteChar(c)) { - return; - } - String prefix = CompletionHelper.computePrefix(content, position, this::isAutoCompleteChar); - JavaAutoCompleteProvider provider = new JavaAutoCompleteProvider(mEditor); - CompletionList list = provider.getCompletionList(prefix, null, position.getLine(), - position.getColumn()); - if (list == null) { - return; - } - for (CompletionItem item : list.getItems()) { - CompletionItemWrapper wrapper = new CompletionItemWrapper(item); - publisher.addItem(wrapper); - } - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - String text = content.getLine(line).substring(0, column); - return getIndentAdvance(text); - } - - @Override - public boolean useTab() { - return true; - } - - @Override - public CharSequence format(CharSequence p1) { - try { - return new Formatter(JavaFormatterOptions.builder().style(JavaFormatterOptions.Style.AOSP).build()).formatSourceAndFixImports(p1.toString()); - } catch (FormatterException e) { - Log.e("JavaFormatter", e.getMessage()); - return p1; - } - } - - public CharSequence format(CharSequence contents, int start, int end) { - JavaFormatterOptions options = - JavaFormatterOptions.builder().style(JavaFormatterOptions.Style.AOSP).build(); - Formatter formatter = new Formatter(options); - Range range = Range.closed(start, end); - Collection> ranges = new ArrayList<>(); - ranges.add(range); - try { - return formatter.formatSource(contents.toString(), ranges); - } catch (FormatterException e) { - Log.d("Formatter", "Unable to format file", e); - return contents; - } - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } - - private final NewlineHandler[] newLineHandlers = new NewlineHandler[]{new BraceHandler(), - new TwoIndentHandler(), new JavaDocStartHandler(), new JavaDocHandler()}; - - @Override - public NewlineHandler[] getNewlineHandlers() { - return newLineHandlers; - } - - @Override - public void destroy() { - - } - - class TwoIndentHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - Log.d("BeforeText", beforeText); - if (beforeText.replace("\r", "").trim().startsWith(".")) { - return false; - } - return beforeText.endsWith(")") && !afterText.startsWith(";"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceAfter = getIndentAdvance(afterText) + (4 * 2); - String text; - StringBuilder sb = new StringBuilder().append('\n').append(text = - TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); - int shiftLeft = 0; - return new NewlineHandleResult(sb, shiftLeft); - } - - - } - - class BraceHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.endsWith("{") && afterText.startsWith("}"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceBefore = getIndentAdvance(beforeText); - int advanceAfter = getIndentAdvance(afterText); - String text; - StringBuilder sb = - new StringBuilder("\n").append(TextUtils.createIndent(count + advanceBefore, - tabSize, useTab())).append('\n').append(text = - TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); - int shiftLeft = text.length() + 1; - return new NewlineHandleResult(sb, shiftLeft); - } - } - - class JavaDocStartHandler implements NewlineHandler { - - private boolean shouldCreateEnd = true; - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.trim().startsWith("/**"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceAfter = getIndentAdvance(afterText); - String text = ""; - StringBuilder sb = - new StringBuilder().append("\n").append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())).append(" * "); - if (shouldCreateEnd) { - sb.append("\n").append(text = TextUtils.createIndent(count + advanceAfter, - tabSize, useTab())).append(" */"); - } - return new NewlineHandleResult(sb, text.length() + 4); - } - } - - class JavaDocHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.trim().startsWith("*") && !beforeText.trim().startsWith("*/"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceAfter = getIndentAdvance(afterText); - StringBuilder sb = - new StringBuilder().append("\n").append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())).append("* "); - return new NewlineHandleResult(sb, 0); - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.interp b/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.interp deleted file mode 100644 index def52b815..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.interp +++ /dev/null @@ -1,40 +0,0 @@ -token literal names: -null -'[' -']' -'true' -'false' -'null' -':' -null -'{' -'}' -',' -null -null - -token symbolic names: -null -null -null -TRUE -FALSE -NULL -COLON -STRING -LBRACKET -RBRACKET -COMMA -NUMBER -WS - -rule names: -json -obj -pair -arr -value - - -atn: -[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 14, 58, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 19, 10, 3, 12, 3, 14, 3, 22, 11, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 28, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 7, 5, 38, 10, 5, 12, 5, 14, 5, 41, 11, 5, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 47, 10, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 56, 10, 6, 3, 6, 2, 2, 7, 2, 4, 6, 8, 10, 2, 2, 2, 62, 2, 12, 3, 2, 2, 2, 4, 27, 3, 2, 2, 2, 6, 29, 3, 2, 2, 2, 8, 46, 3, 2, 2, 2, 10, 55, 3, 2, 2, 2, 12, 13, 5, 10, 6, 2, 13, 3, 3, 2, 2, 2, 14, 15, 7, 10, 2, 2, 15, 20, 5, 6, 4, 2, 16, 17, 7, 12, 2, 2, 17, 19, 5, 6, 4, 2, 18, 16, 3, 2, 2, 2, 19, 22, 3, 2, 2, 2, 20, 18, 3, 2, 2, 2, 20, 21, 3, 2, 2, 2, 21, 23, 3, 2, 2, 2, 22, 20, 3, 2, 2, 2, 23, 24, 7, 11, 2, 2, 24, 28, 3, 2, 2, 2, 25, 26, 7, 10, 2, 2, 26, 28, 7, 11, 2, 2, 27, 14, 3, 2, 2, 2, 27, 25, 3, 2, 2, 2, 28, 5, 3, 2, 2, 2, 29, 30, 7, 9, 2, 2, 30, 31, 7, 8, 2, 2, 31, 32, 5, 10, 6, 2, 32, 7, 3, 2, 2, 2, 33, 34, 7, 3, 2, 2, 34, 39, 5, 10, 6, 2, 35, 36, 7, 12, 2, 2, 36, 38, 5, 10, 6, 2, 37, 35, 3, 2, 2, 2, 38, 41, 3, 2, 2, 2, 39, 37, 3, 2, 2, 2, 39, 40, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 39, 3, 2, 2, 2, 42, 43, 7, 4, 2, 2, 43, 47, 3, 2, 2, 2, 44, 45, 7, 3, 2, 2, 45, 47, 7, 4, 2, 2, 46, 33, 3, 2, 2, 2, 46, 44, 3, 2, 2, 2, 47, 9, 3, 2, 2, 2, 48, 56, 7, 9, 2, 2, 49, 56, 7, 13, 2, 2, 50, 56, 5, 4, 3, 2, 51, 56, 5, 8, 5, 2, 52, 56, 7, 5, 2, 2, 53, 56, 7, 6, 2, 2, 54, 56, 7, 7, 2, 2, 55, 48, 3, 2, 2, 2, 55, 49, 3, 2, 2, 2, 55, 50, 3, 2, 2, 2, 55, 51, 3, 2, 2, 2, 55, 52, 3, 2, 2, 2, 55, 53, 3, 2, 2, 2, 55, 54, 3, 2, 2, 2, 56, 11, 3, 2, 2, 2, 7, 20, 27, 39, 46, 55] \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.tokens b/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.tokens deleted file mode 100644 index 29212732a..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.tokens +++ /dev/null @@ -1,21 +0,0 @@ -T__0=1 -T__1=2 -TRUE=3 -FALSE=4 -NULL=5 -COLON=6 -STRING=7 -LBRACKET=8 -RBRACKET=9 -COMMA=10 -NUMBER=11 -WS=12 -'['=1 -']'=2 -'true'=3 -'false'=4 -'null'=5 -':'=6 -'{'=8 -'}'=9 -','=10 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.interp b/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.interp deleted file mode 100644 index f20f69f9c..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.interp +++ /dev/null @@ -1,59 +0,0 @@ -token literal names: -null -'[' -']' -'true' -'false' -'null' -':' -null -'{' -'}' -',' -null -null - -token symbolic names: -null -null -null -TRUE -FALSE -NULL -COLON -STRING -LBRACKET -RBRACKET -COMMA -NUMBER -WS - -rule names: -T__0 -T__1 -TRUE -FALSE -NULL -COLON -STRING -LBRACKET -RBRACKET -COMMA -ESC -UNICODE -HEX -SAFECODEPOINT -NUMBER -INT -EXP -WS - -channel names: -DEFAULT_TOKEN_CHANNEL -HIDDEN - -mode names: -DEFAULT_MODE - -atn: -[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 14, 130, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 7, 8, 65, 10, 8, 12, 8, 14, 8, 68, 11, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 5, 12, 81, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 3, 16, 5, 16, 94, 10, 16, 3, 16, 3, 16, 3, 16, 6, 16, 99, 10, 16, 13, 16, 14, 16, 100, 5, 16, 103, 10, 16, 3, 16, 5, 16, 106, 10, 16, 3, 17, 3, 17, 3, 17, 7, 17, 111, 10, 17, 12, 17, 14, 17, 114, 11, 17, 5, 17, 116, 10, 17, 3, 18, 3, 18, 5, 18, 120, 10, 18, 3, 18, 3, 18, 3, 19, 6, 19, 125, 10, 19, 13, 19, 14, 19, 126, 3, 19, 3, 19, 2, 2, 20, 3, 3, 5, 4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 10, 19, 11, 21, 12, 23, 2, 25, 2, 27, 2, 29, 2, 31, 13, 33, 2, 35, 2, 37, 14, 3, 2, 10, 10, 2, 36, 36, 49, 49, 94, 94, 100, 100, 104, 104, 112, 112, 116, 116, 118, 118, 5, 2, 50, 59, 67, 72, 99, 104, 5, 2, 2, 33, 36, 36, 94, 94, 3, 2, 50, 59, 3, 2, 51, 59, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47, 5, 2, 11, 12, 15, 15, 34, 34, 2, 134, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 3, 39, 3, 2, 2, 2, 5, 41, 3, 2, 2, 2, 7, 43, 3, 2, 2, 2, 9, 48, 3, 2, 2, 2, 11, 54, 3, 2, 2, 2, 13, 59, 3, 2, 2, 2, 15, 61, 3, 2, 2, 2, 17, 71, 3, 2, 2, 2, 19, 73, 3, 2, 2, 2, 21, 75, 3, 2, 2, 2, 23, 77, 3, 2, 2, 2, 25, 82, 3, 2, 2, 2, 27, 88, 3, 2, 2, 2, 29, 90, 3, 2, 2, 2, 31, 93, 3, 2, 2, 2, 33, 115, 3, 2, 2, 2, 35, 117, 3, 2, 2, 2, 37, 124, 3, 2, 2, 2, 39, 40, 7, 93, 2, 2, 40, 4, 3, 2, 2, 2, 41, 42, 7, 95, 2, 2, 42, 6, 3, 2, 2, 2, 43, 44, 7, 118, 2, 2, 44, 45, 7, 116, 2, 2, 45, 46, 7, 119, 2, 2, 46, 47, 7, 103, 2, 2, 47, 8, 3, 2, 2, 2, 48, 49, 7, 104, 2, 2, 49, 50, 7, 99, 2, 2, 50, 51, 7, 110, 2, 2, 51, 52, 7, 117, 2, 2, 52, 53, 7, 103, 2, 2, 53, 10, 3, 2, 2, 2, 54, 55, 7, 112, 2, 2, 55, 56, 7, 119, 2, 2, 56, 57, 7, 110, 2, 2, 57, 58, 7, 110, 2, 2, 58, 12, 3, 2, 2, 2, 59, 60, 7, 60, 2, 2, 60, 14, 3, 2, 2, 2, 61, 66, 7, 36, 2, 2, 62, 65, 5, 23, 12, 2, 63, 65, 5, 29, 15, 2, 64, 62, 3, 2, 2, 2, 64, 63, 3, 2, 2, 2, 65, 68, 3, 2, 2, 2, 66, 64, 3, 2, 2, 2, 66, 67, 3, 2, 2, 2, 67, 69, 3, 2, 2, 2, 68, 66, 3, 2, 2, 2, 69, 70, 7, 36, 2, 2, 70, 16, 3, 2, 2, 2, 71, 72, 7, 125, 2, 2, 72, 18, 3, 2, 2, 2, 73, 74, 7, 127, 2, 2, 74, 20, 3, 2, 2, 2, 75, 76, 7, 46, 2, 2, 76, 22, 3, 2, 2, 2, 77, 80, 7, 94, 2, 2, 78, 81, 9, 2, 2, 2, 79, 81, 5, 25, 13, 2, 80, 78, 3, 2, 2, 2, 80, 79, 3, 2, 2, 2, 81, 24, 3, 2, 2, 2, 82, 83, 7, 119, 2, 2, 83, 84, 5, 27, 14, 2, 84, 85, 5, 27, 14, 2, 85, 86, 5, 27, 14, 2, 86, 87, 5, 27, 14, 2, 87, 26, 3, 2, 2, 2, 88, 89, 9, 3, 2, 2, 89, 28, 3, 2, 2, 2, 90, 91, 10, 4, 2, 2, 91, 30, 3, 2, 2, 2, 92, 94, 7, 47, 2, 2, 93, 92, 3, 2, 2, 2, 93, 94, 3, 2, 2, 2, 94, 95, 3, 2, 2, 2, 95, 102, 5, 33, 17, 2, 96, 98, 7, 48, 2, 2, 97, 99, 9, 5, 2, 2, 98, 97, 3, 2, 2, 2, 99, 100, 3, 2, 2, 2, 100, 98, 3, 2, 2, 2, 100, 101, 3, 2, 2, 2, 101, 103, 3, 2, 2, 2, 102, 96, 3, 2, 2, 2, 102, 103, 3, 2, 2, 2, 103, 105, 3, 2, 2, 2, 104, 106, 5, 35, 18, 2, 105, 104, 3, 2, 2, 2, 105, 106, 3, 2, 2, 2, 106, 32, 3, 2, 2, 2, 107, 116, 7, 50, 2, 2, 108, 112, 9, 6, 2, 2, 109, 111, 9, 5, 2, 2, 110, 109, 3, 2, 2, 2, 111, 114, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 116, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 115, 107, 3, 2, 2, 2, 115, 108, 3, 2, 2, 2, 116, 34, 3, 2, 2, 2, 117, 119, 9, 7, 2, 2, 118, 120, 9, 8, 2, 2, 119, 118, 3, 2, 2, 2, 119, 120, 3, 2, 2, 2, 120, 121, 3, 2, 2, 2, 121, 122, 5, 33, 17, 2, 122, 36, 3, 2, 2, 2, 123, 125, 9, 9, 2, 2, 124, 123, 3, 2, 2, 2, 125, 126, 3, 2, 2, 2, 126, 124, 3, 2, 2, 2, 126, 127, 3, 2, 2, 2, 127, 128, 3, 2, 2, 2, 128, 129, 8, 19, 2, 2, 129, 38, 3, 2, 2, 2, 14, 2, 64, 66, 80, 93, 100, 102, 105, 112, 115, 119, 126, 3, 8, 2, 2] \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.java b/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.java deleted file mode 100644 index 54f3a31ae..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.java +++ /dev/null @@ -1,153 +0,0 @@ -// Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 -package com.tyron.code.ui.editor.language.json; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.*; -import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.misc.*; - -@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) -public class JSONLexer extends Lexer { - static { RuntimeMetaData.checkVersion("4.9.2", RuntimeMetaData.VERSION); } - - protected static final DFA[] _decisionToDFA; - protected static final PredictionContextCache _sharedContextCache = - new PredictionContextCache(); - public static final int - T__0=1, T__1=2, TRUE=3, FALSE=4, NULL=5, COLON=6, STRING=7, LBRACKET=8, - RBRACKET=9, COMMA=10, NUMBER=11, WS=12; - public static String[] channelNames = { - "DEFAULT_TOKEN_CHANNEL", "HIDDEN" - }; - - public static String[] modeNames = { - "DEFAULT_MODE" - }; - - private static String[] makeRuleNames() { - return new String[] { - "T__0", "T__1", "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACKET", - "RBRACKET", "COMMA", "ESC", "UNICODE", "HEX", "SAFECODEPOINT", "NUMBER", - "INT", "EXP", "WS" - }; - } - public static final String[] ruleNames = makeRuleNames(); - - private static String[] makeLiteralNames() { - return new String[] { - null, "'['", "']'", "'true'", "'false'", "'null'", "':'", null, "'{'", - "'}'", "','" - }; - } - private static final String[] _LITERAL_NAMES = makeLiteralNames(); - private static String[] makeSymbolicNames() { - return new String[] { - null, null, null, "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACKET", - "RBRACKET", "COMMA", "NUMBER", "WS" - }; - } - private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); - public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); - - /** - * @deprecated Use {@link #VOCABULARY} instead. - */ - @Deprecated - public static final String[] tokenNames; - static { - tokenNames = new String[_SYMBOLIC_NAMES.length]; - for (int i = 0; i < tokenNames.length; i++) { - tokenNames[i] = VOCABULARY.getLiteralName(i); - if (tokenNames[i] == null) { - tokenNames[i] = VOCABULARY.getSymbolicName(i); - } - - if (tokenNames[i] == null) { - tokenNames[i] = ""; - } - } - } - - @Override - @Deprecated - public String[] getTokenNames() { - return tokenNames; - } - - @Override - - public Vocabulary getVocabulary() { - return VOCABULARY; - } - - - public JSONLexer(CharStream input) { - super(input); - _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); - } - - @Override - public String getGrammarFileName() { return "JSON.g4"; } - - @Override - public String[] getRuleNames() { return ruleNames; } - - @Override - public String getSerializedATN() { return _serializedATN; } - - @Override - public String[] getChannelNames() { return channelNames; } - - @Override - public String[] getModeNames() { return modeNames; } - - @Override - public ATN getATN() { return _ATN; } - - public static final String _serializedATN = - "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2\16\u0082\b\1\4\2"+ - "\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4"+ - "\13\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22"+ - "\t\22\4\23\t\23\3\2\3\2\3\3\3\3\3\4\3\4\3\4\3\4\3\4\3\5\3\5\3\5\3\5\3"+ - "\5\3\5\3\6\3\6\3\6\3\6\3\6\3\7\3\7\3\b\3\b\3\b\7\bA\n\b\f\b\16\bD\13\b"+ - "\3\b\3\b\3\t\3\t\3\n\3\n\3\13\3\13\3\f\3\f\3\f\5\fQ\n\f\3\r\3\r\3\r\3"+ - "\r\3\r\3\r\3\16\3\16\3\17\3\17\3\20\5\20^\n\20\3\20\3\20\3\20\6\20c\n"+ - "\20\r\20\16\20d\5\20g\n\20\3\20\5\20j\n\20\3\21\3\21\3\21\7\21o\n\21\f"+ - "\21\16\21r\13\21\5\21t\n\21\3\22\3\22\5\22x\n\22\3\22\3\22\3\23\6\23}"+ - "\n\23\r\23\16\23~\3\23\3\23\2\2\24\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n"+ - "\23\13\25\f\27\2\31\2\33\2\35\2\37\r!\2#\2%\16\3\2\n\n\2$$\61\61^^ddh"+ - "hppttvv\5\2\62;CHch\5\2\2!$$^^\3\2\62;\3\2\63;\4\2GGgg\4\2--//\5\2\13"+ - "\f\17\17\"\"\2\u0086\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2"+ - "\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3"+ - "\2\2\2\2\37\3\2\2\2\2%\3\2\2\2\3\'\3\2\2\2\5)\3\2\2\2\7+\3\2\2\2\t\60"+ - "\3\2\2\2\13\66\3\2\2\2\r;\3\2\2\2\17=\3\2\2\2\21G\3\2\2\2\23I\3\2\2\2"+ - "\25K\3\2\2\2\27M\3\2\2\2\31R\3\2\2\2\33X\3\2\2\2\35Z\3\2\2\2\37]\3\2\2"+ - "\2!s\3\2\2\2#u\3\2\2\2%|\3\2\2\2\'(\7]\2\2(\4\3\2\2\2)*\7_\2\2*\6\3\2"+ - "\2\2+,\7v\2\2,-\7t\2\2-.\7w\2\2./\7g\2\2/\b\3\2\2\2\60\61\7h\2\2\61\62"+ - "\7c\2\2\62\63\7n\2\2\63\64\7u\2\2\64\65\7g\2\2\65\n\3\2\2\2\66\67\7p\2"+ - "\2\678\7w\2\289\7n\2\29:\7n\2\2:\f\3\2\2\2;<\7<\2\2<\16\3\2\2\2=B\7$\2"+ - "\2>A\5\27\f\2?A\5\35\17\2@>\3\2\2\2@?\3\2\2\2AD\3\2\2\2B@\3\2\2\2BC\3"+ - "\2\2\2CE\3\2\2\2DB\3\2\2\2EF\7$\2\2F\20\3\2\2\2GH\7}\2\2H\22\3\2\2\2I"+ - "J\7\177\2\2J\24\3\2\2\2KL\7.\2\2L\26\3\2\2\2MP\7^\2\2NQ\t\2\2\2OQ\5\31"+ - "\r\2PN\3\2\2\2PO\3\2\2\2Q\30\3\2\2\2RS\7w\2\2ST\5\33\16\2TU\5\33\16\2"+ - "UV\5\33\16\2VW\5\33\16\2W\32\3\2\2\2XY\t\3\2\2Y\34\3\2\2\2Z[\n\4\2\2["+ - "\36\3\2\2\2\\^\7/\2\2]\\\3\2\2\2]^\3\2\2\2^_\3\2\2\2_f\5!\21\2`b\7\60"+ - "\2\2ac\t\5\2\2ba\3\2\2\2cd\3\2\2\2db\3\2\2\2de\3\2\2\2eg\3\2\2\2f`\3\2"+ - "\2\2fg\3\2\2\2gi\3\2\2\2hj\5#\22\2ih\3\2\2\2ij\3\2\2\2j \3\2\2\2kt\7\62"+ - "\2\2lp\t\6\2\2mo\t\5\2\2nm\3\2\2\2or\3\2\2\2pn\3\2\2\2pq\3\2\2\2qt\3\2"+ - "\2\2rp\3\2\2\2sk\3\2\2\2sl\3\2\2\2t\"\3\2\2\2uw\t\7\2\2vx\t\b\2\2wv\3"+ - "\2\2\2wx\3\2\2\2xy\3\2\2\2yz\5!\21\2z$\3\2\2\2{}\t\t\2\2|{\3\2\2\2}~\3"+ - "\2\2\2~|\3\2\2\2~\177\3\2\2\2\177\u0080\3\2\2\2\u0080\u0081\b\23\2\2\u0081"+ - "&\3\2\2\2\16\2@BP]dfipsw~\3\b\2\2"; - public static final ATN _ATN = - new ATNDeserializer().deserialize(_serializedATN.toCharArray()); - static { - _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; - for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { - _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.tokens b/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.tokens deleted file mode 100644 index 29212732a..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JSONLexer.tokens +++ /dev/null @@ -1,21 +0,0 @@ -T__0=1 -T__1=2 -TRUE=3 -FALSE=4 -NULL=5 -COLON=6 -STRING=7 -LBRACKET=8 -RBRACKET=9 -COMMA=10 -NUMBER=11 -WS=12 -'['=1 -']'=2 -'true'=3 -'false'=4 -'null'=5 -':'=6 -'{'=8 -'}'=9 -','=10 diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/Json.java b/app/src/main/java/com/tyron/code/ui/editor/language/json/Json.java deleted file mode 100644 index c11317e68..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/Json.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.tyron.code.ui.editor.language.json; - -import com.tyron.code.ui.editor.language.Language; -import com.tyron.editor.Editor; - -import java.io.File; - -import io.github.rosemoe.sora.widget.CodeEditor; - -public class Json implements Language { - @Override - public boolean isApplicable(File ext) { - return ext.getName().endsWith(".json"); - } - - @Override - public io.github.rosemoe.sora.lang.Language get(Editor editor) { - return new JsonLanguage(editor); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/json/JsonLanguage.java b/app/src/main/java/com/tyron/code/ui/editor/language/json/JsonLanguage.java deleted file mode 100644 index 44e45a7ca..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/json/JsonLanguage.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.tyron.code.ui.editor.language.json; - -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.tyron.completion.java.rewrite.EditHelper; -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.Token; -import org.apache.commons.io.input.CharSequenceReader; - -import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; -import io.github.rosemoe.sora.lang.analysis.StyleReceiver; -import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; -import io.github.rosemoe.sora.lang.completion.CompletionPublisher; -import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; -import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; -import io.github.rosemoe.sora.lang.styling.Styles; -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.CodeEditor; -import io.github.rosemoe.sora.widget.SymbolPairMatch; - -public class JsonLanguage implements Language { - - private final Editor mEditor; - - private final JsonAnalyzer mAnalyzer; - - public JsonLanguage(Editor editor) { - mEditor = editor; - - mAnalyzer = new JsonAnalyzer(); - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return mAnalyzer; - } - - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_STRONG; - } - - @Override - public void requireAutoComplete(@NonNull ContentReference content, @NonNull CharPosition position, @NonNull CompletionPublisher publisher, @NonNull Bundle extraArguments) throws CompletionCancelledException { - - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return getIndentAdvance(String.valueOf(content.getReference()), line, column); - } - - private int getIndentAdvance(String content, int line, int column) { - JSONLexer lexer = new JSONLexer(CharStreams.fromString(content)); - Token token; - int advance = 0; - while ((token = lexer.nextToken()).getType() != Token.EOF) { - if (token.getType() == JSONLexer.LBRACKET) { - advance++; - } - } - advance = Math.max(0, advance); - return advance * 2; - } - - @Override - public boolean useTab() { - return false; - } - - @Override - public CharSequence format(CharSequence text) { - try { - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - JsonElement jsonElement = JsonParser.parseString(text.toString()); - return gson.toJson(jsonElement); - } catch (Throwable e) { - // format error, return the original string - return text; - } - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return new NewlineHandler[] { - new IndentHandler("{", "}"), - new IndentHandler("[", "]") - }; - } - - @Override - public void destroy() { - - } - - class IndentHandler implements NewlineHandler { - - private final String start; - private final String end; - - public IndentHandler(String start, String end) { - this.start = start; - this.end = end; - } - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.endsWith(start) && afterText.startsWith(end); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceBefore = getIndentAdvance(beforeText, -1, -1); - int advanceAfter = getIndentAdvance(afterText, -1, -1); - String text; - StringBuilder sb = new StringBuilder("\n") - .append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab())) - .append('\n') - .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); - int shiftLeft = text.length() + 1; - return new NewlineHandleResult(sb, shiftLeft); - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/Kotlin.java b/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/Kotlin.java deleted file mode 100644 index cb565bb69..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/Kotlin.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.tyron.code.ui.editor.language.kotlin; - -import com.tyron.code.ui.editor.language.Language; -import com.tyron.editor.Editor; - -import java.io.File; - -import io.github.rosemoe.sora2.interfaces.EditorLanguage; -import io.github.rosemoe.sora2.widget.CodeEditor; - -public class Kotlin implements Language { - @Override - public boolean isApplicable(File ext) { - return ext.getName().endsWith(".kt"); - } - - @Override - public io.github.rosemoe.sora.lang.Language get(Editor editor) { - return new KotlinLanguage(editor); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAnalyzer.java b/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAnalyzer.java deleted file mode 100644 index 7af2da9dc..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAnalyzer.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.tyron.code.ui.editor.language.kotlin; - -import android.graphics.Color; -import android.util.Log; - -import androidx.preference.PreferenceManager; - -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.AndroidModule; -import com.tyron.builder.project.api.Module; -import com.tyron.code.ApplicationLoader; -import com.tyron.code.BuildConfig; -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; -import com.tyron.code.ui.editor.language.HighlightUtil; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.common.SharedPreferenceKeys; -import com.tyron.completion.progress.ProgressManager; -import com.tyron.editor.Editor; -import com.tyron.kotlin_completion.CompletionEngine; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CodePointCharStream; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenSource; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; - -import io.github.rosemoe.sora.lang.styling.Styles; -import io.github.rosemoe.sora2.data.BlockLine; -import io.github.rosemoe.sora2.data.Span; -import io.github.rosemoe.sora2.interfaces.CodeAnalyzer; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.text.TextAnalyzer; -import io.github.rosemoe.sora2.widget.CodeEditor; -import io.github.rosemoe.sora2.widget.EditorColorScheme; - -public class KotlinAnalyzer extends AbstractCodeAnalyzer { - - private final WeakReference mEditorReference; - private final List mDiagnostics; - - public KotlinAnalyzer(Editor editor) { - mEditorReference = new WeakReference<>(editor); - mDiagnostics = new ArrayList<>(); - } - - @Override - public void setup() { - putColor(EditorColorScheme.KEYWORD, KotlinLexer.OVERRIDE, - KotlinLexer.FUN, KotlinLexer.PACKAGE, KotlinLexer.IMPORT, - KotlinLexer.CLASS, KotlinLexer.INTERFACE); - - // todo add block lines - } - - @Override - public void setDiagnostics(Editor editor, List diagnostics) { - mDiagnostics.clear(); - mDiagnostics.addAll(diagnostics); - } - - @Override - public Lexer getLexer(CharStream input) { - return new KotlinLexer(input); - } - - @Override - public void analyzeInBackground(CharSequence content) { - Editor editor = mEditorReference.get(); - if (editor == null) { - return; - } - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - if (currentProject != null) { - Module module = currentProject.getModule(editor.getCurrentFile()); - if (module instanceof AndroidModule) { - if (ApplicationLoader.getDefaultPreferences() - .getBoolean(SharedPreferenceKeys.KOTLIN_HIGHLIGHTING, true)) { - ProgressManager.getInstance().runLater(() -> { - CompletionEngine.getInstance((AndroidModule) module) - .doLint(editor.getCurrentFile(), content.toString(), editor::setDiagnostics); - }, 1500); - } - } - } - } - - private static class UnknownToken implements Token { - - public static UnknownToken INSTANCE = new UnknownToken(); - - @Override - public String getText() { - return ""; - } - - @Override - public int getType() { - return -1; - } - - @Override - public int getLine() { - return 0; - } - - @Override - public int getCharPositionInLine() { - return 0; - } - - @Override - public int getChannel() { - return 0; - } - - @Override - public int getTokenIndex() { - return 0; - } - - @Override - public int getStartIndex() { - return 0; - } - - @Override - public int getStopIndex() { - return 0; - } - - @Override - public TokenSource getTokenSource() { - return null; - } - - @Override - public CharStream getInputStream() { - return null; - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAutoCompleteProvider.java deleted file mode 100644 index de6fc2daf..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinAutoCompleteProvider.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.tyron.code.ui.editor.language.kotlin; - -import android.content.SharedPreferences; - -import androidx.annotation.Nullable; -import androidx.preference.PreferenceManager; - -import com.tyron.code.ui.editor.language.AbstractAutoCompleteProvider; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.AndroidModule; -import com.tyron.builder.project.api.Module; -import com.tyron.common.SharedPreferenceKeys; -import com.tyron.completion.model.CompletionList; -import com.tyron.kotlin_completion.CompletionEngine; - -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.widget.CodeEditor; - -public class KotlinAutoCompleteProvider extends AbstractAutoCompleteProvider { - - private static final String TAG = KotlinAutoCompleteProvider.class.getSimpleName(); - - private final CodeEditor mEditor; - private final SharedPreferences mPreferences; - - - public KotlinAutoCompleteProvider(CodeEditor editor) { - mEditor = editor; - mPreferences = PreferenceManager.getDefaultSharedPreferences(editor.getContext()); - } - - @Nullable - @Override - public CompletionList getCompletionList( - String prefix, TextAnalyzeResult colors, int line, int column) { - if (!mPreferences.getBoolean(SharedPreferenceKeys.KOTLIN_COMPLETIONS, false)) { - return null; - } - - if (com.tyron.completion.java.provider.CompletionEngine.isIndexing()) { - return null; - } - - if (!mPreferences.getBoolean(SharedPreferenceKeys.KOTLIN_COMPLETIONS, false)) { - return null; - } - - Project project = ProjectManager.getInstance() - .getCurrentProject(); - if (project == null) { - return null; - } - - Module currentModule = project.getModule(mEditor.getCurrentFile()); - - if (!(currentModule instanceof AndroidModule)) { - return null; - } - - CompletionEngine engine = CompletionEngine.getInstance((AndroidModule) currentModule); - - if (engine.isIndexing()) { - return null; - } - - // waiting for code editor to support async code completions - return engine.complete(mEditor.getCurrentFile(), mEditor.getText().toString(), prefix, line, column, mEditor.getCursor().getLeft()); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLanguage.java b/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLanguage.java deleted file mode 100644 index c82787a18..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/kotlin/KotlinLanguage.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.tyron.code.ui.editor.language.kotlin; - -import android.os.Bundle; - -import androidx.annotation.NonNull; - -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.Token; - -import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; -import io.github.rosemoe.sora.lang.completion.CompletionPublisher; -import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; -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.text.TextUtils; -import io.github.rosemoe.sora.widget.SymbolPairMatch; - -public class KotlinLanguage implements Language { - - private final Editor mEditor; - private final KotlinAnalyzer mAnalyzer; - - public KotlinLanguage(Editor editor) { - mEditor = editor; - mAnalyzer = new KotlinAnalyzer(mEditor); - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return mAnalyzer; - } - - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_SLIGHT; - } - - @Override - public void requireAutoComplete(@NonNull ContentReference content, @NonNull CharPosition position, @NonNull CompletionPublisher publisher, @NonNull Bundle extraArguments) throws CompletionCancelledException { - - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return getIndentAdvance(String.valueOf(content.getReference())); - } - - public int getIndentAdvance(String p1) { - KotlinLexer lexer = new KotlinLexer(CharStreams.fromString(p1)); - Token token; - int advance = 0; - while ((token = lexer.nextToken()) != null) { - if (token.getType() == KotlinLexer.EOF) { - break; - } - if (token.getType() == KotlinLexer.LCURL) { - advance++; - /*case RBRACE: - advance--; - break;*/ - } - } - advance = Math.max(0, advance); - return advance * 4; - } - - @Override - public boolean useTab() { - return true; - } - - @Override - public CharSequence format(CharSequence text) { - return text; - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return handlers; - } - - @Override - public void destroy() { - - } - - private final NewlineHandler[] handlers = new NewlineHandler[]{new BraceHandler()}; - - class BraceHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.endsWith("{") && afterText.startsWith("}"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceBefore = getIndentAdvance(beforeText); - int advanceAfter = getIndentAdvance(afterText); - String text; - StringBuilder sb = new StringBuilder("\n") - .append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab())) - .append('\n') - .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); - int shiftLeft = text.length() + 1; - return new NewlineHandleResult(sb, shiftLeft); - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/BasicXmlPullAnalyzer.java b/app/src/main/java/com/tyron/code/ui/editor/language/xml/BasicXmlPullAnalyzer.java deleted file mode 100644 index bde05651e..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/BasicXmlPullAnalyzer.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.tyron.code.ui.editor.language.xml; - -import com.tyron.builder.util.CharSequenceReader; -import com.tyron.code.ui.editor.language.HighlightUtil; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -import io.github.rosemoe.sora2.interfaces.CodeAnalyzer; -import io.github.rosemoe.sora2.text.LineNumberCalculator; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; -import io.github.rosemoe.sora2.text.TextAnalyzer; - -public class BasicXmlPullAnalyzer implements CodeAnalyzer { - - @Override - public void analyze(CharSequence content, TextAnalyzeResult result, TextAnalyzer.AnalyzeThread.Delegate delegate) { - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); - XmlPullParser parser = factory.newPullParser(); - - LineNumberCalculator calculator = new LineNumberCalculator(content); - calculator.update(content.length()); - int errLine = 0; - int errColumn = 0; - parser.setInput(new CharSequenceReader(content)); - while (delegate.shouldAnalyze()) { - try { - if (calculator.getLine() + 1 == parser.getLineNumber() && - calculator.getColumn() + 1 == parser.getColumnNumber()) { - break; - } - parser.next(); - } catch (XmlPullParserException e) { - if (errLine == parser.getLineNumber() && errColumn == parser.getColumnNumber()) { - break; - } - errLine = parser.getLineNumber(); - errColumn = parser.getColumnNumber(); - HighlightUtil.setErrorSpan(result, errLine, errColumn); - } - } - } catch (Exception ignored) { - - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/LanguageXML.java b/app/src/main/java/com/tyron/code/ui/editor/language/xml/LanguageXML.java deleted file mode 100644 index 15b9c2708..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/LanguageXML.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.tyron.code.ui.editor.language.xml; - -import android.os.Bundle; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tyron.builder.compiler.manifest.xml.XmlFormatPreferences; -import com.tyron.builder.compiler.manifest.xml.XmlFormatStyle; -import com.tyron.builder.compiler.manifest.xml.XmlPrettyPrinter; -import com.tyron.code.util.ProjectUtils; -import com.tyron.completion.xml.lexer.XMLLexer; -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.Token; - -import java.io.File; -import java.util.List; - -import io.github.rosemoe.sora.lang.Language; -import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; -import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; -import io.github.rosemoe.sora.lang.completion.CompletionHelper; -import io.github.rosemoe.sora.lang.completion.CompletionPublisher; -import io.github.rosemoe.sora.lang.completion.SimpleCompletionItem; -import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; -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.text.TextUtils; -import io.github.rosemoe.sora.util.MyCharacter; -import io.github.rosemoe.sora.widget.CodeEditor; -import io.github.rosemoe.sora.widget.SymbolPairMatch; -import io.github.rosemoe.sora2.data.CompletionItem; - -public class LanguageXML implements Language { - - private final Editor mEditor; - - private final XMLAnalyzer mAnalyzer; - - public LanguageXML(Editor codeEditor) { - mEditor = codeEditor; - - mAnalyzer = new XMLAnalyzer(codeEditor); - } - - public boolean isAutoCompleteChar(char ch) { - return MyCharacter.isJavaIdentifierPart(ch) - || ch == '<' - || ch == '/' - || ch == ':' - || ch == '.'; - } - - @Override - public boolean useTab() { - return true; - } - - @Override - public CharSequence format(CharSequence text) { - XmlFormatPreferences preferences = XmlFormatPreferences.defaults(); - File file = mEditor.getCurrentFile(); - CharSequence formatted = null; - if ("AndroidManifest.xml".equals(file.getName())) { - formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), - preferences, XmlFormatStyle.MANIFEST, "\n"); - } else { - if (ProjectUtils.isLayoutXMLFile(file)) { - formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), - preferences, XmlFormatStyle.LAYOUT, "\n"); - } else if (ProjectUtils.isResourceXMLFile(file)) { - formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), - preferences, XmlFormatStyle.RESOURCE, "\n"); - } - } - if (formatted == null) { - formatted = text; - } - return formatted; - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return new NewlineHandler[]{new StartTagHandler()}; - } - - @Override - public void destroy() { - - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return mAnalyzer; - } - - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_SLIGHT; - } - - @Override - public void requireAutoComplete(@NonNull ContentReference content, - @NonNull CharPosition position, - @NonNull CompletionPublisher publisher, - @NonNull Bundle extraArguments) throws CompletionCancelledException { - String prefix = CompletionHelper.computePrefix(content, position, this::isAutoCompleteChar); - List items = - new XMLAutoCompleteProvider(mEditor).getAutoCompleteItems(prefix, null, - position.getLine(), position.getColumn()); - for (CompletionItem item : items) { - SimpleCompletionItem simpleCompletionItem = - new SimpleCompletionItem(item.label, item.desc, prefix.length(), item.commit); - publisher.addItem(simpleCompletionItem); - } - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return getIndentAdvance(String.valueOf(content.getReference())); - } - - public int getIndentAdvance(String content) { - XMLLexer lexer = new XMLLexer(CharStreams.fromString(content)); - int advance = 0; - Token token; - while ((token = lexer.nextToken()) != null) { - if (token.getType() == XMLLexer.EOF) { - break; - } - - if (token.getType() == XMLLexer.OPEN) { - advance++; - } else if (token.getType() == XMLLexer.SLASH_CLOSE) { - advance--; - } else if (token.getType() == XMLLexer.CLOSE) { - advance--; - } - } - advance = Math.max(0, advance); - return advance * 4; - } - - private class StartTagHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - Log.d("StartTagHandler", "beforeText: " + beforeText + " afterText: " + afterText); - return beforeText.trim().startsWith("<"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - String text; - StringBuilder sb = new StringBuilder() - .append("\n") - .append(text = TextUtils.createIndent(count + 4, tabSize, useTab())); - return new NewlineHandleResult(sb, 0); - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAnalyzer.java b/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAnalyzer.java deleted file mode 100644 index 064647635..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAnalyzer.java +++ /dev/null @@ -1,358 +0,0 @@ -package com.tyron.code.ui.editor.language.xml; - -import android.graphics.Color; -import android.os.Handler; -import android.util.Log; - -import com.tyron.builder.compiler.BuildType; -import com.tyron.builder.compiler.incremental.resource.IncrementalAapt2Task; -import com.tyron.builder.exception.CompilationFailedException; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.model.DiagnosticWrapper; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.AndroidModule; -import com.tyron.builder.project.api.Module; -import com.tyron.code.BuildConfig; -import com.tyron.code.ui.editor.language.AbstractCodeAnalyzer; -import com.tyron.code.ui.editor.language.HighlightUtil; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.code.util.ProjectUtils; -import com.tyron.completion.index.CompilerService; -import com.tyron.completion.java.compiler.CompilerContainer; -import com.tyron.completion.java.JavaCompilerProvider; -import com.tyron.completion.java.compiler.JavaCompilerService; -import com.tyron.completion.xml.lexer.XMLLexer; -import com.tyron.editor.Editor; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.Token; -import org.apache.commons.io.FileUtils; - -import java.io.File; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; -import java.util.concurrent.Executors; -import java.util.stream.Collectors; - -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 io.github.rosemoe.sora.lang.styling.CodeBlock; -import io.github.rosemoe.sora.lang.styling.TextStyle; -import io.github.rosemoe.sora.text.CharPosition; -import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; -import io.github.rosemoe.sora2.text.DiagnosticSpanMapUpdater; - -public class XMLAnalyzer extends AbstractCodeAnalyzer { - - private final WeakReference mEditorReference; - private final Stack mBlockLine = new Stack<>(); - private int mMaxSwitch = 1; - private int mCurrSwitch = 0; - - public XMLAnalyzer(Editor codeEditor) { - mEditorReference = new WeakReference<>(codeEditor); - } - - @Override - public Lexer getLexer(CharStream input) { - return new XMLLexer(input); - } - - @Override - public void analyzeInBackground(CharSequence contents) { - Editor editor = mEditorReference.get(); - if (editor == null) { - return; - } - - File currentFile = editor.getCurrentFile(); - if (currentFile == null) { - return; - } - - List diagnosticWrappers = new ArrayList<>(); - - compile(currentFile, contents.toString(), new ILogger() { - @Override - public void info(DiagnosticWrapper wrapper) { - addMaybe(wrapper); - } - - @Override - public void debug(DiagnosticWrapper wrapper) { - addMaybe(wrapper); - } - - @Override - public void warning(DiagnosticWrapper wrapper) { - addMaybe(wrapper); - } - - @Override - public void error(DiagnosticWrapper wrapper) { - addMaybe(wrapper); - } - - private void addMaybe(DiagnosticWrapper wrapper) { - if (currentFile.equals(wrapper.getSource())) { - diagnosticWrappers.add(wrapper); - } - } - }, () -> { - editor.setDiagnostics(diagnosticWrappers.stream() - .filter(it -> it.getLineNumber() > 0).collect(Collectors.toList())); - }); - } - - @Override - public void setup() { - putColor(EditorColorScheme.COMMENT, XMLLexer.COMMENT); - putColor(EditorColorScheme.HTML_TAG, XMLLexer.Name); - } - - @Override - protected void beforeAnalyze() { - mBlockLine.clear(); - mMaxSwitch = 1; - mCurrSwitch = 0; - } - - @Override - public boolean onNextToken(Token token, Styles styles, MappedSpans.Builder colors) { - int line = token.getLine() - 1; - int column = token.getCharPositionInLine(); - - Token previous = getPreviousToken(); - - switch (token.getType()) { - case XMLLexer.COMMENT: - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.COMMENT)); - return true; - case XMLLexer.Name: - if (previous != null && previous.getType() == XMLLexer.SLASH) { - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.HTML_TAG)); - return true; - } else if (previous != null && previous.getType() == XMLLexer.OPEN) { - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.HTML_TAG)); - CodeBlock block = new CodeBlock(); - block.startLine = previous.getLine() - 1; - block.startColumn = previous.getCharPositionInLine(); - mBlockLine.push(block); - return true; - } - String attribute = token.getText(); - if (attribute.contains(":")) { - colors.addIfNeeded(line, column, EditorColorScheme.ATTRIBUTE_NAME); - colors.addIfNeeded(line, column + attribute.indexOf(":"), - EditorColorScheme.TEXT_NORMAL); - return true; - } - colors.addIfNeeded(line, column, EditorColorScheme.IDENTIFIER_NAME); - return true; - case XMLLexer.EQUALS: - colors.addIfNeeded(line, column, EditorColorScheme.OPERATOR); - return true; - case XMLLexer.STRING: - String text = token.getText(); - if (text.startsWith("\"#")) { - try { - int color = Color.parseColor(text.substring(1, text.length() - 1)); - colors.addIfNeeded(line, column, EditorColorScheme.LITERAL); - - Span span = Span.obtain(column + 1, EditorColorScheme.LITERAL); - span.setUnderlineColor(color); - colors.add(line, span); - - Span middle = Span.obtain(column + text.length() - 1, - EditorColorScheme.LITERAL); - middle.setUnderlineColor(Color.TRANSPARENT); - colors.add(line, middle); - - Span end = Span.obtain(column + text.length(), TextStyle.makeStyle(EditorColorScheme.TEXT_NORMAL)); - end.setUnderlineColor(Color.TRANSPARENT); - colors.add(line, end); - break; - } catch (Exception ignore) { - } - } - colors.addIfNeeded(line, column, EditorColorScheme.LITERAL); - return true; - case XMLLexer.SLASH_CLOSE: - colors.addIfNeeded(line, column, EditorColorScheme.HTML_TAG); - if (!mBlockLine.isEmpty()) { - CodeBlock block = mBlockLine.pop(); - block.endLine = line; - block.endColumn = column; - if (block.startLine != block.endLine) { - if (previous != null && previous.getLine() == token.getLine()) { - block.toBottomOfEndLine = true; - } - styles.addCodeBlock(block); - } - } - return true; - case XMLLexer.SLASH: - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.HTML_TAG)); - if (previous != null && previous.getType() == XMLLexer.OPEN) { - if (!mBlockLine.isEmpty()) { - CodeBlock block = mBlockLine.pop(); - block.endLine = previous.getLine() - 1; - block.endColumn = previous.getCharPositionInLine(); - if (block.startLine != block.endLine) { - if (previous.getLine() == token.getLine()) { - block.toBottomOfEndLine = true; - } - styles.addCodeBlock(block); - } - } - } - return true; - case XMLLexer.OPEN: - case XMLLexer.CLOSE: - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.HTML_TAG)); - return true; - case XMLLexer.SEA_WS: - case XMLLexer.S: - // skip white spaces - return true; - default: - colors.addIfNeeded(line, column, TextStyle.makeStyle(EditorColorScheme.TEXT_NORMAL)); - return true; - } - - return false; - } - - @Override - protected void afterAnalyze(CharSequence content, Styles styles, MappedSpans.Builder colors) { - if (mBlockLine.isEmpty()) { - if (mCurrSwitch > mMaxSwitch) { - mMaxSwitch = mCurrSwitch; - } - } - styles.setSuppressSwitch(mMaxSwitch + 10); - - for (DiagnosticWrapper d : mDiagnostics) { - HighlightUtil.setErrorSpan(styles, d.getStartLine()); - } - } - - private final Handler handler = new Handler(); - long delay = 1000L; - long lastTime; - - private void compile(File file, String contents, ILogger logger, Runnable callback) { - handler.removeCallbacks(runnable); - lastTime = System.currentTimeMillis(); - runnable.setContents(contents); - runnable.setFile(file); - runnable.setLogger(logger); - runnable.setCallback(callback); - handler.postDelayed(runnable, delay); - } - - CompileRunnable runnable = new CompileRunnable(); - - private class CompileRunnable implements Runnable { - - private ILogger logger; - private File file; - private String contents; - private Runnable callback; - - public CompileRunnable() { - } - - public void setCallback(Runnable callback) { - this.callback = callback; - } - - public void setLogger(ILogger logger) { - this.logger = logger; - } - - public void setFile(File file) { - this.file = file; - } - - private void setContents(String contents) { - this.contents = contents; - } - - @Override - public void run() { - if (logger == null) { - return; - } - if (System.currentTimeMillis() < (lastTime - 500)) { - return; - } - - Executors.newSingleThreadExecutor().execute(() -> { - if (file == null || logger == null || contents == null) { - return; - } - boolean isResource = ProjectUtils.isResourceXMLFile(file); - - if (isResource) { - Project project = ProjectManager.getInstance().getCurrentProject(); - if (project != null) { - Module module = project.getModule(file); - if (module instanceof AndroidModule) { - try { - doGenerate(project, (AndroidModule) module, file, contents); - } catch (IOException | CompilationFailedException e) { - if (BuildConfig.DEBUG) { - Log.e("XMLAnalyzer", "Failed compiling", e); - } - } - } - } - } - }); - } - - private void doGenerate(Project project, AndroidModule module, File file, - String contents) throws IOException, CompilationFailedException { - if (!file.canWrite() || !file.canRead()) { - return; - } - - FileUtils.writeStringToFile(file, contents, StandardCharsets.UTF_8); - IncrementalAapt2Task task = new IncrementalAapt2Task(module, logger, false); - - try { - task.prepare(BuildType.DEBUG); - task.run(); - } catch (CompilationFailedException e) { - if (callback != null) { - handler.post(callback); - } - throw e; - } - - if (callback != null) { - handler.post(callback); - } - - // work around to refresh R.java file - File resourceClass = module.getJavaFile(module.getPackageName() + ".R"); - if (resourceClass != null) { - JavaCompilerProvider provider = - CompilerService.getInstance().getIndex(JavaCompilerProvider.KEY); - JavaCompilerService service = provider.getCompiler(project, module); - - CompilerContainer container = service.compile(resourceClass.toPath()); - container.run(__ -> { - - }); - } - } - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAutoCompleteProvider.java deleted file mode 100644 index cc892735a..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/XMLAutoCompleteProvider.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.tyron.code.ui.editor.language.xml; - -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.AndroidModule; -import com.tyron.builder.project.api.Module; -import com.tyron.builder.util.CharSequenceReader; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.completion.main.CompletionEngine; -import com.tyron.completion.model.CompletionList; -import com.tyron.editor.Editor; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -import io.github.rosemoe.sora2.interfaces.AutoCompleteProvider; -import io.github.rosemoe.sora2.text.TextAnalyzeResult; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import io.github.rosemoe.sora2.data.CompletionItem; -import io.github.rosemoe.sora2.widget.CodeEditor; - -import java.util.Stack; -import java.util.stream.Collectors; - -public class XMLAutoCompleteProvider implements AutoCompleteProvider { - - private final Editor mEditor; - - public XMLAutoCompleteProvider(Editor editor) { - mEditor = editor; - } - - @Override - public List getAutoCompleteItems(String prefix, - TextAnalyzeResult analyzeResult, int line, - int column) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - if (currentProject == null) { - return null; - } - Module module = currentProject.getModule(mEditor.getCurrentFile()); - if (!(module instanceof AndroidModule)) { - return null; - } - - File currentFile = mEditor.getCurrentFile(); - if (currentFile == null) { - return null; - } - CompletionList complete = CompletionEngine.getInstance().complete(currentProject, module, - currentFile, mEditor.getContent().toString(), prefix, line, column, - mEditor.getCaret().getStart()); - return complete.items.stream().map(CompletionItem::new).collect(Collectors.toList()); - } - - private List getClosingTagSuggestions() { - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); - XmlPullParser parser = factory.newPullParser(); - parser.setInput(new CharSequenceReader(mEditor.getContent())); - - Stack stack = new Stack<>(); - - int next = parser.nextTag(); - while (true) { - int type = parser.getEventType(); - switch (type) { - case XmlPullParser.START_TAG: - stack.push(parser.getName()); - break; - case XmlPullParser.END_TAG: - stack.pop(); - break; - } - - try { - next = parser.nextTag(); - if (next == XmlPullParser.END_DOCUMENT) { - break; - } - } catch (Exception e) { - break; - } - } - - if (!stack.isEmpty()) { - List list = new ArrayList<>(); - for (int i = stack.size() - 1; i >= 0; i--) { - String s = stack.get(i); - list.add(new CompletionItem(s, "tag")); - } - return list; - } - - } catch (XmlPullParserException | IOException e) { - return null; - } - return null; - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/language/xml/Xml.java b/app/src/main/java/com/tyron/code/ui/editor/language/xml/Xml.java deleted file mode 100644 index 2c5645cf5..000000000 --- a/app/src/main/java/com/tyron/code/ui/editor/language/xml/Xml.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.tyron.code.ui.editor.language.xml; - -import com.tyron.code.ui.editor.language.Language; -import com.tyron.editor.Editor; - -import java.io.File; - - -public class Xml implements Language { - - @Override - public boolean isApplicable(File file) { - return file.getName().endsWith(".xml"); - } - - @Override - public io.github.rosemoe.sora.lang.Language get(Editor editor) { - return new LanguageXML(editor); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/editor/log/AppLogFragment.java b/app/src/main/java/com/tyron/code/ui/editor/log/AppLogFragment.java index d6d38a582..fdf020eca 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/log/AppLogFragment.java +++ b/app/src/main/java/com/tyron/code/ui/editor/log/AppLogFragment.java @@ -19,12 +19,25 @@ import com.tyron.code.ui.editor.log.adapter.LogAdapter; import com.tyron.code.ui.main.MainViewModel; import com.tyron.code.ui.project.ProjectManager; - +import com.tyron.common.util.AndroidUtilities; +import com.tyron.common.util.ShareUtils; +import com.tyron.fileeditor.api.FileEditorManager; +import com.tyron.terminal.TerminalSession; +import com.tyron.terminal.TerminalSessionClientAdapter; +import com.tyron.terminal.view.TerminalView; +import com.tyron.terminal.view.TerminalViewClientAdapter; + +import java.io.IOException; +import java.io.OutputStream; import java.util.List; +import java.util.logging.Handler; public class AppLogFragment extends Fragment implements ProjectManager.OnProjectOpenListener { + /** Only used in IDE Logs **/ + private Handler mHandler; + public static AppLogFragment newInstance(int id) { AppLogFragment fragment = new AppLogFragment(); Bundle bundle = new Bundle(); @@ -38,6 +51,10 @@ public static AppLogFragment newInstance(int id) { private LogViewModel mModel; private LogAdapter mAdapter; private RecyclerView mRecyclerView; + private TerminalView mTerminalView; + + public static OutputStream outputStream; + public static OutputStream errorOutputStream; public AppLogFragment() { @@ -56,11 +73,56 @@ public void onCreate(Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { FrameLayout mRoot = new FrameLayout(requireContext()); + if (id == LogViewModel.BUILD_LOG) { + TerminalSession session = new TerminalSession("", "", new String[0], new String[0], + 0, new TerminalSessionClientAdapter() { + @Override + public void onCopyTextToClipboard(TerminalSession session, String text) { + AndroidUtilities.copyToClipboard(text); + } + + @Override + public void onShareText(TerminalSession terminalSession, String transcriptText) { + ShareUtils.shareText(requireContext(), "Build Logs", transcriptText); + } + }); + + mTerminalView = new TerminalView(requireContext(), null); + mTerminalView.setTextSize(20); + mTerminalView.setTerminalViewClient(new TerminalViewClientAdapter(mTerminalView)); + mTerminalView.attachSession(session); + + outputStream = new OutputStream() { + @Override + public void write(int b) throws IOException { + write(new byte[]{(byte) b}, 0, 1); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + mTerminalView.mEmulator.append(b, off + len); + mTerminalView.postInvalidate(); + } + }; + + mRoot.addView(mTerminalView, new ViewGroup.LayoutParams(-1, -1)); + return mRoot; + } mAdapter = new LogAdapter(); mAdapter.setListener(diagnostic -> { if (diagnostic.getSource() != null) { if (getContext() != null) { - FileEditorManagerImpl.getInstance().openFile(requireContext(), diagnostic.getSource(), fileEditor -> mMainViewModel.openFile(fileEditor)); + FileEditorManager manager = FileEditorManagerImpl.getInstance(); + manager.openFile(requireContext(), diagnostic.getSource(), it -> { +// if (diagnostic.getLineNumber() > 0 && diagnostic.getColumnNumber() > 0) { +// Bundle bundle = new Bundle(it.getFragment() +// .getArguments()); +// bundle.putInt(CodeEditorFragment.KEY_LINE, (int) diagnostic.getLineNumber()); +// bundle.putInt(CodeEditorFragment.KEY_COLUMN, (int) diagnostic.getColumnNumber()); +// it.getFragment().setArguments(bundle); +// manager.openFileEditor(it); +// } + }); } } }); @@ -69,6 +131,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, mRecyclerView.setAdapter(mAdapter); mRoot.addView(mRecyclerView, new FrameLayout.LayoutParams(-1, -1)); + return mRoot; } @@ -76,6 +139,17 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + if (id == LogViewModel.BUILD_LOG) { + mModel.getLogs(id).observe(getViewLifecycleOwner(), diagnosticWrappers -> { + if (diagnosticWrappers.isEmpty()) { + if (mTerminalView.mEmulator != null && mTerminalView.mEmulator.getScreen() != null) { + mTerminalView.mEmulator.clearTranscript(); + mTerminalView.invalidate(); + } + } + }); + return; + } mModel.getLogs(id).observe(getViewLifecycleOwner(), this::process); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/log/adapter/LogAdapter.java b/app/src/main/java/com/tyron/code/ui/editor/log/adapter/LogAdapter.java index 3fd49942f..b7c531ae3 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/log/adapter/LogAdapter.java +++ b/app/src/main/java/com/tyron/code/ui/editor/log/adapter/LogAdapter.java @@ -20,7 +20,7 @@ import com.tyron.builder.model.DiagnosticWrapper; import com.tyron.code.R; -import org.openjdk.javax.tools.Diagnostic; +import javax.tools.Diagnostic; import java.util.ArrayList; import java.util.List; @@ -108,18 +108,20 @@ public ViewHolder(FrameLayout layout) { } public void bind(DiagnosticWrapper diagnostic) { - SpannableStringBuilder builder = new SpannableStringBuilder(); - if (diagnostic.getKind() != null) { - builder.append(diagnostic.getKind().name() + ": ", - new ForegroundColorSpan(getColor(diagnostic.getKind())), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (diagnostic.getMessage(Locale.getDefault()) == null) { + return; } + SpannableStringBuilder builder = new SpannableStringBuilder(); +// if (diagnostic.getKind() != null) { +// builder.append(new ForegroundColorSpan(getColor(diagnostic.getKind())), +// Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); +// } if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { builder.append(diagnostic.getMessage(Locale.getDefault()), new ForegroundColorSpan(getColor(diagnostic.getKind())), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { - builder.append(diagnostic.getMessage(Locale.getDefault())); + builder.append(diagnostic.getMessageCharSequence()); } if (diagnostic.getSource() != null) { builder.append(' '); diff --git a/app/src/main/java/com/tyron/code/ui/editor/scheme/CodeAssistColorScheme.java b/app/src/main/java/com/tyron/code/ui/editor/scheme/CodeAssistColorScheme.java new file mode 100644 index 000000000..b98870887 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/scheme/CodeAssistColorScheme.java @@ -0,0 +1,170 @@ +package com.tyron.code.ui.editor.scheme; + +import android.graphics.Color; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +/** + * An editor color scheme that can be serialized and deserialized as json + */ +@Keep +public class CodeAssistColorScheme extends EditorColorScheme { + + @WorkerThread + public static CodeAssistColorScheme fromFile(@NonNull File file) throws IOException { + String contents = FileUtils.readFileToString(file, StandardCharsets.UTF_8); + CodeAssistColorScheme scheme = + new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().create().fromJson(contents, CodeAssistColorScheme.class); + if (scheme == null) { + throw new IOException("Unable to parse scheme file."); + } + if (scheme.mName == null) { + throw new IOException("Scheme does not contain a name."); + } + if (scheme.colors == null) { + throw new IOException("Scheme does not have colors."); + } + return scheme; + } + + @Expose + @SerializedName("name") + private String mName; + + @Expose + @SerializedName("colors") + private Map mNameToColorMap; + + public CodeAssistColorScheme() { + for (Integer id : Keys.sIdToNameMap.keySet()) { + int color = getColor(id); + setColor(id, color); + } + } + + /** + * Return the key of the id that will be serialized. + * @param id The editor color scheme id + * @return the mapped key + */ + protected String getName(int id) { + return Keys.sIdToNameMap.get(id); + } + + @Override + public void setColor(int type, int color) { + super.setColor(type, color); + + if (mNameToColorMap == null) { + mNameToColorMap = new HashMap<>(); + } + + String name = getName(type); + if (name != null) { + mNameToColorMap.remove(name); + mNameToColorMap.put(name, "#" + Integer.toHexString(color)); + } + } + + @Override + public int getColor(int type) { + if (mNameToColorMap == null) { + mNameToColorMap = new HashMap<>(); + } + + String name = getName(type); + if (name != null) { + String color = mNameToColorMap.get(name); + if (color != null) { + try { + return Color.parseColor(color); + } catch (IllegalArgumentException ignored) { + // fall through + } + } + } + + return super.getColor(type); + } + + @NonNull + public String toString() { + return new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .setPrettyPrinting() + .create() + .toJson(this); + } + + public static final class Keys { + private static final BiMap sIdToNameMap = HashBiMap.create(); + + static { + sIdToNameMap.put(WHOLE_BACKGROUND, "wholeBackground"); + sIdToNameMap.put(COMPLETION_WND_BACKGROUND, "completionPanelBackground"); + sIdToNameMap.put(COMPLETION_WND_CORNER, "completionPanelStrokeColor"); + sIdToNameMap.put(LINE_NUMBER, "lineNumber"); + sIdToNameMap.put(LINE_NUMBER_BACKGROUND, "lineNumberBackground"); + sIdToNameMap.put(LINE_NUMBER_PANEL, "lineNumberPanel"); + sIdToNameMap.put(LINE_NUMBER_PANEL_TEXT, "lineNumberPanelText"); + sIdToNameMap.put(LINE_DIVIDER, "lineDivider"); + sIdToNameMap.put(SELECTION_HANDLE, "selectionHandle"); + sIdToNameMap.put(SELECTION_INSERT, "selectionInsert"); + sIdToNameMap.put(SCROLL_BAR_TRACK, "scrollbarTrack"); + sIdToNameMap.put(SCROLL_BAR_THUMB, "scrollbarThumb"); + sIdToNameMap.put(SCROLL_BAR_THUMB_PRESSED, "scrollbarThumbPressed"); + + sIdToNameMap.put(PROBLEM_TYPO, "problemTypo"); + sIdToNameMap.put(PROBLEM_ERROR, "problemError"); + sIdToNameMap.put(PROBLEM_WARNING, "problemWarning"); + + sIdToNameMap.put(BLOCK_LINE, "blockLine"); + sIdToNameMap.put(BLOCK_LINE_CURRENT, "blockLineCurrent"); + sIdToNameMap.put(UNDERLINE, "underline"); + sIdToNameMap.put(CURRENT_LINE, "currentLine"); + + sIdToNameMap.put(TEXT_NORMAL, "textNormal"); + sIdToNameMap.put(SELECTED_TEXT_BACKGROUND, "selectedTextBackground"); + sIdToNameMap.put(MATCHED_TEXT_BACKGROUND, "matchedTextBackground"); + sIdToNameMap.put(ATTRIBUTE_NAME, "attributeName"); + sIdToNameMap.put(ATTRIBUTE_VALUE, "attributeValue"); + sIdToNameMap.put(HTML_TAG, "htmlTag"); + sIdToNameMap.put(ANNOTATION, "annotation"); + sIdToNameMap.put(FUNCTION_NAME, "functionName"); + sIdToNameMap.put(IDENTIFIER_NAME, "identifierName"); + sIdToNameMap.put(IDENTIFIER_VAR, "identifierVar"); + sIdToNameMap.put(LITERAL, "literal"); + sIdToNameMap.put(OPERATOR, "operator"); + sIdToNameMap.put(COMMENT, "comment"); + sIdToNameMap.put(KEYWORD, "keyword"); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/scheme/CompiledEditorScheme.java b/app/src/main/java/com/tyron/code/ui/editor/scheme/CompiledEditorScheme.java new file mode 100644 index 000000000..967f07cc1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/scheme/CompiledEditorScheme.java @@ -0,0 +1,85 @@ +package com.tyron.code.ui.editor.scheme; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.TypedValue; + +import androidx.annotation.StyleableRes; +import androidx.appcompat.widget.ThemeUtils; + +import com.google.android.material.color.MaterialColors; +import com.google.common.collect.ImmutableMap; +import com.tyron.code.R; + +import java.util.Map; + +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +/** + * An editor color scheme that is based on compiled xml files. + */ +public class CompiledEditorScheme extends EditorColorScheme { + + private static final Map sResIdMap = ImmutableMap.builder() + .put(R.styleable.EditorColorScheme_keyword, KEYWORD) + .put(R.styleable.EditorColorScheme_operator, OPERATOR) + .put(R.styleable.EditorColorScheme_annotation, ANNOTATION) + .put(R.styleable.EditorColorScheme_xmlAttributeName, ATTRIBUTE_NAME) + .put(R.styleable.EditorColorScheme_xmlAttributeValue, ATTRIBUTE_VALUE) + .put(R.styleable.EditorColorScheme_comment, COMMENT) + .put(R.styleable.EditorColorScheme_htmlTag, HTML_TAG) + .put(R.styleable.EditorColorScheme_identifierName, IDENTIFIER_NAME) + .put(R.styleable.EditorColorScheme_identifierVar, IDENTIFIER_VAR) + .put(R.styleable.EditorColorScheme_functionName, FUNCTION_NAME) + .put(R.styleable.EditorColorScheme_literal, LITERAL) + .put(R.styleable.EditorColorScheme_textNormal, TEXT_NORMAL) + .put(R.styleable.EditorColorScheme_blockLineColor, BLOCK_LINE) + .put(R.styleable.EditorColorScheme_problemError, PROBLEM_ERROR) + .put(R.styleable.EditorColorScheme_problemWarning, PROBLEM_WARNING) + .put(R.styleable.EditorColorScheme_problemTypo, PROBLEM_TYPO) + .put(R.styleable.EditorColorScheme_selectedTextBackground, SELECTED_TEXT_BACKGROUND) + .put(R.styleable.EditorColorScheme_completionPanelBackground, COMPLETION_WND_BACKGROUND) + .put(R.styleable.EditorColorScheme_completionPanelStrokeColor, COMPLETION_WND_CORNER) + .put(R.styleable.EditorColorScheme_lineNumberBackground, LINE_NUMBER_BACKGROUND) + .put(R.styleable.EditorColorScheme_lineNumberTextColor, LINE_NUMBER_PANEL_TEXT) + .put(R.styleable.EditorColorScheme_lineNumberDividerColor, LINE_DIVIDER) + .put(R.styleable.EditorColorScheme_wholeBackground, WHOLE_BACKGROUND) + .build(); + + public CompiledEditorScheme(Context context) { + Resources.Theme theme = context.getTheme(); + TypedValue value = new TypedValue(); + theme.resolveAttribute(R.attr.editorColorScheme, value, true); + TypedArray typedArray = context.obtainStyledAttributes(value.data, R.styleable.EditorColorScheme); + for (Integer resId : sResIdMap.keySet()) { + putColor(context, resId, typedArray); + } + typedArray.recycle(); + } + + private void putColor(Context context, @StyleableRes int res, TypedArray array) { + if (!array.hasValue(res)) { + return; + } + + Integer integer = sResIdMap.get(res); + if (integer == null) { + return; + } + + if (array.getType(res) == TypedValue.TYPE_ATTRIBUTE) { + TypedValue typedValue = new TypedValue(); + array.getValue(res, typedValue); + int color = MaterialColors.getColor(context, typedValue.data, -1); + setColorInternal(integer, color); + return; + } + setColorInternal(integer, array.getColor(res, 0)); + } + + private void setColorInternal(int id, int value) { + colors.put(id, value); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutAction.java index b502fec2e..92d4d0dec 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutAction.java @@ -1,10 +1,10 @@ package com.tyron.code.ui.editor.shortcuts; -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Editor; public interface ShortcutAction { boolean isApplicable(String kind); - void apply(CodeEditor editor, ShortcutItem item); + void apply(Editor editor, ShortcutItem item); } diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutsAdapter.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutsAdapter.java index e21ba41c4..947654495 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutsAdapter.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutsAdapter.java @@ -64,7 +64,6 @@ public ViewHolder(View view) { super(view); textView = view.findViewById(R.id.shortcut_label); - textView.setTextColor(0xffffffff); } public void bind(ShortcutItem item) { diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/CursorMoveAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/CursorMoveAction.java index 72ffb911c..dcfec0a15 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/CursorMoveAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/CursorMoveAction.java @@ -2,8 +2,7 @@ import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; - -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Editor; public class CursorMoveAction implements ShortcutAction { @@ -28,7 +27,7 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { + public void apply(Editor editor, ShortcutItem item) { switch (mDirection) { case UP: editor.moveSelectionUp(); break; case DOWN: editor.moveSelectionDown(); break; diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/RedoAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/RedoAction.java index a14012d3d..9c974e35d 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/RedoAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/RedoAction.java @@ -2,8 +2,7 @@ import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; - -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Editor; public class RedoAction implements ShortcutAction { @@ -15,9 +14,9 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { - if (editor.canRedo()) { - editor.redo(); + public void apply(Editor editor, ShortcutItem item) { + if (editor.getContent().canRedo()) { + editor.getContent().redo(); } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextEditAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextEditAction.java index 7b8e611eb..583b62367 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextEditAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextEditAction.java @@ -2,8 +2,7 @@ import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; - -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Editor; public class TextEditAction implements ShortcutAction { @@ -13,7 +12,7 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { + public void apply(Editor editor, ShortcutItem item) { } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextInsertAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextInsertAction.java index 3b95543dc..3ea262802 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextInsertAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextInsertAction.java @@ -1,10 +1,10 @@ package com.tyron.code.ui.editor.shortcuts.action; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; - -import io.github.rosemoe.sora2.text.Cursor; -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Caret; +import com.tyron.editor.Editor; public class TextInsertAction implements ShortcutAction { @@ -16,8 +16,14 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { - Cursor cursor = editor.getCursor(); - editor.getText().insert(cursor.getLeftLine(), cursor.getLeftColumn(), item.label); + public void apply(Editor editor, ShortcutItem item) { + Caret cursor = editor.getCaret(); + + // temporary solution + if (editor instanceof CodeEditorView) { + ((CodeEditorView) editor).commitText(item.label); + } else { + editor.insert(cursor.getStartLine(), cursor.getEndColumn(), item.label); + } } } diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/UndoAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/UndoAction.java index b42303895..48644bcb9 100644 --- a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/UndoAction.java +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/UndoAction.java @@ -2,8 +2,7 @@ import com.tyron.code.ui.editor.shortcuts.ShortcutAction; import com.tyron.code.ui.editor.shortcuts.ShortcutItem; - -import io.github.rosemoe.sora2.widget.CodeEditor; +import com.tyron.editor.Editor; public class UndoAction implements ShortcutAction { @@ -15,9 +14,9 @@ public boolean isApplicable(String kind) { } @Override - public void apply(CodeEditor editor, ShortcutItem item) { - if (editor.canUndo()) { - editor.undo(); + public void apply(Editor editor, ShortcutItem item) { + if (editor.getContent().canUndo()) { + editor.getContent().undo(); } } } diff --git a/app/src/main/java/com/tyron/code/ui/file/FilePickerDialogFixed.java b/app/src/main/java/com/tyron/code/ui/file/FilePickerDialogFixed.java index 5e2e22be5..2af7d2292 100644 --- a/app/src/main/java/com/tyron/code/ui/file/FilePickerDialogFixed.java +++ b/app/src/main/java/com/tyron/code/ui/file/FilePickerDialogFixed.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.util.Log; import android.widget.Button; +import android.widget.TextView; import com.github.angads25.filepicker.controller.adapters.FileListAdapter; import com.github.angads25.filepicker.model.DialogProperties; @@ -48,6 +49,7 @@ protected void onStart() { Field mAdapterField = FilePickerDialog.class.getDeclaredField("mFileListAdapter"); mAdapterField.setAccessible(true); FileListAdapter adapter = (FileListAdapter) mAdapterField.get(this); + assert adapter != null; adapter.setNotifyItemCheckedListener(() -> { int size = MarkedItemList.getFileCount(); if (size == 0) { @@ -64,4 +66,13 @@ protected void onStart() { Log.w("WizardFragment", "Unable to get declared field", e); } } + + /** + * Return the current path of the current directory + * @return the absolute path of the directory + */ + public String getCurrentPath() { + TextView path = findViewById(com.github.angads25.filepicker.R.id.dir_path); + return String.valueOf(path.getText()); + } } diff --git a/app/src/main/java/com/tyron/code/ui/file/action/ImportFileActionGroup.java b/app/src/main/java/com/tyron/code/ui/file/action/ImportFileActionGroup.java new file mode 100644 index 000000000..462fe6e57 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/ImportFileActionGroup.java @@ -0,0 +1,39 @@ +package com.tyron.code.ui.file.action; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.actions.ActionGroup; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.Presentation; +import com.tyron.code.R; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.file.ImportDirectoryAction; +import com.tyron.code.ui.file.action.file.ImportFileAction; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.ui.treeview.TreeNode; + +public class ImportFileActionGroup extends ActionGroup { + + public static final String ID = "fileManagerImportGroup"; + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + TreeNode data = event.getData(CommonFileKeys.TREE_NODE); + if (data == null) { + return; + } + + presentation.setVisible(true); + presentation.setText(event.getDataContext().getString(R.string.menu_import)); + } + + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[]{new ImportFileAction(),new ImportDirectoryAction()}; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/CreateDirectoryAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateDirectoryAction.java index d36d19e9a..05594cb9f 100644 --- a/app/src/main/java/com/tyron/code/ui/file/action/file/CreateDirectoryAction.java +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateDirectoryAction.java @@ -10,6 +10,7 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; import com.tyron.actions.AnActionEvent; import com.tyron.actions.CommonDataKeys; @@ -52,7 +53,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { File currentDir = e.getData(CommonDataKeys.FILE); TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); - AlertDialog dialog = new AlertDialog.Builder(fragment.requireContext()) + AlertDialog dialog = new MaterialAlertDialogBuilder(fragment.requireContext()) .setView(R.layout.create_class_dialog) .setTitle(R.string.menu_action_new_directory) .setPositiveButton(R.string.create_class_dialog_positive, null) @@ -71,7 +72,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { File fileToCreate = new File(currentDir, editText.getText().toString()); if (!fileToCreate.mkdirs()) { progress.runLater(() -> { - new AlertDialog.Builder(fragment.requireContext()) + new MaterialAlertDialogBuilder(fragment.requireContext()) .setTitle(R.string.error) .setMessage(R.string.error_dir_access) .setPositiveButton(android.R.string.ok, null) diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/CreateFileAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateFileAction.java index 5a1412260..a5405ffac 100644 --- a/app/src/main/java/com/tyron/code/ui/file/action/file/CreateFileAction.java +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateFileAction.java @@ -10,10 +10,15 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; import com.tyron.actions.AnActionEvent; import com.tyron.actions.CommonDataKeys; +import com.tyron.builder.project.Project; import com.tyron.code.R; +import com.tyron.code.event.FileCreatedEvent; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.completion.xml.task.InjectResourcesTask; import com.tyron.ui.treeview.TreeNode; import com.tyron.code.ui.file.CommonFileKeys; import com.tyron.code.ui.file.action.ActionContext; @@ -40,8 +45,8 @@ public boolean isApplicable(File file) { @Override public void actionPerformed(@NonNull AnActionEvent e) { - TreeFileManagerFragment fragment = (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); - TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + TreeFileManagerFragment fragment = (TreeFileManagerFragment) e.getRequiredData(CommonDataKeys.FRAGMENT); + TreeNode currentNode = e.getRequiredData(CommonFileKeys.TREE_NODE); ActionContext actionContext = new ActionContext(fragment, fragment.getTreeView(), currentNode); onMenuItemClick(actionContext); @@ -50,7 +55,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { @SuppressWarnings("ConstantConditions") private void onMenuItemClick(ActionContext context) { File currentDir = context.getCurrentNode().getValue().getFile(); - AlertDialog dialog = new AlertDialog.Builder(context.getFragment().requireContext()) + AlertDialog dialog = new MaterialAlertDialogBuilder(context.getFragment().requireContext()) .setView(R.layout.create_class_dialog) .setTitle(R.string.menu_action_new_file) .setPositiveButton(R.string.create_class_dialog_positive, null) @@ -74,6 +79,13 @@ private void onMenuItemClick(ActionContext context) { } else { refreshTreeView(context); dialog.dismiss(); + + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + if (currentProject != null) { + currentProject.getEventManager().dispatchEvent( + new FileCreatedEvent(fileToCreate) + ); + } } }); editText.addTextChangedListener(new SingleTextWatcher() { diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/DeleteFileAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/DeleteFileAction.java index 2bf9fb072..4bba54474 100644 --- a/app/src/main/java/com/tyron/code/ui/file/action/file/DeleteFileAction.java +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/DeleteFileAction.java @@ -5,8 +5,13 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.tyron.actions.AnActionEvent; import com.tyron.actions.CommonDataKeys; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.FileManager; +import com.tyron.code.event.FileDeletedEvent; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; import com.tyron.code.ui.file.tree.TreeUtil; import com.tyron.completion.progress.ProgressManager; import com.tyron.ui.treeview.TreeNode; @@ -25,6 +30,8 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import kotlin.io.FileWalkDirection; import kotlin.io.FilesKt; @@ -50,7 +57,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { TreeView treeView = fragment.getTreeView(); TreeNode currentNode = e.getRequiredData(CommonFileKeys.TREE_NODE); - new AlertDialog.Builder(fragment.requireContext()) + new MaterialAlertDialogBuilder(fragment.requireContext()) .setMessage(String.format(fragment.getString(R.string.dialog_confirm_delete), currentNode.getValue().getFile().getName())) .setPositiveButton(fragment.getString(R.string.dialog_delete), (d, which) -> { @@ -65,8 +72,10 @@ public void actionPerformed(@NonNull AnActionEvent e) { treeView.deleteNode(currentNode); TreeUtil.updateNode(currentNode.getParent()); treeView.refreshTreeView(); + FileEditorManagerImpl.getInstance().closeFile(currentNode.getValue() + .getFile()); } else { - new AlertDialog.Builder(fragment.requireContext()) + new MaterialAlertDialogBuilder(fragment.requireContext()) .setTitle(R.string.error) .setMessage("Failed to delete file.") .setPositiveButton(android.R.string.ok, null) @@ -82,32 +91,53 @@ public void actionPerformed(@NonNull AnActionEvent e) { private boolean deleteFiles(TreeNode currentNode, TreeFileManagerFragment fragment) { + List deletedFiles = new ArrayList<>(); File currentFile = currentNode.getContent().getFile(); FilesKt.walk(currentFile, FileWalkDirection.TOP_DOWN).iterator().forEachRemaining(file -> { + Module module = ProjectManager.getInstance() + .getCurrentProject() + .getModule(file); + if (file.getName().endsWith(".java")) { // todo: add .kt and .xml checks ProgressManager.getInstance().runLater(() -> fragment.getMainViewModel().removeFile(file)); - Module module = ProjectManager.getInstance() - .getCurrentProject() - .getModule(file); if (module instanceof JavaModule) { String packageName = StringSearch.packageName(file); if (packageName != null) { packageName += "." + file.getName() .substring(0, file.getName().lastIndexOf(".")); + ((JavaModule) module).removeJavaFile(packageName); } - ((JavaModule) module).removeJavaFile(packageName); } } + + ProgressManager.getInstance().runLater(() -> { + FileManager fileManager = module.getFileManager(); + if (fileManager.isOpened(file)) { + fileManager.closeFileForSnapshot(file); + } + }); + + deletedFiles.add(file); }); try { FileUtils.forceDelete(currentFile); } catch (IOException e) { return false; } + + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + if (currentProject != null) { + for (File deletedFile : deletedFiles) { + currentProject.getEventManager().dispatchEvent( + new FileDeletedEvent(deletedFile) + ); + } + } + return true; } } diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/ImportDirectoryAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportDirectoryAction.java new file mode 100644 index 000000000..192883c14 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportDirectoryAction.java @@ -0,0 +1,92 @@ +package com.tyron.code.ui.file.action.file; + +import android.content.Context; +import android.os.Environment; + +import androidx.annotation.NonNull; + +import com.github.angads25.filepicker.model.DialogConfigs; +import com.github.angads25.filepicker.model.DialogProperties; +import com.github.angads25.filepicker.view.FilePickerDialog; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.TreeUtil; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +public class ImportDirectoryAction extends FileAction { + + public static final String ID = "fileManagerImportDirectoryAction"; + + @Override + public String getTitle(Context context) { + return context.getString(R.string.menu_action_new_directory); + } + + @Override + public boolean isApplicable(File file) { + return file.isDirectory(); + } + + private void refreshTreeView(TreeNode currentNode, TreeView> treeView) { + TreeUtil.updateNode(currentNode); + treeView.refreshTreeView(); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment fragment = + (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + File currentDir = e.getData(CommonDataKeys.FILE); + TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + + DialogProperties properties = new DialogProperties(); + properties.selection_mode = DialogConfigs.SINGLE_MODE; + properties.selection_type = DialogConfigs.DIR_SELECT; + properties.root = Environment.getExternalStorageDirectory(); + properties.error_dir = fragment.requireContext().getExternalFilesDir(null); + + FilePickerDialog dialog = new FilePickerDialog(fragment.requireContext(), properties); + dialog.setDialogSelectionListener(files -> { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + String file = files[0]; + try { + FileUtils.copyDirectoryToDirectory(new File(file), currentDir); + } catch (IOException ioException) { + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + AndroidUtilities.showSimpleAlert(e.getDataContext(), R.string.error, + ioException.getLocalizedMessage()); + }); + } + + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + refreshTreeView(currentNode, fragment.getTreeView()); + }); + + }); + + }); + dialog.show(); + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/ImportFileAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportFileAction.java new file mode 100644 index 000000000..ccbde3c47 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportFileAction.java @@ -0,0 +1,89 @@ +package com.tyron.code.ui.file.action.file; + +import android.content.Context; +import android.os.Environment; + +import androidx.annotation.NonNull; + +import com.github.angads25.filepicker.model.DialogConfigs; +import com.github.angads25.filepicker.model.DialogProperties; +import com.github.angads25.filepicker.view.FilePickerDialog; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.TreeUtil; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +public class ImportFileAction extends FileAction { + public static final String ID = "fileManagerImportFileAction"; + + @Override + public String getTitle(Context context) { + return context.getString(R.string.menu_action_new_file); + } + + @Override + public boolean isApplicable(File file) { + return file.isDirectory(); + } + + private void refreshTreeView(TreeNode currentNode, TreeView> treeView) { + TreeUtil.updateNode(currentNode); + treeView.refreshTreeView(); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment fragment = + (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + File currentDir = e.getData(CommonDataKeys.FILE); + TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + + DialogProperties properties = new DialogProperties(); + properties.selection_mode = DialogConfigs.MULTI_MODE; + properties.selection_type = DialogConfigs.FILE_SELECT; + properties.root = Environment.getExternalStorageDirectory(); + properties.error_dir = fragment.requireContext().getExternalFilesDir(null); + + FilePickerDialog dialog = new FilePickerDialog(fragment.requireContext(), properties); + dialog.setDialogSelectionListener(files -> { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + for (String file : files) { + try { + FileUtils.copyFileToDirectory(new File(file), currentDir); + } catch (IOException ioException) { + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + AndroidUtilities.showSimpleAlert(e.getDataContext(), R.string.error, + ioException.getLocalizedMessage()); + }); + } + } + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + refreshTreeView(currentNode, fragment.getTreeView()); + }); + + }); + }); + dialog.show(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/file/action/xml/CreateLayoutAction.java b/app/src/main/java/com/tyron/code/ui/file/action/xml/CreateLayoutAction.java index 65e7060f1..9318afd9d 100644 --- a/app/src/main/java/com/tyron/code/ui/file/action/xml/CreateLayoutAction.java +++ b/app/src/main/java/com/tyron/code/ui/file/action/xml/CreateLayoutAction.java @@ -10,8 +10,6 @@ import com.tyron.code.R; import com.tyron.code.template.CodeTemplate; import com.tyron.code.template.xml.LayoutTemplate; -import com.tyron.ui.treeview.TreeNode; -import com.tyron.ui.treeview.TreeView; import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; import com.tyron.code.ui.file.CommonFileKeys; import com.tyron.code.ui.file.RegexReason; @@ -21,6 +19,8 @@ import com.tyron.code.ui.file.tree.model.TreeFile; import com.tyron.code.ui.project.ProjectManager; import com.tyron.code.util.ProjectUtils; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; import java.io.File; import java.io.IOException; @@ -31,7 +31,7 @@ public class CreateLayoutAction extends FileAction { @Override public String getTitle(Context context) { - return context.getString(R.string.menu_new); + return context.getString(R.string.menu_new_layout); } @Override @@ -44,9 +44,10 @@ public boolean isApplicable(File file) { @Override public void actionPerformed(@NonNull AnActionEvent e) { - TreeFileManagerFragment fragment = (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + TreeFileManagerFragment fragment = + (TreeFileManagerFragment) e.getRequiredData(CommonDataKeys.FRAGMENT); TreeView treeView = fragment.getTreeView(); - TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + TreeNode currentNode = e.getRequiredData(CommonFileKeys.TREE_NODE); CreateClassDialogFragment dialogFragment = CreateClassDialogFragment.newInstance(getTemplates(), @@ -55,16 +56,15 @@ public void actionPerformed(@NonNull AnActionEvent e) { dialogFragment.show(fragment.getChildFragmentManager(), null); dialogFragment.setOnClassCreatedListener((className, template) -> { try { - File createdFile = ProjectManager.createFile( - currentNode.getContent().getFile(), - className, - template - ); + File createdFile = ProjectManager.createFile(currentNode.getContent().getFile(), + className, template); + + if (createdFile == null) { + throw new IOException(fragment.getString(R.string.error_file_creation)); + } - TreeNode newNode = new TreeNode<>( - TreeFile.fromFile(createdFile), - currentNode.getLevel() + 1 - ); + TreeNode newNode = new TreeNode<>(TreeFile.fromFile(createdFile), + currentNode.getLevel() + 1); treeView.addNode(currentNode, newNode); treeView.refreshTreeView(); @@ -72,11 +72,7 @@ public void actionPerformed(@NonNull AnActionEvent e) { createdFile, fileEditor -> fragment.getMainViewModel().openFile(fileEditor)); } catch (IOException exception) { - new MaterialAlertDialogBuilder(fragment.requireContext()) - .setMessage(exception.getMessage()) - .setPositiveButton(android.R.string.ok, null) - .setTitle(R.string.error) - .show(); + new MaterialAlertDialogBuilder(fragment.requireContext()).setMessage(exception.getMessage()).setPositiveButton(android.R.string.ok, null).setTitle(R.string.error).show(); } }); } diff --git a/app/src/main/java/com/tyron/code/ui/file/dialog/CreateClassDialogFragment.java b/app/src/main/java/com/tyron/code/ui/file/dialog/CreateClassDialogFragment.java index b7aee604e..5f7611892 100644 --- a/app/src/main/java/com/tyron/code/ui/file/dialog/CreateClassDialogFragment.java +++ b/app/src/main/java/com/tyron/code/ui/file/dialog/CreateClassDialogFragment.java @@ -22,7 +22,7 @@ import com.tyron.code.ui.file.RegexReason; import com.tyron.common.util.SingleTextWatcher; -import org.openjdk.javax.lang.model.SourceVersion; +import javax.lang.model.SourceVersion; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/com/tyron/code/ui/file/dialog/FileManagerFragment.java b/app/src/main/java/com/tyron/code/ui/file/dialog/FileManagerFragment.java index 1e4a8a42e..c4a7ed56a 100644 --- a/app/src/main/java/com/tyron/code/ui/file/dialog/FileManagerFragment.java +++ b/app/src/main/java/com/tyron/code/ui/file/dialog/FileManagerFragment.java @@ -119,7 +119,7 @@ private void openFile(File file) { if (parent != null) { if (parent instanceof MainFragment) { - ((MainFragment) parent).openFile(FileEditorManagerImpl.getInstance().openFile(file, true)[0]); + ((MainFragment) parent).openFile(FileEditorManagerImpl.getInstance().openFile(requireContext(), file, true)[0]); } } } diff --git a/app/src/main/java/com/tyron/code/ui/file/event/RefreshRootEvent.java b/app/src/main/java/com/tyron/code/ui/file/event/RefreshRootEvent.java new file mode 100644 index 000000000..dbad88457 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/event/RefreshRootEvent.java @@ -0,0 +1,24 @@ +package com.tyron.code.ui.file.event; + +import androidx.annotation.NonNull; + +import com.tyron.code.event.Event; + +import java.io.File; + +/** + * Used to notify the file manager that its root needs to be refreshed + */ +public class RefreshRootEvent extends Event { + + private final File mRoot; + + public RefreshRootEvent(@NonNull File root) { + mRoot = root; + } + + @NonNull + public File getRoot() { + return mRoot; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/tree/TreeFileManagerFragment.java b/app/src/main/java/com/tyron/code/ui/file/tree/TreeFileManagerFragment.java index 469e32621..15f7f6429 100644 --- a/app/src/main/java/com/tyron/code/ui/file/tree/TreeFileManagerFragment.java +++ b/app/src/main/java/com/tyron/code/ui/file/tree/TreeFileManagerFragment.java @@ -1,14 +1,21 @@ package com.tyron.code.ui.file.tree; +import android.annotation.SuppressLint; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.widget.FrameLayout; +import android.widget.HorizontalScrollView; import android.widget.PopupMenu; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.widget.ThemeUtils; +import androidx.core.view.ViewCompat; +import androidx.core.widget.NestedScrollView; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -17,6 +24,17 @@ import com.tyron.actions.ActionPlaces; import com.tyron.actions.CommonDataKeys; import com.tyron.actions.DataContext; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.BuildConfig; +import com.tyron.code.R; +import com.tyron.code.event.EventManager; +import com.tyron.code.event.EventReceiver; +import com.tyron.code.event.SubscriptionReceipt; +import com.tyron.code.event.Unsubscribe; +import com.tyron.code.ui.file.event.RefreshRootEvent; +import com.tyron.code.util.ApkInstaller; +import com.tyron.code.util.EventManagerUtilsKt; +import com.tyron.code.util.UiUtilsKt; import com.tyron.completion.progress.ProgressManager; import com.tyron.ui.treeview.TreeNode; import com.tyron.ui.treeview.TreeView; @@ -52,6 +70,10 @@ public static TreeFileManagerFragment newInstance(File root) { private FileViewModel mFileViewModel; private TreeView treeView; + public TreeFileManagerFragment() { + super(R.layout.tree_file_manager_fragment); + } + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -60,50 +82,62 @@ public void onCreate(@Nullable Bundle savedInstanceState) { mFileViewModel = new ViewModelProvider(requireActivity()).get(FileViewModel.class); } - @Nullable + @SuppressLint("ClickableViewAccessibility") @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - FrameLayout root = new FrameLayout(requireContext()); - root.setBackgroundColor(0xff212121); - root.setLayoutParams( - new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT)); + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + ViewCompat.requestApplyInsets(view); + UiUtilsKt.addSystemWindowInsetToPadding(view, false, true, false, true); + + SwipeRefreshLayout refreshLayout = view.findViewById(R.id.refreshLayout); + refreshLayout.setOnRefreshListener(() -> partialRefresh(() -> { + refreshLayout.setRefreshing(false); + treeView.refreshTreeView(); + })); + treeView = new TreeView<>( requireContext(), TreeNode.root(Collections.emptyList())); - root.addView(treeView.getView(), new FrameLayout.LayoutParams(-1, -1)); - - SwipeRefreshLayout refreshLayout = new SwipeRefreshLayout(requireContext()); - refreshLayout.addView(root); - refreshLayout.setOnRefreshListener(() -> { - ProgressManager.getInstance().runNonCancelableAsync(() -> { - if (!treeView.getAllNodes().isEmpty()) { - TreeNode node = treeView.getAllNodes().get(0); - TreeUtil.updateNode(node); - if (getActivity() != null) { - requireActivity().runOnUiThread(() -> { - refreshLayout.setRefreshing(false); - treeView.refreshTreeView(); - }); - } - } - }); + HorizontalScrollView horizontalScrollView = view.findViewById(R.id.horizontalScrollView); + horizontalScrollView.addView(treeView.getView(), new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.MATCH_PARENT + )); + treeView.getView().setNestedScrollingEnabled(false); + + EventManager eventManager = ApplicationLoader.getInstance() + .getEventManager(); + + EventManagerUtilsKt.subscribeEvent(eventManager, getViewLifecycleOwner(), RefreshRootEvent.class, (event, unsubscribe) -> { + File refreshRoot = event.getRoot(); + TreeNode currentRoot = treeView.getRoot(); + if (currentRoot != null && refreshRoot.equals(currentRoot.getValue().getFile())) { + partialRefresh(() -> treeView.refreshTreeView()); + } else { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + TreeNode node = TreeNode.root(TreeUtil.getNodes(refreshRoot)); + ProgressManager.getInstance().runLater(() -> { + if (getActivity() == null) { + return; + } + treeView.refreshTreeView(node); + }); + }); + } }); - return refreshLayout; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { treeView.setAdapter(new TreeFileNodeViewFactory(new TreeFileNodeListener() { @Override public void onNodeToggled(TreeNode treeNode, boolean expanded) { if (treeNode.isLeaf()) { - if (treeNode.getValue().getFile().isFile()) { - FileEditorManagerImpl.getInstance().openFile(requireContext(), treeNode.getValue().getFile(), fileEditor -> { - mMainViewModel.openFile(fileEditor); - }); + File file = treeNode.getValue().getFile(); + if (file.isFile()) { + // TODO: cleaner api to do this + if (file.getName().endsWith(".apk")) { + ApkInstaller.installApplication(requireContext(), BuildConfig.APPLICATION_ID, file.getAbsolutePath()); + } else { + FileEditorManagerImpl.getInstance().openFile(requireContext(), treeNode.getValue().getFile(), true); + } } } } @@ -121,6 +155,22 @@ public boolean onNodeLongClicked(View view, TreeNode treeNode, boolean }); } + + private void partialRefresh(Runnable callback) { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + if (!treeView.getAllNodes().isEmpty()) { + TreeNode node = treeView.getAllNodes().get(0); + TreeUtil.updateNode(node); + ProgressManager.getInstance().runLater(() -> { + if (getActivity() == null) { + return; + } + callback.run(); + }); + } + }); + } + @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); diff --git a/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFile.java b/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFile.java index 1f9fa0109..36cecfa81 100644 --- a/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFile.java +++ b/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFile.java @@ -9,6 +9,7 @@ import com.tyron.code.R; import java.io.File; +import java.util.Objects; public class TreeFile { @@ -40,4 +41,21 @@ public Drawable getIcon(Context context) { return AppCompatResources.getDrawable(context, R.drawable.round_insert_drive_file_24); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TreeFile treeFile = (TreeFile) o; + return Objects.equals(mFile, treeFile.mFile); + } + + @Override + public int hashCode() { + return Objects.hash(mFile); + } } diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorDialogFragment.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorDialogFragment.java index 877ae2241..6076611c5 100644 --- a/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorDialogFragment.java +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorDialogFragment.java @@ -19,19 +19,23 @@ import com.tyron.builder.project.api.AndroidModule; import com.tyron.builder.project.api.Module; import com.tyron.code.R; +import com.tyron.code.ui.layoutEditor.dom.FakeDomElement; import com.tyron.code.ui.project.ProjectManager; import com.tyron.completion.index.CompilerService; -import com.tyron.completion.xml.util.StyleUtils; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.xml.completion.repository.api.AttrResourceValue; +import com.tyron.xml.completion.repository.api.ResourceNamespace; +import com.tyron.completion.xml.util.AttributeProcessingUtil; import com.tyron.completion.xml.XmlIndexProvider; import com.tyron.completion.xml.XmlRepository; -import com.tyron.completion.xml.model.AttributeInfo; -import com.tyron.completion.xml.model.DeclareStyleable; +import org.eclipse.lemminx.commons.TextDocument; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMElement; + +import java.io.IOException; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; import kotlin.Pair; @@ -154,44 +158,39 @@ private void onAttributeItemClick(int pos, Pair attribute) { MaterialAutoCompleteTextView editText = v.findViewById(R.id.value); XmlRepository xmlRepository = getXmlRepository(); - String attributeName = attribute.getFirst(); - String attributeNamespace = ""; - if (attributeName.contains(":")) { - attributeNamespace = attributeName.substring(0, attributeName.indexOf(':')); - attributeName = attributeName.substring(attributeName.indexOf(':') + 1); - } if (xmlRepository != null) { - List values = new ArrayList<>(); - Set styles = new HashSet<>(); - Map declareStyleables = - xmlRepository.getDeclareStyleables(); - styles.addAll(StyleUtils.getStyles(declareStyleables, mTag, mParentTag)); - - for (DeclareStyleable style : styles) { - for (AttributeInfo attributeInfo : style.getAttributeInfos()) { - if (!attributeNamespace.equals(attributeInfo.getNamespace())) { - continue; - } - if (!attributeName.equals(attributeInfo.getName())) { - continue; - } + FakeDomElement fakeDomElement = new FakeDomElement(-1, -1); + fakeDomElement.setTagName(mTag); - if (attributeInfo.getFormats() == null || attributeInfo.getFormats().isEmpty()) { - AttributeInfo extraAttribute = - xmlRepository.getExtraAttribute(attributeName); - if (extraAttribute != null) { - attributeInfo = extraAttribute; - } - } - values.addAll(attributeInfo.getValues()); - } + FakeDomElement fakeParent = new FakeDomElement(-1, -1); + fakeParent.setTagName(mParentTag); + fakeDomElement.setParent(fakeParent); + + String attributeName = attribute.getFirst(); + if (attributeName.contains(":")) { + // strip the namespace prefix + attributeName = attributeName.substring(attributeName.indexOf(':') + 1); } + + AttrResourceValue attr = + AttributeProcessingUtil.getLayoutAttributeFromNode( + xmlRepository.getRepository(), fakeDomElement, + attributeName, + ResourceNamespace.RES_AUTO); + + List values = new ArrayList<>(); + if (attr != null) { + values.addAll(attr.getAttributeValues().keySet()); + } + ArrayAdapter adapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, values); - editText.setThreshold(1); - editText.showDropDown(); - editText.setAdapter(adapter); + ProgressManager.getInstance().runLater(() -> { + editText.setThreshold(1); + editText.showDropDown(); + editText.setAdapter(adapter); + }, 300); } editText.setText(attribute.getSecond(), false); @@ -220,7 +219,11 @@ private XmlRepository getXmlRepository() { XmlIndexProvider index = CompilerService.getInstance().getIndex(XmlIndexProvider.KEY); XmlRepository xmlRepository = index.get(currentProject, mainModule); - xmlRepository.initialize((AndroidModule) mainModule); + try { + xmlRepository.initialize((AndroidModule) mainModule); + } catch (IOException e) { + // ignored + } return xmlRepository; } } diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/dom/FakeDomElement.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/dom/FakeDomElement.java new file mode 100644 index 000000000..7b8457d17 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/dom/FakeDomElement.java @@ -0,0 +1,42 @@ +package com.tyron.code.ui.layoutEditor.dom; + +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.dom.DOMNode; + +public class FakeDomElement extends DOMElement { + + private DOMElement parent; + + private String tagName; + + public FakeDomElement(int start, int end) { + super(start, end); + } + + @Override + public String getTagName() { + return tagName; + } + + public void setTagName(String tagName) { + this.tagName = tagName; + } + + public DOMElement getParent() { + return parent; + } + + @Override + public DOMElement getParentElement() { + return getParent(); + } + + @Override + public DOMNode getParentNode() { + return getParent(); + } + + public void setParent(DOMElement parent) { + this.parent = parent; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/library/AddDependencyDialogFragment.java b/app/src/main/java/com/tyron/code/ui/library/AddDependencyDialogFragment.java deleted file mode 100644 index e2befd9b7..000000000 --- a/app/src/main/java/com/tyron/code/ui/library/AddDependencyDialogFragment.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.tyron.code.ui.library; - -import android.app.Dialog; -import android.os.Bundle; -import android.text.Editable; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.tyron.code.R; -import com.tyron.common.util.SingleTextWatcher; - -public class AddDependencyDialogFragment extends DialogFragment { - - public static final String TAG = AddDependencyDialogFragment.class.getSimpleName(); - public static final String ADD_KEY = "addDependency"; - - @SuppressWarnings("ConstantConditions") - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()); - // noinspection InflateParams - View inflate = getLayoutInflater().inflate(R.layout.add_dependency_dialog, null); - EditText groupId = inflate.findViewById(R.id.et_group_id); - EditText artifactId = inflate.findViewById(R.id.et_artifact_id); - EditText versionName = inflate.findViewById(R.id.et_version_name); - - builder.setView(inflate); - - builder.setPositiveButton(R.string.wizard_create, (d, w) -> { - Bundle bundle = new Bundle(); - bundle.putString("groupId", String.valueOf(groupId.getText())); - bundle.putString("artifactId", String.valueOf(artifactId.getText())); - bundle.putString("versionName", String.valueOf(versionName.getText())); - getParentFragmentManager().setFragmentResult(ADD_KEY, bundle); - }); - builder.setNegativeButton(android.R.string.cancel, null); - - AlertDialog dialog = builder.create(); - dialog.setOnShowListener(d -> { - final Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); - positiveButton.setEnabled(false); - - SingleTextWatcher textWatcher = new SingleTextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - boolean valid = validate(groupId, artifactId, versionName); - positiveButton.setEnabled(valid); - } - }; - groupId.addTextChangedListener(textWatcher); - artifactId.addTextChangedListener(textWatcher); - versionName.addTextChangedListener(textWatcher); - }); - return dialog; - } - - private boolean validate(EditText groupId, EditText artifactId, EditText versionName) { - String groupIdString = String.valueOf(groupId.getText()); - String artifactIdString = String.valueOf(artifactId.getText()); - String versionNameString = String.valueOf(versionName.getText()); - if (groupIdString.contains(":")) { - return false; - } - if (groupIdString.isEmpty()) { - return false; - } - if (artifactIdString.isEmpty()) { - return false; - } - if (artifactIdString.contains(":")) { - return false; - } - if (versionNameString.isEmpty()) { - return false; - } - return !versionNameString.contains(":"); - } -} diff --git a/app/src/main/java/com/tyron/code/ui/library/LibraryManagerFragment.java b/app/src/main/java/com/tyron/code/ui/library/LibraryManagerFragment.java deleted file mode 100644 index 3b668e63f..000000000 --- a/app/src/main/java/com/tyron/code/ui/library/LibraryManagerFragment.java +++ /dev/null @@ -1,353 +0,0 @@ -package com.tyron.code.ui.library; - -import android.app.ProgressDialog; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; -import androidx.core.view.MenuProvider; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.transition.TransitionManager; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.transition.MaterialFade; -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; -import com.tyron.builder.log.ILogger; -import com.tyron.builder.project.Project; -import com.tyron.builder.project.api.JavaModule; -import com.tyron.builder.project.api.Module; -import com.tyron.code.ApplicationLoader; -import com.tyron.code.R; -import com.tyron.code.ui.library.adapter.LibraryManagerAdapter; -import com.tyron.code.ui.project.DependencyManager; -import com.tyron.code.ui.project.ProjectManager; -import com.tyron.code.util.DependencyUtils; -import com.tyron.completion.progress.ProgressManager; -import com.tyron.resolver.DependencyResolver; -import com.tyron.resolver.model.Dependency; -import com.tyron.resolver.model.Pom; -import com.tyron.resolver.repository.Repository; -import com.tyron.resolver.repository.RepositoryManager; -import com.tyron.resolver.repository.RepositoryManagerImpl; - -import org.apache.commons.io.FileUtils; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.stream.Collectors; - -public class LibraryManagerFragment extends Fragment implements ProjectManager.OnProjectOpenListener { - - public static final String TAG = LibraryManagerFragment.class.getSimpleName(); - private static final String ARG_PATH = "path"; - private static final Type TYPE = new TypeToken>(){}.getType(); - - - public static LibraryManagerFragment newInstance(String modulePath) { - Bundle args = new Bundle(); - args.putString(ARG_PATH, modulePath); - LibraryManagerFragment fragment = new LibraryManagerFragment(); - fragment.setArguments(args); - return fragment; - } - - private RepositoryManager mRepositoryManager; - private String mModulePath; - private boolean isDumb = false; - private LibraryManagerAdapter mAdapter; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - File cacheDir = ApplicationLoader.applicationContext.getExternalFilesDir("cache"); - mRepositoryManager = new RepositoryManagerImpl(); - mRepositoryManager.setCacheDirectory(cacheDir); - mRepositoryManager.addRepository("maven", "https://repo1.maven.org/maven2"); - mRepositoryManager.addRepository("maven-google", "https://maven.google.com"); - mRepositoryManager.addRepository("jitpack", "https://jitpack.io"); - mRepositoryManager.addRepository("jcenter", "https://jcenter.bintray.com"); - mRepositoryManager.initialize(); - mModulePath = requireArguments().getString(ARG_PATH); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.library_manager_fragment, container, false); - - mAdapter = new LibraryManagerAdapter(); - - RecyclerView recyclerView = view.findViewById(R.id.libraries_recyclerview); - recyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); - recyclerView.setAdapter(mAdapter); - - Toolbar toolbar = view.findViewById(R.id.toolbar); - toolbar.setNavigationOnClickListener(v -> - getParentFragmentManager().popBackStack()); - toolbar.addMenuProvider(new MenuProvider() { - @Override - public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) { - menu.add(R.string.menu_add_libs_gradle) - .setOnMenuItemClickListener(item -> { - Project currentProject = - ProjectManager.getInstance().getCurrentProject(); - if (currentProject != null) { - Module mainModule = currentProject.getMainModule(); - File rootFile = mainModule.getRootFile(); - File gradleFile = new File(rootFile, "build.gradle"); - if (gradleFile.exists()) { - try { - List poms = DependencyUtils.parseGradle(mRepositoryManager, - gradleFile, ILogger.EMPTY); - List