Part 8: More on Interactivity - JEB Decompiler
Author: PNF Software

JEB Plugin Development Tutorial part 8/8

# More on Interactivity

The source code for part 8 of this sample plugin is located on GitHub:

- Clone this repo: `git clone https://github.com/pnfsoftware/jeb2-plugindemo-js.git`
- Switch to the tutorial8 branch: `git checkout tutorial8`

## User Actions

### CanExecute

JEB provides the ability to interact with the units. Those units are called interactive units.

The simplest way to interact with units is through well-known actions.

The actions have to be implemented by the plugin developer. An action has an ActionContext as a parameter, which allows the plugin to retrieve:

- the action id: one defined in the Actions class. It indicates which action is involved (Comment, Rename...)
- the address: the current position in the document
- the item id: we will discuss it later.

First, why are all these actions grayed out? Because `canExecuteAction()` does not return true - yet.

    public boolean canExecuteAction(ActionContext actionContext) {
        logger.info("%s called with address %s and actionId %d",
                "canExecuteAction", actionContext.getAddress(), actionContext.getActionId());
        return false;
    }

As you can see in the logs, this method is called each time you move the caret in the document. What if we try to return true instead of false?

As expected, all actions become clickable.

Note that you can also use the toolbar icons or, even better, the keyboard shortcuts.

Nothing happens when you click on any action: it is up to the plugin developer to implement the desired feature.

We will implement a simple action: renaming of a String. (Note: we could as well rename functions, methods, variables... but we need to check in the model where they are used to replace all occurrences, consistently. The added complexity is out-of-scope in this API introduction tutorial.)

First, we need to activate the rename feature when we are on a String:

> Assignment 1: Save the string references and test if the caret in on a String.

### PrepareExecution

When clicking on the "Rename" action button, the method `IInteractiveUnit.prepareExecution()` is called. Its goal is to prepare the execution of the code and, in the case of renaming, it also provides the initial value that we want to edit: it is called before displaying the following pop up:

You need to return true in the prepareExecution method to indicate that the processing should continue.

The `prepareExecution` method has one more parameter of type IActionData. To fill up the name field, we need to retrieve the correct Action Data type:

Use ActionRenameData to prefill the rename field with its current value.

    public boolean prepareExecution(ActionContext actionContext, IActionData actionData) {
        if(actionContext.getActionId() == Actions.RENAME) {
            StringLiteral string = getElementAt(actionContext.getAddress(), strings);
            if(string != null) {
                ((ActionRenameData)actionData).setCurrentName(string.getValue());
                return true;
            }
        }
        return false;
    }

Note that IActionData embeds a generic map to pass discretionary objects from `prepareExecution` to `executeAction` method.

### ExecuteAction

The executeAction method is the last step: it performs the action and modifies the model.

What should we modify now?

- `IInput`: this is a pointer to original file, it makes no sense.
- `AstRoot`: this is our kind-of model, but there is no way to retrieve the input data (the toString() method has a specific implementation and the toSource() auto-formats and remove comments.

`IUnit` is the central part of your plugin: it is responsible for updating the model and keep coherence regarding all its document. But there is something wrong with the initial design: we don't save a reference Document in the Unit.

Documents shall at least work with the same object as IUnit, and at least have a reference on their IUnit in order to retrieve the data (this is a good practice in JEB).

Therefore, we will now modify our unit to keep references of all lines (using a List). One problem that will remain is the mandatory conversion at the `ITextDocumentPart` level:

    public List<? extends ILine> getLines

The `getLines` method is called each time you scroll/move around the document, so it would be too costly to recalculate the list of ILines each time it is called. It must be buffered and refreshed on changes: the unit must notify its Documents when the model changes.

To make notifications work, you must:

- Register the notified element:

    public class JavascriptDocument extends EventSource implements ITextDocument, IEventListener {
        public JavascriptDocument(JavascriptUnit unit) {
            this.unit = unit;
            unit.addListener(this);
            refreshPart();
        }

        // ...

        public void onEvent(IEvent e) {
            if(e.getType() == J.UnitChange) {
                refreshPart();
                this.notifyListeners(e);
            }
        }
    }

- Call the notify method in the executeAction method (on success):

    notifyListeners(new JebEvent(J.UnitChange));

> Refer to the technical draft "Staying informed of unit changes" for more details about unit changes tracking within documents.

> Assignment 2: Finish the renaming implementation.

## Navigation

JEB natively manages navigation feature with four predefined actions selectable from menu or directly with shortcuts:

- Jump To: move caret to a specific address. It uses the `ITextDocument.addressToCoordinates()` that we already implemented for Notifications.
- Navigate Forward / Navigate Backward: move caret to the previous/next position (caret position history is saved each time you jump)
- Follow: jump to an address bound to the current element. The best example is on a function call: you can jump to its definition

> Assignment 3: Implement Jump To for function names

Now, let's look at Follow. We can see that it is active only when we set the caret on strings, var, function... this is because the Follow feature is bound to Items, more precisely, the IActionableItem.

When clicking on Follow, if the caret is positioned on an Item, the function `IInteractiveUnit.getAddressOfItem()` is called.

> Assignment 4: Implement Follow for function name (it should work on the latest b();)

A solution to the assignments can be found by checking out the branch `tutorial8` of the sample code.

Back to Part 1

#### Search

From here you can search these documents. Enter your search terms below.

#### Keyboard Shortcuts

Keys | Action
--- | ---
`?` | Open this help
`n` | Next page
`p` | Previous page
`s` | Search