/**
* Copyright (c) 2009, 2014 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.execute;
import static com.mulgasoft.emacsplus.EmacsPlusUtils.UNIVERSAL_ARG;
import static com.mulgasoft.emacsplus.IEmacsPlusCommandDefinitionIds.KBDMACRO_EXECUTE;
import static com.mulgasoft.emacsplus.IEmacsPlusCommandDefinitionIds.REPEAT_COMMAND;
import static com.mulgasoft.emacsplus.IEmacsPlusCommandDefinitionIds.UNIVERSAL_ARGUMENT;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
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.ui.PlatformUI;
import org.eclipse.ui.contexts.IContextActivation;
import org.eclipse.ui.contexts.IContextService;
/**
* A singleton class to hold and support repeating the last command
*
* @author mfeber - Initial API and implementation
*/
public class RepeatCommandSupport extends RepeatingSupport {
// This context enables an alternate binding (default: the letter Z)
private final static String RCONTEXT = "com.mulgasoft.emacsplus.repeating"; //$NON-NLS-1$
private Cmd repeater = null;
boolean repeating = false;
IContextActivation repeatc = null;
private static RepeatCommandSupport instance;
private RepeatCommandSupport() {}
public static RepeatCommandSupport getInstance() {
if (instance == null) {
instance = new RepeatCommandSupport();
}
return instance;
}
public static boolean isRepeatCommand(String commandId) {
return REPEAT_COMMAND.equals(commandId);
}
public String getId() {
return repeater != null ? repeater.getId() : null;
}
@SuppressWarnings("unchecked")
public Map<String, Object> getParams() {
return (Map<String, Object>)(repeater != null ? repeater.getParams() : Collections.emptyMap());
}
public Integer getCount() {
Integer result = 1;
Object p = getParams().get(UNIVERSAL_ARG);
if (p instanceof String) {
result = convertCount((String)p);
} else if (p != null){
result = (Integer)p;
}
return result;
}
protected int convertCount(String universalArg) {
int result = 1;
if (universalArg != null && universalArg.length() > 0) {
try {
result = Integer.parseInt(universalArg);
} catch (NumberFormatException e) { // if invalid number, proceed with count=1
}
}
return result;
}
/**
* @see org.eclipse.core.commands.IExecutionListener#preExecute(java.lang.String, org.eclipse.core.commands.ExecutionEvent)
*/
public void preExecute(String commandId, ExecutionEvent event) {
if (!excludeFromRepeat(commandId)) {
if (isRepeatCommand(commandId)) {
// arm
this.repeating = true;
storeCount(event);
activateContext();
} else if (!this.repeating) {
currentEvent.push(event);
clearContext();
}
}
}
/**
* @see org.eclipse.core.commands.IExecutionListener#postExecuteSuccess(java.lang.String, java.lang.Object)
*/
public void postExecuteSuccess(String commandId, Object returnValue) {
if (isRepeatCommand(commandId)) {
// disarm
this.repeating = false;
} else if (!(this.repeating || excludeFromRepeat(commandId))) {
ExecutionEvent event = popEvent();
boolean wasMeta = isInMetaXCommand();
if (!isInMetaXCommand(commandId) && (wasMeta || getTrigger(event) != null)) {
storeCommand(commandId, event);
}
}
}
private void activateContext() {
if (this.repeatc == null) {
this.repeatc = ((IContextService)PlatformUI.getWorkbench().getService(IContextService.class)).activateContext(RCONTEXT);
}
}
private void clearContext() {
if (this.repeatc != null) {
((IContextService)PlatformUI.getWorkbench().getService(IContextService.class)).deactivateContext(repeatc);
this.repeatc = null;
}
}
private void fail(String commandId) {
if (isRepeatCommand(commandId)) {
// disarm
this.repeating = false;
} else {
popEvent();
clearContext();
}
}
/**
* @see org.eclipse.core.commands.IExecutionListener#postExecuteFailure(java.lang.String, org.eclipse.core.commands.ExecutionException)
*/
public void postExecuteFailure(String commandId, ExecutionException exception) {
fail(commandId);
}
/**
* @see org.eclipse.core.commands.IExecutionListener#notHandled(java.lang.String, org.eclipse.core.commands.NotHandledException)
*/
public void notHandled(String commandId, NotHandledException exception) {
fail(commandId);
}
@Override
public void notEnabled(String commandId, NotEnabledException exception) {
popEvent();
}
private class Cmd {
private String id;
private Map<?,?> params;
private Cmd(String id, Map<?,?> params) {
this.id = id;
this.params = params;
}
private Cmd(String id, ExecutionEvent event) {
this(id, event.getParameters());
}
@SuppressWarnings("serial")
private Cmd(String id, final Integer count) {
this(id, new HashMap<String,Object>() {{put(UNIVERSAL_ARG, count); }});
}
public String getId() {
return id;
}
public Map<?, ?> getParams() {
return params;
}
public void setParams(Map<?, ?> params) {
this.params = params;
}
}
public void storeCommand(String commandId, Integer count) {
repeater = new Cmd(commandId, count);
}
public void storeCommand(String commandId, ExecutionEvent event) {
repeater = new Cmd(commandId, event);
}
protected void storeCount(ExecutionEvent event) {
String universalArg = event.getParameter(UNIVERSAL_ARG);
if (universalArg != null && universalArg.length() > 0) {
try {
Integer count = Integer.parseInt(universalArg);
if (repeater != null) {
@SuppressWarnings("unchecked")
Map<String,Object> params = (Map<String,Object>)repeater.getParams();
if (params.isEmpty()) {
params = new HashMap<String,Object>();
repeater.setParams(params);
}
params.put(UNIVERSAL_ARG, count);
}
} catch (NumberFormatException e) {} // ignore if invalid number
}
}
/**
* Check if we should remember this command
*
* @param id command
* @return ignore command if true, else remember it
*/
private boolean excludeFromRepeat(String id) {
// Ignore all but the execute command, when in a kbd macro
boolean inKbd = (KbdMacroSupport.getInstance().isExecuting() && !KBDMACRO_EXECUTE.equals(id));
return (excludeCmds.get(id) != null ? true : inKbd);
}
@SuppressWarnings("serial")
protected static final HashMap<String,Boolean> excludeCmds = new HashMap<String,Boolean>() {
{
put(UNIVERSAL_ARGUMENT, Boolean.TRUE);
}
};
}