KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > ui > javaeditor > JavaMoveLinesAction


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jdt.internal.ui.javaeditor;
12
13 import java.util.ResourceBundle JavaDoc;
14
15 import org.eclipse.core.runtime.Assert;
16
17 import org.eclipse.swt.custom.StyledText;
18 import org.eclipse.swt.graphics.Point;
19 import org.eclipse.swt.widgets.Event;
20
21 import org.eclipse.jface.text.BadLocationException;
22 import org.eclipse.jface.text.IDocument;
23 import org.eclipse.jface.text.IRegion;
24 import org.eclipse.jface.text.IRewriteTarget;
25 import org.eclipse.jface.text.ITextSelection;
26 import org.eclipse.jface.text.ITextViewer;
27 import org.eclipse.jface.text.ITextViewerExtension5;
28 import org.eclipse.jface.text.Region;
29 import org.eclipse.jface.text.TextSelection;
30 import org.eclipse.jface.text.TextUtilities;
31 import org.eclipse.jface.text.source.ILineRange;
32 import org.eclipse.jface.text.source.ISourceViewer;
33 import org.eclipse.jface.text.source.LineRange;
34
35 import org.eclipse.ui.IEditorInput;
36 import org.eclipse.ui.texteditor.IEditorStatusLine;
37 import org.eclipse.ui.texteditor.ITextEditor;
38 import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
39 import org.eclipse.ui.texteditor.TextEditorAction;
40
41 import org.eclipse.jdt.core.ICompilationUnit;
42 import org.eclipse.jdt.core.IJavaProject;
43
44 import org.eclipse.jdt.internal.ui.JavaPlugin;
45 import org.eclipse.jdt.internal.ui.javaeditor.IndentUtil.IndentResult;
46
47 /**
48  * Action for moving selected lines in a Java editor.
49  * @since 3.1
50  */

51 public class JavaMoveLinesAction extends TextEditorAction {
52
53     /**
54      * State shared by the Move / Copy lines action quadruple.
55      * @since 3.1
56      */

57     private static final class SharedState {
58         /** The compilation unit editor that all four actions operate on. */
59         public CompilationUnitEditor fEditor;
60         /**
61          * The indent token shared by all four actions.
62          */

63         public IndentResult fResult= null;
64         /**
65          * Set to true before modifying the document, to false after.
66          */

67         boolean fIsChanging= false;
68         
69         /** <code>true</code> if a compound move / copy is going on. */
70         private boolean fEditInProgress= false;
71         /** The exit strategy that will detect the ending of a compound edit */
72         private final CompoundEditExitStrategy fExitStrategy;
73         
74         public SharedState(CompilationUnitEditor editor) {
75             fEditor= editor;
76             fExitStrategy= new CompoundEditExitStrategy(new String JavaDoc[] {ITextEditorActionDefinitionIds.MOVE_LINES_UP, ITextEditorActionDefinitionIds.MOVE_LINES_DOWN, ITextEditorActionDefinitionIds.COPY_LINES_UP, ITextEditorActionDefinitionIds.COPY_LINES_DOWN});
77             fExitStrategy.addCompoundListener(new ICompoundEditListener() {
78                 public void endCompoundEdit() {
79                     SharedState.this.endCompoundEdit();
80                 }
81             });
82         }
83         
84         /**
85          * Ends the compound change.
86          */

87         public void beginCompoundEdit() {
88             if (fEditInProgress || fEditor == null)
89                 return;
90
91             fEditInProgress= true;
92
93             fExitStrategy.arm(fEditor.getViewer());
94
95             IRewriteTarget target= (IRewriteTarget)fEditor.getAdapter(IRewriteTarget.class);
96             if (target != null) {
97                 target.beginCompoundChange();
98             }
99         }
100         /**
101          * Ends the compound change.
102          */

103         public void endCompoundEdit() {
104             if (!fEditInProgress || fEditor == null)
105                 return;
106
107             fExitStrategy.disarm();
108
109             IRewriteTarget target= (IRewriteTarget)fEditor.getAdapter(IRewriteTarget.class);
110             if (target != null) {
111                 target.endCompoundChange();
112             }
113
114             fResult= null;
115             fEditInProgress= false;
116         }
117     }
118
119     /* keys */
120
121     /** Key for status message upon illegal move. <p>Value {@value}</p> */
122
123     /* state variables - define what this action does */
124
125     /** <code>true</code> if lines are shifted upwards, <code>false</code> otherwise. */
126     private final boolean fUpwards;
127     /** <code>true</code> if lines are to be copied instead of moved. */
128     private final boolean fCopy;
129     /** The shared state of the move/copy action quadruple. */
130     private final SharedState fSharedState;
131
132     /**
133      * Creates the quadruple of move and copy actions. The returned array contains
134      * the actions in the following order:
135      * [0] move up
136      * [1] move down
137      * [2] copy up (duplicate)
138      * [3] copy down (duplicate & select)
139      * @param bundle the resource bundle
140      * @param editor the editor
141      * @return the quadruple of actions
142      */

143     public static JavaMoveLinesAction[] createMoveCopyActionSet(ResourceBundle JavaDoc bundle, CompilationUnitEditor editor) {
144         SharedState state= new SharedState(editor);
145         JavaMoveLinesAction[] actions= new JavaMoveLinesAction[4];
146         actions[0]= new JavaMoveLinesAction(bundle, "Editor.MoveLinesUp.", true, false, state); //$NON-NLS-1$
147
actions[1]= new JavaMoveLinesAction(bundle, "Editor.MoveLinesDown.", false, false, state); //$NON-NLS-1$
148
actions[2]= new JavaMoveLinesAction(bundle, "Editor.CopyLineUp.", true, true, state); //$NON-NLS-1$
149
actions[3]= new JavaMoveLinesAction(bundle, "Editor.CopyLineDown.", false, true, state); //$NON-NLS-1$
150
return actions;
151     }
152     
153     /*
154      * @see org.eclipse.ui.texteditor.TextEditorAction#setEditor(org.eclipse.ui.texteditor.ITextEditor)
155      */

156     public void setEditor(ITextEditor editor) {
157         Assert.isTrue(editor instanceof CompilationUnitEditor);
158         super.setEditor(editor);
159         if (fSharedState != null)
160             fSharedState.fEditor= (CompilationUnitEditor) editor;
161     }
162
163     /**
164      * Creates and initializes the action for the given text editor.
165      * The action configures its visual representation from the given resource
166      * bundle.
167      *
168      * @param bundle the resource bundle
169      * @param prefix a prefix to be prepended to the various resource keys
170      * (described in <code>ResourceAction</code> constructor), or <code>null</code> if none
171      * @param upwards <code>true</code>if the selected lines should be moved upwards,
172      * <code>false</code> if downwards
173      * @param copy if <code>true</code>, the action will copy lines instead of moving them
174      * @param state the shared state
175      * @see TextEditorAction#TextEditorAction(ResourceBundle, String, ITextEditor)
176      */

177     private JavaMoveLinesAction(ResourceBundle JavaDoc bundle, String JavaDoc prefix, boolean upwards, boolean copy, SharedState state) {
178         super(bundle, prefix, state.fEditor);
179         fUpwards= upwards;
180         fCopy= copy;
181         fSharedState= state;
182         update();
183     }
184
185     /**
186      * Checks if <code>selection</code> is contained by the visible region of <code>viewer</code>.
187      * As a special case, a selection is considered contained even if it extends over the visible
188      * region, but the extension stays on a partially contained line and contains only white space.
189      *
190      * @param selection the selection to be checked
191      * @param viewer the viewer displaying a visible region of <code>selection</code>'s document.
192      * @return <code>true</code>, if <code>selection</code> is contained, <code>false</code> otherwise.
193      */

194     private boolean containedByVisibleRegion(ITextSelection selection, ISourceViewer viewer) {
195         int min= selection.getOffset();
196         int max= min + selection.getLength();
197         IDocument document= viewer.getDocument();
198
199         IRegion visible;
200         if (viewer instanceof ITextViewerExtension5)
201             visible= ((ITextViewerExtension5) viewer).getModelCoverage();
202         else
203             visible= viewer.getVisibleRegion();
204
205         int visOffset= visible.getOffset();
206         try {
207             if (visOffset > min) {
208                 if (document.getLineOfOffset(visOffset) != selection.getStartLine())
209                     return false;
210                 if (!isWhitespace(document.get(min, visOffset - min))) {
211                     showStatus();
212                     return false;
213                 }
214             }
215             int visEnd= visOffset + visible.getLength();
216             if (visEnd < max) {
217                 if (document.getLineOfOffset(visEnd) != selection.getEndLine())
218                     return false;
219                 if (!isWhitespace(document.get(visEnd, max - visEnd))) {
220                     showStatus();
221                     return false;
222                 }
223             }
224             return true;
225         } catch (BadLocationException e) {
226         }
227         return false;
228     }
229
230     /**
231      * Given a selection on a document, computes the lines fully or partially covered by
232      * <code>selection</code>. A line in the document is considered covered if
233      * <code>selection</code> comprises any characters on it, including the terminating delimiter.
234      * <p>Note that the last line in a selection is not considered covered if the selection only
235      * comprises the line delimiter at its beginning (that is considered part of the second last
236      * line).
237      * As a special case, if the selection is empty, a line is considered covered if the caret is
238      * at any position in the line, including between the delimiter and the start of the line. The
239      * line containing the delimiter is not considered covered in that case.
240      * </p>
241      *
242      * @param document the document <code>selection</code> refers to
243      * @param selection a selection on <code>document</code>
244      * @param viewer the <code>ISourceViewer</code> displaying <code>document</code>
245      * @return a selection describing the range of lines (partially) covered by
246      * <code>selection</code>, without any terminating line delimiters
247      * @throws BadLocationException if the selection is out of bounds (when the underlying document has changed during the call)
248      */

249     private ITextSelection getMovingSelection(IDocument document, ITextSelection selection, ISourceViewer viewer) throws BadLocationException {
250         int low= document.getLineOffset(selection.getStartLine());
251         int endLine= selection.getEndLine();
252         int high= document.getLineOffset(endLine) + document.getLineLength(endLine);
253
254         // get everything up to last line without its delimiter
255
String JavaDoc delim= document.getLineDelimiter(endLine);
256         if (delim != null)
257             high -= delim.length();
258
259         return new TextSelection(document, low, high - low);
260     }
261
262     /**
263      * Computes the region of the skipped line given the text block to be moved. If
264      * <code>fUpwards</code> is <code>true</code>, the line above <code>selection</code>
265      * is selected, otherwise the line below.
266      *
267      * @param document the document <code>selection</code> refers to
268      * @param selection the selection on <code>document</code> that will be moved.
269      * @return the region comprising the line that <code>selection</code> will be moved over, without its terminating delimiter.
270      */

271     private ITextSelection getSkippedLine(IDocument document, ITextSelection selection) {
272         int skippedLineN= (fUpwards ? selection.getStartLine() - 1 : selection.getEndLine() + 1);
273         if (skippedLineN > document.getNumberOfLines() || (!fCopy && (skippedLineN < 0 || skippedLineN == document.getNumberOfLines())))
274             return null;
275         try {
276             if (fCopy && skippedLineN == -1)
277                 skippedLineN= 0;
278             IRegion line= document.getLineInformation(skippedLineN);
279             return new TextSelection(document, line.getOffset(), line.getLength());
280         } catch (BadLocationException e) {
281             // only happens on concurrent modifications
282
return null;
283         }
284     }
285
286     /**
287      * Checks for white space in a string.
288      *
289      * @param string the string to be checked or <code>null</code>
290      * @return <code>true</code> if <code>string</code> contains only white space or is
291      * <code>null</code>, <code>false</code> otherwise
292      */

293     private boolean isWhitespace(String JavaDoc string) {
294         return string == null ? true : string.trim().length() == 0;
295     }
296
297     /*
298      * @see org.eclipse.jface.action.IAction#run()
299      */

300     public void runWithEvent(Event event) {
301
302         // get involved objects
303
if (fSharedState.fEditor == null)
304             return;
305
306         if (!validateEditorInputState())
307             return;
308
309         ISourceViewer viewer= fSharedState.fEditor.getViewer();
310         if (viewer == null)
311             return;
312
313         IDocument document= viewer.getDocument();
314         if (document == null)
315             return;
316
317         StyledText widget= viewer.getTextWidget();
318         if (widget == null)
319             return;
320
321         // get selection
322
Point p= viewer.getSelectedRange();
323         if (p == null)
324             return;
325
326         ITextSelection sel= new TextSelection(document, p.x, p.y);
327
328         ITextSelection skippedLine= getSkippedLine(document, sel);
329         if (skippedLine == null)
330             return;
331
332         try {
333
334             ITextSelection movingArea= getMovingSelection(document, sel, viewer);
335
336             // if either the skipped line or the moving lines are outside the widget's
337
// visible area, bail out
338
if (!containedByVisibleRegion(movingArea, viewer) || !containedByVisibleRegion(skippedLine, viewer))
339                 return;
340
341             // get the content to be moved around: the moving (selected) area and the skipped line
342
String JavaDoc moving= movingArea.getText();
343             String JavaDoc skipped= skippedLine.getText();
344             if (moving == null || skipped == null || document.getLength() == 0)
345                 return;
346
347             String JavaDoc delim;
348             String JavaDoc insertion;
349             int offset;
350             if (fUpwards) {
351                 delim= document.getLineDelimiter(skippedLine.getEndLine());
352                 if (fCopy) {
353                     delim= TextUtilities.getDefaultLineDelimiter(document);
354                     insertion= moving + delim;
355                     offset= movingArea.getOffset();
356                 } else {
357                     Assert.isNotNull(delim);
358                     insertion= moving + delim + skipped;
359                     offset= skippedLine.getOffset();
360                 }
361             } else {
362                 delim= document.getLineDelimiter(movingArea.getEndLine());
363                 if (fCopy) {
364                     if (delim == null) {
365                         delim= TextUtilities.getDefaultLineDelimiter(document);
366                         insertion= delim + moving;
367                     } else
368                         insertion= moving + delim;
369                     offset= skippedLine.getOffset();
370                 } else {
371                     Assert.isNotNull(delim);
372                     insertion= skipped + delim + moving;
373                     offset= movingArea.getOffset();
374                 }
375             }
376             int lenght= fCopy ? 0 : insertion.length();
377
378             // modify the document
379
ILineRange selectionBefore= getLineRange(document, movingArea);
380             
381             if (fCopy)
382                 fSharedState.endCompoundEdit();
383             fSharedState.beginCompoundEdit();
384             fSharedState.fIsChanging= true;
385             
386             document.replace(offset, lenght, insertion);
387             
388             ILineRange selectionAfter;
389             if (fUpwards && fCopy)
390                 selectionAfter= selectionBefore;
391             else if (fUpwards)
392                 selectionAfter= new LineRange(selectionBefore.getStartLine() - 1, selectionBefore.getNumberOfLines());
393             else if (fCopy)
394                 selectionAfter= new LineRange(selectionBefore.getStartLine() + selectionBefore.getNumberOfLines(), selectionBefore.getNumberOfLines());
395             else
396                 selectionAfter= new LineRange(selectionBefore.getStartLine() + 1, selectionBefore.getNumberOfLines());
397             
398             fSharedState.fResult= IndentUtil.indentLines(document, selectionAfter, getProject(), fSharedState.fResult);
399             
400             // move the selection along
401
IRegion region= getRegion(document, selectionAfter);
402             selectAndReveal(viewer, region.getOffset(), region.getLength());
403             
404         } catch (BadLocationException x) {
405             // won't happen without concurrent modification - bail out
406
return;
407         } finally {
408             fSharedState.fIsChanging= false;
409             if (fCopy)
410                 fSharedState.endCompoundEdit();
411         }
412     }
413
414     private IJavaProject getProject() {
415         IEditorInput editorInput= fSharedState.fEditor.getEditorInput();
416         ICompilationUnit unit= JavaPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editorInput);
417         if (unit != null)
418             return unit.getJavaProject();
419         return null;
420     }
421
422     private ILineRange getLineRange(IDocument document, ITextSelection selection) throws BadLocationException {
423         final int offset= selection.getOffset();
424         int startLine= document.getLineOfOffset(offset);
425         int endOffset= offset + selection.getLength();
426         int endLine= document.getLineOfOffset(endOffset);
427         final int nLines= endLine - startLine + 1;
428         return new LineRange(startLine, nLines);
429     }
430     
431     private IRegion getRegion(IDocument document, ILineRange lineRange) throws BadLocationException {
432         final int startLine= lineRange.getStartLine();
433         int offset= document.getLineOffset(startLine);
434         final int numberOfLines= lineRange.getNumberOfLines();
435         if (numberOfLines < 1)
436             return new Region(offset, 0);
437         int endLine= startLine + numberOfLines - 1;
438         int endOffset= document.getLineOffset(endLine) + document.getLineLength(endLine);
439         return new Region(offset, endOffset - offset);
440     }
441
442     /**
443      * Performs similar to AbstractTextEditor.selectAndReveal, but does not update
444      * the viewers highlight area.
445      *
446      * @param viewer the viewer that we want to select on
447      * @param offset the offset of the selection
448      * @param length the length of the selection
449      */

450     private void selectAndReveal(ITextViewer viewer, int offset, int length) {
451         // invert selection to avoid jumping to the end of the selection in st.showSelection()
452
viewer.setSelectedRange(offset + length, -length);
453         //viewer.revealRange(offset, length); // will trigger jumping
454
StyledText st= viewer.getTextWidget();
455         if (st != null)
456             st.showSelection(); // only minimal scrolling
457
}
458
459     /**
460      * Displays information in the status line why a line move is not possible
461      */

462     private void showStatus() {
463         IEditorStatusLine status= (IEditorStatusLine) fSharedState.fEditor.getAdapter(IEditorStatusLine.class);
464         if (status == null)
465             return;
466         status.setMessage(false, JavaEditorMessages.Editor_MoveLines_IllegalMove_status, null);
467     }
468
469     /*
470      * @see org.eclipse.ui.texteditor.IUpdate#update()
471      */

472     public void update() {
473         super.update();
474
475         if (isEnabled())
476             setEnabled(canModifyEditor());
477
478     }
479 }
480
Popular Tags