KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > texteditor > MoveLinesAction


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.ui.texteditor;
12
13 import java.util.ResourceBundle JavaDoc;
14
15 import org.eclipse.swt.custom.StyledText;
16 import org.eclipse.swt.graphics.Point;
17 import org.eclipse.swt.widgets.Event;
18
19 import org.eclipse.core.runtime.Assert;
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.TextSelection;
29 import org.eclipse.jface.text.TextUtilities;
30 import org.eclipse.jface.text.source.ISourceViewer;
31
32 import org.eclipse.ui.internal.texteditor.CompoundEditExitStrategy;
33 import org.eclipse.ui.internal.texteditor.ICompoundEditListener;
34
35 /**
36  * Action for moving selected lines in an editor.
37  * @since 3.0
38  */

39 public class MoveLinesAction extends TextEditorAction {
40
41     /* configuration variables - define what this action does */
42
43     /** <code>true</code> if lines are shifted upwards, <code>false</code> otherwise. */
44     private final boolean fUpwards;
45     /** <code>true</code> if lines are to be copied instead of moved. */
46     private final boolean fCopy;
47     /** The editor we are working on. */
48     private final AbstractTextEditor fEditor;
49
50     /* compound members of this action */
51
52     /**
53      * The exit strategy that will detect the ending of a compound edit.
54      * @since 3.1
55      */

56     private final CompoundEditExitStrategy fStrategy;
57
58     /* process variables - may change in every run() */
59
60     /**
61      * Set to <code>true</code> by <code>getMovingSelection</code> if the resulting selection
62      * should include the last delimiter.
63      */

64     private boolean fAddDelimiter;
65     /** <code>true</code> if a compound move / copy is going on. */
66     private boolean fEditInProgress= false;
67
68     /**
69      * Creates and initializes the action for the given text editor.
70      * The action configures its visual representation from the given resource
71      * bundle.
72      *
73      * @param bundle the resource bundle
74      * @param prefix a prefix to be prepended to the various resource keys
75      * (described in <code>ResourceAction</code> constructor), or <code>null</code> if none
76      * @param editor the text editor
77      * @param upwards <code>true</code>if the selected lines should be moved upwards,
78      * <code>false</code> if downwards
79      * @param copy if <code>true</code>, the action will copy lines instead of moving them
80      * @see TextEditorAction#TextEditorAction(ResourceBundle, String, ITextEditor)
81      */

82     public MoveLinesAction(ResourceBundle JavaDoc bundle, String JavaDoc prefix, AbstractTextEditor editor, boolean upwards, boolean copy) {
83         super(bundle, prefix, editor);
84         fEditor= editor;
85         fUpwards= upwards;
86         fCopy= copy;
87         String JavaDoc[] commandIds= copy ? new String JavaDoc[] {ITextEditorActionDefinitionIds.COPY_LINES_UP, ITextEditorActionDefinitionIds.COPY_LINES_DOWN } : new String JavaDoc[] {ITextEditorActionDefinitionIds.MOVE_LINES_UP, ITextEditorActionDefinitionIds.MOVE_LINES_DOWN };
88         fStrategy= new CompoundEditExitStrategy(commandIds);
89         fStrategy.addCompoundListener(new ICompoundEditListener() {
90             public void endCompoundEdit() {
91                 MoveLinesAction.this.endCompoundEdit();
92             }
93         });
94         update();
95     }
96
97     /**
98      * Ends the compound change.
99      */

100     private void beginCompoundEdit() {
101         if (fEditInProgress || fEditor == null)
102             return;
103
104         fEditInProgress= true;
105
106         fStrategy.arm(fEditor.getSourceViewer());
107
108         IRewriteTarget target= (IRewriteTarget)fEditor.getAdapter(IRewriteTarget.class);
109         if (target != null) {
110             target.beginCompoundChange();
111         }
112     }
113
114     /**
115      * Checks if <code>selection</code> is contained by the visible region of <code>viewer</code>.
116      * As a special case, a selection is considered contained even if it extends over the visible
117      * region, but the extension stays on a partially contained line and contains only white space.
118      *
119      * @param selection the selection to be checked
120      * @param viewer the viewer displaying a visible region of <code>selection</code>'s document.
121      * @return <code>true</code>, if <code>selection</code> is contained, <code>false</code> otherwise.
122      */

123     private boolean containedByVisibleRegion(ITextSelection selection, ISourceViewer viewer) {
124         int min= selection.getOffset();
125         int max= min + selection.getLength();
126         IDocument document= viewer.getDocument();
127
128         IRegion visible;
129         if (viewer instanceof ITextViewerExtension5)
130             visible= ((ITextViewerExtension5) viewer).getModelCoverage();
131         else
132             visible= viewer.getVisibleRegion();
133
134         int visOffset= visible.getOffset();
135         try {
136             if (visOffset > min) {
137                 if (document.getLineOfOffset(visOffset) != selection.getStartLine())
138                     return false;
139                 if (!isWhitespace(document.get(min, visOffset - min))) {
140                     showStatus();
141                     return false;
142                 }
143             }
144             int visEnd= visOffset + visible.getLength();
145             if (visEnd < max) {
146                 if (document.getLineOfOffset(visEnd) != selection.getEndLine())
147                     return false;
148                 if (!isWhitespace(document.get(visEnd, max - visEnd))) {
149                     showStatus();
150                     return false;
151                 }
152             }
153             return true;
154         } catch (BadLocationException e) {
155         }
156         return false;
157     }
158
159     /**
160      * Ends the compound change.
161      */

162     private void endCompoundEdit() {
163         if (!fEditInProgress || fEditor == null)
164             return;
165
166         IRewriteTarget target= (IRewriteTarget)fEditor.getAdapter(IRewriteTarget.class);
167         if (target != null) {
168             target.endCompoundChange();
169         }
170
171         fEditInProgress= false;
172     }
173
174     /**
175      * Given a selection on a document, computes the lines fully or partially covered by
176      * <code>selection</code>. A line in the document is considered covered if
177      * <code>selection</code> comprises any characters on it, including the terminating delimiter.
178      * <p>Note that the last line in a selection is not considered covered if the selection only
179      * comprises the line delimiter at its beginning (that is considered part of the second last
180      * line).
181      * As a special case, if the selection is empty, a line is considered covered if the caret is
182      * at any position in the line, including between the delimiter and the start of the line. The
183      * line containing the delimiter is not considered covered in that case.
184      * </p>
185      *
186      * @param document the document <code>selection</code> refers to
187      * @param selection a selection on <code>document</code>
188      * @param viewer the <code>ISourceViewer</code> displaying <code>document</code>
189      * @return a selection describing the range of lines (partially) covered by
190      * <code>selection</code>, without any terminating line delimiters
191      * @throws BadLocationException if the selection is out of bounds (when the underlying document has changed during the call)
192      */

193     private ITextSelection getMovingSelection(IDocument document, ITextSelection selection, ISourceViewer viewer) throws BadLocationException {
194         int low= document.getLineOffset(selection.getStartLine());
195         int endLine= selection.getEndLine();
196         int high= document.getLineOffset(endLine) + document.getLineLength(endLine);
197
198         // get everything up to last line without its delimiter
199
String JavaDoc delim= document.getLineDelimiter(endLine);
200         if (delim != null)
201             high -= delim.length();
202
203         // the new selection will cover the entire lines being moved, except for the last line's
204
// delimiter. The exception to this rule is an empty last line, which will stay covered
205
// including its delimiter
206
if (delim != null && document.getLineLength(endLine) == delim.length())
207             fAddDelimiter= true;
208         else
209             fAddDelimiter= false;
210
211         return new TextSelection(document, low, high - low);
212     }
213
214     /**
215      * Computes the region of the skipped line given the text block to be moved. If
216      * <code>fUpwards</code> is <code>true</code>, the line above <code>selection</code>
217      * is selected, otherwise the line below.
218      *
219      * @param document the document <code>selection</code> refers to
220      * @param selection the selection on <code>document</code> that will be moved.
221      * @return the region comprising the line that <code>selection</code> will be moved over, without its terminating delimiter.
222      */

223     private ITextSelection getSkippedLine(IDocument document, ITextSelection selection) {
224         int skippedLineN= (fUpwards ? selection.getStartLine() - 1 : selection.getEndLine() + 1);
225         if (skippedLineN > document.getNumberOfLines() || (!fCopy && (skippedLineN < 0 || skippedLineN == document.getNumberOfLines())))
226             return null;
227         try {
228             if (fCopy && skippedLineN == -1)
229                 skippedLineN= 0;
230             IRegion line= document.getLineInformation(skippedLineN);
231             return new TextSelection(document, line.getOffset(), line.getLength());
232         } catch (BadLocationException e) {
233             // only happens on concurrent modifications
234
return null;
235         }
236     }
237
238     /**
239      * Checks for white space in a string.
240      *
241      * @param string the string to be checked or <code>null</code>
242      * @return <code>true</code> if <code>string</code> contains only white space or is
243      * <code>null</code>, <code>false</code> otherwise
244      */

245     private boolean isWhitespace(String JavaDoc string) {
246         return string == null ? true : string.trim().length() == 0;
247     }
248
249     /*
250      * @see org.eclipse.jface.action.IAction#run()
251      */

252     public void runWithEvent(Event event) {
253
254         // get involved objects
255
if (fEditor == null)
256             return;
257
258         if (!validateEditorInputState())
259             return;
260
261         ISourceViewer viewer= fEditor.getSourceViewer();
262         if (viewer == null)
263             return;
264
265         IDocument document= viewer.getDocument();
266         if (document == null)
267             return;
268
269         StyledText widget= viewer.getTextWidget();
270         if (widget == null)
271             return;
272
273         // get selection
274
Point p= viewer.getSelectedRange();
275         if (p == null)
276             return;
277
278         ITextSelection sel= new TextSelection(document, p.x, p.y);
279
280         ITextSelection skippedLine= getSkippedLine(document, sel);
281         if (skippedLine == null)
282             return;
283
284         try {
285
286             ITextSelection movingArea= getMovingSelection(document, sel, viewer);
287
288             // if either the skipped line or the moving lines are outside the widget's
289
// visible area, bail out
290
if (!containedByVisibleRegion(movingArea, viewer) || !containedByVisibleRegion(skippedLine, viewer))
291                 return;
292
293             // get the content to be moved around: the moving (selected) area and the skipped line
294
String JavaDoc moving= movingArea.getText();
295             String JavaDoc skipped= skippedLine.getText();
296             if (moving == null || skipped == null || document.getLength() == 0)
297                 return;
298
299             String JavaDoc delim;
300             String JavaDoc insertion;
301             int offset, deviation;
302             if (fUpwards) {
303                 delim= document.getLineDelimiter(skippedLine.getEndLine());
304                 if (fCopy) {
305                     delim= TextUtilities.getDefaultLineDelimiter(document);
306                     insertion= moving + delim;
307                     offset= movingArea.getOffset();
308                     deviation= 0;
309                 } else {
310                     Assert.isNotNull(delim);
311                     insertion= moving + delim + skipped;
312                     offset= skippedLine.getOffset();
313                     deviation= -skippedLine.getLength() - delim.length();
314                 }
315             } else {
316                 delim= document.getLineDelimiter(movingArea.getEndLine());
317                 if (fCopy) {
318                     if (delim == null) {
319                         delim= TextUtilities.getDefaultLineDelimiter(document);
320                         insertion= delim + moving;
321                     } else {
322                         insertion= moving + delim;
323                     }
324                     offset= skippedLine.getOffset();
325                     deviation= movingArea.getLength() + delim.length();
326                 } else {
327                     Assert.isNotNull(delim);
328                     insertion= skipped + delim + moving;
329                     offset= movingArea.getOffset();
330                     deviation= skipped.length() + delim.length();
331                 }
332             }
333
334             // modify the document
335
beginCompoundEdit();
336             if (fCopy) {
337 // fDescription= new EditDescription(offset, 0, insertion.length());
338
document.replace(offset, 0, insertion);
339             } else {
340 // fDescription= new EditDescription(offset, insertion.length(), insertion.length());
341
document.replace(offset, insertion.length(), insertion);
342             }
343
344             // move the selection along
345
int selOffset= movingArea.getOffset() + deviation;
346             int selLength= movingArea.getLength() + (fAddDelimiter ? delim.length() : 0);
347             if (! (viewer instanceof ITextViewerExtension5))
348                 selLength= Math.min(selLength, viewer.getVisibleRegion().getOffset() + viewer.getVisibleRegion().getLength() - selOffset);
349             else {
350                 // TODO need to check what is necessary in the projection case
351
}
352             selectAndReveal(viewer, selOffset, selLength);
353         } catch (BadLocationException x) {
354             // won't happen without concurrent modification - bail out
355
return;
356         }
357     }
358
359     /**
360      * Performs similar to AbstractTextEditor.selectAndReveal, but does not update
361      * the viewers highlight area.
362      *
363      * @param viewer the viewer that we want to select on
364      * @param offset the offset of the selection
365      * @param length the length of the selection
366      */

367     private void selectAndReveal(ITextViewer viewer, int offset, int length) {
368         // invert selection to avoid jumping to the end of the selection in st.showSelection()
369
viewer.setSelectedRange(offset + length, -length);
370         //viewer.revealRange(offset, length); // will trigger jumping
371
StyledText st= viewer.getTextWidget();
372         if (st != null)
373             st.showSelection(); // only minimal scrolling
374
}
375
376     /**
377      * Displays information in the status line why a line move is not possible
378      */

379     private void showStatus() {
380         IEditorStatusLine status= (IEditorStatusLine) fEditor.getAdapter(IEditorStatusLine.class);
381         if (status == null)
382             return;
383         status.setMessage(false, EditorMessages.Editor_MoveLines_IllegalMove_status, null);
384     }
385
386     /*
387      * @see org.eclipse.ui.texteditor.IUpdate#update()
388      */

389     public void update() {
390         super.update();
391
392         if (isEnabled())
393             setEnabled(canModifyEditor());
394
395     }
396 }
397
Popular Tags