Showing posts with label commands. Show all posts
Showing posts with label commands. Show all posts

Saturday, January 23, 2010

Adding actions to Eclipse console commands

This article assumes you know what Declarative Services (DS) are and have a basic understanding of OSGi.

Introduction

There is one big problem with the Eclipse console commands. You can have only one command with a specific name. That's of course the case in all consoles known to me. However in most consoles the commands express by their name what they actually do. Now in Eclipse we have good commands like "ss" for a quick overview of the currently known bundles in the system. Then there are the framework commands like "launch", "shutdown" and "exit" which fairly behave like you would expect them by their name. But do you know what the "list", "ls" or "enable", "en" commands do? Well, they come from the "Service Component Runtime" (SCR) bundle.
list/ls [-c] [bundle id] - Lists all components; add -c to display the complete info for each component;
enable/en  - Enables the specified component;
So when you load the SCR bundle the command "list" will be taken. And it will list components. A better name would have been "listcomponents". An even better approach would have been, if the Eclipse command framework was initially planned to have commands that can be extended by actions. Because then we would have a "list" command that could be extended by actions. We would have "list components", "list bundles" and "list services". There would be much less namespace clutter and a tighter set of commands. But since we cannot create a new "list" command, as SCR occupies it already, we have to go a slightly different way.

What are command actions?

Command actions are a way to group commands and redirect the concrete execution to an action. So for the SCR I would like to suggest the following command: component This command would have several actions: list,enable,disable,info and for the sake of completeness the short versions of the original SCR commands as actions: ls,en,dis,i A command action is an OSGi services, that can dynamically extend an existing command. Its service interface is really simple:
public interface CommandAction {
 void execute(CommandActionContext context) throws Exception;
}
The action gets in a context that allows it to query execution parameters that the user typed in the command line. It also allows the action to print text on the console.
public interface CommandActionContext {
 String getArgument(int index);

 boolean hasOption(String name);

 boolean isVerbose();

 void print(String text);

 void println(String text);
}
A sample action would look like this:
public class FileCopyAction implements CommandAction {
 void execute(CommandActionContext context) throws Exception {
   String from = context.getArgument(0);
   String to = context.getArgument(1);
   if (from == null || to == null) {
     throw new IllegalArgumentException("You must specify from and to arguments");
   }
   // Copy code here...
 }
}
Its OSGi DS definition would look like this:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.example.commands.file.CopyAction">
  <implementation class="org.example.commands.file.CopyAction"/>
  <service>
     <provide interface="org.eclipse.osgi.framework.console.action.CommandAction"/>
  </service>
  <property name="command" type="String" value="file"/>
  <property name="action" type="String" value="copy"/>
</scr:component>
Now we need a way to bring a command and its actions together. This is implemented in an abstract class that implements the standard Equinox CommandProvider. It also parses the command line and prints help. It takes the first argument from the command line and searches an action that matches the argument. It then calls the found actions execute method with the rest of the command line neatly seperated into arguments and options (that start with "-" or "--"). It also catches any exception that the action may throw during its execution. If the action throws an IllegalArgumentException, our command implementation will print help for the command. If we want to introduce a new command "file" all we have to do is to create a small subclass from the abstract base class "ActionCommand".
public class FileCommand extends ActionCommand {
 public void _file(CommandInterpreter ci) {
   execute("file", ci);
 }
}
Per definition, to create a command in Eclipse Equinox we have to have a public method beginning with an underscore. So there is a small duplication of names here, since our ActionCommand baseclass does not know the name of the method its called from, we have to hand it in as first parameter to the execute method. Somewhere we would also have to define the file command itself. Its published as a CommandProvider.
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.example.commands.file">
  <implementation class="org.example.commands.file.FileCommand"/>
  <service>
     <provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
  </service>
  <reference cardinality="1..n" interface="org.eclipse.osgi.framework.console.action.CommandAction" name="CommandAction" policy="static" target="(command=file)"/>
</scr:component>
Please note, that this command component will only be instantiated if there is at least one CommandAction service registered that has its "command" property set to "file" and is therefor providing an action for our command. See the example file copy action in "action" (It does not really copy a file):
That's all. Happy extending your own commands. And maybe this will find its way back into the Equinox code and we can finally have one global "list" command with lots of actions.

The code is available at git.

Tuesday, December 15, 2009

Toggle Commands that toggle other contributions

Introduction

This is a follow up to Commands Part 6: Toggle & Radio menu contributions of fellow blogger Prakash G.R.. He described how to use command toggle and radio states. Here I will show you how to drive other contributions in the Eclipse Workbench using a toggle command.

What we want to achieve

I needed to show another toolbar button when a certain toggle command was executed. Imagine something like "Show clock" that will show or hide a little clock in the worbench window status bar. Such contribution can be easily added using a menuContribution for the trim area. First create a toolbar in the trim area and then contribute controls/commands to this toolbar also using the menuContribution extension point. Since we want the clock contribution
<menucontribution locationuri="toolbar:mytoolbar">
<control class="test.ui.clocl.internal.ClockControlContribution" id="test.ui.clock">
<visiblewhen checkenabled="false">
<with variable="activeWorkbenchWindow">
  <test args="test.ui.clock.ToggleCommand"
    forcepluginactivation="true"
    property="org.eclipse.core.commands.toggle"
    value="true"/>
</with>
</visiblewhen>
</control>
</menucontribution>
That will create a toolbar contribution that is only visible when the test.ui.clock.ToggleCommand is in the "true" state, when its checked. Lets define the command:
<command defaulthandler="org.eclipse.core.commands.extender.ToggleCommandHandler" id="test.ui.clock.ToggleCommand" name="Show clock">
<state
class="org.eclipse.ui.handlers.RegistryToggleState:true"
id="org.eclipse.ui.commands.toggleState">
</state>
</command>
How this command works and what the state is you have already read in Prakash's blog entry. The default handler for this command does a little more than the one in the latter mentioned blog entry. It is defined like this:
/**
* Generic command that toggles the executed command and re-evaluates property testers for the
* org.eclipse.core.commands.toggle property.
*
*/
public class ToggleCommandHandler extends AbstractHandler {

public Object execute(final ExecutionEvent event) throws ExecutionException {
HandlerUtil.toggleCommandState(event.getCommand());
final IWorkbenchWindow ww = HandlerUtil.getActiveWorkbenchWindowChecked(event);
final IEvaluationService service = (IEvaluationService) ww.getService(IEvaluationService.class);
if (service != null) {
  service.requestEvaluation("org.eclipse.core.commands.toggle");
}
return null;
}
}

How to toggle the visibility of the clock contribution

The connection between toggling the command and making the clock contribution visible is hidden in a property tester, that the clock contribution uses:
public class CommandsPropertyTester extends PropertyTester {
public static final String NAMESPACE = "org.eclipse.core.commands"; //$NON-NLS-1$
public static final String PROPERTY_BASE = NAMESPACE + '.';
public static final String TOGGLE_PROPERTY_NAME = "toggle"; //$NON-NLS-1$
public static final String TOGGLE_PROPERTY = PROPERTY_BASE + TOGGLE_PROPERTY_NAME;

public boolean test(final Object receiver, final String property, final Object[] args, final Object expectedValue) {
if (receiver instanceof IServiceLocator && args.length == 1 && args[0] instanceof String) {
  final IServiceLocator locator = (IServiceLocator) receiver;
  if (TOGGLE_PROPERTY_NAME.equals(property)) {
    final String commandId = args[0].toString();
    final ICommandService commandService = (ICommandService)locator.getService(ICommandService.class);
    final Command command = commandService.getCommand(commandId);
    final State state = command.getState(RegistryToggleState.STATE_ID);
    if (state != null) {
      return state.getValue().equals(expectedValue);
    }
  }
}
return false;
}
}
It is defined in plugin.xml like this:
<propertytester
class="org.eclipse.core.commands.extender.internal.CommandsPropertyTester"
id="org.eclipse.core.expressions.testers.CommandsPropertyTester"
namespace="org.eclipse.core.commands"
properties="toggle"
type="org.eclipse.ui.services.IServiceLocator"/>
That means we define a new property for the namespace "org.eclipse.core.command" and the property is named "toggle". It will operate on IServiceLocator variables. Such variable that is an IServiceLocator is the "activeWorkbenchWindow" variable. Now you should understand the visibleWhen expression of the clock contribution. It should be only visible when the toggle for the clock toggle command is "true". The re-evaluation of the property testers is triggered by the generic ToggleCommand.

A little tip at the end

If you put the ToggleCommand and property tester in a seperate bundle for easier re-use in all your projects and other bundles make sure you either start the bundle at the beginning or set the "test" expressions "forcePluginActivation" to true to let the Eclipse expression framework activate the bundle for you. Otherwise the property tester is completly ignored and the clock would be always visible.

Bonus

Since the state of the toggle is preserved in an instance preference value, the visibility of all associated contributions that use the property tester to check for the toggle state of the command is as well. Of course you can also reverse the test expression for your contributions if you have a toggle command that says something like "Hide clock".