mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Compute code actions synchronously
This commit is contained in:
Generated
+1
@@ -68,6 +68,7 @@
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_editor_bottom_sheet.xml" value="0.19572649572649573" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_editor_code_actions.xml" value="0.25" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_editor_text_actions.xml" value="0.25" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_filetree_item.xml" value="0.25" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_reference_attr_editor.xml" value="0.25" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_resource_input_field.xml" value="0.25" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_string_attr_editor.xml" value="0.25" />
|
||||
|
||||
@@ -20,8 +20,10 @@
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.gson.Gson;
|
||||
import com.itsaky.androidide.EditorActivity;
|
||||
@@ -49,7 +51,7 @@ import com.itsaky.lsp.models.Location;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -57,6 +59,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
|
||||
/** AndroidIDE specific implementation of the LanguageClient */
|
||||
public class IDELanguageClientImpl implements ILanguageClient {
|
||||
|
||||
@@ -221,6 +225,11 @@ public class IDELanguageClientImpl implements ILanguageClient {
|
||||
*/
|
||||
public void performCodeAction(IDEEditor editor, CodeActionItem action) {
|
||||
if (activity() == null || editor == null || action == null) {
|
||||
LOG.error(
|
||||
"Unable to perform code action",
|
||||
"activity=" + activity(),
|
||||
"editor=" + editor,
|
||||
"action=" + action);
|
||||
StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
@@ -229,15 +238,19 @@ public class IDELanguageClientImpl implements ILanguageClient {
|
||||
progress.setSubMessageEnabled(false);
|
||||
progress.setWelcomeTextEnabled(false);
|
||||
progress.setCancelable(false);
|
||||
progress.setMessage(activity().getString(R.string.msg_performing_fixes));
|
||||
progress.setMessage(activity().getString(R.string.msg_performing_actions));
|
||||
progress.show(activity().getSupportFragmentManager(), "quick_fix_progress");
|
||||
|
||||
new TaskExecutor()
|
||||
.executeAsyncProvideError(
|
||||
() -> performCodeActionAsync(editor, action),
|
||||
(a, b) -> {
|
||||
progress.dismiss();
|
||||
if (a == null || b != null || !a) {
|
||||
(result, throwable) -> {
|
||||
ThreadUtils.runOnUiThread(progress::dismiss);
|
||||
if (result == null || throwable != null || !result) {
|
||||
LOG.error(
|
||||
"Unable to perform code action",
|
||||
"result=" + result,
|
||||
"throwable=" + throwable);
|
||||
StudioApp.getInstance()
|
||||
.toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
|
||||
} else {
|
||||
@@ -282,6 +295,7 @@ public class IDELanguageClientImpl implements ILanguageClient {
|
||||
}
|
||||
|
||||
private Boolean performCodeActionAsync(final IDEEditor editor, final CodeActionItem action) {
|
||||
LOG.debug("Performing code action:", action);
|
||||
final var changes = action.getChanges();
|
||||
if (changes.isEmpty()) {
|
||||
return Boolean.FALSE;
|
||||
|
||||
@@ -19,20 +19,27 @@ package com.itsaky.androidide.views.editor;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.appcompat.view.menu.MenuBuilder;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import io.github.rosemoe.sora.event.ClickEvent;
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent;
|
||||
import io.github.rosemoe.sora.widget.component.EditorTextActionWindow;
|
||||
import com.itsaky.lsp.models.CodeActionItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.github.rosemoe.sora.event.ClickEvent;
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent;
|
||||
import io.github.rosemoe.sora.widget.component.EditorTextActionWindow;
|
||||
|
||||
/**
|
||||
* Text action mode actions for the editor.
|
||||
*
|
||||
@@ -41,6 +48,7 @@ import java.util.stream.Collectors;
|
||||
public class EditorTextActionMode implements IDEEditor.ITextActionPresenter {
|
||||
|
||||
private static final Logger LOG = Logger.newInstance("EditorTextActionMode");
|
||||
private final List<CodeActionItem> codeActions = new ArrayList<>(0);
|
||||
private final Set<IDEEditor.TextAction> registeredActions = new TreeSet<>();
|
||||
private IDEEditor editor;
|
||||
private ActionMode actionMode;
|
||||
@@ -125,6 +133,18 @@ public class EditorTextActionMode implements IDEEditor.ITextActionPresenter {
|
||||
this.unsubscribeEvents = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCodeActions(@NonNull List<CodeActionItem> actions) {
|
||||
this.codeActions.clear();
|
||||
this.codeActions.addAll(actions);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<CodeActionItem> getActions() {
|
||||
return codeActions;
|
||||
}
|
||||
|
||||
public void clearRegisteredActions() {
|
||||
this.registeredActions.clear();
|
||||
}
|
||||
@@ -154,7 +174,7 @@ public class EditorTextActionMode implements IDEEditor.ITextActionPresenter {
|
||||
mode.setTitle(android.R.string.selectTextMode);
|
||||
|
||||
for (var action : registeredActions) {
|
||||
if (canShowAction(action)) {
|
||||
if (canShowAction(editor, action)) {
|
||||
menu.add(0, action.id, 0, action.titleId)
|
||||
.setIcon(action.icon)
|
||||
.setShowAsActionFlags(
|
||||
@@ -218,15 +238,4 @@ public class EditorTextActionMode implements IDEEditor.ITextActionPresenter {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean canShowAction(@NonNull IDEEditor.TextAction action) {
|
||||
|
||||
// all the actions are visible by default
|
||||
// so we need to get a confirmation from the editor
|
||||
if (action.visible) {
|
||||
return editor.shouldShowTextAction(action.id);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,25 @@ import android.graphics.drawable.GradientDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.TextActionItemAdapter;
|
||||
import com.itsaky.androidide.databinding.LayoutEditorActionsBinding;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.lsp.models.CodeActionItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.github.rosemoe.sora.event.HandleStateChangeEvent;
|
||||
import io.github.rosemoe.sora.event.ScrollEvent;
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent;
|
||||
@@ -38,12 +49,6 @@ import io.github.rosemoe.sora.event.Unsubscribe;
|
||||
import io.github.rosemoe.sora.widget.CodeEditor;
|
||||
import io.github.rosemoe.sora.widget.EditorTouchEventHandler;
|
||||
import io.github.rosemoe.sora.widget.base.EditorPopupWindow;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Presents text actions in a popup widow.
|
||||
@@ -58,6 +63,7 @@ public class EditorTextActionWindow extends EditorPopupWindow
|
||||
|
||||
private static final long DELAY = 200;
|
||||
protected final List<SubscriptionReceipt<?>> subscriptionReceipts;
|
||||
private final List<CodeActionItem> codeActions = new ArrayList<>(0);
|
||||
private final Set<IDEEditor.TextAction> registeredActions = new TreeSet<>();
|
||||
private IDEEditor editor;
|
||||
private LayoutEditorActionsBinding binding;
|
||||
@@ -125,7 +131,7 @@ public class EditorTextActionWindow extends EditorPopupWindow
|
||||
|
||||
final var actions =
|
||||
this.registeredActions.stream()
|
||||
.filter(this::canShowAction)
|
||||
.filter(action -> canShowAction(editor, action))
|
||||
.collect(Collectors.toList());
|
||||
this.binding.textActions.setAdapter(
|
||||
new TextActionItemAdapter(actions, this::performTextAction));
|
||||
@@ -144,6 +150,18 @@ public class EditorTextActionWindow extends EditorPopupWindow
|
||||
this.unsubscribeEvents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCodeActions(@NonNull List<CodeActionItem> actions) {
|
||||
codeActions.clear();
|
||||
codeActions.addAll(actions);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<CodeActionItem> getActions() {
|
||||
return codeActions;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Drawable createBackground() {
|
||||
final var drawable = new GradientDrawable();
|
||||
@@ -166,7 +184,7 @@ public class EditorTextActionWindow extends EditorPopupWindow
|
||||
final var dp16 = dp8 * 2;
|
||||
final var actions =
|
||||
this.registeredActions.stream()
|
||||
.filter(this::canShowAction)
|
||||
.filter(action -> canShowAction(editor, action))
|
||||
.collect(Collectors.toList());
|
||||
actionsList.setAdapter(new TextActionItemAdapter(actions, this::performTextAction));
|
||||
this.binding
|
||||
@@ -186,17 +204,6 @@ public class EditorTextActionWindow extends EditorPopupWindow
|
||||
super.show();
|
||||
}
|
||||
|
||||
private boolean canShowAction(@NonNull IDEEditor.TextAction action) {
|
||||
|
||||
// all the actions are visible by default
|
||||
// so we need to get a confirmation from the editor
|
||||
if (action.visible) {
|
||||
return editor.shouldShowTextAction(action.id);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void subscribeToEvents() {
|
||||
this.subscriptionReceipts.add(
|
||||
this.editor.subscribeEvent(SelectionChangeEvent.class, this::onSelectionChanged));
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
*/
|
||||
package com.itsaky.androidide.views.editor;
|
||||
|
||||
import static com.itsaky.androidide.views.editor.IDEEditor.TextAction.QUICKFIX;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
@@ -23,10 +25,12 @@ import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
@@ -58,17 +62,23 @@ import com.itsaky.lsp.models.SignatureHelp;
|
||||
import com.itsaky.lsp.models.SignatureHelpParams;
|
||||
import com.itsaky.lsp.util.DiagnosticUtil;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent;
|
||||
import io.github.rosemoe.sora.event.Unsubscribe;
|
||||
import io.github.rosemoe.sora.widget.CodeEditor;
|
||||
import io.github.rosemoe.sora.widget.component.EditorAutoCompletion;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent;
|
||||
import io.github.rosemoe.sora.event.Unsubscribe;
|
||||
import io.github.rosemoe.sora.widget.CodeEditor;
|
||||
import io.github.rosemoe.sora.widget.component.EditorAutoCompletion;
|
||||
|
||||
public class IDEEditor extends CodeEditor {
|
||||
|
||||
@@ -856,7 +866,7 @@ public class IDEEditor extends CodeEditor {
|
||||
actionPresenter.registerAction(
|
||||
new TextAction(
|
||||
createTextActionDrawable(R.drawable.ic_quickfix),
|
||||
R.string.msg_fix_diagnostic,
|
||||
R.string.msg_code_actions,
|
||||
TextAction.QUICKFIX,
|
||||
index++));
|
||||
|
||||
@@ -957,29 +967,6 @@ public class IDEEditor extends CodeEditor {
|
||||
case TextAction.COMMENT_LINE:
|
||||
case TextAction.UNCOMMENT_LINE:
|
||||
return commentUncomment;
|
||||
case TextAction.QUICKFIX:
|
||||
if (getEditorLanguage() instanceof IDELanguage
|
||||
&& mLanguageServer != null
|
||||
&& mLanguageClient != null) {
|
||||
final var lang = (IDELanguage) getEditorLanguage();
|
||||
final var diagnostics = lang.getDiagnostics();
|
||||
if (diagnostics.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final var diagnostic =
|
||||
DiagnosticUtil.binarySearchDiagnostic(
|
||||
diagnostics, getCursorAsLSPPosition());
|
||||
if (diagnostic == null) {
|
||||
LOG.info(
|
||||
"No diagnostic found at cursor position. Skipping quickfix lookup...");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1022,7 +1009,7 @@ public class IDEEditor extends CodeEditor {
|
||||
expandSelection();
|
||||
break;
|
||||
case TextAction.QUICKFIX:
|
||||
showCodeActions();
|
||||
showCodeActions(mTextActionPresenter.getActions());
|
||||
getTextActionPresenter().dismiss();
|
||||
break;
|
||||
}
|
||||
@@ -1051,15 +1038,15 @@ public class IDEEditor extends CodeEditor {
|
||||
final var lang = (IDELanguage) getEditorLanguage();
|
||||
final var diagnostics = lang.getDiagnostics();
|
||||
if (diagnostics.isEmpty()) {
|
||||
LOG.error("Cannot show code actions. No diagnostic items available.");
|
||||
return;
|
||||
LOG.error(
|
||||
"No diagnostic items available. Code actions will be shown based on cursor position.");
|
||||
}
|
||||
|
||||
final var diagnostic =
|
||||
DiagnosticUtil.binarySearchDiagnostic(diagnostics, getCursorAsLSPPosition());
|
||||
if (diagnostic == null) {
|
||||
LOG.info("No diagnostic found at cursor position. Skipping code action lookup...");
|
||||
return;
|
||||
if (!diagnostics.isEmpty() && diagnostic == null) {
|
||||
LOG.info(
|
||||
"No diagnostic found at cursor position. Code actions will be shown based on cursor position.");
|
||||
}
|
||||
|
||||
final var future = codeActions(Collections.singletonList(diagnostic));
|
||||
@@ -1079,7 +1066,7 @@ public class IDEEditor extends CodeEditor {
|
||||
}));
|
||||
}
|
||||
|
||||
private void showCodeActions(List<CodeActionItem> actions) {
|
||||
public void showCodeActions(List<CodeActionItem> actions) {
|
||||
final var titles = actions.stream().map(CodeActionItem::getTitle).toArray(String[]::new);
|
||||
final var builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
builder.setTitle("Select code action...");
|
||||
@@ -1195,7 +1182,6 @@ public class IDEEditor extends CodeEditor {
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface ITextActionPresenter {
|
||||
|
||||
/**
|
||||
* Bind the action presenter with the given editor instance.
|
||||
*
|
||||
@@ -1230,6 +1216,108 @@ public class IDEEditor extends CodeEditor {
|
||||
* events and release any held resources.
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* Update the list of computed code actions.
|
||||
*
|
||||
* @param actions The new list of code actions.
|
||||
*/
|
||||
void updateCodeActions(@NonNull List<CodeActionItem> actions);
|
||||
|
||||
/**
|
||||
* Get the computed code actions.
|
||||
*
|
||||
* @return The list of code actions.
|
||||
*/
|
||||
@NonNull
|
||||
List<CodeActionItem> getActions();
|
||||
|
||||
default boolean canShowAction(
|
||||
@NonNull IDEEditor editor, @NonNull IDEEditor.TextAction action) {
|
||||
if (action.id == QUICKFIX) {
|
||||
try {
|
||||
// If code actions are not returned within 150ms, hide the action.
|
||||
final var future = computeCodeActions(editor);
|
||||
final var result = future.get(150, TimeUnit.MILLISECONDS);
|
||||
updateCodeActions(
|
||||
result == null ? Collections.emptyList() : result.getActions());
|
||||
return !getActions().isEmpty();
|
||||
} catch (Throwable th) {
|
||||
if (!(th instanceof TimeoutException)) {
|
||||
LOG.error("Unable to calculate code actions", th);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// all the actions are visible by default
|
||||
// so we need to get a confirmation from the editor
|
||||
if (action.visible) {
|
||||
return editor.shouldShowTextAction(action.id);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
default void checkForCodeActions(IDEEditor editor) {
|
||||
computeCodeActions(editor)
|
||||
.whenComplete(
|
||||
((codeActionResult, throwable) -> {
|
||||
if (throwable != null) {
|
||||
LOG.error("Error computing code actions", throwable);
|
||||
}
|
||||
|
||||
final var codeAction = findAction(QUICKFIX);
|
||||
final List<CodeActionItem> actions =
|
||||
codeActionResult != null
|
||||
? codeActionResult.getActions()
|
||||
: Collections.emptyList();
|
||||
updateCodeActions(actions);
|
||||
if (codeAction != null) {
|
||||
codeAction.visible =
|
||||
codeActionResult != null
|
||||
&& !codeActionResult.getActions().isEmpty();
|
||||
}
|
||||
|
||||
invalidateActions();
|
||||
}));
|
||||
}
|
||||
|
||||
default CompletableFuture<CodeActionResult> computeCodeActions(IDEEditor editor) {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
if (editor.getFile() == null) {
|
||||
throw new CompletionException(
|
||||
new NullPointerException(
|
||||
"Cannot compute code actions. "
|
||||
+ "No file is set to editor."));
|
||||
}
|
||||
|
||||
final var file = editor.getFile().toPath();
|
||||
final var range = editor.getCursorRange();
|
||||
List<DiagnosticItem> diagnostics =
|
||||
editor.getEditorLanguage() instanceof IDELanguage
|
||||
? ((IDELanguage) editor.getEditorLanguage())
|
||||
.getDiagnostics()
|
||||
: Collections.emptyList();
|
||||
|
||||
final var diagnostic =
|
||||
DiagnosticUtil.binarySearchDiagnostic(
|
||||
diagnostics, editor.getCursorAsLSPPosition());
|
||||
diagnostics =
|
||||
diagnostic == null
|
||||
? Collections.emptyList()
|
||||
: Collections.singletonList(diagnostic);
|
||||
final var params = new CodeActionParams(file, range, diagnostics);
|
||||
LOG.debug("Requesting code actions for params:", params);
|
||||
try {
|
||||
return editor.codeActions(params).get();
|
||||
} catch (Throwable e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,10 +30,9 @@
|
||||
<string name="get_started">Loslegen</string>
|
||||
<string name="nothing_here">Nichts hier …</string>
|
||||
<!-- LSP, Diagnostics & Completions -->
|
||||
<string name="msg_fix_diagnostic">[Schnelle Lösung]</string>
|
||||
<string name="msg_cannot_perform_fix">Schnellkorrektur kann nicht durchgeführt werden</string>
|
||||
<string name="msg_performing_fixes">Korrekturen durchführen…</string>
|
||||
<string name="msg_code_actions">Schnellkorrekturen</string>
|
||||
<string name="msg_performing_actions">Korrekturen durchführen…</string>
|
||||
<string name="msg_code_actions">Aktionen</string>
|
||||
<string name="msg_api_info_since">Seit API %d</string>
|
||||
<string name="msg_api_info_removed">In API %d entfernt</string>
|
||||
<string name="msg_api_info_deprecated">Veraltet in API %d</string>
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
|
||||
<!-- LSP, Diagnostics & Completions -->
|
||||
<string name="msg_cannot_perform_fix">Impossible de lancer QuickFix</string>
|
||||
<string name="msg_performing_fixes">Réglage de quelques bugs…</string>
|
||||
<string name="msg_code_actions">Quickfixes</string>
|
||||
<string name="msg_performing_actions">Réglage de quelques bugs…</string>
|
||||
<string name="msg_api_info_since">Depuis l\'API %d</string>
|
||||
<string name="msg_api_info_removed">Supprimé depuis l\'API %d</string>
|
||||
<string name="msg_api_info_deprecated">Obsolète depuis l\'API %d</string>
|
||||
@@ -357,7 +356,7 @@
|
||||
<string name="hint_string_value">String value</string>
|
||||
<string name="hint_string_resource">String resource</string>
|
||||
<string name="hint_resource_reference">any resource reference</string>
|
||||
<string name="msg_fix_diagnostic">QuickFix</string>
|
||||
<string name="msg_code_actions">Actions</string>
|
||||
<string name="title_format_code">Formater code</string>
|
||||
<string name="msg_swipe_up">Balayez vers le haut pour accéder au contenue de la console, des logs et autres.</string>
|
||||
<string name="msg_ide_crashed">AndroidIDE a crashé</string>
|
||||
|
||||
@@ -27,14 +27,12 @@
|
||||
<string name="app_logs">एप लॉग्स</string>
|
||||
<string name="ide_logs">IDE लॉग्स</string>
|
||||
<string name="label_splash_image">स्प्लेश इमेज</string>
|
||||
<string name="cancel">रद्द करे</string>
|
||||
<string name="get_started">शुरु करे</string>
|
||||
<string name="nothing_here">यहाँ कुछ भी नहीं …</string>
|
||||
|
||||
<!-- LSP, Diagnostics & Completions -->
|
||||
<string name="msg_fix_diagnostic">[क्विक फिक्स]</string>
|
||||
<string name="msg_cannot_perform_fix">कोड क्रिया करने में असमर्थ </string>
|
||||
<string name="msg_performing_fixes">सुधार कर रहे है …</string>
|
||||
<string name="msg_performing_actions">सुधार कर रहे है …</string>
|
||||
<string name="msg_code_actions">कोड क्रियाएं</string>
|
||||
<string name="msg_api_info_since">एपीआई %d से उपलब्ध है</string>
|
||||
<string name="msg_api_info_removed">एपीआई %d में हटाया गया</string>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<!-- LSP, Diagnostics & Completions -->
|
||||
<string name="msg_cannot_perform_fix">Tidak dapat menampilkan perbaikan cepat (QuickFix)</string>
|
||||
<string name="msg_performing_fixes">Menampilkan perbaikan…</string>
|
||||
<string name="msg_performing_actions">Menampilkan perbaikan…</string>
|
||||
<string name="msg_code_actions">Quickfixes</string>
|
||||
<string name="msg_api_info_since">Sejak API %d</string>
|
||||
<string name="msg_api_info_removed">Dihapus pada API %d</string>
|
||||
|
||||
@@ -29,17 +29,13 @@
|
||||
<string name="app_logs">Лог приложения</string>
|
||||
<string name="ide_logs">Лог IDE</string>
|
||||
<string name="label_splash_image">Splash image</string>
|
||||
<string name="cancel">Отмена</string>
|
||||
<string name="get_started">Давайте начнём!</string>
|
||||
<string name="nothing_here">Тут ничего …</string>
|
||||
|
||||
<!-- LSP, Diagnostics & Completions -->
|
||||
<string name="msg_sdk_info_load_failed">Ошибка загрузки информации о SDK</string>
|
||||
<string name="msg_fix_diagnostic">[Быстрое Решение]</string>
|
||||
<string name="msg_no_code_actions">Быстрого Решения не найдено</string>
|
||||
<string name="msg_cannot_perform_fix">Невозможно применить Быстрое Решение</string>
|
||||
<string name="msg_performing_fixes">Применение изменений…</string>
|
||||
<string name="msg_code_actions">Быстрые Решения</string>
|
||||
<string name="msg_performing_actions">Применение изменений…</string>
|
||||
<string name="msg_code_actions">действия</string>
|
||||
<string name="msg_api_info_since">Начиная с API %d</string>
|
||||
<string name="msg_api_info_removed">Удалено в API %d</string>
|
||||
<string name="msg_api_info_deprecated">Устаревшее с API %d</string>
|
||||
|
||||
@@ -31,10 +31,9 @@
|
||||
<string name="nothing_here">这里啥也没有 …</string>
|
||||
|
||||
<!-- LSP, Diagnostics & Completions -->
|
||||
<string name="msg_fix_diagnostic">[快速修复]</string>
|
||||
<string name="msg_code_actions">行动</string>
|
||||
<string name="msg_cannot_perform_fix">无法执行快速修复</string>
|
||||
<string name="msg_performing_fixes">正在修复…</string>
|
||||
<string name="msg_code_actions">快速修复</string>
|
||||
<string name="msg_performing_actions">正在修复…</string>
|
||||
<string name="msg_api_info_since">在API %d 添加</string>
|
||||
<string name="msg_api_info_removed">在API %d 移除</string>
|
||||
<string name="msg_api_info_deprecated">在API %d 弃用</string>
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
|
||||
<!-- LSP, Diagnostics & Completions -->
|
||||
<string name="msg_cannot_perform_fix">Unable to perform quick fix</string>
|
||||
<string name="msg_performing_fixes">Performing fixes…</string>
|
||||
<string name="msg_code_actions">Quickfixes</string>
|
||||
<string name="msg_performing_actions">Performing code action…</string>
|
||||
<string name="msg_api_info_since">Since API %d</string>
|
||||
<string name="msg_api_info_removed">Removed in API %d</string>
|
||||
<string name="msg_api_info_deprecated">Deprecated in API %d</string>
|
||||
@@ -343,7 +342,7 @@
|
||||
<string name="hint_string_value">String value</string>
|
||||
<string name="hint_string_resource">String resource</string>
|
||||
<string name="hint_resource_reference">Any resource reference</string>
|
||||
<string name="msg_fix_diagnostic">QuickFix</string>
|
||||
<string name="msg_code_actions">Actions</string>
|
||||
<string name="title_format_code">Format code</string>
|
||||
<string name="idepref_java_useGoogleStyle_title">Use Google Java Style code formatting</string>
|
||||
<string name="idepref_java_useGoogleStyle_summary">Use Google Java Style code formatting configuration for formatting Java source code.</string>
|
||||
|
||||
@@ -17,8 +17,13 @@
|
||||
|
||||
package com.itsaky.lsp.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -28,17 +33,167 @@ import java.util.List;
|
||||
*/
|
||||
public class DiagnosticUtil {
|
||||
|
||||
/**
|
||||
* Binary search all the diagnostic items in the given range.
|
||||
*
|
||||
* @param diagnostics The diagnostics to search items from.
|
||||
* @param range The range to look for diagnostics in.
|
||||
* @return The list of found diagnostics.
|
||||
*/
|
||||
public static List<DiagnosticItem> findDiagnosticsInRange(
|
||||
List<DiagnosticItem> diagnostics, Range range) {
|
||||
if (diagnostics == null || range == null || diagnostics.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final var left = findLeftBound(diagnostics, range.getStart());
|
||||
final var right = findRightBound(diagnostics, range.getEnd());
|
||||
|
||||
if (left < 0 || right >= diagnostics.size()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return diagnostics.subList(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the leftmost diagnostic element containing the given position.
|
||||
*
|
||||
* @param diagnostics The list of diagnostics.
|
||||
* @param position The position to search for.
|
||||
* @return The index of the diagnostic item.
|
||||
*/
|
||||
private static int findLeftBound(List<DiagnosticItem> diagnostics, @NonNull Position position) {
|
||||
final var index =
|
||||
binarySearchDiagnosticPosition(
|
||||
diagnostics, position.getLine(), position.getColumn());
|
||||
if (index == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
var left = index - 1;
|
||||
var i = left;
|
||||
|
||||
do {
|
||||
left = i;
|
||||
i = binarySearchDiagnosticPositionInRange(diagnostics, position, 0, left);
|
||||
} while (i != -1);
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the rightmost diagnostic element containing the given position.
|
||||
*
|
||||
* @param diagnostics The list of diagnostics.
|
||||
* @param position The position to search for.
|
||||
* @return The index of the diagnostic item.
|
||||
*/
|
||||
private static int findRightBound(
|
||||
List<DiagnosticItem> diagnostics, @NonNull Position position) {
|
||||
final var index =
|
||||
binarySearchDiagnosticPosition(
|
||||
diagnostics, position.getLine(), position.getColumn());
|
||||
if (index == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (index == diagnostics.size() - 1) {
|
||||
return index;
|
||||
}
|
||||
|
||||
var right = index + 1;
|
||||
var i = right;
|
||||
|
||||
do {
|
||||
right = i;
|
||||
i =
|
||||
binarySearchDiagnosticPositionInRange(
|
||||
diagnostics, position, right, diagnostics.size() - 1);
|
||||
} while (i != -1);
|
||||
|
||||
return right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search the position of the diagnostic item within the given list bounds containing the
|
||||
* given position.
|
||||
*
|
||||
* @param diagnostics The list of diagnostics.
|
||||
* @param position The position that will be used to check against the diagnostic range.
|
||||
* @param start The start index of the item in the diagnostics list.
|
||||
* @param end The end index of the item in the diagnostic list.
|
||||
* @return The index of the found diagnostic.
|
||||
*/
|
||||
private static int binarySearchDiagnosticPositionInRange(
|
||||
List<DiagnosticItem> diagnostics, Position position, int start, int end) {
|
||||
var left = start;
|
||||
var right = end;
|
||||
while (left <= right) {
|
||||
final var mid = (left + right) / 2;
|
||||
final var d = diagnostics.get(mid);
|
||||
final var r = d.getRange();
|
||||
final var c = r.containsForBinarySearch(position);
|
||||
if (c < 0) {
|
||||
left = mid - 1;
|
||||
} else if (c > 1) {
|
||||
right = mid + 1;
|
||||
} else {
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search the diagnostic item which contains the given line and column.
|
||||
*
|
||||
* @param diagnostics The list of diagnostics.
|
||||
* @param position The position to search for.
|
||||
* @return The diagnostic item.
|
||||
*/
|
||||
public static DiagnosticItem binarySearchDiagnostic(
|
||||
List<DiagnosticItem> diagnostics, Position position) {
|
||||
return binarySearchDiagnostic(diagnostics, position.getLine(), position.getColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search the diagnostic item which contains the given line and column.
|
||||
*
|
||||
* @param diagnostics The list of diagnostics.
|
||||
* @param line The line to search for.
|
||||
* @param column The column to search for.
|
||||
* @return The diagnostic item.
|
||||
*/
|
||||
public static DiagnosticItem binarySearchDiagnostic(
|
||||
List<DiagnosticItem> diagnostics, int line, int column) {
|
||||
if (diagnostics.isEmpty()) {
|
||||
final var index = binarySearchDiagnosticPosition(diagnostics, line, column);
|
||||
if (index == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return diagnostics.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search the diagnostic item which contains the given line and column.
|
||||
*
|
||||
* @param diagnostics The list of diagnostics.
|
||||
* @param line The line to search for.
|
||||
* @param column The column to search for.
|
||||
* @return The index of the found diagnostic item.
|
||||
*/
|
||||
public static int binarySearchDiagnosticPosition(
|
||||
List<DiagnosticItem> diagnostics, int line, int column) {
|
||||
if (diagnostics.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final var pos = new Position(line, column);
|
||||
int left = 0;
|
||||
int right = diagnostics.size() - 1;
|
||||
@@ -53,10 +208,10 @@ public class DiagnosticUtil {
|
||||
} else if (c > 0) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
return d;
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,13 @@
|
||||
package com.itsaky.lsp.java.rewrite;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.lsp.models.CodeActionItem;
|
||||
import com.itsaky.lsp.models.CodeActionKind;
|
||||
import com.itsaky.lsp.models.DocumentChange;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -49,7 +51,7 @@ public abstract class Rewrite {
|
||||
|
||||
public List<CodeActionItem> asCodeActions(CompilerProvider compiler, String title) {
|
||||
final Map<Path, TextEdit[]> edits = rewrite(compiler);
|
||||
if (edits == null) {
|
||||
if (edits == null || edits.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user