X-Git-Url: http://git.uio.no/git/?a=blobdiff_plain;f=case-study%2Fjdt-before%2Fui%2Forg%2Feclipse%2Fjdt%2Finternal%2Fui%2Fjavaeditor%2FJavaMoveLinesAction.java;fp=case-study%2Fjdt-before%2Fui%2Forg%2Feclipse%2Fjdt%2Finternal%2Fui%2Fjavaeditor%2FJavaMoveLinesAction.java;h=c7c164e28fc3466c3c1100e39cc2e8a71e255948;hb=1b2798f607d741df30e5197f427381cbff326adc;hp=0000000000000000000000000000000000000000;hpb=246231e4bd9b24345490f369747c0549ca308c4d;p=ifi-stolz-refaktor.git 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 index 00000000..c7c164e2 --- /dev/null +++ b/case-study/jdt-before/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaMoveLinesAction.java @@ -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; + + /** true 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.

Value {@value}

*/ + + /* state variables - define what this action does */ + + /** true if lines are shifted upwards, false otherwise. */ + private final boolean fUpwards; + /** true 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 ResourceAction constructor), or null if none + * @param upwards trueif the selected lines should be moved upwards, + * false if downwards + * @param copy if true, 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 selection is contained by the visible region of viewer. + * 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 selection's document. + * @return true, if selection is contained, false 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 + * selection. A line in the document is considered covered if + * selection comprises any characters on it, including the terminating delimiter. + *

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. + *

+ * + * @param document the document selection refers to + * @param selection a selection on document + * @param viewer the ISourceViewer displaying document + * @return a selection describing the range of lines (partially) covered by + * selection, 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 + * fUpwards is true, the line above selection + * is selected, otherwise the line below. + * + * @param document the document selection refers to + * @param selection the selection on document that will be moved. + * @return the region comprising the line that selection 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 null + * @return true if string contains only white space or is + * null, false 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()); + + } +}