Bug 572862: [SourceEditor] Add QuickRefactoringAssistProposal
Change-Id: I36c204b5f447a537e036dbb8825561c90c5e4a7b
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/refactoring/core/QuickRefactoring.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/refactoring/core/QuickRefactoring.java
new file mode 100644
index 0000000..29ee62f
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/refactoring/core/QuickRefactoring.java
@@ -0,0 +1,44 @@
+/*=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.refactoring.core;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.ltk.core.refactoring.Refactoring;
+import org.eclipse.ltk.core.refactoring.TextChange;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
+
+@NonNullByDefault
+public abstract class QuickRefactoring extends Refactoring {
+
+
+ public abstract String getBundleId();
+
+ public abstract TextChange createTextChange(
+ SubMonitor m) throws CoreException;
+
+
+ protected CoreException handleBadLocation(final BadLocationException e) {
+ return new CoreException(new Status(IStatus.ERROR, getBundleId(),
+ "Unexpected error (concurrent change?)", e ));
+ }
+
+
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/refactoring/core/RefactoringMessages.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/refactoring/core/RefactoringMessages.java
index 241627d..7a57710 100644
--- a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/refactoring/core/RefactoringMessages.java
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/refactoring/core/RefactoringMessages.java
@@ -16,10 +16,14 @@
import org.eclipse.osgi.util.NLS;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
/**
* Common refactoring messages.
*/
+@NonNullByDefault
+@SuppressWarnings("null")
public class RefactoringMessages extends NLS {
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/refactoring/core/TextChangeCompatibility.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/refactoring/core/TextChangeCompatibility.java
index 6603dd2..68691c9 100644
--- a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/refactoring/core/TextChangeCompatibility.java
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/refactoring/core/TextChangeCompatibility.java
@@ -14,66 +14,64 @@
package org.eclipse.statet.ltk.refactoring.core;
-import org.eclipse.core.runtime.Assert;
import org.eclipse.ltk.core.refactoring.CategorizedTextEditGroup;
import org.eclipse.ltk.core.refactoring.GroupCategorySet;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextEditChangeGroup;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.RangeMarker;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
/**
- * A utility class to provide compatibility with the old
- * text change API of adding text edits directly and auto
- * inserting them into the tree.
+ * A utility class to provide compatibility with the old text change API of adding text edits
+ * directly and auto inserting them into the tree.
*/
+@NonNullByDefault
public class TextChangeCompatibility {
- public static void addTextEdit(final TextChange change, final String name, final TextEdit edit) throws MalformedTreeException {
+ public static void addTextEdit(final TextChange change, final String name, final TextEdit edit)
+ throws MalformedTreeException {
addTextEdit(change, name, edit, true);
}
- public static void addTextEdit(final TextChange change, final String name, final TextEdit edit, final boolean enable) throws MalformedTreeException {
- Assert.isNotNull(change);
- Assert.isNotNull(name);
- Assert.isNotNull(edit);
- TextEdit root= change.getEdit();
- if (root == null) {
- root= new MultiTextEdit();
- change.setEdit(root);
- }
- insert(root, edit);
- final TextEditChangeGroup group= new TextEditChangeGroup(change, new TextEditGroup(name, edit));
+ public static void addTextEdit(final TextChange change, final String name, final TextEdit edit,
+ final boolean enable)
+ throws MalformedTreeException {
+ insert(getRoot(change), edit);
+ final TextEditChangeGroup group= new TextEditChangeGroup(change,
+ new TextEditGroup(name, edit) );
group.setEnabled(enable);
change.addTextEditChangeGroup(group);
}
- public static void addTextEdit(final TextChange change, final String name, final TextEdit edit, final GroupCategorySet groupCategories) throws MalformedTreeException {
- Assert.isNotNull(change);
- Assert.isNotNull(name);
- Assert.isNotNull(edit);
- TextEdit root= change.getEdit();
- if (root == null) {
- root= new MultiTextEdit();
- change.setEdit(root);
- }
- insert(root, edit);
- change.addTextEditChangeGroup(new TextEditChangeGroup(
- change,
- new CategorizedTextEditGroup(name, edit, groupCategories)));
+ public static void addTextEdit(final TextChange change, final String name, final TextEdit edit,
+ final GroupCategorySet groupCategories)
+ throws MalformedTreeException {
+ insert(getRoot(change), edit);
+ final TextEditChangeGroup group= new TextEditChangeGroup(change,
+ new CategorizedTextEditGroup(name, edit, groupCategories) );
+ change.addTextEditChangeGroup(group);
+ }
+
+ public static void addMarker(final TextChange change, final RangeMarker marker)
+ throws MalformedTreeException {
+ insert(getRoot(change), marker);
}
- public static void insert(final TextEdit parent, final TextEdit edit) throws MalformedTreeException {
+ public static void insert(final TextEdit parent, final TextEdit edit)
+ throws MalformedTreeException {
if (!parent.hasChildren()) {
parent.addChild(edit);
return;
}
- final TextEdit[] children= parent.getChildren();
+ final var children= parent.getChildren();
// First dive down to find the right parent.
for (int i= 0; i < children.length; i++) {
final TextEdit child= children[i];
@@ -96,6 +94,15 @@
}
+ private static TextEdit getRoot(final TextChange change) {
+ TextEdit root= change.getEdit();
+ if (root == null) {
+ root= new MultiTextEdit();
+ change.setEdit(root);
+ }
+ return root;
+ }
+
private static boolean covers(final TextEdit thisEdit, final TextEdit otherEdit) {
if (thisEdit.getLength() == 0) { // an insertion point can't cover anything
return false;
@@ -104,11 +111,12 @@
final int thisEnd= thisEdit.getExclusiveEnd();
if (otherEdit.getLength() == 0) {
final int otherOffset= otherEdit.getOffset();
- return thisOffset < otherOffset && otherOffset < thisEnd;
- } else {
+ return (thisOffset < otherOffset && otherOffset < thisEnd);
+ }
+ else {
final int otherOffset= otherEdit.getOffset();
final int otherEnd= otherEdit.getExclusiveEnd();
- return thisOffset <= otherOffset && otherEnd <= thisEnd;
+ return (thisOffset <= otherOffset && otherEnd <= thisEnd);
}
}
diff --git a/ltk/org.eclipse.statet.ltk.ui/plugin.properties b/ltk/org.eclipse.statet.ltk.ui/plugin.properties
index f2a1970..ba881b9 100644
--- a/ltk/org.eclipse.statet.ltk.ui/plugin.properties
+++ b/ltk/org.eclipse.statet.ltk.ui/plugin.properties
@@ -69,6 +69,8 @@
commands_ToggleReportProblemWhenTyping_description= Toggles the activation of reporting problems as you type in editors of current type
commands_QuickAssistRenameInFile_name= Quick Assist - Rename in File
commands_QuickAssistRenameInFile_description= Links all references for a local rename in the current file
+commands_QuickAssistConvertToPipeForward_name= Quick Assist - Convert to Forward Pipe
+commands_QuickAssistConvertToPipeForward_description= Converts an expression to pipe forward syntax
commands_RefactorRenameInWorkspace_name= Rename in Workspace...
commands_RefactorRenameInWorkspace_description= Renames the selected identifier in the workspace
commands_RefactorRenameInSelectedRegion_name= Rename in Selected Region...
diff --git a/ltk/org.eclipse.statet.ltk.ui/plugin.xml b/ltk/org.eclipse.statet.ltk.ui/plugin.xml
index a27d0db..f51472b 100644
--- a/ltk/org.eclipse.statet.ltk.ui/plugin.xml
+++ b/ltk/org.eclipse.statet.ltk.ui/plugin.xml
@@ -225,6 +225,11 @@
categoryId="org.eclipse.statet.workbench.commandCategorys.Source"
name="%commands_QuickAssistRenameInFile_name"
description="%commands_QuickAssistRenameInFile_description"/>
+ <command
+ id="org.eclipse.statet.ltk.commands.QuickAssistConvertToPipeForward"
+ categoryId="org.eclipse.statet.workbench.commandCategorys.Source"
+ name="%commands_QuickAssistConvertToPipeForward_name"
+ description="%commands_QuickAssistConvertToPipeForward_description"/>
<!-- refactor -->
<command
@@ -428,6 +433,11 @@
contextId="org.eclipse.statet.workbench.contexts.TextEditor"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="M1+2 R"/>
+ <key
+ commandId="org.eclipse.statet.ltk.commands.QuickAssistConvertToPipeForward"
+ contextId="org.eclipse.statet.workbench.contexts.TextEditor"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
+ sequence="M1+2 P"/>
<key
commandId="org.eclipse.statet.ltk.commands.RefactorRenameInWorkspace"
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/internal/ltk/ui/refactoring/TextEditAnnotator.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/internal/ltk/ui/refactoring/TextEditAnnotator.java
new file mode 100644
index 0000000..5555e84
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/internal/ltk/ui/refactoring/TextEditAnnotator.java
@@ -0,0 +1,182 @@
+/*=============================================================================#
+ # Copyright (c) 2009, 2021 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.internal.ltk.ui.refactoring;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.text.edits.CopyTargetEdit;
+import org.eclipse.text.edits.DeleteEdit;
+import org.eclipse.text.edits.InsertEdit;
+import org.eclipse.text.edits.MoveSourceEdit;
+import org.eclipse.text.edits.MoveTargetEdit;
+import org.eclipse.text.edits.RangeMarker;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.text.edits.TextEditVisitor;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
+
+/**
+ * Class to annotate edits made by a quick fix/assist to be shown via the quick fix pop-up preview.
+ * E.g. the added changes are shown in bold.
+ */
+@NonNullByDefault
+public class TextEditAnnotator extends TextEditVisitor {
+
+
+ private int writtenToOffset= 0;
+ private int chunks;
+
+ private final StringBuilder sb;
+
+ private final IDocument previewDocument;
+
+
+ public TextEditAnnotator(final StringBuilder sb, final IDocument previewDoc) {
+ this.sb= sb;
+ this.previewDocument= previewDoc;
+ }
+
+
+ public void unchangedUntil(final int offset) {
+ if (offset > this.writtenToOffset) {
+ appendContent(this.previewDocument, this.writtenToOffset, offset, true);
+ this.writtenToOffset= offset;
+ }
+ }
+
+
+ @Override
+ public boolean visit(final MoveTargetEdit edit) {
+ return true; //rangeAdded(edit);
+ }
+
+ @Override
+ public boolean visit(final CopyTargetEdit edit) {
+ return true; //return rangeAdded(edit);
+ }
+
+ @Override
+ public boolean visit(final InsertEdit edit) {
+ return rangeAdded(edit);
+ }
+
+ @Override
+ public boolean visit(final ReplaceEdit edit) {
+ if (edit.getLength() > 0) {
+ return rangeAdded(edit);
+ }
+ return rangeRemoved(edit);
+ }
+
+ @Override
+ public boolean visit(final MoveSourceEdit edit) {
+ return rangeRemoved(edit);
+ }
+
+ @Override
+ public boolean visit(final DeleteEdit edit) {
+ return rangeRemoved(edit);
+ }
+
+ @Override
+ public boolean visit(final RangeMarker edit) {
+ unchangedUntil(edit.getOffset());
+ return true;
+ }
+
+
+ protected boolean rangeRemoved(final TextEdit edit) {
+ unchangedUntil(edit.getOffset());
+ return false;
+ }
+
+ protected boolean rangeAdded(final TextEdit edit) {
+ return annotateEdit(edit, "<b>", "</b>"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ protected boolean annotateEdit(final TextEdit edit, final String startTag, final String endTag) {
+ unchangedUntil(edit.getOffset());
+ this.sb.append(startTag);
+ appendContent(this.previewDocument, edit.getOffset(), edit.getExclusiveEnd(), false);
+ this.sb.append(endTag);
+ this.writtenToOffset= edit.getExclusiveEnd();
+ return false;
+ }
+
+ private void appendContent(final IDocument text, final int startOffset, final int endOffset,
+ final boolean surroundLinesOnly) {
+ final int surroundLines= 1;
+ try {
+ final boolean firstChunk= (this.chunks++ == 0);
+ int startLine= text.getLineOfOffset(
+ (surroundLinesOnly && firstChunk) ? endOffset : startOffset );
+ final int endLine= text.getLineOfOffset(endOffset);
+
+ boolean dotsAdded= false;
+ if (surroundLinesOnly && startOffset == 0) { // no surround lines for the top no-change range
+ startLine= endLine;
+ dotsAdded= true;
+ }
+
+ for (int i= startLine; i <= endLine; i++) {
+ if (surroundLinesOnly) {
+ if ((i - startLine > surroundLines) && (endLine - i > surroundLines)) {
+ if (!dotsAdded) {
+ this.sb.append("...<br/>"); //$NON-NLS-1$
+ dotsAdded= true;
+ }
+ else if (endOffset == text.getLength()) {
+ return; // no surround lines for the bottom no-change range
+ }
+ continue;
+ }
+ }
+
+ final IRegion lineInfo= text.getLineInformation(i);
+ final int start= lineInfo.getOffset();
+ final int end= start + lineInfo.getLength();
+
+ final int from= Math.max(start, startOffset);
+ final int to= Math.min(end, endOffset);
+ final String content= text.get(from, to - from);
+ if (surroundLinesOnly && (from == start) && content.isEmpty()) {
+ continue; // ignore empty lines except when range started in the middle of a line
+ }
+ for (int k= 0; k < content.length(); k++) {
+ final char ch= content.charAt(k);
+ switch (ch) {
+ case '<':
+ this.sb.append("<"); //$NON-NLS-1$
+ break;
+ case '>':
+ this.sb.append(">"); //$NON-NLS-1$
+ break;
+ default:
+ this.sb.append(ch);
+ break;
+ }
+ }
+ if (to == end && to != endOffset) { // new line when at the end of the line, and not end of range
+ this.sb.append("<br/>"); //$NON-NLS-1$
+ }
+ }
+ }
+ catch (final BadLocationException e) {}
+ }
+
+}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkActions.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkActions.java
index 76981c3..0a19ae1 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkActions.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkActions.java
@@ -184,6 +184,14 @@
public static final String QUICK_ASSIST_RENAME_IN_FILE=
"org.eclipse.statet.ltk.commands.QuickAssistRenameInFile";
+ /**
+ * ID of command 'Quick Assist - Convert to Pipe Forward Statement'.
+ *
+ * Value: @value
+ */
+ public static final String QUICK_ASSIST_CONVERT_TO_PIPE_FORWARD=
+ "org.eclipse.statet.ltk.commands.QuickAssistConvertToPipeForward";
+
//--Search--
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/refactoring/RefactoringExecutionHelper.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/refactoring/RefactoringExecutionHelper.java
index 49d7431..d5c2781 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/refactoring/RefactoringExecutionHelper.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/refactoring/RefactoringExecutionHelper.java
@@ -14,6 +14,8 @@
package org.eclipse.statet.ltk.ui.refactoring;
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
+
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -29,7 +31,6 @@
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.Position;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
@@ -44,6 +45,9 @@
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.progress.IProgressService;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
import org.eclipse.statet.internal.ltk.ui.refactoring.Messages;
import org.eclipse.statet.ltk.model.core.element.SourceUnit;
import org.eclipse.statet.ltk.refactoring.core.ScheduledRefactoring;
@@ -55,6 +59,7 @@
* undo change onto the undo stack and folding editor edits into one editor
* undo object.
*/
+@NonNullByDefault
public class RefactoringExecutionHelper {
@@ -62,28 +67,25 @@
private final IProgressService execContext;
- private final Shell parent;
+ private final Shell parentShell;
private final int stopSeverity;
- private SourceUnit insertPositionSourceUnit;
- private Position insertPosition;
+ private @Nullable SourceUnit insertPositionSourceUnit;
+ private @Nullable Position insertPosition;
/**
* @param refactoring
* @param stopSeverity a refactoring status constant from {@link RefactoringStatus}
- * @param parent
+ * @param parentShell
* @param context
*/
- public RefactoringExecutionHelper(final Refactoring refactoring, final int stopSeverity, final Shell parent, final IProgressService context) {
- assert (refactoring != null);
- assert (parent != null);
- assert (context != null);
-
- this.refactoring= refactoring;
+ public RefactoringExecutionHelper(final Refactoring refactoring, final int stopSeverity,
+ final Shell parentShell, final IProgressService context) {
+ this.refactoring= nonNullAssert(refactoring);
this.stopSeverity= stopSeverity;
- this.parent= parent;
- this.execContext= context;
+ this.parentShell= nonNullAssert(parentShell);
+ this.execContext= nonNullAssert(context);
}
@@ -91,138 +93,128 @@
* Must be called in the UI thread.<br>
* <strong>Use {@link #perform(boolean, boolean)} unless you know exactly what you are doing!</strong>
*
- * @param forkChangeExecution if the change should not be executed in the UI thread: This may not work in any case
+ * @param forkChangeExecution if the change should not be executed in the UI thread: This may not work in any case
* @param cancelable if set, the operation will be cancelable
* @throws InterruptedException thrown when the operation is cancelled
* @throws InvocationTargetException thrown when the operation failed to execute
*/
- public void perform(final boolean forkChangeExecution, final boolean cancelable) throws InterruptedException, InvocationTargetException {
+ public void perform(final boolean forkChangeExecution, final boolean cancelable)
+ throws InterruptedException, InvocationTargetException {
final AtomicReference<PerformChangeOperation> op= new AtomicReference<>();
try {
- this.execContext.busyCursorWhile(new IRunnableWithProgress() {
- @Override
- public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
- final SubMonitor m= SubMonitor.convert(monitor, RefactoringExecutionHelper.this.refactoring.getName(), 10);
+ this.execContext.busyCursorWhile((final IProgressMonitor monitor) -> {
+ final SubMonitor m= SubMonitor.convert(monitor, this.refactoring.getName(), 10);
+
+ final IJobManager manager= Job.getJobManager();
+ final Thread workingThread= Thread.currentThread();
+ final ISchedulingRule rule= (this.refactoring instanceof ScheduledRefactoring) ?
+ ((ScheduledRefactoring)this.refactoring).getSchedulingRule() :
+ ResourcesPlugin.getWorkspace().getRoot();
+
+ manager.beginRule(rule, m.newChild(1));
+ try {
+ if (cancelable && monitor.isCanceled()) {
+ throw new InterruptedException();
+ }
- final IJobManager manager= Job.getJobManager();
- final Thread workingThread= Thread.currentThread();
- final ISchedulingRule rule= (RefactoringExecutionHelper.this.refactoring instanceof ScheduledRefactoring) ?
- ((ScheduledRefactoring) RefactoringExecutionHelper.this.refactoring).getSchedulingRule() :
- ResourcesPlugin.getWorkspace().getRoot();
+ this.refactoring.setValidationContext(this.parentShell);
- manager.beginRule(rule, m.newChild(1));
- PerformChangeOperation operation= null;
- try {
- if (cancelable && monitor.isCanceled()) {
- throw new InterruptedException();
+ final RefactoringStatus status= this.refactoring.checkAllConditions(
+ m.newChild(1, SubMonitor.SUPPRESS_NONE));
+ if (status.getSeverity() >= this.stopSeverity) {
+ final AtomicBoolean canceled= new AtomicBoolean();
+ this.parentShell.getDisplay().syncExec(() -> {
+ final Dialog dialog= RefactoringUI.createRefactoringStatusDialog(
+ status, this.parentShell, this.refactoring.getName(), false );
+ final int selection= dialog.open();
+ canceled.set(selection == IDialogConstants.CANCEL_ID);
+ });
+ if (canceled.get()) {
+ throw new OperationCanceledException();
}
-
- RefactoringExecutionHelper.this.refactoring.setValidationContext(RefactoringExecutionHelper.this.parent);
-
- final RefactoringStatus status= RefactoringExecutionHelper.this.refactoring.checkAllConditions(
- m.newChild(1, SubMonitor.SUPPRESS_NONE));
- if (status.getSeverity() >= RefactoringExecutionHelper.this.stopSeverity) {
- final AtomicBoolean canceled= new AtomicBoolean();
- RefactoringExecutionHelper.this.parent.getDisplay().syncExec(new Runnable() {
- @Override
- public void run() {
- final Dialog dialog= RefactoringUI.createRefactoringStatusDialog(status, RefactoringExecutionHelper.this.parent, RefactoringExecutionHelper.this.refactoring.getName(), false);
- final int selection= dialog.open();
- canceled.set(selection == IDialogConstants.CANCEL_ID);
- }
- });
- if (canceled.get()) {
- throw new OperationCanceledException();
+ }
+
+ final Change change= this.refactoring.createChange(
+ m.newChild(2, SubMonitor.SUPPRESS_NONE) );
+ change.initializeValidationData(
+ m.newChild(1, SubMonitor.SUPPRESS_NONE) );
+
+ final SourceUnitChange insertTracker= (this.insertPositionSourceUnit != null) ?
+ search(change) : null;
+
+ final var operation= new PerformChangeOperation(change);
+ operation.setUndoManager(RefactoringCore.getUndoManager(), this.refactoring.getName());
+ operation.setSchedulingRule(rule);
+
+ if (cancelable && monitor.isCanceled()) {
+ throw new InterruptedException();
+ }
+
+ op.set(operation);
+ if (forkChangeExecution) {
+ operation.run(m.newChild(4, SubMonitor.SUPPRESS_NONE));
+ }
+ else {
+ final AtomicReference<Exception> opException= new AtomicReference<>();
+ final Display display= this.parentShell.getDisplay();
+ manager.transferRule(rule, display.getThread());
+ display.syncExec(() -> {
+ try {
+ operation.run(m.newChild(4, SubMonitor.SUPPRESS_NONE));
+ }
+ catch (final CoreException | RuntimeException e) {
+ opException.set(e);
+ }
+ finally {
+ manager.transferRule(rule, workingThread);
+ }
+ });
+ if (opException.get() != null) {
+ final Exception e= opException.get();
+ if (e instanceof CoreException) {
+ throw (CoreException)e;
+ }
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException)e;
}
}
-
- final Change change= RefactoringExecutionHelper.this.refactoring.createChange(
- m.newChild(2, SubMonitor.SUPPRESS_NONE) );
- change.initializeValidationData(
- m.newChild(1, SubMonitor.SUPPRESS_NONE) );
-
- final SourceUnitChange insertTracker= (RefactoringExecutionHelper.this.insertPositionSourceUnit != null) ?
- search(change) : null;
-
- operation= new PerformChangeOperation(change);
- operation.setUndoManager(RefactoringCore.getUndoManager(), RefactoringExecutionHelper.this.refactoring.getName());
- operation.setSchedulingRule(rule);
-
- if (cancelable && monitor.isCanceled()) {
- throw new InterruptedException();
- }
- op.set(operation);
-
- if (forkChangeExecution) {
- operation.run(m.newChild(4, SubMonitor.SUPPRESS_NONE));
- }
- else {
- final AtomicReference<Exception> opException= new AtomicReference<>();
- final Runnable runnable= new Runnable() {
- @Override
- public void run() {
- try {
- final PerformChangeOperation operation= op.get();
- operation.run(m.newChild(4, SubMonitor.SUPPRESS_NONE));
- }
- catch (final CoreException | RuntimeException e) {
- opException.set(e);
- }
- finally {
- manager.transferRule(rule, workingThread);
- }
- }
- };
- final Display display= RefactoringExecutionHelper.this.parent.getDisplay();
- manager.transferRule(rule, display.getThread());
- display.syncExec(runnable);
- if (opException.get() != null) {
- final Exception e= opException.get();
- if (e instanceof CoreException) {
- throw (CoreException) e;
- }
- if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- }
- }
- }
-
- final RefactoringStatus validationStatus= operation.getValidationStatus();
- if (validationStatus != null && validationStatus.hasFatalError()) {
- MessageDialog.openError(RefactoringExecutionHelper.this.parent, RefactoringExecutionHelper.this.refactoring.getName(), NLS.bind(
- Messages.ExecutionHelper_CannotExecute_message,
- validationStatus.getMessageMatchingSeverity(RefactoringStatus.FATAL) ));
- return;
- }
-
- if (insertTracker != null) {
- RefactoringExecutionHelper.this.insertPosition= insertTracker.getInsertPosition();
- }
}
- catch (final OperationCanceledException e) {
- throw new InterruptedException(e.getMessage());
+
+ final RefactoringStatus validationStatus= operation.getValidationStatus();
+ if (validationStatus != null && validationStatus.hasFatalError()) {
+ MessageDialog.openError(this.parentShell, this.refactoring.getName(), NLS.bind(
+ Messages.ExecutionHelper_CannotExecute_message,
+ validationStatus.getMessageMatchingSeverity(RefactoringStatus.FATAL) ));
+ return;
}
- catch (final CoreException | RuntimeException e) {
- throw new InvocationTargetException(e);
- }
- finally {
- manager.endRule(rule);
- RefactoringExecutionHelper.this.refactoring.setValidationContext(null);
+
+ if (insertTracker != null) {
+ this.insertPosition= insertTracker.getInsertPosition();
}
}
-
+ catch (final OperationCanceledException e) {
+ throw new InterruptedException(e.getMessage());
+ }
+ catch (final CoreException | RuntimeException e) {
+ throw new InvocationTargetException(e);
+ }
+ finally {
+ manager.endRule(rule);
+ this.refactoring.setValidationContext(null);
+ }
});
}
catch (final InvocationTargetException e) {
final PerformChangeOperation operation= op.get();
if (operation != null && operation.changeExecutionFailed()) {
- final ChangeExceptionHandler handler= new ChangeExceptionHandler(this.parent, this.refactoring);
+ final ChangeExceptionHandler handler= new ChangeExceptionHandler(this.parentShell,
+ this.refactoring );
final Throwable inner= e.getTargetException();
if (inner instanceof RuntimeException) {
- handler.handle(operation.getChange(), (RuntimeException) inner);
+ handler.handle(operation.getChange(), (RuntimeException)inner);
}
else if (inner instanceof CoreException) {
- handler.handle(operation.getChange(), (CoreException) inner);
+ handler.handle(operation.getChange(), (CoreException)inner);
}
else {
throw e;
@@ -240,18 +232,18 @@
this.insertPositionSourceUnit= su;
}
- public Position getInsertPosition() {
+ public @Nullable Position getInsertPosition() {
return this.insertPosition;
}
- private SourceUnitChange search(final Change change) {
+ private @Nullable SourceUnitChange search(final Change change) {
if (change instanceof SourceUnitChange) {
- if (((SourceUnitChange) change).getSourceUnit() == this.insertPositionSourceUnit) {
- return (SourceUnitChange) change;
+ if (((SourceUnitChange)change).getSourceUnit() == this.insertPositionSourceUnit) {
+ return (SourceUnitChange)change;
}
}
if (change instanceof CompositeChange) {
- final Change[] children= ((CompositeChange) change).getChildren();
+ final var children= ((CompositeChange)change).getChildren();
for (int i= 0; i < children.length; i++) {
final SourceUnitChange child= search(children[i]);
if (child != null) {
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/QuickRefactoringAssistProposal.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/QuickRefactoringAssistProposal.java
new file mode 100644
index 0000000..d4f5512
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/QuickRefactoringAssistProposal.java
@@ -0,0 +1,154 @@
+/*=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.ui.sourceediting.assist;
+
+import static org.eclipse.statet.ltk.ui.LtkUI.BUNDLE_ID;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.Position;
+import org.eclipse.ltk.core.refactoring.TextChange;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.ui.progress.IProgressService;
+import org.eclipse.ui.statushandlers.StatusManager;
+
+import org.eclipse.statet.jcommons.lang.NonNull;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ecommons.text.ui.DefaultBrowserInformationInput;
+
+import org.eclipse.statet.internal.ltk.ui.refactoring.TextEditAnnotator;
+import org.eclipse.statet.ltk.model.core.element.SourceUnit;
+import org.eclipse.statet.ltk.refactoring.core.QuickRefactoring;
+import org.eclipse.statet.ltk.ui.refactoring.RefactoringExecutionHelper;
+import org.eclipse.statet.ltk.ui.sourceediting.SourceEditor;
+
+
+@NonNullByDefault
+public class QuickRefactoringAssistProposal<TContext extends AssistInvocationContext>
+ extends CommandAssistProposal<TContext> {
+
+
+ private final QuickRefactoring refactoring;
+
+
+ public QuickRefactoringAssistProposal(final ProposalParameters<TContext> parameters,
+ final QuickRefactoring refactoring) {
+ super(parameters);
+ this.refactoring= refactoring;
+
+ check();
+ }
+
+ public QuickRefactoringAssistProposal(final TContext invocationContext, final String commandId,
+ final String label,
+ final QuickRefactoring refactoring) {
+ super(invocationContext, commandId, label, null);
+ this.refactoring= refactoring;
+
+ check();
+ }
+
+ protected void check() {
+ if (getInvocationContext().getSourceUnit() == null) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+
+ protected QuickRefactoring getRefactoring() {
+ return this.refactoring;
+ }
+
+
+ @Override
+ public @Nullable Object getAdditionalProposalInfo(final IProgressMonitor monitor) {
+ final SubMonitor m= SubMonitor.convert(monitor, 2 + 2);
+ try {
+ final StringBuilder sb= new StringBuilder();
+ final TextChange change= this.refactoring.createTextChange(m.newChild(2));
+ change.setKeepPreviewEdits(true);
+ final IDocument previewDocument= change.getPreviewDocument(m.newChild(1));
+ final TextEdit rootEdit= change.getPreviewEdit(change.getEdit());
+ final TextEditAnnotator ea= new TextEditAnnotator(sb, previewDocument);
+ rootEdit.accept(ea); m.worked(1);
+ return new DefaultBrowserInformationInput(getDisplayString(),
+ sb.toString(), DefaultBrowserInformationInput.FORMAT_HTMLSOURCE_INPUT,
+ getInvocationContext().getTabSize() );
+ }
+ catch (final CoreException e) {
+ StatusManager.getManager().handle(new Status(IStatus.ERROR, BUNDLE_ID, -1,
+ String.format("An error occured when generating the preview for quick assist '%1$s'.",
+ getDisplayString() ),
+ e ));
+ return null;
+ }
+ finally {
+ m.done();
+ }
+ }
+
+
+ @Override
+ public void apply(final ITextViewer viewer,
+ final char trigger, final int stateMask, final int offset) {
+ final var context= getInvocationContext();
+ @SuppressWarnings("null")
+ final @NonNull SourceUnit sourceUnit= context.getSourceUnit();
+ final SourceEditor editor= context.getEditor();
+ final var textWidget= editor.getViewer().getTextWidget();
+ if (textWidget == null) {
+ return;
+ }
+
+ IProgressService execContext= null;
+ final var serviceLocator= editor.getServiceLocator();
+ if (serviceLocator != null) {
+ execContext= serviceLocator.getService(IProgressService.class);
+ }
+ if (execContext == null) {
+ return;
+ }
+ final var applyData= getApplyData();
+ try {
+ final var refactoring= this.refactoring;
+ final var helper= new RefactoringExecutionHelper(refactoring,
+ offset, textWidget.getShell(), execContext );
+ helper.enableInsertPosition(sourceUnit);
+ helper.perform(false, false);
+
+ final Position position= helper.getInsertPosition();
+ if (position != null) {
+ applyData.setSelection(position.getOffset());
+ }
+ }
+ catch (final InterruptedException e) {}
+ catch (final InvocationTargetException e) {
+ StatusManager.getManager().handle(new Status(IStatus.ERROR, BUNDLE_ID, -1,
+ String.format("An error occured when executing quick assist '%1$s'.",
+ getDisplayString() ),
+ e ));
+ }
+ }
+
+}