/**
* 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.ITextSelection;
import org.eclipse.ui.texteditor.ITextEditor;
import com.mulgasoft.emacsplus.EmacsPlusUtils;
import com.mulgasoft.emacsplus.IEmacsPlusCommandDefinitionIds;
import com.mulgasoft.emacsplus.MarkUtils;
import org.eclipse.jface.text.Position;
/**
* Interchange balanced expressions around point, leaving point at end of them.
*
* @author Mark Feber - initial API and implementation
*/
public class TransposeSexpHandler extends EmacsPlusCmdHandler {
private final static String UNBALANCED = "Sexp_Unbalanced"; //$NON-NLS-1$
private final static String NOT_SET = "Mark_Not_Set"; //$NON-NLS-1$
private SexpForwardHandler foresexp = new SexpForwardHandler();
private SexpBackwardHandler backsexp = new SexpBackwardHandler();
/**
* @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#transform(ITextEditor, IDocument, ITextSelection, ExecutionEvent)
*/
@Override
protected int transform(ITextEditor editor, IDocument document, ITextSelection currentSelection,
ExecutionEvent event) throws BadLocationException {
try {
return transform(editor,document,currentSelection,event,false);
} catch (BadLocationException e) {
EmacsPlusUtils.showMessage(editor, UNBALANCED, true);
throw e;
}
}
/**
* Swap sexps (or words)
*
* @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, boolean wordp) throws BadLocationException {
int result = NO_OFFSET;
// clear any selection before embarking on the kill as depending on the selection, it's offset may not be the cursor offset
ITextSelection sexpSelection = MarkUtils.setSelection(editor, getCursorOffset(editor), 0);
int offset = sexpSelection.getOffset();
if (getUniversalCount() < 0) {
try {
executeCommand(IEmacsPlusCommandDefinitionIds.BACKWARD_SEXP, null, editor);
ITextSelection csel = getCurrentSelection(editor);
// make sure the backward movement accomplished something
if (offset != csel.getOffset()) {
result = transformAtPoint(editor,document,csel,event,wordp);
// after transform, we're between the two sexps, so just get the correct offset
result = getCurrentSelection(editor).getOffset();
}
} catch (Exception e) {
throw new BadLocationException();
}
} else if (getUniversalCount() == 0) {
result = transformAtPointAndMark(editor,document,sexpSelection,event,wordp);
} else {
result = transformAtPoint(editor,document,sexpSelection,event,wordp);
}
return result;
}
/**
* Swap sexps (or words) around the cursor"
* - sexp1 ^ sexp2 -> sexp2 ^ sexp1
* - sexp^1 sexp2 -> sexp2 ^ sexp1
*
* @param editor
* @param document
* @param currentSelection
* @param event
* @param wordp
* @return the offset after the swap
* @throws BadLocationException
*/
protected int transformAtPoint(ITextEditor editor, IDocument document, ITextSelection currentSelection, ExecutionEvent event, boolean wordp)
throws BadLocationException {
int result = NO_OFFSET;
Sexps sexps = getSexpsAtPoint(editor,document,currentSelection,wordp);
if ((result = swapSexps(editor,document,sexps)) == NO_OFFSET) {
EmacsPlusUtils.showMessage(editor, UNBALANCED, true);
throw new BadLocationException();
}
return result;
}
/**
* Swap sexps between point and mark
* Position cursor at beginning of non-marked sexp
*
* @param editor
* @param document
* @param currentSelection
* @param event
* @param wordp
* @return the offset after the swap or NO_OFFSET
* @throws BadLocationException
*/
protected int transformAtPointAndMark(ITextEditor editor, IDocument document, ITextSelection currentSelection, ExecutionEvent event, boolean wordp)
throws BadLocationException {
int result = NO_OFFSET;
int mark = MarkUtils.getMark(editor);
if (mark == NO_OFFSET) {
asyncShowMessage(editor, NOT_SET, true);
} else {
Sexps sexps = getSexpsAtPointAndMark(editor, document, currentSelection, wordp);
Position p1, p2, mp;
if (sexps != null) {
mp = new Position(mark,0);
p1 = new Position(sexps.getForward().getOffset(), sexps.getForward().getLength());
p2 = new Position(sexps.getBackward().getOffset(), sexps.getBackward().getLength());
document.addPosition(p1);
document.addPosition(p2);
try {
if ((result = swapSexps(editor, document, sexps)) == NO_OFFSET) {
asyncShowMessage(editor, UNBALANCED, true);
throw new BadLocationException();
}
} finally {
// position cursor, so that repeat invocation swaps sexps 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;
}
/**
* Get the previous and following sexps around point
* @param editor
* @param document
* @param selection
* @param wordp
* @return A Sexps structure containing the two sexps as ITextSelections
*
* @throws BadLocationException
*/
private Sexps getSexpsAtPoint(ITextEditor editor, IDocument document, ITextSelection selection, boolean wordp) throws BadLocationException {
ITextSelection forward =foresexp.getTransSexp(document, selection.getOffset(), wordp);
ITextSelection backward = null;
if (forward != null) {
// check if cursor was inside sexp1
if (forward.getOffset() < selection.getOffset()) {
backward = foresexp.getTransSexp(document, forward.getOffset() + forward.getLength(), wordp);
ITextSelection tmp = forward;
forward = backward;
backward = tmp;
} else {
backward = backsexp.getTransSexp(document, forward.getOffset(), wordp);
}
}
return new Sexps(forward,backward);
}
/**
* Get the previous and following sexps around point
* @param editor
* @param document
* @param selection
* @param wordp
* @return A Sexps structure containing the two sexps as ITextSelections
*
* @throws BadLocationException
*/
private Sexps getSexpsAtPointAndMark(ITextEditor editor, IDocument document, ITextSelection selection, boolean wordp)
throws BadLocationException {
Sexps result = null;
int fore = MarkUtils.getMark(editor);
int back = getCursorOffset(editor);
if (fore < back) {
int tmp = back;
back = fore;
fore = tmp;
}
ITextSelection forward = foresexp.getTransSexp(document, fore, wordp);
ITextSelection backward = foresexp.getTransSexp(document, back, wordp);;
if (forward != null && backward != null) {
result = new Sexps(forward,backward);;
if (result.intersectP()) {
result = null;
}
}
return result;
}
private int swapSexps(ITextEditor editor, IDocument document, Sexps sexps) throws BadLocationException {
int result = NO_OFFSET;
ITextSelection forward = sexps.getForward();
ITextSelection backward = sexps.getBackward();
if (forward != null && backward != null && !sexps.intersectP()) {
result = forward.getOffset() + forward.getLength();
String sexp1text = backward.getText();
String sexp2text = forward.getText();
// swap the text from bottom up
updateText(document,forward, sexp1text);
updateText(document,backward, sexp2text);
}
return result;
}
/**
* @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#isZero()
*/
protected boolean isZero() {
return true;
}
/**
* Force undo protect
* @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#undoProtect()
*/
protected boolean undoProtect() {
return true;
}
private class Sexps {
ITextSelection forward; // the sexp following point
ITextSelection backward; // the sexp preceding point
public Sexps (ITextSelection forward, ITextSelection backward){
this.forward = forward;
this.backward = backward;
}
public ITextSelection getForward() {
return forward;
}
public ITextSelection getBackward() {
return backward;
}
public boolean equals(Object o) {
boolean result = o == this;
if (!result && o instanceof Sexps) {
Sexps sexp = (Sexps)o;
result = forward.getOffset() == sexp.getForward().getOffset() &&
forward.getLength() == sexp.getForward().getLength() &&
backward.getOffset() == sexp.getBackward().getOffset() &&
backward.getLength() == sexp.getBackward().getLength();
}
return result;
}
public int hashCode() {
int result = 17;
if (forward != null) {
result = 31 * result + forward.getOffset();
result = 31 * result + forward.getLength();
}
if (backward != null) {
result = 31 * result + backward.getOffset();
result = 31 * result + backward.getLength();
}
return result;
}
public boolean intersectP() {
boolean result = false;
if (forward != null && backward != null) {
int abegin = forward.getOffset();
int aend = abegin + forward.getLength();
int bbegin = backward.getOffset();
int bend = bbegin + backward.getLength();
result = ((abegin >= bbegin) && (abegin < bend)) ||
((bbegin >= abegin) && (bbegin <= aend)) ||
(forward.getOffset() == backward.getOffset() && forward.getLength() == backward.getLength()) ;
}
return result;
}
}
}