/**
* 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 java.util.HashMap;
import java.util.Map;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.NotEnabledException;
import org.eclipse.core.commands.NotHandledException;
import org.eclipse.core.commands.common.CommandException;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.console.IConsoleView;
import org.eclipse.ui.console.TextConsoleViewer;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.texteditor.ITextEditor;
import com.mulgasoft.emacsplus.EmacsPlusUtils;
import com.mulgasoft.emacsplus.IEmacsPlusCommandDefinitionIds;
import com.mulgasoft.emacsplus.MarkUtils;
/**
* Abstract handler for all mark aware movement commands
*
* If the mark is set and attached to the current selection, then extend the selection
* with the movement; otherwise just move
*
* If shift-select-mode is enabled, and the SHIFT key is depressed when invoking
* the movement command, the mark is set before moving point. For more information
* @see com.mulgasoft.emacsplus.commands.ShiftSelectModeHandler
*
* @author Mark Feber - initial API and implementation
*/
public abstract class EmacsMovementHandler extends EmacsPlusNoEditHandler implements IConsoleDispatch {
/** When true, the previous command was invoked with a shift-select key binding */
boolean wasShifted = false; // remember the previous state
/** When true, the command was invoked with a shift-select key binding */
private static boolean shifted = false; // holds the current state
/** Briefly retain the event in case of ^U invocation of command */
private ExecutionEvent executeEvent = null; // holds the invoking execution event or null
// The SHIFT modifier family of bindings is loaded by the Options plugin.
// Only enable mode, if Options (with all the necessary key bindings) is loaded
// Once set, the user can change this flag to disable shift mode
private static class SelectMode {
// is shift mode enabled?
private static boolean enabled = isShiftEnabled();
}
/**
* @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#execute(org.eclipse.core.commands.ExecutionEvent)
*/
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
// if the movement command was called via transforWithCount after detecting a universal
// arg, the execute method is only called once regardless of the number of iterations
// so remember the event that initially invoked us
executeEvent = event;
return super.execute(event);
}
/**
* Move using either a selection command or an non-selection command depending
* on the state of the mark, currentSelection, and position
*
* @param editor
* @param currentSelection
* @param withoutSelect - non-selection command id
* @param withSelect - selection command id
*
* @return true if the movement was made with selection
* @throws BadLocationException
*/
protected boolean moveWithMark(ITextEditor editor, ITextSelection currentSelection, String withoutSelect, String withSelect)
throws BadLocationException {
boolean result = false;
try {
setShiftedState();
boolean markit = isMarkEnabled(editor,currentSelection);
if (isShiftMode()) {
if (wasShifted) {
if (isShifted()) {
// just keep selecting
markit = true;
} else {
Map<String,String> params = new HashMap<String,String>();
params.put(SHIFT_ARG,ShiftState.CLEAR.toString());
// clear selection and turn off mark; execute a command so kbd macros can emulate
// include an empty Event so that when defining a kbd macro the command will be included
EmacsPlusUtils.executeCommand(IEmacsPlusCommandDefinitionIds.SET_MARK, params, new Event(), editor);
markit = false;
}
} else if (isShifted()) {
Map<String,String> params = new HashMap<String,String>();
params.put(SHIFT_ARG,ShiftState.SET.toString());
// clear selection and restart mark; execute a command so kbd macros can emulate
// include an empty Event so that when defining a kbd macro the command will be included
EmacsPlusUtils.executeCommand(IEmacsPlusCommandDefinitionIds.SET_MARK, params, new Event(), editor);
markit = true;
}
}
if (markit) {
EmacsPlusUtils.executeCommand(withSelect, null, editor);
// set mark flag if we're back at ground zero, else clear
setFlagMark(getCurrentSelection(editor).getLength() == 0);
result = true;
} else {
EmacsPlusUtils.executeCommand(withoutSelect, null, editor);
}
} catch (ExecutionException e) {
} catch (NotDefinedException e) {
} catch (NotEnabledException e) {
} catch (NotHandledException e) {
} catch (CommandException e) {
}
return result;
}
// When invoked with ^U, movement can expand the selection
// so, check each time
@Override
protected ITextSelection getCmdSelection(ITextEditor editor,
ITextSelection selection) throws ExecutionException {
ITextSelection cSelection = getCurrentSelection(editor);
if (!cSelection.equals(selection)) {
return cSelection;
} else {
return super.getCmdSelection(editor, selection);
}
}
/**
* @see com.mulgasoft.emacsplus.commands.IConsoleDispatch#consoleDispatch(TextConsoleViewer, IConsoleView, ExecutionEvent)
*/
public Object consoleDispatch(TextConsoleViewer viewer, IConsoleView activePart, ExecutionEvent event) {
StyledText st = viewer.getTextWidget();
String id = event.getCommand().getId();
boolean isSelect = isMarkEnabled(viewer,(ITextSelection)viewer.getSelection());
int action = getDispatchId(id,isSelect);
if (action > -1) {
st.invokeAction(action);
} else if ((id = getId(isSelect)) != null) {
// support sexps
try {
EmacsPlusUtils.executeCommand(id, null, activePart);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
/**
* Fetch the correct dispatch id
*
* @return return new id or -1
*/
private int getDispatchId(String id, boolean selectIt) {
Integer dispatch = (selectIt ? dispatchSelectIds.get(id) : dispatchCmdIds.get(id));
if (dispatch == null) {
dispatch = -1;
}
return dispatch;
}
private String getId(boolean isSelect) {
if (isSelect) {
return getSelectId();
} else {
return getNoSelectId();
}
}
/**
* Get the selecting command id for use in the ConsoleView
*
* @return a command id that supports the ConsoleView or null
*/
protected String getSelectId() {
return null;
}
/**
* Get the non-selecting command id for use in the ConsoleView
*
* @return a command id that supports the ConsoleView or null
*/
protected String getNoSelectId() {
return null;
}
// Shift key processing
/**
* Check if the Options plugin is loaded by checking whether the shift-select-mode command is handled
* as the handler is set up in the Options plugin.xml
*
* @return true if Options loaded, else false
*/
// Checking for the handler seems to be the only reliable way of checking - is there a better way?
private static boolean isShiftEnabled() {
// The shift-select-mode handler is 'registered' in the Options plugin.xml
boolean result = false;
ICommandService ics = ((ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class));
if (ics != null && ics.getDefinedCommandIds().contains(IEmacsPlusCommandDefinitionIds.SHIFT_SELECT)) {
Command command = ics.getCommand(IEmacsPlusCommandDefinitionIds.SHIFT_SELECT);
result = command.isHandled();
}
return result;
}
/**
* Change the enabled state of shift-select-mode to value
*
* @param value true to enable, false to disable
*/
void setShiftMode(boolean value) {
SelectMode.enabled = value;
}
/**
* Is shift-select-mode enabled?
*
* @return true if enabled, else false
*/
boolean isShiftMode() {
return SelectMode.enabled;
}
/**
* Check the SHIFT state of the command's invocation.
*
* @return true if invoked by a key binding containing SHIFT modifier, else false
*/
public static boolean isShifted() {
return EmacsMovementHandler.shifted;
}
/**
* Force clearing of shifted flag when appropriate
*/
public static void clearShifted() {
shifted = false;
}
/**
* Examine the ExecutionEvent trigger for the presence of the SHIFT key.
* Note that event may be null depending on how the command was invoked
*
* @param event the Event trigger
*/
private void setShifted(ExecutionEvent event) {
if (isShiftMode() && event != null) {
EmacsMovementHandler.shifted = getShifted(event);
}
}
/**
* Set the shift select flags for this command:
* - remember the previous state of shift
* - verify that this is a shift select movement command
* - if so, examine the event to see if SHIFT is set
*/
private void setShiftedState() {
if (executeEvent != null) {
// remember state of previous command
wasShifted = isShifted();
// on shift-select-mode commands, check for the SHIFT key
if (MarkUtils.isShiftCommand(executeEvent.getCommand().getId())) {
setShifted(executeEvent);
} else {
clearShifted();
// we only care if its shifted
executeEvent = null;
}
}
}
/**
* Does the trigger for this movement command contain the SHIFT key?
*
* Enforce that the <binding> and <binding>+SHIFT belong to the same Command.
* If not, don't apply shift selection (if enabled) for this command (i.e. return false).
*
* @param event the Execution event that invoked this command
*
* @return true if SHIFT modifier was set, else false
*/
private boolean getShifted(ExecutionEvent event) {
// NB: only single keystroke commands are valid
boolean result = false;
Object trigger = event.getTrigger();
Event e = null;
if (trigger != null && trigger instanceof Event && ((e = (Event)trigger).stateMask & SWT.SHIFT )!= 0) {
String cmdId = event.getCommand().getId();
int mask = (e.stateMask & SWT.MODIFIER_MASK) ^ SWT.SHIFT;
int u_code = Character.toUpperCase((char)e.keyCode);
IBindingService bs = (IBindingService) PlatformUI.getWorkbench().getService(IBindingService.class);
if (cmdId != null && bs != null) {
TriggerSequence[] sequences = bs.getActiveBindingsFor(cmdId);
for (TriggerSequence s : sequences) {
if (s instanceof KeySequence) {
KeyStroke[] strokes = ((KeySequence)s).getKeyStrokes();
if (strokes.length == 1) {
KeyStroke k = strokes[strokes.length - 1];
// if keyCode is alpha, then we test uppercase, else keyCode
if (k.getModifierKeys() == mask
&& (k.getNaturalKey() == u_code || k.getNaturalKey() == e.keyCode)) {
result = true;
break;
}
}
}
}
}
}
return result;
}
}