/** * 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.minibuffer; import java.util.SortedMap; import org.eclipse.core.commands.Command; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.IExecutionListener; import org.eclipse.core.commands.NotHandledException; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.commands.ICommandService; import org.eclipse.ui.texteditor.ITextEditor; import com.mulgasoft.emacsplus.Beeper; import com.mulgasoft.emacsplus.EmacsPlusActivator; import com.mulgasoft.emacsplus.EmacsPlusUtils; import com.mulgasoft.emacsplus.IEmacsPlusCommandDefinitionIds; import com.mulgasoft.emacsplus.RingBuffer; import com.mulgasoft.emacsplus.execute.CommandHelp; import com.mulgasoft.emacsplus.execute.CommandSupport; import com.mulgasoft.emacsplus.execute.ICommandResult; import com.mulgasoft.emacsplus.execute.MetaXDialog; /** * Minibuffer for reading a valid command, with completion, for execution * * @author Mark Feber - initial API and implementation */ public class MetaXMinibuffer extends CompletionMinibuffer implements IExecutionListener { private static String NOCOMMANDS_MSG = A_MSG + EmacsPlusActivator.getResourceString("MetaX_NoCommands") + Z_MSG; //$NON-NLS-1$ private static String BINDINGS = EmacsPlusActivator.getResourceString("Cmd_Bindings"); //$NON-NLS-1$ private CommandSupport commander = null; private SortedMap<String, Command> commandList; // perform completion again on <CR> if needed boolean completeOnExit = true; // if true, then ignore disabled setting on command private boolean ignoreDisabled = false; /** * @param executable */ public MetaXMinibuffer(IMinibufferExecutable executable) { super(executable); setLowercase(true); } protected String getMinibufferPrefix() { return getCompletionMinibufferPrefix(); } /** * @return the ignoreDisabled flag */ protected boolean isIgnoreDisabled() { return ignoreDisabled; } /** * @param ignoreDisabled */ public void setIgnoreDisabled(boolean ignoreDisabled) { this.ignoreDisabled = ignoreDisabled; } // Process the command private void setResultString(String resultString) { setResultString(resultString, false); } ICommandResult copyCommand(final Command command, final String name) { return new ICommandResult() { public Command getCommand() { return command; } public String getName() { return name; } }; } /** * @see com.mulgasoft.emacsplus.minibuffer.ExecutingMinibuffer#executeResult(org.eclipse.ui.texteditor.ITextEditor, java.lang.Object) */ protected boolean executeResult(ITextEditor editor, Object command) { boolean result = true; closeDialog(); CommandSupport commandControl = getCommandSupport(); if (commandControl != null) { String commandS = (String) command; Command com = commandList.get(commandS); // Invoke completion on exit, if the string came through uncompleted if (completeOnExit && com == null) { try { SortedMap<String, Command> viewTree = commandControl .getCommandSubTree(commandList, commandS, false, isIgnoreDisabled()); if (viewTree.size() == 1) { commandS = viewTree.firstKey(); com = commandList.get(commandS); } } catch (Exception e) { // Could be a java.util.regex.PatternSyntaxException on weird input // when looking for a match; just ignore and command will abort } } if (com != null) { addToHistory(commandS); // add to command history setExecuting(true); setResultString(commandS); // setResultMessage(commandS,false); try { setMxLaunch(true); result = exitExecuteResult(getEditor(), copyCommand(com, commandS)); } finally { setMxLaunch(false); } } } return result; } /**************** Specialize minibuffer ******************/ protected void closeDialog() { try { if (getMiniDialog() != null) { ((MetaXDialog)getMiniDialog()).shutdown(); super.closeDialog(); } } catch (Exception e) {} } private CommandSupport getCommandSupport() { if (commander == null) { commander = new CommandSupport(); commandList = commander.getCommandList(getEditor()); if (commandList.isEmpty()) { setResultString(NOCOMMANDS_MSG, true); commander = null; leave(true); } } return commander; } /** * Expose the command completion dialog */ protected void showCompletions() { CommandSupport commandControl = getCommandSupport(); if (commandControl != null) { String command = getMBString(); SortedMap<String, Command> viewTree; if (isSearching() && getSearchStr().length() > 0) { @SuppressWarnings("unchecked") // need local variable for unchecked annotation SortedMap<String, Command> tmpTree = (SortedMap<String,Command>)getSearchResults(); viewTree = tmpTree; } else { viewTree = commandControl.getCommandSubTree(commandList, command, false, isIgnoreDisabled()); } if (viewTree != null) { if (viewTree.size() > 1) { if (getMiniDialog() == null) { setMiniDialog(new MetaXDialog(null, this, getEditor())); } ((MetaXDialog) getMiniDialog()).open(viewTree); setShowingCompletions(true); } EmacsPlusUtils.forceStatusUpdate(getEditor()); String newCommand; if (viewTree.size() == 0) { updateStatusLine((isSearching() ? EMPTY_STR : command) + NOMATCH_MSG); } else if (viewTree.size() == 1) { closeDialog(); newCommand = viewTree.firstKey(); if (!command.equals(newCommand) && !isSearching()) { initMinibuffer(newCommand); } updateStatusLine(newCommand + COMPLETE_MSG); } else if (!isSearching()) { if (command.length() > 0) { newCommand = commandControl.getCommonString(viewTree, command); if (!command.equals(newCommand)) { initMinibuffer(newCommand); } } updateStatusLine(getMBString()); } } else { updateStatusLine(command + NOMATCH_MSG); } } } /** * @see com.mulgasoft.emacsplus.minibuffer.CompletionMinibuffer#getCompletions(java.lang.String) */ @Override protected SortedMap<String,?> getCompletions(String searchSubstr) { SortedMap<String,?> result = null; CommandSupport commandControl = getCommandSupport(); if (commandControl != null ) { result = commandControl.getCommandSubTree(commandList, RWILD + searchSubstr + RWILD, true, isIgnoreDisabled()); } return result; } /** * @see com.mulgasoft.emacsplus.minibuffer.CompletionMinibuffer#getCompletions() */ @Override protected SortedMap<String, ?> getCompletions() { return null; } /**************** Specialize minibuffer ******************/ @Override protected boolean initializeBuffer(ITextEditor editor, IWorkbenchPage page) { if (!getExecutable().isUniversalPresent()) { EmacsPlusUtils.clearMessage(editor); } return true; } @Override public boolean beginSession(ITextEditor editor, IWorkbenchPage page, ExecutionEvent event) { try { setMxLaunch(true); boolean result = super.beginSession(editor, page, event); if (!result){ super.setResultMessage(NOCOMMANDS_MSG, true); } return result; } finally { setMxLaunch(false); } } protected void leave(boolean closeDialog) { try { super.leave(closeDialog); } finally { this.commandList = null; } } protected void crExitKbdMacro() { // meta-x doesn't appear in the kbd macro, so we don't register our exit } /** * @see com.mulgasoft.emacsplus.minibuffer.ExecutingMinibuffer#setResultMessage(java.lang.String, boolean) */ @Override protected void setResultMessage(String message, boolean error) { String cmd; if (!isExecuting()) { if (error) { super.setResultMessage(message, true, true); } else { super.setResultMessage(ABORT_MSG, true, true); } Beeper.interrupt(); } else if ((cmd = getResultString()) != null) { Command com = null; if ((com = commandList.get(cmd)) != null) { // get the active bindings String bindings = CommandHelp.getKeyBindingString(com,true); if (bindings != null) { cmd = String.format(BINDINGS, cmd, bindings); } else { cmd = null; } } // some commands want the status area to themselves, so don't update if (IEmacsPlusCommandDefinitionIds.statusCommands.get(com.getId())== null) { super.setResultMessage(cmd, false, true); } } } // Listeners /** * @see com.mulgasoft.emacsplus.minibuffer.CompletionMinibuffer#addOtherListeners(IWorkbenchPage, ISourceViewer, StyledText) */ @Override protected void addOtherListeners(IWorkbenchPage page, ISourceViewer viewer, StyledText widget) { ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getAdapter(ICommandService.class); if (commandService != null) { commandService.addExecutionListener(this); } super.addOtherListeners(page, viewer, widget); } /** * @see com.mulgasoft.emacsplus.minibuffer.CompletionMinibuffer#removeOtherListeners(IWorkbenchPage, ISourceViewer, StyledText) */ @Override protected void removeOtherListeners(IWorkbenchPage page, ISourceViewer viewer, StyledText widget) { ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getAdapter(ICommandService.class); if (commandService != null) { commandService.removeExecutionListener(this); } super.removeOtherListeners(page, viewer, widget); } // ISelectExecute method /** * Execute the command. Called from mouse click in MetaXDialog * * @see com.mulgasoft.emacsplus.execute.ISelectExecute#execute(java.lang.Object) */ public void execute(Object selection) { String key = (String)selection; setExecuting(true); executeResult(getEditor(),key); leave(true); } // IFocusListener /** * @see FocusListener#focusGained(org.eclipse.swt.events.FocusEvent) */ @Override public void focusGained(FocusEvent e) { if (!isInBegin() && !isExecuting()) { leave(); } } // ISelectionChangedListener /** * @see com.mulgasoft.emacsplus.minibuffer.CompletionMinibuffer#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent) */ public void selectionChanged(SelectionChangedEvent event) { if (!isExecuting()) { leave(); } } // IExecutionListener /** * @see org.eclipse.core.commands.IExecutionListener#notHandled(java.lang.String, org.eclipse.core.commands.NotHandledException) */ public void notHandled(String commandId, NotHandledException exception) { leave(); } /** * @see org.eclipse.core.commands.IExecutionListener#postExecuteFailure(java.lang.String, org.eclipse.core.commands.ExecutionException) */ public void postExecuteFailure(String commandId, ExecutionException exception) { if (!isExecuting()) { leave(); } } /** * @see org.eclipse.core.commands.IExecutionListener#postExecuteSuccess(java.lang.String, java.lang.Object) */ public void postExecuteSuccess(String commandId, Object returnValue) { } /** * @see org.eclipse.core.commands.IExecutionListener#preExecute(java.lang.String, org.eclipse.core.commands.ExecutionEvent) */ public void preExecute(String commandId, ExecutionEvent event) { } /**** Local RingBuffer: use lazy initialization holder class idiom ****/ /** * @see com.mulgasoft.emacsplus.minibuffer.HistoryMinibuffer#getHistoryRing() */ @Override @SuppressWarnings("unchecked") protected RingBuffer<String> getHistoryRing() { return MetaXRing.ring; } private static class MetaXRing { static final RingBuffer<String> ring = new RingBuffer<String>(); } }