/**
* Copyright (c) 2009, 2010 Mark Feber, MulgaSoft
*
* 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
*
*/
package com.mulgasoft.emacsplus.commands;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.Position;
import org.eclipse.ui.texteditor.ITextEditor;
import com.mulgasoft.emacsplus.EmacsPlusUtils;
import com.mulgasoft.emacsplus.IEmacsPlusCommandDefinitionIds;
import com.mulgasoft.emacsplus.MarkUtils;
/**
* Implements: transpose-paragraphs
*
* Interchange the current paragraph with the next one.
* With prefix argument ARG a non-zero integer, moves the current
* paragraph past ARG paragraphs, leaving point after the current paragraph.
* If ARG is positive, moves the current paragraph forwards
* If ARG is negative moves it backwards.
* If ARG is zero, exchanges the current paragraph with the one containing the mark.
*
* @author Mark Feber - initial API and implementation
*/
public class ParagraphTransposeHandler extends ParagraphHandler {
private final static String NOT_SET = "Mark_Not_Set"; //$NON-NLS-1$
private ParagraphForwardHandler ff = new ParagraphForwardHandler();
private ParagraphBackwardHandler bb = new ParagraphBackwardHandler();
/**
* @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#transform(ITextEditor, IDocument, ITextSelection, ExecutionEvent)
*
* @param editor
* @param document
* @param currentSelection
* @param event
* @return the new offset or -1
* @throws BadLocationException
*/
protected int transform(ITextEditor editor, IDocument document, ITextSelection currentSelection, ExecutionEvent event)
throws BadLocationException {
int result = NO_OFFSET;
if (getUniversalCount() < 0) {
try {
executeCommand(IEmacsPlusCommandDefinitionIds.BACKWARD_PARAGRAPH, null, editor);
transformAtPoint(editor,document,currentSelection,event);
// after transform, we're between the two paragraphs, so just move to the front of the line
result = document.getLineInformationOfOffset(getCurrentSelection(editor).getOffset()).getOffset();
} catch (Exception e) {
throw new BadLocationException();
}
} else if (getUniversalCount() == 0) {
result = transformAtPointAndMark(editor,document,currentSelection,event);
} else {
result = transformAtPoint(editor,document,currentSelection,event);
}
return result;
}
/**
* Swap paragraphs around the cursor"
* case 1: paragraph1 ^ paragraph2 -> paragraph2 ^ paragraph1
* case 2: paragraph^1 paragraph2 -> paragraph2 ^ paragraph1
*
* @param editor
* @param document
* @param currentSelection
* @param event
* @return the new offset after the swap
* @throws BadLocationException
*/
protected int transformAtPoint(ITextEditor editor, IDocument document, ITextSelection currentSelection, ExecutionEvent event)
throws BadLocationException {
int offset = getCursorOffset(editor);
Paragraph para1 = new Paragraph(-1,-1);
Paragraph para2 = new Paragraph(-1,-1);
getParagraphsAtPoint(editor, document, currentSelection, offset, para1, para2);
int result = swapParagraphs(document, para1, para2);
if (result == -1) {
// restore previous location
setCursorOffset(editor, offset);
throw new BadLocationException();
}
return result;
}
/**
* Swap paragraphs between point and mark
* Position cursor at beginning of non-marked paragraph
*
* @param editor
* @param document
* @param currentSelection
* @param event
* @return the new offset after the swap or NO_OFFSET
* @throws BadLocationException
*/
protected int transformAtPointAndMark(ITextEditor editor, IDocument document, ITextSelection currentSelection,
ExecutionEvent event) throws BadLocationException {
int result = NO_OFFSET;
int point = getCursorOffset(editor);
int mark = MarkUtils.getMark(editor);
if (mark == NO_OFFSET) {
EmacsPlusUtils.showMessage(editor, NOT_SET, true);
} else {
Paragraph para1 = new Paragraph(-1, -1);
Paragraph para2 = new Paragraph(-1, -1);
getParagraphsAtPointAndMark(editor, document, currentSelection, point, para1, para2);
Position p1 = null, p2 = null, mp = null;
try {
if (para1.isOk() && para2.isOk()) {
p1 = new Position(para1.getBegin(), para1.getLength());
p2 = new Position(para2.getBegin(), para2.getLength());
mp = new Position(mark);
document.addPosition(mp);
document.addPosition(p1);
document.addPosition(p2);
result = swapParagraphs(document, para1, para2);
}
if (result == NO_OFFSET) {
// restore previous location
setCursorOffset(editor, point);
throw new BadLocationException();
}
} finally {
if (p1 != null) {
// position cursor, so that repeat invocation swaps paragraphs back to original positions
mark = mp.getOffset();
if (MarkUtils.getMark(editor) == NO_OFFSET) { // Eclipse may have removed the mark
MarkUtils.setMark(editor, mark);
}
if (mark >= p1.getOffset() && mark <= p1.getOffset() + p1.getLength()) {
result = p2.getOffset();
} else {
result = p1.getOffset();
}
document.removePosition(mp);
document.removePosition(p1);
document.removePosition(p2);
}
}
}
return result;
}
private int swapParagraphs(IDocument document, Paragraph para1, Paragraph para2) throws BadLocationException {
int result = -1;
if (para1.isOk() && para2.isOk()) {
// we're cool, so swap
// check that we're not at a non-blank line (top) before adjusting line
int bline = document.getLineOfOffset(para1.getBegin());
if (isBlank(document,bline)) {
++bline;
}
// check that we're not at a non-blank line (bottom) before adjusting line
int eline = document.getLineOfOffset(para2.getEnd());
if (isBlank(document,eline)) {
--eline;
}
// get the text for paragraph 1
IRegion line1Begin = document.getLineInformation(bline);
IRegion lineEnd = document.getLineInformation(document.getLineOfOffset(para1.getEnd()) -1);
int para1len = lineEnd.getOffset() + lineEnd.getLength() - line1Begin.getOffset();
String para1text = document.get(line1Begin.getOffset(), para1len);
// get the text for paragraph 2
IRegion line2Begin = document.getLineInformation(document.getLineOfOffset(para2.getBegin()) + 1);
lineEnd = document.getLineInformation(eline);
int para2len = lineEnd.getOffset() + lineEnd.getLength() - line2Begin.getOffset();
String para2text = document.get(line2Begin.getOffset(), para2len);
// swap the text from bottom up
updateText(document,line2Begin.getOffset(), para2len, para1text);
updateText(document,line1Begin.getOffset(), para1len, para2text);
// cursor goes below swapped paragraphs
result = para2.getEnd();
}
return result;
}
private void getParagraphsAtPoint(ITextEditor editor, IDocument document, ITextSelection selection, int point, Paragraph para1, Paragraph para2) {
int end= ff.getParagraphOffset(editor, document, selection);
// update selection - move to beginning of selection/line
setSelection(editor, end, 0);
ITextSelection csel = getCurrentSelection(editor);
int begin = bb.getParagraphOffset(editor, document, csel);
if (begin < point) {
// in case 2
if (end != -1 && begin != -1) {
// then we started in mid paragraph
para1.setBegin(begin);
para1.setEnd(end);
// update selection - move to end of first paragraph
setSelection(editor, para1.getEnd(), 0);
csel = getCurrentSelection(editor);
para2.setEnd(ff.getParagraphOffset(editor, document, csel));
csel = getCurrentSelection(editor);
// update selection - move to beginning of selection/line
setSelection(editor, csel.getOffset(), 0);
para2.setBegin(bb.getParagraphOffset(editor, document, csel));
}
} else if (end != -1) {
// in case 1
para2.setBegin(begin);
para2.setEnd(end);
csel = getCurrentSelection(editor);
para1.setBegin(bb.getParagraphOffset(editor, document, csel));
csel = getCurrentSelection(editor);
para1.setEnd(ff.getParagraphOffset(editor, document, csel));
}
}
private void getParagraphsAtPointAndMark(ITextEditor editor, IDocument document, ITextSelection selection, int point, Paragraph para1, Paragraph para2) {
int mark = MarkUtils.getMark(editor);
ITextSelection csel = selection;
if (mark != point) {
// always start at top paragraph
if (mark < point) {
setSelection(editor,mark,0);
csel = getCurrentSelection(editor);
mark = point;
}
para1.setEnd(ff.getParagraphOffset(editor, document, csel));
// update selection - move to beginning of selection/line
setSelection(editor, para1.getEnd(), 0);
csel = getCurrentSelection(editor);
para1.setBegin(bb.getParagraphOffset(editor, document, csel));
setSelection(editor,mark,0);
csel = getCurrentSelection(editor);
para2.setEnd(ff.getParagraphOffset(editor, document, csel));
// update selection - move to beginning of selection/line
setSelection(editor, para2.getEnd(), 0);
csel = getCurrentSelection(editor);
para2.setBegin(bb.getParagraphOffset(editor, document, csel));
if (para1.equals(para2)) {
// disable transpose on identity
para1.setBegin(-1);
}
}
}
/**
* Force undo protect
* @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#undoProtect()
*/
protected boolean undoProtect() {
return true;
}
/**
* @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#isZero()
*/
protected boolean isZero() {
return true;
}
private class Paragraph {
/** The paragraph begin offset */
private int begin;
/** The paragraph end offset */
private int end;
/**
* Create a paragraph
*
* @param begin
* @param end
*/
public Paragraph(int begin, int end) {
this.begin = begin;
this.end = end;
}
public int getBegin() {
return begin;
}
private void setBegin(int begin) {
this.begin = begin;
}
public int getEnd() {
return end;
}
private void setEnd(int end) {
this.end = end;
}
public int getLength() {
return (isOk() ? end - begin : -1);
}
public boolean isOk() {
return begin != -1 && end != -1;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object o) {
boolean result = o == this;
if (!result && o instanceof Paragraph) {
result = ((Paragraph)o).getBegin() == begin && ((Paragraph)o).getEnd() == end;
}
return result;
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
int result = 17;
result = 31 * result + begin;
result = 31 * result + end;
return result;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "begin=" + begin + ", end=" + end; //$NON-NLS-1$ //$NON-NLS-2$;
}
}
}