/** * 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; import static com.mulgasoft.emacsplus.EmacsPlusUtils.getBindingService; import static com.mulgasoft.emacsplus.EmacsPlusUtils.getTotalBindings; import java.util.ArrayList; import java.util.Collection; import java.util.Hashtable; import java.util.Map; import org.eclipse.core.commands.Command; import org.eclipse.core.commands.ParameterizedCommand; import org.eclipse.jface.action.IAction; import org.eclipse.jface.bindings.Binding; import org.eclipse.jface.bindings.BindingManagerEvent; import org.eclipse.jface.bindings.IBindingManagerListener; import org.eclipse.jface.bindings.Scheme; import org.eclipse.jface.bindings.TriggerSequence; import org.eclipse.jface.bindings.keys.KeySequence; import org.eclipse.jface.bindings.keys.KeyStroke; import org.eclipse.jface.dialogs.IPageChangeProvider; import org.eclipse.jface.dialogs.IPageChangedListener; import org.eclipse.jface.dialogs.PageChangedEvent; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.widgets.Listener; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IPartListener2; import org.eclipse.ui.IWindowListener; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPartReference; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.commands.ICommandService; import org.eclipse.ui.internal.keys.BindingService; import org.eclipse.ui.keys.IBindingService; import org.eclipse.ui.part.MultiEditor; import org.eclipse.ui.part.MultiPageEditorPart; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.TextEditorAction; import com.mulgasoft.emacsplus.KillRingListeners.EmacsActionDelegate; import com.mulgasoft.emacsplus.execute.KbdMacroSupport; import com.mulgasoft.emacsplus.execute.RepeatCommandSupport; import com.mulgasoft.emacsplus.preferences.EmacsPlusPreferenceConstants; /** * Activate all the pretty listeners * * @author Mark Feber - initial API and implementation */ @SuppressWarnings("restriction") // For dangerous use of BindingService.addBinding/removeBinding public enum EmacsPlusActivation implements IPartListener2 { ACTIVATION_INSTANCE; private static Map<String,InstallState> editors; private static Map<String,String> actors; static { editors = new Hashtable<String,InstallState>(); actors = new Hashtable<String,String>(); IPreferenceStore store = EmacsPlusActivator.getDefault().getPreferenceStore(); if (store != null) { // Just set the value, it will be interpreted in activateEditor ACTIVATION_INSTANCE.digitArgument = (store.getBoolean(EmacsPlusPreferenceConstants.P_CTRL_DIGIT_ARGUMENT)); } } // Has the kill ring been activated? private boolean activated = false; // cache preference value for whether or not to force digit-argument bindings private boolean digitArgument = false; private Boolean ctrlBindings = null; // the set of character keys used in digit-argument private static final char[] digitKeys = {'-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; // Eclipse forces us to remember the most recently activated editor private IEditorPart activatedPart = null; private EmacsPlusActivation() {}; public static EmacsPlusActivation getInstance(){ return ACTIVATION_INSTANCE; } private boolean isActivated() { return activated; } private void setActivated(boolean activated) { this.activated = activated; } /** * Activate the listeners necessary to support Emacs+. * Add us as the part listener for the workbench page * Also, add the necessary listeners for the initial active editor on Eclipse startup * * If the user disables startup activation of the plug-in, then any kills (e.g. word delete, etc.) * will not be inserted into the ring until after the plug-in has been "manually" activated by * invoking an Emacs+ specific key binding, opening its preference page, etc. */ void activateListeners() { if (!isActivated()) { setActivated(true); IWorkbench bench = PlatformUI.getWorkbench(); IWorkbenchWindow window = bench.getActiveWorkbenchWindow(); // detect when the workbench is (re)activated, or a new window is opened bench.addWindowListener(KillRingListeners.getActivationListener()); bench.addWindowListener(getWindowActivationListener()); activateKeySchemeListener(bench); activateRepeatListener(bench); activatePage(window,true); } } private void activateRepeatListener(IWorkbench bench) { ((ICommandService)bench.getService(ICommandService.class)).addExecutionListener(RepeatCommandSupport.getInstance()); } /** * Potentially delay adding the dynamic bindings as even though they are in the emacs+ scheme, prefix keys can * still wind up blocking any other usage in the default binding due to yet another eclipse keybinding issue. */ private void activateKeySchemeListener(IWorkbench bench) { IBindingService bindingSvc = ((IBindingService) bench.getService(IBindingService.class)); // if emacs+ scheme is already enabled, add the dynamic bindings activateDynamics(bindingSvc.getActiveScheme().getId()); bindingSvc.addBindingManagerListener(new IBindingManagerListener() { public void bindingManagerChanged(BindingManagerEvent event) { if (event.isActiveSchemeChanged()) { Scheme schemeIn = event.getManager().getActiveScheme(); // Scheme schemeOut = event.getScheme(); // when changing schemes, this is called twice - // - once with the schemeIn=null & schemeOut=exiting scheme // - second with the schemeIn=new Scheme & schemeOut=null // but the dynamics will only be activated once either on eclipse start (above) or here on change to... if (schemeIn != null) { activateDynamics(schemeIn.getId()); } } } }); } private void activateDynamics(final String id) { if (EmacsPlusUtils.EMP_SCHEMEID.equals(id)) { // Run in next ui thread to ensure that the keybinding is completely set up and the dynamic bindings // are added to the emacs+ scheme EmacsPlusUtils.asyncUiRun(new Runnable() { public void run() { DynamicInitializer.initialize(); } }); } } /** * @param window * @param onPlugActivation true on plugin activation */ private void activatePage(IWorkbenchWindow window, boolean onPlugActivation) { IWorkbenchPage page = getActivePage(window); if (page != null) { page.addPartListener(this); // simulate part activation on workbench (re)activation activateEditor(page.getActiveEditor(),onPlugActivation); } } private void deactivatePage(IWorkbenchWindow window) { IWorkbenchPage page = getActivePage(window); if (page != null) { page.removePartListener(this); } } private IWorkbenchPage getActivePage(IWorkbenchWindow window) { IWorkbenchPage page = null; if (window != null) { page = window.getActivePage(); } if (page == null) { // Look for a window and get the page off it! IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows(); for (int i = 0; i < windows.length; i++) { if (windows[i] != null) { window = windows[i]; page = windows[i].getActivePage(); if (page != null) break; } } } return page; } private IWindowListener getWindowActivationListener() { return new IWindowListener() { // @Override public void windowActivated(IWorkbenchWindow window) { activatePage(window, false); } // @Override public void windowClosed(IWorkbenchWindow window) { deactivatePage(window); } // @Override public void windowDeactivated(IWorkbenchWindow window) { deactivatePage(window); KbdMacroSupport.interruptKbdMacro(); } // @Override public void windowOpened(IWorkbenchWindow window) { } }; } public void partActivated(IWorkbenchPartReference partRef) { if (partRef instanceof IEditorReference) { activateEditor(((IEditorReference) partRef).getEditor(false),false); } } public void partDeactivated(IWorkbenchPartReference partRef) { if (partRef instanceof IEditorReference) { IEditorPart epart = ((IEditorReference) partRef).getEditor(false); MarkUtils.removeActivationListeners(getActiveEditor(epart)); BufferLocal.getInstance().handleDeactivate(epart); } else { // check for clipped text from other views and add to the kill ring KillRing.getInstance().checkClipboard(); } } public void partBroughtToTop(IWorkbenchPartReference partRef) { } public void partClosed(IWorkbenchPartReference partRef) { if (partRef instanceof IEditorReference) { try { IEditorPart editor = ((IEditorReference) partRef).getEditor(false); if (editor instanceof MultiPageEditorPart) { // TODO: AFAIK there is no way to capture all the text editors in a multi-page // if there is more that one. This just grabs one, but it is better than nothing editor = (ITextEditor)editor.getAdapter(ITextEditor.class); } if (editor instanceof ITextEditor) { // Hack alert: Eclipse activates the next editor before closing the previous editor // so, if it is looking at the same document, don't remove the kill buffer listener if (activatedPart == null || (editor != activatedPart && editor.getEditorInput() != activatedPart.getEditorInput())) { IDocumentProvider provider = ((ITextEditor) editor).getDocumentProvider(); if (provider != null) { IDocument document = provider.getDocument(editor.getEditorInput()); if (document != null) { document.removeDocumentListener(getDocListener()); MarkRing.removeMarks(document); } } } removeActionListeners(editor); } } catch (Exception e) { e.printStackTrace(); } activatedPart = null; } } public void partHidden(IWorkbenchPartReference partRef) { } public void partInputChanged(IWorkbenchPartReference partRef) { if (partRef instanceof IEditorReference) { IEditorPart editor = ((IEditorReference) partRef).getEditor(false); addListeners(editor); } } public void partOpened(IWorkbenchPartReference partRef) { if (partRef instanceof IEditorReference) { IEditorPart editor = ((IEditorReference) partRef).getEditor(false); addListeners(editor); } } public void partVisible(IWorkbenchPartReference partRef) {} private IDocumentListener getDocListener() { return KillRing.getInstance(); } private void addListeners(IEditorPart editor) { ITextEditor active = getActiveEditor(editor); if (active != null) { doAddListeners(active); } if ((editor instanceof MultiPageEditorPart) || (editor instanceof MultiEditor)) { // Now, set up listeners for when the page changes within the editor if (editor instanceof IPageChangeProvider) { ((IPageChangeProvider) editor).addPageChangedListener(getPageListener()); } } } private ITextEditor getActiveEditor(IEditorPart editor) { return EmacsPlusUtils.getActiveTextEditor(editor); } private static IPageChangedListener pageListener; private IPageChangedListener getPageListener() { if (pageListener == null) { pageListener = new IPageChangedListener() { public void pageChanged(PageChangedEvent event) { Object obj = event.getSelectedPage(); if (obj instanceof ITextEditor) { activateEditor((ITextEditor)obj,false); } } }; } return pageListener; } /** * Add the basic listeners required on the editor * * @param editor */ private void doAddListeners(ITextEditor editor) { // Listen for edits on this doc addDocumentListeners(editor); // Listen for command executions addCommandListeners(editor); } /** * Add the kill ring instance as a document listener * * @param editor */ private void addDocumentListeners(ITextEditor editor){ IDocumentProvider provider = ((ITextEditor) editor).getDocumentProvider(); if (provider != null) { IDocument document = provider.getDocument(editor.getEditorInput()); if (document != null) { document.addDocumentListener(getDocListener()); } } } /** * Call whenever part (or sub-part) is activated * * @param epart * @param onPlugActivation true on plugin activation */ private void activateEditor(IEditorPart epart, boolean onPlugActivation) { if (epart != null) { activatedPart = epart; addListeners(epart); MarkUtils.addActivationListeners(getActiveEditor(epart)); Runnable check = new Runnable() { public void run() { checkOnActivation(activatedPart); } }; if (onPlugActivation) { // when activating Emacs+, run in the UI thread PlatformUI.getWorkbench().getDisplay().asyncExec(check); } else { check.run(); } } } /** * Miscellaneous tasks to perform during part activation * * @param epart */ private void checkOnActivation(IEditorPart epart) { ITextEditor editor = getActiveEditor(epart); if (editor != null) { KbdMacroSupport.getInstance().continueKbdMacro(editor); BufferLocal.getInstance().handleActivate(editor); checkIMEListener(editor); // check here as key bindings are not set up until part is activated, // and we need the InstallState set up as well (addListeners) checkDigitArgument(editor); } } /** * Disable Option+<char> in-line pre-edit text areas on Mac OS X * if the preference in set. * For Mac users that prefer the Option Meta binding, but don't want * Meta+<char> commands to generate the initial character (e.g. typing <Option>+u can * generate a dangling umlaut in addition to upper casing. * However, <Option>+u u, will still generate the mixed character * org.eclipse.swt.widgets.IME * * @param editor */ private static void checkIMEListener(final ITextEditor editor) { if (EmacsPlusUtils.isDisableOptionIMEPreferenece() && EmacsPlusUtils.isMac()) { StyledText widget = MarkUtils.getStyledWidget(editor); if (widget != null && widget.getIME() != null) { Listener[] listeners = widget.getIME().getListeners(SWT.ImeComposition); if (listeners != null) { for (Listener l : listeners) { widget.getIME().removeListener(SWT.ImeComposition, l); } } } } } private class InstallState { boolean digitEnabled = false; Collection<Binding> disabledBindings = null; } private boolean isDigitArgument() { return digitArgument; } /** * Sets the preference controlling the runtime enforcement of Ctrl-<number> * as digit-argument (as implemented by universal-argument) * * @param digitArgument * @return true if the preference value has changed */ public boolean setDigitArgument(boolean digitArgument) { boolean reset = this.digitArgument != digitArgument; this.digitArgument = digitArgument; if (reset) { IWorkbenchPage page = EmacsPlusUtils.getWorkbenchPage(); if (page != null) { IEditorPart editor = page.getActiveEditor(); if (editor instanceof ITextEditor) { checkDigitArgument(editor, editors.get(editor.getClass().getName())); } } } return reset; } private void checkDigitArgument(IEditorPart editor) { ITextEditor active = getActiveEditor(editor); if (active != null) { checkDigitArgument(active, editors.get(active.getClass().getName())); } } /** * Check if we should disable/enable bindings that interfere with digit-argument interpretation * * @param editor * @param digitState */ private void checkDigitArgument(final IEditorPart editor, final InstallState digitState) { // active only when Ctrl+<number> is bound to universal-argument if (digitState != null && hasDigitBindings(false)) { final int modKey = (EmacsPlusUtils.isMac() ? SWT.COMMAND : SWT.CTRL); // CTRL+<n> is sometimes used as a prefix character in other plugins. The following code forces // the Emacs digit-argument interpretation by using Eclipse internals to remove the offending // bindings automagically. This is controlled by a user-settable preference (initially disabled) // TODO binding disable/enable should more properly be done by context rather than editor type if (isDigitArgument()) { if (digitState.digitEnabled == false) { Collection<Binding> oldDisabledBindings = digitState.disabledBindings; Collection<Binding> disabled = disableBindings(editor, modKey, digitKeys, false); digitState.disabledBindings = disabled; // restore any old bindings restoreBindings(oldDisabledBindings,getBindingService()); } } else { if (digitState.digitEnabled == true && digitState.disabledBindings != null) { // restore disabled bindings restoreBindings(digitState.disabledBindings, getBindingService()); digitState.disabledBindings = null; } // check for any Emacs+ commands to disable (e.g. C-1 when in java editor) if (digitState.disabledBindings == null) { Collection<Binding> disabled = disableBindings(editor, modKey, digitKeys, true); digitState.disabledBindings = (disabled != null ? disabled : new ArrayList<Binding>()); } } digitState.digitEnabled = isDigitArgument(); } } /** * Add the command listeners to support the Emacs+ kill ring to the editor * This only needs to be done at most once per editor class * * @param editor */ private void addCommandListeners(IEditorPart editor) { addActionListeners(editor); // install execution listeners once per editor type String eclass = editor.getClass().getName(); if ((editors.get(eclass) == null)) { ICommandService ics = (ICommandService) editor.getSite().getService(ICommandService.class); if (ics != null) { Command com = null; // remember listener install state editors.put(eclass, new InstallState()); if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.COPY_QUALIFIED_NAME)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(false)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.KILL_FORWARD_SEXP)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(false)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.KILL_BACKWARD_SEXP)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(true)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.EMP_CUT)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(false)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.EMP_COPY)) != null) { com.addExecutionListener(KillRingListeners.getCopyExecListener(false)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.CUT_LINE)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(false)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.CUT_LINE_TO_BEGINNING)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(true)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.CUT_LINE_TO_END)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(false)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.DELETE_LINE)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(false)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.DELETE_LINE_TO_BEGINNING)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(true)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.DELETE_LINE_TO_END)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(false)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.DELETE_NEXT_WORD)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(false)); } if ((com = ics.getCommand(IEmacsPlusCommandDefinitionIds.DELETE_PREVIOUS_WORD)) != null) { com.addExecutionListener(KillRingListeners.getExecListener(true)); } } } } /** * Disable bindings that would interfere with Ctrl digit-argument bindings * If disableEmacs is true, then disable Emacs+ Ctrl arguments instead * This requires the internal BindingService.removeBinding method * * @param editor * @param stateMask * @param keys * @param disableEmacs - if true, invert interpretation * @return the collection of disabled bindings */ private Collection<Binding> disableBindings(IEditorPart editor, int stateMask, char[] keys, boolean disableEmacs) { Collection<Binding> removed = null; BindingService bs = getBindingService(); // only works if we get access to internal Binding Service if (bs != null) { // cache this as its a bit expensive to compute Map<TriggerSequence,Collection<Binding>> total = (disableEmacs ? getTotalBindings() : null); for (char key : keys) { KeySequence trigger = KeySequence.getInstance(KeyStroke.getInstance(stateMask,key)); Binding b = bs.getPerfectMatch(trigger); boolean ise; if (b != null) { if ((!(ise = isEmacsBinding(b)) && !disableEmacs) || (ise && disableEmacs && hasVariants(bs,total,trigger))) { bs.removeBinding(b); if (removed == null) { removed = new ArrayList<Binding>(); } removed.add(b); b = bs.getPerfectMatch(trigger); } } if (!disableEmacs) { Map<TriggerSequence, Binding> bindings = EmacsPlusUtils.getPartialMatches(bs,trigger); if (!bindings.isEmpty()) { Collection<Binding> r = removeBindings(bindings.values(), bs, editor); if (r != null) { if (removed == null) { removed = r; } else { removed.addAll(r); } } } } } } return removed; } /** * Check if the key sequence is either a prefix sequence or has multiple bindings * (not necessarily conflicts) * * @param bs * @param bindings * @param trigger * @return true if condition is detected */ private boolean hasVariants(BindingService bs, Map<TriggerSequence,Collection<Binding>> bindings, KeySequence trigger) { boolean result = bs.isPartialMatch(trigger); if (!result) { Collection<Binding> binds = bindings.get(KeySequence.getInstance(trigger.getKeyStrokes())); result = (binds != null && binds.size() > 1); } return result; } /** * Remove any bindings in the array that do not belong to Emacs+ * This requires the internal BindingService.removeBinding method * * @param bindings * @param editor */ private Collection<Binding> removeBindings(Collection<Binding> bindings, BindingService bs, IEditorPart editor) { ArrayList<Binding> result = null; if (bindings != null && bs != null) { for (Binding binding : bindings) { if (isEmacsBinding(binding)) { continue; } else { try { if (result == null) { result = new ArrayList<Binding>(); } // smash and grab bs.removeBinding(binding); result.add(binding); } catch (Exception e) { // ignore any problems } } } } return result; } /** * Attempt to restore bindings in the array * This requires the internal BindingService.addBinding method * * @param bindings * @param bs */ private void restoreBindings(Collection<Binding> bindings, BindingService bs) { if (bindings != null && bs != null) { for (Binding binding : bindings) { try { bs.addBinding(binding); } catch (Exception e) { // ignore any error } } } } private boolean isEmacsBinding(Binding binding) { boolean result = false; ParameterizedCommand p = binding.getParameterizedCommand(); if (p != null) { Command c = p.getCommand(); if (c != null && c.getId().contains(EmacsPlusUtils.MULGASOFT)) { result = true; } } return result; } /** * Check for presence of digit-argument bindings * * @return true if digit-argument is relevant */ public boolean hasDigitBindings(boolean force) { boolean result = false; // The normal binding service returns the 'current' bindings, so we must get the context free set if (force || ctrlBindings == null) { Map<TriggerSequence,Collection<Binding>> bindings = getTotalBindings(); if (!bindings.isEmpty()) { int modifier = EmacsPlusUtils.isMac() ? SWT.COMMAND : SWT.CTRL; // kludge: just see of <MODIFIER>+0 is bound in our binding scheme (universal-argument) Collection<Binding> binds = bindings.get(KeySequence.getInstance(KeyStroke.getInstance(modifier,'0'))); if (binds != null) { for (Binding b : binds) { if (IEmacsPlusCommandDefinitionIds.UNIVERSAL_ARGUMENT.equals(b.getParameterizedCommand().getId())) { result = true; break; } } } ctrlBindings = result; } } else { result = ctrlBindings; } return result; } /** * Restore all the naked Eclipse actions from inside our delegates * * @param editorpart */ private void removeActionListeners(IEditorPart editorpart) { String inputName = editorpart.getEditorInput().getName(); if (actors.containsKey(inputName)) { actors.remove(inputName); ITextEditor editor = (ITextEditor) editorpart; removeClipActionListener(editor, IEmacsPlusCommandDefinitionIds.EMP_COPY); removeClipActionListener(editor, ActionFactory.COPY.getId()); removeClipActionListener(editor, IEmacsPlusCommandDefinitionIds.EMP_CUT); removeClipActionListener(editor, ActionFactory.CUT.getId()); removeClipActionListener(editor, IEmacsPlusCommandDefinitionIds.COPY_QUALIFIED_NAME); removeClipActionListener(editor, IEmacsPlusCommandDefinitionIds.COPYQUALIFIEDNAME); } } /** * Restore the naked Eclipse action stored inside our delegate * * @param editor * @param actionName */ private void removeClipActionListener(ITextEditor editor, String actionName) { IAction action = editor.getAction(actionName); if (action != null && (action instanceof EmacsActionDelegate)) { IAction innerAction = ((EmacsActionDelegate)action).getAction(); if (innerAction != null) { editor.setAction(actionName, innerAction); } } } /** * Add (once per editor) the listeners necessary to capture copy and cut actions that * cannot be intercepted via the command mechanism * * @param editorpart */ private void addActionListeners(IEditorPart editorpart) { IAction action; if (editorpart instanceof ITextEditor) { ITextEditor editor = (ITextEditor) editorpart; String inputName = editorpart.getEditorInput().getName(); if (!actors.containsKey(inputName)) { actors.put(inputName, inputName); // Egregious hack required because menu actions do not go through // the command handler addClipActionListener(editor, IEmacsPlusCommandDefinitionIds.EMP_COPY,true); addClipActionListener(editor, ActionFactory.COPY.getId(),true); addClipActionListener(editor, IEmacsPlusCommandDefinitionIds.EMP_CUT,false); addClipActionListener(editor, ActionFactory.CUT.getId(),false); // Specifically check for java version of the id String qname = IEmacsPlusCommandDefinitionIds.COPY_QUALIFIED_NAME; action = editor.getAction(qname); if (action == null) { // This is the generic form qname = IEmacsPlusCommandDefinitionIds.COPYQUALIFIEDNAME; action = editor.getAction(qname); } if (action != null && !(action instanceof KillRingListeners.EmacsActionDelegate)) { setAction(editor,qname, new KillRingListeners.EmacsCopyActionDelegate(action, qname)); } } } } /** * Wrap and replace the Eclipse action with our delegate * * @param editor * @param actionName */ private void addClipActionListener(ITextEditor editor, String actionName, boolean isCopy) { IAction action = editor.getAction(actionName); IAction delegateAction; if (action != null && (action instanceof TextEditorAction)) { if (isCopy) { delegateAction = new KillRingListeners.EmacsCopyActionDelegate((TextEditorAction) action, actionName); } else { delegateAction = new KillRingListeners.EmacsCutActionDelegate((TextEditorAction) action, actionName); } setAction(editor,actionName, delegateAction); } } /** * It is possible that setAction will call a listener that must be run in the UI thread, * so post the invocation. This was initially reported in the context of Flex Builder * @param editor * @param actionName * @param action */ private void setAction(final ITextEditor editor, final String actionName, final IAction action) { EmacsPlusUtils.asyncUiRun(new Runnable() { public void run() { try { editor.setAction(actionName,action); } catch (Exception e) { // Print but ignore any exception at this point e.printStackTrace(); } } }); } }