]> git.uio.no Git - ifi-stolz-refaktor.git/blobdiff - case-study/jdt-before/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaMoveLinesAction.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-before / ui / org / eclipse / jdt / internal / ui / javaeditor / JavaMoveLinesAction.java
diff --git a/case-study/jdt-before/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaMoveLinesAction.java b/case-study/jdt-before/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaMoveLinesAction.java
new file mode 100644 (file)
index 0000000..c7c164e
--- /dev/null
@@ -0,0 +1,483 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     Tom Eicher (Avaloq Evolution AG) - block selection mode
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.javaeditor;
+
+import java.util.ResourceBundle;
+
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Event;
+
+import org.eclipse.core.runtime.Assert;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.IRewriteTarget;
+import org.eclipse.jface.text.ITextSelection;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.ITextViewerExtension5;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextSelection;
+import org.eclipse.jface.text.TextUtilities;
+import org.eclipse.jface.text.source.ILineRange;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.source.LineRange;
+
+import org.eclipse.ui.IEditorInput;
+
+import org.eclipse.ui.texteditor.IEditorStatusLine;
+import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
+import org.eclipse.ui.texteditor.TextEditorAction;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.javaeditor.IndentUtil.IndentResult;
+
+
+/**
+ * Action for moving selected lines in a Java editor.
+ * @since 3.1
+ */
+public class JavaMoveLinesAction extends TextEditorAction {
+
+       /**
+        * State shared by the Move / Copy lines action quadruple.
+        * @since 3.1
+        */
+       private static final class SharedState {
+               /** The compilation unit editor that all four actions operate on. */
+               public CompilationUnitEditor fEditor;
+               /**
+                * The indent token shared by all four actions.
+                */
+               public IndentResult fResult= null;
+
+               /** <code>true</code> if a compound move / copy is going on. */
+               private boolean fEditInProgress= false;
+               /** The exit strategy that will detect the ending of a compound edit */
+               private final CompoundEditExitStrategy fExitStrategy;
+
+               public SharedState(CompilationUnitEditor editor) {
+                       fEditor= editor;
+                       fExitStrategy= new CompoundEditExitStrategy(new String[] {ITextEditorActionDefinitionIds.MOVE_LINES_UP, ITextEditorActionDefinitionIds.MOVE_LINES_DOWN, ITextEditorActionDefinitionIds.COPY_LINES_UP, ITextEditorActionDefinitionIds.COPY_LINES_DOWN});
+                       fExitStrategy.addCompoundListener(new ICompoundEditListener() {
+                               public void endCompoundEdit() {
+                                       SharedState.this.endCompoundEdit();
+                               }
+                       });
+               }
+
+               /**
+                * Ends the compound change.
+                */
+               public void beginCompoundEdit() {
+                       if (fEditInProgress || fEditor == null)
+                               return;
+
+                       fEditInProgress= true;
+
+                       fExitStrategy.arm(fEditor.getViewer());
+
+                       IRewriteTarget target= (IRewriteTarget)fEditor.getAdapter(IRewriteTarget.class);
+                       if (target != null) {
+                               target.beginCompoundChange();
+                       }
+               }
+               /**
+                * Ends the compound change.
+                */
+               public void endCompoundEdit() {
+                       if (!fEditInProgress || fEditor == null)
+                               return;
+
+                       fExitStrategy.disarm();
+
+                       IRewriteTarget target= (IRewriteTarget)fEditor.getAdapter(IRewriteTarget.class);
+                       if (target != null) {
+                               target.endCompoundChange();
+                       }
+
+                       fResult= null;
+                       fEditInProgress= false;
+               }
+       }
+
+       /* keys */
+
+       /** Key for status message upon illegal move. <p>Value {@value}</p> */
+
+       /* state variables - define what this action does */
+
+       /** <code>true</code> if lines are shifted upwards, <code>false</code> otherwise. */
+       private final boolean fUpwards;
+       /** <code>true</code> if lines are to be copied instead of moved. */
+       private final boolean fCopy;
+       /** The shared state of the move/copy action quadruple. */
+       private final SharedState fSharedState;
+
+       /**
+        * Creates the quadruple of move and copy actions. The returned array contains
+        * the actions in the following order:
+        * [0] move up
+        * [1] move down
+        * [2] copy up (duplicate)
+        * [3] copy down (duplicate & select)
+        * @param bundle the resource bundle
+        * @param editor the editor
+        * @return the quadruple of actions
+        */
+       public static JavaMoveLinesAction[] createMoveCopyActionSet(ResourceBundle bundle, CompilationUnitEditor editor) {
+               SharedState state= new SharedState(editor);
+               JavaMoveLinesAction[] actions= new JavaMoveLinesAction[4];
+               actions[0]= new JavaMoveLinesAction(bundle, "Editor.MoveLinesUp.", true, false, state); //$NON-NLS-1$
+               actions[1]= new JavaMoveLinesAction(bundle, "Editor.MoveLinesDown.", false, false, state); //$NON-NLS-1$
+               actions[2]= new JavaMoveLinesAction(bundle, "Editor.CopyLineUp.", true, true, state); //$NON-NLS-1$
+               actions[3]= new JavaMoveLinesAction(bundle, "Editor.CopyLineDown.", false, true, state); //$NON-NLS-1$
+               return actions;
+       }
+
+       /*
+        * @see org.eclipse.ui.texteditor.TextEditorAction#setEditor(org.eclipse.ui.texteditor.ITextEditor)
+        */
+       @Override
+       public void setEditor(ITextEditor editor) {
+               Assert.isTrue(editor instanceof CompilationUnitEditor);
+               super.setEditor(editor);
+               if (fSharedState != null)
+                       fSharedState.fEditor= (CompilationUnitEditor) editor;
+       }
+
+       /**
+        * Creates and initializes the action for the given text editor.
+        * The action configures its visual representation from the given resource
+        * bundle.
+        *
+        * @param bundle the resource bundle
+        * @param prefix a prefix to be prepended to the various resource keys
+        *   (described in <code>ResourceAction</code> constructor), or  <code>null</code> if none
+        * @param upwards <code>true</code>if the selected lines should be moved upwards,
+        * <code>false</code> if downwards
+        * @param copy if <code>true</code>, the action will copy lines instead of moving them
+        * @param state the shared state
+        * @see TextEditorAction#TextEditorAction(ResourceBundle, String, ITextEditor)
+        */
+       private JavaMoveLinesAction(ResourceBundle bundle, String prefix, boolean upwards, boolean copy, SharedState state) {
+               super(bundle, prefix, state.fEditor);
+               fUpwards= upwards;
+               fCopy= copy;
+               fSharedState= state;
+               update();
+       }
+
+       /**
+        * Checks if <code>selection</code> is contained by the visible region of <code>viewer</code>.
+        * As a special case, a selection is considered contained even if it extends over the visible
+        * region, but the extension stays on a partially contained line and contains only white space.
+        *
+        * @param selection the selection to be checked
+        * @param viewer the viewer displaying a visible region of <code>selection</code>'s document.
+        * @return <code>true</code>, if <code>selection</code> is contained, <code>false</code> otherwise.
+        */
+       private boolean containedByVisibleRegion(ITextSelection selection, ISourceViewer viewer) {
+               int min= selection.getOffset();
+               int max= min + selection.getLength();
+               IDocument document= viewer.getDocument();
+
+               IRegion visible;
+               if (viewer instanceof ITextViewerExtension5)
+                       visible= ((ITextViewerExtension5) viewer).getModelCoverage();
+               else
+                       visible= viewer.getVisibleRegion();
+
+               int visOffset= visible.getOffset();
+               try {
+                       if (visOffset > min) {
+                               if (document.getLineOfOffset(visOffset) != selection.getStartLine())
+                                       return false;
+                               if (!isWhitespace(document.get(min, visOffset - min))) {
+                                       showStatus();
+                                       return false;
+                               }
+                       }
+                       int visEnd= visOffset + visible.getLength();
+                       if (visEnd < max) {
+                               if (document.getLineOfOffset(visEnd) != selection.getEndLine())
+                                       return false;
+                               if (!isWhitespace(document.get(visEnd, max - visEnd))) {
+                                       showStatus();
+                                       return false;
+                               }
+                       }
+                       return true;
+               } catch (BadLocationException e) {
+               }
+               return false;
+       }
+
+       /**
+        * Given a selection on a document, computes the lines fully or partially covered by
+        * <code>selection</code>. A line in the document is considered covered if
+        * <code>selection</code> comprises any characters on it, including the terminating delimiter.
+        * <p>Note that the last line in a selection is not considered covered if the selection only
+        * comprises the line delimiter at its beginning (that is considered part of the second last
+        * line).
+        * As a special case, if the selection is empty, a line is considered covered if the caret is
+        * at any position in the line, including between the delimiter and the start of the line. The
+        * line containing the delimiter is not considered covered in that case.
+        * </p>
+        *
+        * @param document the document <code>selection</code> refers to
+        * @param selection a selection on <code>document</code>
+        * @param viewer the <code>ISourceViewer</code> displaying <code>document</code>
+        * @return a selection describing the range of lines (partially) covered by
+        * <code>selection</code>, without any terminating line delimiters
+        * @throws BadLocationException if the selection is out of bounds (when the underlying document has changed during the call)
+        */
+       private ITextSelection getMovingSelection(IDocument document, ITextSelection selection, ISourceViewer viewer) throws BadLocationException {
+               int low= document.getLineOffset(selection.getStartLine());
+               int endLine= selection.getEndLine();
+               int high= document.getLineOffset(endLine) + document.getLineLength(endLine);
+
+               // get everything up to last line without its delimiter
+               String delim= document.getLineDelimiter(endLine);
+               if (delim != null)
+                       high -= delim.length();
+
+               return new TextSelection(document, low, high - low);
+       }
+
+       /**
+        * Computes the region of the skipped line given the text block to be moved. If
+        * <code>fUpwards</code> is <code>true</code>, the line above <code>selection</code>
+        * is selected, otherwise the line below.
+        *
+        * @param document the document <code>selection</code> refers to
+        * @param selection the selection on <code>document</code> that will be moved.
+        * @return the region comprising the line that <code>selection</code> will be moved over, without its terminating delimiter.
+        */
+       private ITextSelection getSkippedLine(IDocument document, ITextSelection selection) {
+               int skippedLineN= (fUpwards ? selection.getStartLine() - 1 : selection.getEndLine() + 1);
+               if (skippedLineN > document.getNumberOfLines() || (!fCopy && (skippedLineN < 0 ||  skippedLineN == document.getNumberOfLines())))
+                       return null;
+               try {
+                       if (fCopy && skippedLineN == -1)
+                               skippedLineN= 0;
+                       IRegion line= document.getLineInformation(skippedLineN);
+                       return new TextSelection(document, line.getOffset(), line.getLength());
+               } catch (BadLocationException e) {
+                       // only happens on concurrent modifications
+                       return null;
+               }
+       }
+
+       /**
+        * Checks for white space in a string.
+        *
+        * @param string the string to be checked or <code>null</code>
+        * @return <code>true</code> if <code>string</code> contains only white space or is
+        * <code>null</code>, <code>false</code> otherwise
+        */
+       private boolean isWhitespace(String string) {
+               return string == null ? true : string.trim().length() == 0;
+       }
+
+       /*
+        * @see org.eclipse.jface.action.IAction#run()
+        */
+       @Override
+       public void runWithEvent(Event event) {
+
+               // get involved objects
+               if (fSharedState.fEditor == null)
+                       return;
+
+               if (!validateEditorInputState())
+                       return;
+
+               ISourceViewer viewer= fSharedState.fEditor.getViewer();
+               if (viewer == null)
+                       return;
+
+               IDocument document= viewer.getDocument();
+               if (document == null)
+                       return;
+
+               StyledText widget= viewer.getTextWidget();
+               if (widget == null)
+                       return;
+
+               // get selection
+               ITextSelection sel= (ITextSelection) viewer.getSelectionProvider().getSelection();
+               if (sel.isEmpty())
+                       return;
+
+               ITextSelection skippedLine= getSkippedLine(document, sel);
+               if (skippedLine == null)
+                       return;
+
+               try {
+
+                       ITextSelection movingArea= getMovingSelection(document, sel, viewer);
+
+                       // if either the skipped line or the moving lines are outside the widget's
+                       // visible area, bail out
+                       if (!containedByVisibleRegion(movingArea, viewer) || !containedByVisibleRegion(skippedLine, viewer))
+                               return;
+
+                       // get the content to be moved around: the moving (selected) area and the skipped line
+                       String moving= movingArea.getText();
+                       String skipped= skippedLine.getText();
+                       if (moving == null || skipped == null || document.getLength() == 0)
+                               return;
+
+                       String delim;
+                       String insertion;
+                       int offset;
+                       if (fUpwards) {
+                               delim= document.getLineDelimiter(skippedLine.getEndLine());
+                               if (fCopy) {
+                                       delim= TextUtilities.getDefaultLineDelimiter(document);
+                                       insertion= moving + delim;
+                                       offset= movingArea.getOffset();
+                               } else {
+                                       Assert.isNotNull(delim);
+                                       insertion= moving + delim + skipped;
+                                       offset= skippedLine.getOffset();
+                               }
+                       } else {
+                               delim= document.getLineDelimiter(movingArea.getEndLine());
+                               if (fCopy) {
+                                       if (delim == null) {
+                                               delim= TextUtilities.getDefaultLineDelimiter(document);
+                                               insertion= delim + moving;
+                                       } else
+                                               insertion= moving + delim;
+                                       offset= skippedLine.getOffset();
+                               } else {
+                                       Assert.isNotNull(delim);
+                                       insertion= skipped + delim + moving;
+                                       offset= movingArea.getOffset();
+                               }
+                       }
+                       int lenght= fCopy ? 0 : insertion.length();
+
+                       // modify the document
+                       ILineRange selectionBefore= getLineRange(document, movingArea);
+
+                       if (fCopy)
+                               fSharedState.endCompoundEdit();
+                       fSharedState.beginCompoundEdit();
+
+                       document.replace(offset, lenght, insertion);
+
+                       ILineRange selectionAfter;
+                       if (fUpwards && fCopy)
+                               selectionAfter= selectionBefore;
+                       else if (fUpwards)
+                               selectionAfter= new LineRange(selectionBefore.getStartLine() - 1, selectionBefore.getNumberOfLines());
+                       else if (fCopy)
+                               selectionAfter= new LineRange(selectionBefore.getStartLine() + selectionBefore.getNumberOfLines(), selectionBefore.getNumberOfLines());
+                       else
+                               selectionAfter= new LineRange(selectionBefore.getStartLine() + 1, selectionBefore.getNumberOfLines());
+
+                       fSharedState.fResult= IndentUtil.indentLines(document, selectionAfter, getProject(), fSharedState.fResult);
+
+                       // move the selection along
+                       IRegion region= getRegion(document, selectionAfter);
+                       selectAndReveal(viewer, region.getOffset(), region.getLength());
+
+               } catch (BadLocationException x) {
+                       // won't happen without concurrent modification - bail out
+                       return;
+               } finally {
+                       if (fCopy)
+                               fSharedState.endCompoundEdit();
+               }
+       }
+
+       private IJavaProject getProject() {
+               IEditorInput editorInput= fSharedState.fEditor.getEditorInput();
+               ICompilationUnit unit= JavaPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editorInput);
+               if (unit != null)
+                       return unit.getJavaProject();
+               return null;
+       }
+
+       private ILineRange getLineRange(IDocument document, ITextSelection selection) throws BadLocationException {
+               final int offset= selection.getOffset();
+               int startLine= document.getLineOfOffset(offset);
+               int endOffset= offset + selection.getLength();
+               int endLine= document.getLineOfOffset(endOffset);
+               final int nLines= endLine - startLine + 1;
+               return new LineRange(startLine, nLines);
+       }
+
+       private IRegion getRegion(IDocument document, ILineRange lineRange) throws BadLocationException {
+               final int startLine= lineRange.getStartLine();
+               int offset= document.getLineOffset(startLine);
+               final int numberOfLines= lineRange.getNumberOfLines();
+               if (numberOfLines < 1)
+                       return new Region(offset, 0);
+               int endLine= startLine + numberOfLines - 1;
+               int endOffset;
+               if (fSharedState.fEditor.isBlockSelectionModeEnabled()) {
+                       // in block selection mode, don't select the last delimiter as we count an empty selected line
+                       IRegion endLineInfo= document.getLineInformation(endLine);
+                       endOffset= endLineInfo.getOffset() + endLineInfo.getLength();
+               } else {
+                       endOffset= document.getLineOffset(endLine) + document.getLineLength(endLine);
+               }
+               return new Region(offset, endOffset - offset);
+       }
+
+       /**
+        * Performs similar to AbstractTextEditor.selectAndReveal, but does not update
+        * the viewers highlight area.
+        *
+        * @param viewer the viewer that we want to select on
+        * @param offset the offset of the selection
+        * @param length the length of the selection
+        */
+       private void selectAndReveal(ITextViewer viewer, int offset, int length) {
+               // invert selection to avoid jumping to the end of the selection in st.showSelection()
+               viewer.setSelectedRange(offset + length, -length);
+               //viewer.revealRange(offset, length); // will trigger jumping
+               StyledText st= viewer.getTextWidget();
+               if (st != null)
+                       st.showSelection(); // only minimal scrolling
+       }
+
+       /**
+        * Displays information in the status line why a line move is not possible
+        */
+       private void showStatus() {
+               IEditorStatusLine status= (IEditorStatusLine) fSharedState.fEditor.getAdapter(IEditorStatusLine.class);
+               if (status == null)
+                       return;
+               status.setMessage(false, JavaEditorMessages.Editor_MoveLines_IllegalMove_status, null);
+       }
+
+       /*
+        * @see org.eclipse.ui.texteditor.IUpdate#update()
+        */
+       @Override
+       public void update() {
+               super.update();
+
+               if (isEnabled())
+                       setEnabled(canModifyEditor());
+
+       }
+}