/**
* 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.minibuffer;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.texteditor.ITextEditor;
import com.mulgasoft.emacsplus.EmacsPlusActivator;
import com.mulgasoft.emacsplus.EmacsPlusUtils;
import com.mulgasoft.emacsplus.RingBuffer;
/**
* Minibuffer for simple and complex alignment prompts
*
* Align the current region using an ad-hoc rule read from the minibuffer.
* The selection marks the limits of the region. This function will prompt
* for the regexp to align with. If no prefix arg was specified, you
* only need to supply the characters to be lined up and any preceding
* whitespace is replaced. If a prefix arg was specified, the full
* regexp with parenthesized whitespace should be supplied; it will also
* prompt for which parenthesis group within regexp to modify, the amount
* of spacing to use, and whether or not to repeat the rule throughout
* the line.
*
* @author Mark Feber - initial API and implementation
*/
public class AlignMinibuffer extends TextMinibuffer {
private static final String ALIGN_COMPLEX= EmacsPlusActivator.getResourceString("Align_Complex"); //$NON-NLS-1$
private static final String ALIGN_GROUP = EmacsPlusActivator.getResourceString("Align_Group"); //$NON-NLS-1$
private static final String ALIGN_SPACES = EmacsPlusActivator.getResourceString("Align_Spaces"); //$NON-NLS-1$
private static final String ALIGN_REPEAT = String.format(EmacsPlusActivator.getResourceString("Align_Repeat"),YESORNO_Y, YESORNO_N); //$NON-NLS-1$
private final static String INITIAL_EXP = "(\\s*)"; //$NON-NLS-1$
private final static int GROUP_DEFAULT = 1;
public final static int SPACE_DEFAULT = 1;
private final static boolean REPEAT_DEFAULT = false;
private ExecuteState executeState = null;
private AlignControl alignControl = null;
/**
* @param executable
*/
public AlignMinibuffer(IMinibufferExecutable executable, boolean isComplex) {
super(executable);
if (isComplex) {
executeState = sComplex;
} else {
executeState = sSimple;
}
}
@Override
public boolean beginSession(ITextEditor editor, IWorkbenchPage page, ExecutionEvent event) {
boolean result = super.beginSession(editor, page, event);
// initialize with executeState's default content
initMinibuffer(executeState.getMinibufferDefault(this));
if (getHistoryRing().isEmpty()) {
addToHistory(INITIAL_EXP); // seed command history
}
return result;
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.ExecutingMinibuffer#getMinibufferPrefix()
*/
@Override
protected String getMinibufferPrefix() {
return executeState.getMinibufferPrefix(this);
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#handlesCtrl()
*/
@Override
protected boolean handlesAlt() {
// enable history for Apropos
return true;
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.ExecutingMinibuffer#executeResult(org.eclipse.ui.texteditor.ITextEditor, java.lang.Object)
*/
@Override
protected boolean executeResult(ITextEditor editor, Object commandResult) {
boolean result = true;
result = executeState.executeResult(editor, commandResult, this);
if (result) {
if (getAlignControl() != null) {
result = super.executeResult(editor, getAlignControl());
} else {
// TODO error message?
}
} else {
// transition to next state
initMinibuffer(executeState.getMinibufferDefault(this));
}
return result;
}
/**
* Dispatch through current execute state if it has a special handler
*
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#charEvent(org.eclipse.swt.events.VerifyEvent)
*/
protected void charEvent(VerifyEvent event) {
if (!executeState.executeCharEvent(this, event)) {
super.charEvent(event);
}
}
AlignControl getAlignControl() {
return alignControl;
}
void setAlignControl(AlignControl alignControl) {
this.alignControl = alignControl;
}
void setExecuteState(ExecuteState executeState) {
this.executeState = executeState;
}
private interface ExecuteState {
String getMinibufferPrefix(ExecutingMinibuffer mini);
String getMinibufferDefault(ExecutingMinibuffer mini);
/**
* Execution invoked on current state. If evaluation returns
* -true transition to next state (or invoke command's executeResult)
* -false remain in current state
* @param editor
* @param minibufferResult
* @param mini
* @return true to transition, false to remain
*/
boolean executeResult(ITextEditor editor, Object minibufferResult, ExecutingMinibuffer mini);
boolean executeCharEvent(ExecutingMinibuffer mini, VerifyEvent event);
public <T> RingBuffer<T> getRingHistory();
}
/**
* Only state of simple align-regexp
*
* Get the ad-hoc part of the regexp rule and
* enter the final execution phase
*/
private static final ExecuteState sSimple = new ExecuteState() {
public boolean executeResult(ITextEditor editor, Object minibufferResult, ExecutingMinibuffer mini) {
boolean result = true;
AlignMinibuffer am = (AlignMinibuffer)mini;
AlignControl ac = null;
String regexp = (String)minibufferResult;
if (regexp != null && regexp.length() > 0) {
am.addToHistory(regexp); // add to command history
ac = am.new AlignControl(INITIAL_EXP + regexp);
am.setAlignControl(ac);
}
return result;
}
public boolean executeCharEvent(ExecutingMinibuffer mini, VerifyEvent event) {return false;}
public String getMinibufferPrefix(ExecutingMinibuffer mini) {
return mini.getExecutable().getMinibufferPrefix();
}
public String getMinibufferDefault(ExecutingMinibuffer mini){
return EMPTY_STR;
}
@SuppressWarnings("unchecked")
public RingBuffer<String> getRingHistory() {
return AlignRing.ring;
}
};
/**
* First state of complex align-regexp
*
* Get the complex regexp from the user (primarily for specifying complex groups) and
* transition to the next state
*/
private static final ExecuteState sComplex = new ExecuteState() {
public boolean executeResult(ITextEditor editor, Object minibufferResult, ExecutingMinibuffer mini) {
boolean result = false;
AlignMinibuffer am = (AlignMinibuffer)mini;
AlignControl ac = null;
String regexp = (String)minibufferResult;
if (regexp != null && regexp.length() > 0) {
am.addToHistory(regexp); // add to command history
ac = am.new AlignControl(regexp);
am.setAlignControl(ac);
am.setExecuteState(sGroup); // next state
} else {
result = true; // flag for exit
}
return result;
}
public boolean executeCharEvent(ExecutingMinibuffer mini, VerifyEvent event) {return false;}
public String getMinibufferPrefix(ExecutingMinibuffer mini) {
return ALIGN_COMPLEX;
}
public String getMinibufferDefault(ExecutingMinibuffer mini){
return INITIAL_EXP;
}
@SuppressWarnings("unchecked")
public RingBuffer<String> getRingHistory() {
return AlignRing.ring;
}
};
/**
* Second state of complex align-regexp
*
* Get the number of the group of the complex regexp to be used in the alignment and
* transition to the next state
*/
private static final ExecuteState sGroup = new ExecuteState() {
public boolean executeResult(ITextEditor editor, Object minibufferResult, ExecutingMinibuffer mini) {
boolean result = false;
AlignMinibuffer am = (AlignMinibuffer)mini;
String number = (String)minibufferResult;
if (number != null && number.length() > 0) {
try {
AlignControl ac = am.getAlignControl();
ac.group = EmacsPlusUtils.emacsParseInt(number);
// am.addToHistory(Integer.toString(ac.group)); // add to command history
am.addToHistory(ac.group); // add to command history
am.setExecuteState(sSpaces); // next state
} catch (NumberFormatException e) {
am.setResultMessage(String.format(BAD_NUMBER,number), true, true);
am.setAlignControl(null); // clear and
result = true; // flag for exit
}
}
return result;
}
public boolean executeCharEvent(ExecutingMinibuffer mini, VerifyEvent event) {
((AlignMinibuffer)mini).numCharEvent(event);
return true;
}
public String getMinibufferPrefix(ExecutingMinibuffer mini) {
return ALIGN_GROUP;
}
public String getMinibufferDefault(ExecutingMinibuffer mini){
return Integer.toString(GROUP_DEFAULT);
}
@SuppressWarnings("unchecked")
public RingBuffer<Integer> getRingHistory() {
return NumberRing.ring;
}
};
/**
* Third state of complex align-regexp
*
* Get the number of spaces to be used in the alignment and
* transition to the next state
*/
private static final ExecuteState sSpaces = new ExecuteState() {
public boolean executeResult(ITextEditor editor, Object minibufferResult, ExecutingMinibuffer mini) {
boolean result = false;
AlignMinibuffer am = (AlignMinibuffer)mini;
String number = (String)minibufferResult;
if (number != null && number.length() > 0) {
try {
AlignControl ac = am.getAlignControl();
ac.spacing = EmacsPlusUtils.emacsParseInt(number);
am.addToHistory(ac.spacing); // add to command history
am.setExecuteState(sRepeat); // next state
} catch (NumberFormatException e) {
am.setResultMessage(String.format(BAD_NUMBER,number), true, true);
am.setAlignControl(null); // clear and
result = true; // flag for exit
}
}
return result;
}
public boolean executeCharEvent(ExecutingMinibuffer mini, VerifyEvent event) {
((AlignMinibuffer)mini).numCharEvent(event);
return true;
}
public String getMinibufferPrefix(ExecutingMinibuffer mini) {
return ALIGN_SPACES;
}
public String getMinibufferDefault(ExecutingMinibuffer mini){
return Integer.toString(SPACE_DEFAULT);
}
@SuppressWarnings("unchecked")
public RingBuffer<Integer> getRingHistory() {
return NumberRing.ring;
}
};
/**
* Fourth state of complex align-regexp
*
* Get the boolean repeat value (i.e. repeat the alignment throughout each line) and
* enter the final execution phase
*/
private static final ExecuteState sRepeat = new ExecuteState() {
private boolean onError = false;
public boolean executeResult(ITextEditor editor, Object minibufferResult, ExecutingMinibuffer mini) {
boolean result = true;
AlignMinibuffer am = (AlignMinibuffer)mini;
String yesOrNo = (String)minibufferResult;
if (yesOrNo != null && yesOrNo.length() > 0) {
try {
am.getAlignControl().repeat = am.isYesOrNo(yesOrNo);
am.setResultMessage(EMPTY_STR, true, true); // clear out error message
} catch (YesOrNoException e) {
try {
onError = true;
am.setResultMessage(YESORNO_BAD, true, true);
} finally {
onError = false;
}
result = false;
}
}
return result;
}
public boolean executeCharEvent(ExecutingMinibuffer mini, VerifyEvent event) {
((AlignMinibuffer)mini).immediateCharEvent(event);
return true;
}
public String getMinibufferPrefix(ExecutingMinibuffer mini) {
if (onError) {
return EMPTY_STR;
} else {
return ALIGN_REPEAT;
}
}
public String getMinibufferDefault(ExecutingMinibuffer mini){
return EMPTY_STR;
}
public RingBuffer<? super Object> getRingHistory() {
return null;
}
};
/**
* Utility class to store the result of (possible multiple)
* prompt results
*
* @author Mark Feber - initial API and implementation
*/
public class AlignControl {
public AlignControl(String pattern) {
this.pattern = pattern;
}
private String pattern;
protected int group = GROUP_DEFAULT;
protected int spacing = SPACE_DEFAULT;
protected boolean repeat = REPEAT_DEFAULT;
private int maxColumn = -1;
public int getMaxColumn() {
return maxColumn;
}
public void setMaxColumn(int maxColumn) {
this.maxColumn = maxColumn;
}
public String getPattern() {
return pattern;
}
public int getGroup() {
return group;
}
public int getSpacing() {
return spacing;
}
public boolean getRepeat() {
return repeat;
}
}
// /**
// * @see FocusListener#focusLost(org.eclipse.swt.events.FocusEvent)
// */
// public void focusLost(FocusEvent e) {
// // see IPartListener comment
// }
/**** Local RingBuffer ****/
/**
* Dispatch through state object
*
* @see com.mulgasoft.emacsplus.minibuffer.HistoryMinibuffer#getHistoryRing()
*/
@Override
protected <T> RingBuffer<T> getHistoryRing() {
return executeState.getRingHistory();
}
/**** State RingBuffers: use lazy initialization holder class idiom ****/
private static class AlignRing {
static final RingBuffer<String> ring = new RingBuffer<String>();
}
private static class NumberRing {
static final RingBuffer<Integer> ring = new RingBuffer<Integer>();
}
}