/** * Copyright (c) 2009, 2013 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 static com.mulgasoft.emacsplus.EmacsPlusUtils.isMac; 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.ParameterizedCommand; import org.eclipse.core.commands.common.NotDefinedException; import org.eclipse.jface.bindings.Binding; 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.IDocumentExtension4; import org.eclipse.jface.text.IFindReplaceTarget; import org.eclipse.jface.text.ITextListener; import org.eclipse.jface.text.ITextViewerExtension; import org.eclipse.jface.text.TextEvent; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.custom.VerifyKeyListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.VerifyEvent; import org.eclipse.swt.widgets.Event; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.keys.IBindingService; import org.eclipse.ui.texteditor.AbstractTextEditor; import org.eclipse.ui.texteditor.IStatusFieldExtension; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.StatusLineContributionItem; import com.mulgasoft.emacsplus.EmacsPlusUtils; import com.mulgasoft.emacsplus.MarkUtils; import com.mulgasoft.emacsplus.execute.KbdMacroSupport; /** * Commands that want to read from the minibuffer should use of subclass of this * * Based on a true story: org.eclipse.ui.texteditor.IncrementalFindTarget * * @author Mark Feber - initial API and implementation */ public abstract class WithMinibuffer implements FocusListener, ISelectionChangedListener, ITextListener, MouseListener, VerifyKeyListener { protected final static String EMPTY_STR = ""; //$NON-NLS-1$ // The element before which to insert our status updates private static final String POSITION_ID = "ElementState"; //$NON-NLS-1$ // The identifier for the StatusLineContributionItem private static final String STATUS_ID = "minibuffer"; //$NON-NLS-1$ static final String N_GEN = "\\c"; //$NON-NLS-1$ static final String N_NEW = "\\n"; //$NON-NLS-1$ static final String N_RET = "\\r"; //$NON-NLS-1$ static final String N_TAB = "\\t"; //$NON-NLS-1$ static final String N_BS = "\\b"; //$NON-NLS-1$ static final String N_FF = "\\f"; //$NON-NLS-1$ static final char CR = '\r'; static final char LF = '\n'; private ITextEditor editor; private IWorkbenchPage page; private IDocument document; private boolean installed = false; private static StatusLineContributionItem statusItem; /* mini-buffer commands lose their identity once the mini-buffer setup is complete, so remember it here */ private String commandId = null; // the command id of the invoker private String resultString = null; // final message on completion private boolean resultError = false; // final status type on completion private boolean inBegin = false; private boolean executing = false; private MinibufferImpl minibufferStringImpl; private StyledText widget = null; private ISourceViewer viewer = null; private boolean lowercase = false; private int lastKeyCode = -1; private String eol = null; private static boolean mxLaunch = false; // we don't want to clear the M-x status result on M-x launch private boolean initStatusMsg() { return !mxLaunch; } protected abstract void addOtherListeners(IWorkbenchPage page,ISourceViewer viewer, StyledText widget); protected abstract void removeOtherListeners(IWorkbenchPage page,ISourceViewer viewer, StyledText widget); /** * Perform any minibuffer setup. Called from beginSession before any actions are taken * * @param editor * @param page * @return true if initialization was successful, else false */ protected abstract boolean initializeBuffer(ITextEditor editor, IWorkbenchPage page); /** * Perform the desired action on the result of the minibuffer input * * @param editor * @param minibufferResult usually a String * * @return true if we should exit after the execution */ protected abstract boolean executeResult(ITextEditor editor, Object minibufferResult); /** * Get the prefix for the minibuffer prompt * This may be computed based on the current state of input in the minibuffer * * @return the prefix String */ protected abstract String getMinibufferPrefix(); // we use this to temporarily disable key filters on Ctrl protected IBindingService bindingService; protected boolean isInBegin() {return inBegin;} // TODO: move to ExecutingMinibuffer protected boolean isExecuting() {return executing;} protected void setExecuting(boolean executing) { this.executing = executing; } protected boolean isCompleting() { return false; } protected void showCompletions() {} /** * @return the installed state */ protected boolean isInstalled() { return installed; } /** * minibuffer impl will force lower case if true * * @param lowercase */ protected void setLowercase(boolean lowercase) { this.lowercase = lowercase; } protected boolean isLowercase() { return lowercase; } /** * Set when a minibuffer command is launched from M-x * * @param true iff launched from M-x */ static void setMxLaunch(boolean mxLaunch) { WithMinibuffer.mxLaunch = mxLaunch; } /** * @return the editor */ protected ITextEditor getEditor() { return editor; } protected IDocument getDocument() { if (document == null) { document = editor.getDocumentProvider().getDocument(editor.getEditorInput()); } return document; } /** * @return the page */ protected IWorkbenchPage getPage() { return page; } protected StyledText getTextWidget() { return widget; } protected ISourceViewer getViewer() { return viewer; } protected IBindingService getBindingService() { return bindingService; } protected MinibufferImpl getMB() { return minibufferStringImpl; } public void setCommandId(String commandId) { this.commandId = commandId; } public String getCommandId() { return commandId; } protected void beep() { EmacsPlusUtils.beep(); } public boolean beginSession(ITextEditor editor, IWorkbenchPage page, ExecutionEvent event) { boolean ok = false; this.editor = editor; this.page = page; // TODO: Hack? When the command view was double clicked, then M-x executing // the editor doesn't really have focus, so force it. try { inBegin = true; editor.setFocus(); } finally { inBegin = false; } minibufferStringImpl = new MinibufferImpl(getDocument(),lowercase); if (initializeBuffer(editor,page)) { ok = install(); if (ok) { if (handlesCtrl() || handlesAlt()){ setKeyFilter(false); } if (initStatusMsg()) { // clear any normal/error message doSetResultMessage(EMPTY_STR, true); doSetResultMessage(EMPTY_STR, false); } // initialize our status message updateStatusLine(EMPTY_STR); } else { endSession(); } } return ok; } public void endSession() { if (this.editor != null) { leave(); } } /** * Installs this target. I.e. adds all required listeners. */ private boolean install() { if (editor instanceof AbstractTextEditor && !isInstalled()) { bindingService = (IBindingService) PlatformUI.getWorkbench().getService(IBindingService.class); viewer = findSourceViewer(editor); if (viewer != null) { widget = viewer.getTextWidget(); if (widget == null || widget.isDisposed()) { viewer = null; widget = null; return false; } addStatusContribution(editor); widget.addMouseListener(this); widget.addFocusListener(this); viewer.addTextListener(this); ISelectionProvider selectionProvider = viewer.getSelectionProvider(); if (selectionProvider != null) selectionProvider.addSelectionChangedListener(this); if (viewer instanceof ITextViewerExtension){ ((ITextViewerExtension) viewer).prependVerifyKeyListener(this); KbdMacroSupport.getInstance().continueKbdMacro(this,editor); } else { widget.addVerifyKeyListener(this); } addOtherListeners(page,viewer, widget); installed = true; } } return installed; } /** * Uninstalls itself. I.e. removes all listeners installed in * <code>install</code>. */ private void uninstall() { try { if (isInstalled()) { setKeyFilter(true); if (viewer != null) { removeStatusContribution(editor); viewer.removeTextListener(this); ISelectionProvider selectionProvider = viewer.getSelectionProvider(); if (selectionProvider != null) selectionProvider.removeSelectionChangedListener(this); if (widget != null && !widget.isDisposed()) { widget.removeMouseListener(this); widget.removeFocusListener(this); } if (viewer instanceof ITextViewerExtension) { ((ITextViewerExtension) viewer).removeVerifyKeyListener(this); KbdMacroSupport.setKbdMinibuffer(null); } else { if (widget != null && !widget.isDisposed()) widget.removeVerifyKeyListener(this); } removeOtherListeners(page, viewer, widget); } } } finally { widget = null; page = null; // TODO: elsewhere? // bindingService = null; installed = false; } } private boolean left = false; protected void leave() { leave(false);} protected void leave(boolean closeDialog) { try { if (!left) { uninstall(); if (closeDialog || !isExecuting()) { closeDialog(); } String resultStr = getResultString(); setResultMessage(resultStr != null ? resultStr : EMPTY_STR, getResultError()); } } finally { MarkUtils.setCurrentCommand(null); setExecuting(false); editor = null; left = true; } } protected void closeDialog() {} /** * Write the message to the minibuffer part of the status line */ public void updateStatusLine() { updateStatusLine(prevMessage); } private String prevMessage = EMPTY_STR; protected void updateStatusLine(String message) { if (statusItem != null && (!KbdMacroSupport.getInstance().isExecuting()) ) { { prevMessage = message; String normalizedMessage = normalizeString(message); statusItem.setText(getMinibufferPrefix() + normalizedMessage); ((IStatusFieldExtension) statusItem).setVisible(true); // make sure we're still active if (editor != null) { EmacsPlusUtils.forceStatusUpdate(editor); } } } } /** * Normalize message display - the default behavior is to return it unchanged * @param message * @return the message */ protected String normalizeString(String message) { return message; } protected String normalizeChar(char ocp) { String result = null; if (ocp < ' ') { switch (ocp) { case CR: result = N_RET; break; case LF: result = N_NEW; break; case '\t': result = N_TAB; break; case '\f': result = N_FF; break; case '\b': result = N_BS; break; default: result = N_GEN + ocp; } } else { result = String.valueOf(ocp);; } return result; } /************ result message ******************/ protected void setResultString(String resultString, boolean resultError) { this.resultString = resultString; this.resultError = resultError; } protected String getResultString() { return resultString; } protected boolean getResultError() { return resultError; } protected void setResultMessage(String message) { doSetResultMessage(message,resultError); } /** * Write the message to the standard part of the status line * * @param message * @param error */ protected void setResultMessage(String message, boolean error) { doSetResultMessage(message,error); } private void doSetResultMessage(String message, boolean error) { String mes = EMPTY_STR; if (message != null) { mes = ((message.length() > 0) ? getMinibufferPrefix() : EMPTY_STR) + message; } if (editor != null) { if (page != null) { page.activate(editor); } EmacsPlusUtils.showMessage(editor, mes, error); } } private synchronized void removeStatusContribution(IWorkbenchPart part) { statusItem.setVisible(false); EmacsPlusUtils.getStatusLineManager(part).remove(statusItem); EmacsPlusUtils.forceStatusUpdate(part); statusItem.setText(EMPTY_STR); } private synchronized void addStatusContribution(IWorkbenchPart editor) { statusItem = getStatusLineItem(); try { EmacsPlusUtils.getStatusLineManager(editor).insertBefore(POSITION_ID, statusItem); } catch (IllegalArgumentException e) { EmacsPlusUtils.getStatusLineManager(editor).add(statusItem); } statusItem.setVisible(true); statusItem.setText(EMPTY_STR); } private synchronized StatusLineContributionItem getStatusLineItem() { if (statusItem == null) { statusItem = new StatusLineContributionItem(STATUS_ID, true, getStatusLineLength()); } return statusItem; } // TODO: compute a reasonable length protected int getStatusLineLength() { return 80 + 3; } /* ***************** key handling *******************/ protected abstract boolean handlesCtrl(); protected abstract boolean handlesAlt(); protected abstract boolean handlesTab(); /** * @return the lastKeyCode */ protected int getLastKeyCode() { return lastKeyCode; } // Added to enable TRAVERSE key workaround (see HistoryMinibuffer) protected void setLastKeyCode(int keyCode) { lastKeyCode = keyCode; } protected void handleKey(VerifyEvent event) { try { if (!event.doit) return; if (event.character != 0) { // process typed character charEvent(event); } else { // some other key down noCharEvent(event); } } catch (Exception e) { System.out.println(e); e.printStackTrace(); } finally { setLastKeyCode(event.keyCode); } } protected final static Character QUOTING = 'Q'; protected boolean isQuoting(VerifyEvent event){ return (event.data instanceof Character && (Character)event.data == QUOTING && (event.stateMask & SWT.CTRL) != 0); } protected void charEvent(VerifyEvent event) { switch (event.character) { case 0x0D: // CR - execute command (if complete) \r if (isQuoting(event)) { event.doit = false; dispatchCtrl(event); break; } executeCR(event); break; case 0x1B: // ESC - another way to leave // TODO - could also be ^[ KbdMacroSupport.getInstance().exitWhenDefining(); leave(true); event.doit = false; break; case 0x08: // BS backSpaceChar(event); break; case 0x7F: // DEL deleteChar(event); break; case SWT.TAB: // disable after tab traversal interception if (handlesTab()){ dispatchTab(event); } event.doit = false; break; //case '?': // ? completion disabled as its used as a simple wildcard case ' ': // space completion if (isCompleting()) { showCompletions(); event.doit = false; break; } default: // If we're on a mac, then treat ALT & COMMAND the same in the minibuffer boolean ismac = isMac(); // mask away any extraneous modifier characters for any direct equality tests. see SWT.MODIFIER_MASK // make ALT and COMMAND behave equivalently on the mac for Ctrl or Alt dispatch int sm = event.stateMask & SWT.MODIFIER_MASK; if (checkControl(event)) { if (dispatchCtrl(event)) { event.doit = false; break; } } else if (checkAlt(event)) { if (dispatchAlt(event)) { event.doit = false; break; } } else if (checkAltCtrl(event) && dispatchAltCtrl(event)) { event.doit = false; break; } else { // SWT.ALT | SWT.CTRL covers AltGraph - used in international keyboards (see Eclipse bug 43049) // Also special chars on MacOs (see Eclipse bug 272994) // Although, testing on a mac shows that the Option-<char>, comes in as keyCode == 0 and no modifiers boolean special = (ismac ? ((sm == (SWT.ALT | SWT.SHIFT)) || sm == SWT.ALT) : sm == (SWT.ALT | SWT.CTRL)); // but if the key has a command binding associated, then leave and process if (special && hasBinding(event)) { ITextEditor ed = editor; leave(); executeBinding(ed,event); event.doit = false; } else if (sm == 0 || sm == SWT.SHIFT || special) { event.doit = false; if (event.keyCode != 0 || (ismac && event.character != 0)) { addIt(event); } } } } } /** * Is this a Ctrl VerifyEvent we want to handle * * @param event * @return true if yes, else false */ boolean checkControl(VerifyEvent event) { return (event.stateMask & SWT.CTRL) != 0 && ((event.stateMask & SWT.ALT) == 0 && (!isMac() || (event.stateMask & SWT.COMMAND) == 0)) && handlesCtrl(); } /** * Is this an Alt VerifyEvent we want to handle * * @param event * @return true if yes, else false */ boolean checkAlt(VerifyEvent event) { return ((event.stateMask & SWT.ALT) != 0 || (isMac() && (event.stateMask & SWT.COMMAND) != 0)) && (event.stateMask & SWT.CTRL) == 0 && handlesAlt(); } /** * Is this a Ctrl Alt VerifyEvent we want to handle * * @param event * @return true if yes, else false */ boolean checkAltCtrl(VerifyEvent event) { int sm = event.stateMask & SWT.MODIFIER_MASK; return (sm == (SWT.ALT | SWT.CTRL) || (isMac() && sm == (SWT.COMMAND | SWT.CTRL))); } /** * @param event */ protected void executeCR(VerifyEvent event) { boolean shouldLeave = true; try { shouldLeave = executeResult(editor, getMBString()); } finally { event.doit = false; if (shouldLeave) { crExitKbdMacro(); leave(true); } } } /** * Normally, store exit of the minibuffer on CR * However a minibuffer such as MetaXMinibuffer, that chains execution, should not */ protected void crExitKbdMacro() { KbdMacroSupport.getInstance().exitWhenDefining(); } protected void noCharEvent(VerifyEvent event) { switch (event.keyCode) { case SWT.CTRL: if (handlesCtrl()) { break; } closeDialog(); // else close dialog and leave leave(); break; case SWT.ALT: if (handlesAlt()) { break; } closeDialog(); // else close dialog and leave leave(); break; case SWT.PAGE_DOWN: // leave case SWT.PAGE_UP: case SWT.ARROW_DOWN: case SWT.ARROW_UP: leave(); break; // minimal support for in line editing case SWT.HOME: getMB().toBegin(); event.doit = false; break; case SWT.END: getMB().toEnd(); event.doit = false; break; case SWT.ARROW_LEFT: getMB().toLeft(); event.doit = false; break; case SWT.ARROW_RIGHT: getMB().toRight(); event.doit = false; break; } } /** * Allow children to handle Ctrl+<X> verify key events * Default implementation calls generic dispatch * * @param event * @return true */ protected boolean dispatchCtrl(VerifyEvent event) { return defaultDispatch(event); } /** * Allow children to handle <TAB> * Default does nothing * * @param event * @return false */ protected boolean dispatchTab(VerifyEvent event) { return false; } /** * Allow children to handle Alt+<X> verify key events * Default implementation calls generic dispatch * * @param event * @return true */ protected boolean dispatchAlt(VerifyEvent event) { return defaultDispatch(event); } /** * Allow children to handle Alt+Ctrl+<X> verify key events * Default implementation returns false * * @param event * @return true if Alt+Ctrl event handled */ protected boolean dispatchAltCtrl(VerifyEvent event) { return false; } /** * Default handler for Ctrl+<X> and Alt+<X> verify key events * Look for a binding and send it if bound, and leave * * @param event * @return true (always leave) */ protected boolean defaultDispatch(VerifyEvent event) { // default behavior queue binding and leaves ITextEditor ed = editor; leave(); executeBinding(ed,event); return true; } protected boolean hasBinding(KeyEvent event) { return hasBinding(event,((event.stateMask & SWT.MODIFIER_MASK) == 0 ? SWT.MOD3 : event.stateMask)); } protected boolean hasBinding(KeyEvent event, int mode) { boolean result = false; // ensure key is upper case KeyStroke key = KeyStroke.getInstance(mode, Character.toUpperCase(event.keyCode)); boolean isFilterDisabled = !getKeyFilter(); try { if (isFilterDisabled) { setKeyFilter(true); } result = bindingService.isPerfectMatch(KeySequence.getInstance(key)); } finally { if (isFilterDisabled) { setKeyFilter(false); } } return result; } /** * Based on the KeyEvent, get the perfect match binding * * @param event * @param checkEnabled if true, the command in the binding must be enabled * @return a binding that perfectly matches the KeyEvent, or null */ // TODO explain why always force ALT in normal case? private Binding getBinding(KeyEvent event, int mode, boolean checkEnabled) { Binding result = null; // ensure key is upper case KeyStroke key = KeyStroke.getInstance(mode, Character.toUpperCase(event.keyCode)); boolean isFilterDisabled = !getKeyFilter(); try { if (isFilterDisabled) { setKeyFilter(true); } // Shadowed commands shouldn't be enabled, but they are ... so protect // (e.g. C-x will perfect match and be enabled even though shadowed by C-x C-x) if (checkEnabled && bindingService.isPartialMatch(KeySequence.getInstance(key))) { result = null; } else { result = bindingService.getPerfectMatch(KeySequence.getInstance(key)); if (result != null && checkEnabled && !result.getParameterizedCommand().getCommand().isEnabled()) { result = null; } } } finally { if (isFilterDisabled) { setKeyFilter(false); } } return result; } /** * Execute the binding if possible, else resend the event * * @param ed * @param event from which to determine the binding * @return true if binding executed */ protected boolean executeBinding(ITextEditor ed, VerifyEvent event) { return executeBinding(ed,((event.stateMask & SWT.MODIFIER_MASK)== 0 ? SWT.MOD3 : event.stateMask),event); } protected boolean executeBinding(ITextEditor ed, int mode, VerifyEvent event) { boolean result = false; Binding binding = getBinding(event, mode, true); if (binding != null && ed != null) { // inform kbd macro of direct execution by minibuffer if (KbdMacroSupport.getInstance().isExecuting(binding)) { // when kbd macro is executing, do it now callBinding(ed,binding); } else { // else 'schedule' it for when this command completes asyncCallBinding(ed,binding); } event.doit = false; result = true; } else { // queue event resendEvent(event); } return result; } /** * Add an asynchronous call to execute the command after current command completes */ protected void asyncCallBinding(final ITextEditor editor, final Binding binding) { if (binding != null) { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { public void run() { callBinding(editor, binding); } }); } } protected void asyncPostEvent(final KeyEvent event) { if (event != null) { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { public void run() { resendEvent(event); } }); } } /** * @param editor * @param binding * @throws ExecutionException * @throws NotDefinedException * @throws NotEnabledException * @throws NotHandledException */ private void callBinding(final ITextEditor editor, final Binding binding) { try { IHandlerService service = (IHandlerService) editor.getSite().getService(IHandlerService.class); ParameterizedCommand pcommand = binding.getParameterizedCommand(); service.executeCommand(pcommand, null); } catch (Exception e) { // Shouldn't happen, but fail quietly } } protected final String getEol() { IDocument document = getDocument(); if (eol == null) { try { if (document instanceof IDocumentExtension4) { eol = ((IDocumentExtension4)document).getDefaultLineDelimiter(); } else { eol = document.getLineDelimiter(0); } } catch (BadLocationException e) { } } return eol; } /** * Synthesize and re-send a sendable event * * @param event */ void resendEvent(KeyEvent event) { boolean isFilterDisabled = !getKeyFilter(); try { if (isFilterDisabled) { setKeyFilter(true); } Event synthEvent = new Event(); synthEvent.stateMask = event.stateMask; // display.post() wants the key character sans control synthEvent.character = (char)event.keyCode; synthEvent.keyCode = event.keyCode; synthEvent.doit = true; synthEvent.type = SWT.KeyDown; event.display.post(synthEvent); } finally { if (isFilterDisabled) { setKeyFilter(false); } } } protected boolean getKeyFilter() { return (bindingService.isKeyFilterEnabled()); } protected void setKeyFilter(boolean val) { bindingService.setKeyFilterEnabled(val); } protected IFindReplaceTarget getTarget() { return (IFindReplaceTarget)getEditor().getAdapter(IFindReplaceTarget.class); } /* ***************** All the many listeners ***************** */ //FocusListener /* * @see FocusListener#focusGained(org.eclipse.swt.events.FocusEvent) */ public void focusGained(FocusEvent e) { leave(); } /* * @see FocusListener#focusLost(org.eclipse.swt.events.FocusEvent) */ public void focusLost(FocusEvent e) { leave(); } // ISelectionChangedListener public void selectionChanged(SelectionChangedEvent event) { leave(); } // ITextListener /* * @see ITextListener#textChanged(TextEvent) */ public void textChanged(TextEvent event) { if (event.getDocumentEvent() != null) leave(true); } // MouseListener /* * @see MouseListener##mouseDoubleClick(MouseEvent) */ public void mouseDoubleClick(MouseEvent e) { leave(true); } /* * @see MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent) */ public void mouseDown(MouseEvent e) { leave(true); } /* * @see MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent) */ public void mouseUp(MouseEvent e) { leave(true); } // VerifyKeyListener /* * @see VerifyKeyListener#verifyKey(VerifyEvent) */ public void verifyKey(VerifyEvent event) { if (!cmdinit) { // store the correct minibuffer command on first character as the Eclipse command // system has called the ICommandService listener's postExecute (which removed the setting) MarkUtils.setCurrentCommand(getCommandId()); cmdinit = true; } handleKey(event); } private boolean cmdinit = false; /* ***************** minibuffer string *******************/ protected int[] getEolChars() { return getMB().getEolChars(); } protected void addIt(VerifyEvent event) { updateStatusLine(getMB().addChar(event.character)); event.doit = false; } protected void backSpaceChar(VerifyEvent event) { updateStatusLine(getMB().bsChar()); event.doit = false; } protected void addIt(String addStr) { getMB().append(addStr); } protected void deleteChar(VerifyEvent event) { updateStatusLine(getMB().delChar()); event.doit = false; } protected void initMinibuffer(String newString) { updateStatusLine(setMBString(newString)); } protected String getMBString() { return getMB().getString(); } protected String setMBString(String newString) { return getMB().init(newString); } protected String getSearchString() { return getMBString(); } protected int getMBLength() { return getMB().getLength(); } protected void setMBLength(int length) { getMB().setLength(length); } /* ***************** unfortunate evil *******************/ // The protected method & private field that gives us the editor viewer for registration purposes private static String RE_METHOD_ID = "getSourceViewer"; //$NON-NLS-1$ private static String RE_MEMBER_ID = "fSourceViewer"; //$NON-NLS-1$ // Totally evil code, as Eclipse has no adapter for accessing the viewer private ISourceViewer findSourceViewer(ITextEditor editor) { // evil ISourceViewer result = null; if (editor != null && editor instanceof AbstractTextEditor) { result = (ISourceViewer) EmacsPlusUtils.getAM((AbstractTextEditor) editor, RE_METHOD_ID); //$NON-NLS-1$ if (result == null) { // even more evil result = (ISourceViewer) EmacsPlusUtils.getAF((AbstractTextEditor) editor, RE_MEMBER_ID); //$NON-NLS-1$ } } return result; } }