/** * 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.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; import com.mulgasoft.emacsplus.EmacsPlusUtils; import com.mulgasoft.emacsplus.MarkUtils; /** * Implements: apply-macro-to-region-lines * (C-x C-k r) * * Repeat the last defined keyboard macro on each line that begins in the region. * It does this line by line, by moving point to the beginning of the line * and then executing the macro. * * @author Mark Feber - initial API and implementation */ public class KbdMacroApplyHandler extends KbdMacroExecuteHandler { private Selector selector = null; protected void runMacro(final ITextEditor editor,final IDocument document,final ITextSelection selection, final KbdLock vkf, final int count, final String cmdId, final MacroCount keepCount) { selector = new Selector(selection,editor,document,cmdId, keepCount); // selectNext changes selection in editor, so must be called within ui thread if (selector.selectNext()) { selector.startUndo(); super.runMacro(editor, document, selector.getSelection(), vkf, count, cmdId, keepCount); } } /** * @see com.mulgasoft.emacsplus.commands.KbdMacroExecuteHandler#executeOnce(ITextEditor, IDocument, ITextSelection, KbdMacroExecuteHandler.KbdLock) */ @Override protected void executeOnce(ITextEditor editor, IDocument document, ITextSelection currentSelection, final KbdLock vkf) throws BadLocationException { try { super.executeOnce(editor, document, null, vkf); } finally { EmacsPlusUtils.asyncUiRun(new Runnable() { public void run() { if (!isInterrupted()) { try { if (selector.selectNext()) { incrementExecutionCount(); // prepare for next execution pushExecution(selector.editor,vkf); // re-register key listener as well KbdMacroApplyHandler.super.runMacro(selector.editor, selector.document, selector.getSelection(), vkf, 1, selector.cmdId,null); } else { selector.stopUndo(); } } catch (Exception e) { EmacsPlusUtils.showMessage(selector.editor, KBD_INTERRUPTED, true); } } else { selector.stopUndo(); } } }); } } /** * Finesse undoProtect as we want an undo to apply to all iterations of the apply-macro * * @see com.mulgasoft.emacsplus.commands.KbdMacroExecuteHandler#undoProtect(ITextEditor, KbdMacroExecuteHandler.MacroCount) */ protected Runnable[] undoProtect(ITextEditor editor, final MacroCount keepCount) { return new Runnable[2]; } /** * Utility class to keep track of the lines over which we're moving */ private class Selector { int begin; // the first line int end; // the last line ITextEditor editor; IDocument document; String cmdId; Runnable[] undo; Selector(ITextSelection selection, ITextEditor editor, IDocument document, String id, MacroCount keepCount) { int begin = selection.getOffset(); int end = begin + selection.getLength(); // get begin and end line information try { begin = document.getLineOfOffset(begin); int e = document.getLineOfOffset(end); // if the selection ends at the beginning of the last line, ignore it if (document.getLineOffset(e) == end) { --e; } end = e; } catch (BadLocationException e) {} this.cmdId = id; this.editor = editor; this.document = document; // set for pre-increment this.begin = --begin; this.end = end; undo = KbdMacroApplyHandler.super.undoProtect(editor, keepCount); } void startUndo() { EmacsPlusUtils.asyncUiRun(undo[0]); } void stopUndo() { EmacsPlusUtils.asyncUiRun(undo[1]); } /** * Move to the beginning of the next line in sequence * * @return true on success, false when we've run out of lines */ boolean selectNext() { boolean result = (++begin <= end); if (result) { getWorkbenchPage().activate(editor); try { MarkUtils.setCursorOffset(editor, document.getLineOffset(begin)); } catch (BadLocationException e) { result = false; } } return result; } /** * Get the current position as a selection * @return current offset selection */ ITextSelection getSelection() { ITextSelection result = null; try { result = new TextSelection(document,document.getLineOffset(begin),0); } catch (BadLocationException e) {} return result; } }; }