/**
* 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.TextSelection;
import org.eclipse.ui.console.IConsoleView;
import org.eclipse.ui.console.TextConsoleViewer;
import org.eclipse.ui.texteditor.ITextEditor;
import com.mulgasoft.emacsplus.EmacsPlusUtils;
/**
* Implements: backward-up-list
*
* backward-up-list with a generalized bracket set
*
* backward-up-list moves backward up past one unmatched opening delimiter.
* A positive argument serves as a repeat count; a negative argument reverses
* the direction of motion, so that the command moves forward and up one or more levels.
*
* @author Mark Feber - initial API and implementation
*/
public class BackwardUpListHandler extends SexpBaseBackwardHandler implements IConsoleDispatch {
/**
* @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 {
boolean isBackup = getUniversalCount() > 0; // normal direction
int offset = doTransform(document,currentSelection,getCursorOffset(editor,currentSelection),isBackup);
if (offset < 0) {
unbalanced(editor,false);
// trapped by generalized beep() exit
throw new BadLocationException();
} else {
return endTransform(editor,offset,currentSelection,new TextSelection(document,offset,offset - currentSelection.getOffset()));
}
}
/**
* @see com.mulgasoft.emacsplus.commands.SexpBaseBackwardHandler#consoleDispatch(TextConsoleViewer, IConsoleView, ExecutionEvent)
*/
public Object consoleDispatch(final TextConsoleViewer viewer, final IConsoleView activePart, ExecutionEvent event) {
IDocument doc = viewer.getDocument();
boolean isBackup = getUniversalCount() > 0; // normal direction
ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection();
try {
int offset = doTransform(doc, selection, viewer.getTextWidget().getCaretOffset(),isBackup);
if (offset == NO_OFFSET) {
unbalanced(activePart,false);
} else {
endTransform(viewer, offset, selection, new TextSelection(null,offset,offset - selection.getOffset()));
}
} catch (BadLocationException e) {}
return null;
}
protected int doTransform(IDocument document, ITextSelection selection, int cursorOffset,boolean isBackup)
throws BadLocationException {
return backUpOffset(document,selection,cursorOffset,isBackup);
}
private int backUpOffset(IDocument document, ITextSelection selection, int cursorOffset, boolean isBackup)
throws BadLocationException {
int result = NO_OFFSET;
int offset = cursorOffset;
// remember the cursor so we don't go short
int endList = cursorOffset;
// but check for start at open bracket
if (isBackup) {
if (isOpen(document.getChar(offset))) {
// remember the end of the current sub-list that started at offset
IRegion end = getBracketMatch(getThisDocument(),offset+1);
if (end != null) {
endList = end.getOffset()+ end.getLength()-1;
}
if (--offset < 0) {
return result;
}
}
} else {
IRegion region = document.getLineInformationOfOffset(offset);
// check for position immediately after a close bracket
if (region.getOffset() != offset && isClose(document.getChar(offset-1))) {
if (offset < region.getOffset() + region.getLength()) {
offset++;
} else {
// move over eol
offset = offset + EmacsPlusUtils.getEol(document).length();
}
endList = offset;
}
}
TextSelection textSelection = new TextSelection(document,offset,0);
IRegion match = matchBracket(document,textSelection,endList);
if (match != null) {
if (isBackup) {
if (match.getOffset() + match.getLength() >= endList) {
result = match.getOffset();
}
} else {
result = match.getOffset() + match.getLength();
}
}
return result;
}
/**
* Find the next matching bracket
*
* @param document
* @param selection
* @return The bracketed region or null
* @throws BadLocationException
*/
private IRegion matchBracket(IDocument document, ITextSelection selection, int endList) throws BadLocationException {
if (selection == null) {
return null;
}
int offset = selection.getOffset();
if (isOpen(document.getChar(offset))) {
// go to the end and check that it is past the initial ending
IRegion end = getBracketMatch(document,offset+1);
if (end == null) {
return null;
} else {
offset = end.getOffset();
if (offset + end.getLength() < endList) {
if (--offset > -1) {
// back up and recurse
return matchBracket(document,new TextSelection(document,offset,0),endList);
} else {
return null;
}
} else {
return end;
}
}
} else if (offset - 1 > 0 && isClose(document.getChar(offset-1))) {
// if at the end of a sub-list, jump to the beginning and recurse
IRegion begin = getBracketMatch(document,offset);
return matchBracket(document,new TextSelection(document,begin.getOffset(),0),endList);
}
ITextSelection nextSexp = getNextSexp(document, selection,false,UP);
if (selection.equals(nextSexp)) {
return null;
}
// if neither begin or end, look backward for next one and recurse
return matchBracket(document,nextSexp,endList);
}
/**
* @see com.mulgasoft.emacsplus.commands.SexpHandler#endTransform(ITextEditor, int, ITextSelection, ITextSelection)
*/
@Override
protected int endTransform(ITextEditor editor, int offset,
ITextSelection origSelection, ITextSelection selection) {
if (isMarkEnabled(editor,origSelection)) {
return selectTransform(editor,offset,origSelection,selection);
} else {
return noSelectTransform(editor,offset,selection,true);
}
}
/**
* @see com.mulgasoft.emacsplus.commands.SexpHandler#endTransform(TextConsoleViewer, int, ITextSelection, ITextSelection)
*/
@Override
protected int endTransform(TextConsoleViewer viewer, int offset,
ITextSelection origSelection, ITextSelection selection) {
if (isMarkEnabled(viewer, origSelection)) {
return selectTransform(viewer, offset, origSelection, selection);
} else {
return noSelectTransform(viewer, offset, selection, false);
}
}
/**
* Skip everything but brackets
*
* @see com.mulgasoft.emacsplus.commands.SexpHandler#skipChar(char, boolean)
*/
protected boolean skipChar(char c, boolean wordp) {
// skip everything but brackets
return !isBracket(c,OPEN) && !isBracket(c,CLOSE);
}
}