/** * 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.Command; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.ParameterizedCommand; import org.eclipse.core.commands.common.CommandException; import org.eclipse.jface.bindings.Binding; import org.eclipse.jface.bindings.Trigger; 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.IDocument; import org.eclipse.jface.text.ITextSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Event; import org.eclipse.ui.texteditor.ITextEditor; import com.mulgasoft.emacsplus.EmacsPlusActivator; import com.mulgasoft.emacsplus.EmacsPlusUtils; import com.mulgasoft.emacsplus.IEmacsPlusCommandDefinitionIds; import com.mulgasoft.emacsplus.execute.IUniversalResult; import com.mulgasoft.emacsplus.execute.KbdMacroSupport; import com.mulgasoft.emacsplus.minibuffer.UniversalMinibuffer; /** * Implements: universal-argument * * C-u: Begin a numeric argument for the following command. * - If there's a command binding, call the command with the argument * - If unmodified character, insert the character argument times into the buffer * -- on character insertion, if argument < 0 then leave cursor at front, else move to end * * @author Mark Feber - initial API and implementation */ public class UniversalHandler extends ExecuteCommandHandler implements INonEditingCommand { private static String UA_ERROR = EmacsPlusActivator.getResourceString("UA_Error"); //$NON-NLS-1$ private final static String ERROR_PREFIX = "%s: "; //$NON-NLS-1$ private final static String BS = String.valueOf(SWT.BS); private final static String INITIAL_PREFIX = "C-u"; //$NON-NLS-1$ private String prefix = INITIAL_PREFIX; /** * @see com.mulgasoft.emacsplus.minibuffer.IMinibufferExecutable#getMinibufferPrefix() */ public String getMinibufferPrefix() { return prefix; } /** * @see com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler#transform(ITextEditor, IDocument, ITextSelection, ExecutionEvent) */ protected int transform(ITextEditor editor, IDocument document, ITextSelection currentSelection, ExecutionEvent event) throws BadLocationException { prefix = INITIAL_PREFIX; //reset in case of M-x invocation UniversalMinibuffer mini = new UniversalMinibuffer(this); Object eTrigger = event.getTrigger(); if (eTrigger instanceof Event) { Event ev = (Event)eTrigger; prefix = mini.getTrigger(ev.keyCode,ev.stateMask); } if (KbdMacroSupport.getInstance().isExecuting()) { // call without asynchronous wrapper // TODO: with ^U, key sequences (instead of commands) appear in kbd macro return bufferTransform(mini, editor, event); } else { return miniTransform(mini, editor, event); } } /** * @see com.mulgasoft.emacsplus.minibuffer.IMinibufferExecutable#executeResult(org.eclipse.ui.texteditor.ITextEditor, java.lang.Object) */ public boolean doExecuteResult(final ITextEditor editor, final Object minibufferResult) { showResultMessage(editor); this.executeWithSelectionCheck(editor, new IWithSelectionCheck () { public void execute() { innerExecuteResult(editor,minibufferResult); } }); return true; } /** * Do the command/character execution * * @param editor * @param minibufferResult * @return true */ private boolean innerExecuteResult(ITextEditor editor, Object minibufferResult) { IUniversalResult ua = (IUniversalResult)minibufferResult; int arg = ua.getCount(); boolean isMacro = false; String trigger = ua.getKeyString(); Binding binding = ua.getKeyBinding(); if (binding != null) { ParameterizedCommand pcmd = binding.getParameterizedCommand(); if (pcmd != null) { Command cmd = pcmd.getCommand(); try { Event synthEvent = makeEvent(ua); isMacro = EmacsPlusUtils.isMacroId(cmd.getId()); String newid = null; // check for 0 dispatch and execute directly if present if (arg == 0 && (newid = getDispatchId(cmd.getId(),0)) != null) { executeCommand(newid, synthEvent, editor); } else { executeUniversal(editor, cmd, synthEvent, arg, ua.isNumeric()); } } catch (ExecutionException e) { errorResult(editor,ERROR_PREFIX + e.getLocalizedMessage()); } catch (CommandException e) { errorResult(editor,ERROR_PREFIX + e.getLocalizedMessage()); } } } else if (trigger != null) { if (BS.equals(trigger)) { // Hack in backspace as it is handled internally executeWithDispatch(editor, IEmacsPlusCommandDefinitionIds.DELETE_PREVIOUS, arg); } else { // use negative arg to determine offset placement after insertion rather than // returning a natural number error as in Emacs int abs = ((arg < 0) ? -arg : arg); StringBuilder buf = new StringBuilder(abs); for (int i = 0; i < abs; i++) { buf.append(trigger); } try { int offset = getCursorOffset(editor); getThisDocument(editor).replace(offset, 0, buf.toString()); // on negative count, leave cursor where it is setCursorOffset(editor, offset + (arg > 0 ? (arg * trigger.length()) : 0)); } catch (BadLocationException e) { errorResult(editor,ERROR_PREFIX + e.getLocalizedMessage()); } } } else { errorResult(editor, UA_ERROR); } if (!isMacro || isResultError()) { showResultMessage(editor); } return true; } private void errorResult(ITextEditor editor, String formatMessage) { setResultMessage(String.format(formatMessage, getResultMessage()), true); beep(); } private Event makeEvent(IUniversalResult ua) { Event result = new Event(); KeySequence keys = ua.getTrigger(); if (keys != null) { Trigger[] triggers = keys.getTriggers(); if (triggers[0] instanceof KeyStroke) { // really, all it can be anyway KeyStroke ks = (KeyStroke)triggers[triggers.length - 1]; result.keyCode = ks.getNaturalKey(); result.stateMask = ks.getModifierKeys() & SWT.MODIFIER_MASK; } } return result; } }