/**
* 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.core.commands.ExecutionException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.ui.texteditor.ITextEditor;
/**
* Common methods for case conversion
* They follow the XEmacs convention of using the selection if present, or the next (or previous) word
* If a selection is present, then ignore any ^U argument
*
* @author Mark Feber - initial API and implementation
*/
public abstract class CaseCommandHandler extends EmacsPlusCmdHandler {
private boolean forward = true;
private ITextSelection initialSel = null;
// unicode regex: [\p{L}[\p{Mn}[\p{Pc}[\p{Nd}[\p{Nl}[\p{Sc}]]]]]]+
// \p{L} = \p{Letter}: letter from any language
// \p{Mn} = \p{Non_Spacing_Mark}: a character intended to be combined with another character without taking up extra space (e.g. accents, umlauts, etc.).
// \p{Pc} = \p{Connector_Punctuation}: a punctuation character such as an underscore that connects words.
// \p{Nd} = \p{Decimal_Digit_Number}: a digit zero through nine in any script except ideographic scripts.
// \p{Nl} = \p{Letter_Number}: a number that looks like a letter, such as a Roman numeral.letter from any language
// \p{Sc} = \p{Currency_Symbol}: any currency sign.
// word characters preceded by (ignored) non-word or BOL/BOB
private final static String REVERSE_TOKEN_EXP = "(?<=" + END_TOKEN_REGEX+ "|^)" + TOKEN_REGEX; //$NON-NLS-1$ //$NON-NLS-2$
private final static String TOKEN_EXP = TOKEN_REGEX + "(?="+ END_TOKEN_REGEX + "|$)"; //$NON-NLS-1$ //$NON-NLS-2$
/**
* @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#execute(org.eclipse.core.commands.ExecutionEvent)
*/
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
// reset state before execution
reset();
return super.execute(event);
}
/**
* @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 {
return currentSelection.getOffset() + (forward ? currentSelection.getLength() : 0);
}
/**
* For initial, empty selection support cursor embedded in a token on reverse direction as the FDA code doesn't
* @throws ExecutionException
*
* @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#getNewSelection(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.ITextSelection)
*/
protected ITextSelection getNewSelection(IDocument document, ITextSelection selection) throws ExecutionException{
ITextSelection result = selection;
if (!forward && selection.getLength() == 0 && initialSel != null && initialSel.equals(selection)) {
// special code to anchor reverse selection at current position (which may be inside full token)
ITextSelection fore = getNextSelection(document,selection,true,END_TOKEN_REGEX);
if (fore != null && fore.getOffset() != selection.getOffset() ) {
// if we're starting from embedded position, search from end and then prune result to initial position
result = getNextSelection(document, fore, false, REVERSE_TOKEN_EXP);
result = new TextSelection(document,result.getOffset(),result.getLength() - (fore.getOffset() - initialSel.getOffset()));
if (result.equals(selection)) {
// if we're back where we started, then not embedded so search back one token
result = getNextSelection(document, selection, false, REVERSE_TOKEN_EXP);
}
} else if (fore == null && (initialSel.getOffset() >= document.getLength())) {
// if we're starting at document end
result = getNextSelection(document,new TextSelection(document,selection.getOffset()-1,0),false,REVERSE_TOKEN_EXP);
} else {
// we're starting eow
result = getNextSelection(document, selection, false, REVERSE_TOKEN_EXP);
}
} else {
if (!forward && result.getOffset() > 0) {
// back up over preceding non-word character
result = new TextSelection(document, result.getOffset()-1, result.getLength());
}
result = getNextSelection(document, result, forward, (forward ? TOKEN_EXP : REVERSE_TOKEN_EXP));
}
if (result == null)
// break out of count
throw new ExecutionException(null);
return result;
}
protected boolean checkSelection(ITextSelection selection) {
return selection.getLength() > 0;
}
/**
* Support inverse operation from the normal command
*
* @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#getInverseId(java.lang.String)
*/
protected String getInverseId(String id) {
// change direction
forward = false;
// and use current command
return null;
}
/**
* If a region is selected initially, then don't repeat
*
* @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#isLooping()
*/
@Override
protected boolean isLooping() {
return (isSelection() ? false : super.isLooping());
}
private boolean isSelection() {
return (initialSel != null && initialSel.getLength() > 0);
}
@Override
protected void preTransform(ITextEditor editor, ITextSelection selection) {
initialSel = selection;
super.preTransform(editor, selection);
}
@Override
protected void postTransform() {
if (!forward && !isSelection()) {
// preserve initial cursor position
setSelection(getThisEditor(), initialSel);
}
reset();
super.postTransform();
}
private void reset() {
forward = true;
initialSel = null;
}
}