Package org.eclipse.ui.texteditor

Source Code of org.eclipse.ui.texteditor.MoveLinesAction

/*******************************************************************************
* Copyright (c) 2000, 2009 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.ui.texteditor;

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.TextSelection;
import org.eclipse.jface.text.TextUtilities;

import org.eclipse.ui.internal.texteditor.CompoundEditExitStrategy;
import org.eclipse.ui.internal.texteditor.ICompoundEditListener;

/**
* Action for moving selected lines in an editor.
* @since 3.0
*/
public class MoveLinesAction extends TextEditorAction {

  /* configuration 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 text viewer we are working on.
   * @since 3.5
   */
  private ITextViewer fTextViewer;

  /* compound members of this action */

  /**
   * The exit strategy that will detect the ending of a compound edit.
   * @since 3.1
   */
  private final CompoundEditExitStrategy fStrategy;

  /* process variables - may change in every run() */

  /**
   * Set to <code>true</code> by <code>getMovingSelection</code> if the resulting selection
   * should include the last delimiter.
   */
  private boolean fAddDelimiter;
  /** <code>true</code> if a compound move / copy is going on. */
  private boolean fEditInProgress= false;

  /**
   * 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 editor the text editor
   * @param textViewer the text viewer
   * @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
   * @see TextEditorAction#TextEditorAction(ResourceBundle, String, ITextEditor)
   * @since 3.5
   */
  public MoveLinesAction(ResourceBundle bundle, String prefix, ITextEditor editor, ITextViewer textViewer, boolean upwards, boolean copy) {
    super(bundle, prefix, editor);
    fTextViewer= textViewer;
    fUpwards= upwards;
    fCopy= copy;
    String[] commandIds= copy ? new String[] {ITextEditorActionDefinitionIds.COPY_LINES_UP, ITextEditorActionDefinitionIds.COPY_LINES_DOWN } : new String[] {ITextEditorActionDefinitionIds.MOVE_LINES_UP, ITextEditorActionDefinitionIds.MOVE_LINES_DOWN };
    fStrategy= new CompoundEditExitStrategy(commandIds);
    fStrategy.addCompoundListener(new ICompoundEditListener() {
      public void endCompoundEdit() {
        MoveLinesAction.this.endCompoundEdit();
      }
    });
    update();
  }

  /**
   * 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 editor the text editor
   * @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
   * @see TextEditorAction#TextEditorAction(ResourceBundle, String, ITextEditor)
   * @deprecated As of 3.5, replaced by
   *             {@link #MoveLinesAction(ResourceBundle, String, ITextEditor, ITextViewer, boolean, boolean)}
   */
  public MoveLinesAction(ResourceBundle bundle, String prefix, AbstractTextEditor editor, boolean upwards, boolean copy) {
    this(bundle, prefix, editor, editor != null ? editor.getSourceViewer() : null, upwards, copy);
  }

  /**
   * Ends the compound change.
   */
  private void beginCompoundEdit() {
    ITextEditor editor= getTextEditor();
    if (fEditInProgress || fTextViewer == null || editor == null)
      return;

    fEditInProgress= true;

    fStrategy.arm(fTextViewer);

    IRewriteTarget target= (IRewriteTarget)editor.getAdapter(IRewriteTarget.class);
    if (target != null) {
      target.beginCompoundChange();
    }
  }

  /**
   * 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, ITextViewer 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;
  }

  /**
   * Ends the compound change.
   */
  private void endCompoundEdit() {
    ITextEditor editor= getTextEditor();
    if (!fEditInProgress || editor == null)
      return;

    IRewriteTarget target= (IRewriteTarget)editor.getAdapter(IRewriteTarget.class);
    if (target != null) {
      target.endCompoundChange();
    }

    fEditInProgress= 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, ITextViewer 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();

    // the new selection will cover the entire lines being moved, except for the last line's
    // delimiter. The exception to this rule is an empty last line, which will stay covered
    // including its delimiter
    if (delim != null && document.getLineLength(endLine) == delim.length())
      fAddDelimiter= true;
    else
      fAddDelimiter= false;

    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()
   */
  public void runWithEvent(Event event) {
    if (fTextViewer == null)
      return;

    if (!validateEditorInputState())
      return;

    // get involved objects

    IDocument document= fTextViewer.getDocument();
    if (document == null)
      return;

    StyledText widget= fTextViewer.getTextWidget();
    if (widget == null)
      return;

    // get selection
    ITextSelection sel= (ITextSelection) fTextViewer.getSelectionProvider().getSelection();
    if (sel.isEmpty())
      return;

    ITextSelection skippedLine= getSkippedLine(document, sel);
    if (skippedLine == null)
      return;

    try {

      ITextSelection movingArea= getMovingSelection(document, sel, fTextViewer);

      // if either the skipped line or the moving lines are outside the widget's
      // visible area, bail out
      if (!containedByVisibleRegion(movingArea, fTextViewer) || !containedByVisibleRegion(skippedLine, fTextViewer))
        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, deviation;
      if (fUpwards) {
        delim= document.getLineDelimiter(skippedLine.getEndLine());
        if (fCopy) {
          delim= TextUtilities.getDefaultLineDelimiter(document);
          insertion= moving + delim;
          offset= movingArea.getOffset();
          deviation= 0;
        } else {
          Assert.isNotNull(delim);
          insertion= moving + delim + skipped;
          offset= skippedLine.getOffset();
          deviation= -skippedLine.getLength() - delim.length();
        }
      } 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();
          deviation= movingArea.getLength() + delim.length();
        } else {
          Assert.isNotNull(delim);
          insertion= skipped + delim + moving;
          offset= movingArea.getOffset();
          deviation= skipped.length() + delim.length();
        }
      }

      // modify the document
      beginCompoundEdit();
      if (fCopy) {
//        fDescription= new EditDescription(offset, 0, insertion.length());
        document.replace(offset, 0, insertion);
      } else {
//        fDescription= new EditDescription(offset, insertion.length(), insertion.length());
        document.replace(offset, insertion.length(), insertion);
      }

      // move the selection along
      int selOffset= movingArea.getOffset() + deviation;
      int selLength= movingArea.getLength() + (fAddDelimiter ? delim.length() : 0);
      if (! (fTextViewer instanceof ITextViewerExtension5))
        selLength= Math.min(selLength, fTextViewer.getVisibleRegion().getOffset() + fTextViewer.getVisibleRegion().getLength() - selOffset);
      else {
        // TODO need to check what is necessary in the projection case
      }
      selectAndReveal(fTextViewer, selOffset, selLength);
    } catch (BadLocationException x) {
      // won't happen without concurrent modification - bail out
      return;
    }
  }

  /**
   * 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() {
    ITextEditor editor= getTextEditor();
    if (editor == null)
      return;

    IEditorStatusLine status= (IEditorStatusLine)editor.getAdapter(IEditorStatusLine.class);
    if (status == null)
      return;
    status.setMessage(false, EditorMessages.Editor_MoveLines_IllegalMove_status, null);
  }

  /*
   * @see org.eclipse.ui.texteditor.TextEditorAction#setEditor(org.eclipse.ui.texteditor.ITextEditor)
   * @since 3.5
   */
  public void setEditor(ITextEditor editor) {
    ITextEditor currentEditor= getTextEditor();
    if (currentEditor != editor && currentEditor != null && editor != null) {
      if (editor instanceof AbstractTextEditor)
        fTextViewer= ((AbstractTextEditor)editor).getSourceViewer();
      else
        fTextViewer= null;
    }
    super.setEditor(editor);
  }

  /*
   * @see org.eclipse.ui.texteditor.IUpdate#update()
   */
  public void update() {
    super.update();

    if (isEnabled())
      setEnabled(canModifyEditor() && fTextViewer != null);

  }
}
TOP

Related Classes of org.eclipse.ui.texteditor.MoveLinesAction

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.