Tutorials & Samples

Action API Tutorial

Tutorials source code is available in the SDK itself. Choose ‘Codenvy Tutorials’ project type and the Tutorial you are interested in. You will find all Tutorial files in the Project Explorer

The system of actions allows plugins to add their own items to IDE menus and toolbars. An action is a class, derived from the Action class, whose actionPerformed method is called when the menu item or toolbar button is selected. Actions are organized into groups, which, in turn, can contain other groups. A group of actions can form a toolbar or a menu. Subgroups of the action group can form submenus of the menu.

Executing and Updating Actions

The system of actions allows plugins to add their own items to IDE menus and toolbars. An action is a class, derived from the Action class, whose actionPerformed method is called when the menu item or toolbar button is selected. For example, one of the action classes is responsible for the “File | Open File…” menu item and for the “Open File” toolbar button.

Actions are organized into groups, which, in turn, can contain other groups. A group of actions can form a toolbar or a menu. Subgroups of the group can form submenus of the menu.

Every action and action group has an unique identifier. Identifiers of many of the standard IDE actions are defined in the IdeActions class.

Every action can be included in multiple groups, and thus appear in multiple places within the IDE user interface. Different places where actions can appear are defined by constants in the ActionPlaces interface. For every place where the action appears, a new Presentation is created. Thus, the same action can have different text or icons when it appears in different places of the user interface. Different presentations for the action are created by copying the presentation returned by the Action.getTemplatePresentation() method.

To update the state of the action, the method Action.update() is periodically called by IDE. The object of type ActionEvent passed to this method carries the information about the current context for the action, and in particular, the specific presentation which needs to be updated.

The ActionEvent instance is also passed to the actionPerformed method.

Creating an Action

An action is a class derived from the Action class. To define your action, in your plugin, create a Java class derived from the Action class. In this class, override the actionPerformed method to be called when a menu item or a toolbar button is selected.

To clarify this procedure, consider the following code snippet of a .java file that defines the TextBoxes class derived from the Action class:

public class TextBoxes extends Action { 
    // If you register the action from Java code, this constructor is used to set the menu item name
    // (optionally, you can specify the menu description and an icon to display next to the menu item).
    // You can omit this constructor when registering the action in the plugin.xml file.
    public TextBoxes() {
     // Set the menu item name.
        super("Text _Boxes");
     // Set the menu item name, description and icon.
     // super("Text _Boxes","Item description", Resources.INSTANCE.icon()); 
    }
  
    public void actionPerformed(ActionEvent event) {
       String txt=Window.prompt("What is your name?", "");
       Window.alert("Hello, " + txt + "!\n I am glad to see you.");
    }
}

Registering Actions

To register an action from code, two steps are required. First, an instance of the class derived from Action must be passed to the registerAction method of the ActionManager class, to associate the action with an ID. Second, the action needs to be added to one or more groups. To get an instance of an action group by ID, it is necessary to call ActionManager.getAction() and cast the returned value to the DefaultActionGroup class.

You can create a extension that registers actions on IDE startup using the following procedure:

@Extension(title = "Demo Extension", version = "3.0.0")
public class DemoExtension {
 @Inject
 public DemoExtension(ActionManager actionManager, DemoAction demoAction) {
        //Create new popup group 'Demo' and add it to main menu
        DefaultActionGroup demoGroup = new DefaultActionGroup("Demo", true, actionManager);
        actionManager.registerAction("demoGroup", demoGroup);
        DefaultActionGroup mainMenu = (DefaultActionGroup)actionManager.getAction(IdeActions.GROUP_MAIN_MENU);
        mainMenu.add(demoGroup);
         
        //add demoAction to demoGroup
        actionManager.registerAction("demoAction", demoAction);
        demoGroup.add(demoAction);
   }

Notification API Tutorial

Tutorials source code is available in the SDK itself. Choose ‘Codenvy Tutorials’ project type and the Tutorial you are interested in. You will find all Tutorial files in the Project Explorer

This article describes the concept of a unified API for notifying the user about status of the operations invoked along with social activities and any other events. It is designed to replace outdated StatusBar concept.

Notification API

Notification API is quite a simple, but still powerful framework that allows extensions to display their status information. The entry point for the framework is Notification Manager having a few methods:

  • void showNotification(Notification notification) : shows given notification. This notification can be of 3 types: INFO, WARNING, ERROR. They can have 2 statuses: IN_PROGRESS, FINISHED. They can have 2 statuses: READ and UNREAD. The way notification is presented depends on all parameters (type, state, status).

In order to show own general notification ones needs to create an instance of notification and throw this instance to NotificationManager.showNotification.
Example:

Notification notification = new Notification("my test message", Notification.Type.INFO);
notificationManager.showNotification(notification);

In order to show own progress notification one needs to perform the same steps, but in such a case an instance of it is required since notification status will be changed. When you change something in the notification these changes will be applied to its views.
Example:

...
// Place where notification needs to be showed
Notification notification = new Notification("I'm doing some thing...", Notification.Status.PROGRESS);
notificationManager.showNotification(notification) ;
...
// Place where something in a notification needs to be changed
notification.setMessage("I'm doing step #2 of some thing");
...
// Place where progress needs to be stopped
notification.setMessage("I've stopped");
notification.setStatus(Notification.Status.FINISHED);

Notification Behavior

Current implementation of a notification system rests on the concept that implies:

  • space saving – more space for editor, not for unimportant notifications
  • drawing user’s attention to events and happenings in his/her workspace

Notification system consists of two major parts:

Notification pop-up

It shows up in the top right corner of a workspace, just after a command has been called. The pop-up appears automatically and disappears after a 5 sec timeout. A user can force closing of a popup by pressing close icon.

Notification center

Notification/message center gathers all messages (info, warning and error) indicating number of unread messages.

  • If a user closed a notification pop-up, such a notification is thought to be read, therefore it is added to a notification center as a read message.
  • If a notification pop-up automatically disappeared after a 5-sec timeout, such a notification is added to a notification center as an unread message and contributes to a total count of unread messages
  • The number of unread messages is displayed next to a notification center envelope icon
  • It is possible to delete messages from a Notification center, in other words, mark them as read.

Notification Types

Notifications can be of three types:
INFO – the default type for the majority of use cases, provides a user with information on various processes and commands, like build, run, git commands, paas login and deploy etc.
WARNING – this type of message displays when certain actions or settings applied by a user may potentially lead to an error (breakpoint on the wrong line etc).
ERROR – information on error
In addition, notifications can be:
IN PROGRESS – for example, creating application on AppFog
FINISHED – for example, repository successfully initialized
Logically, IN PROGRESS notifications acquire FINISHED status upon completion.

Default Wizard API Tutorial

Tutorials source code is available in the SDK itself. Choose ‘Codenvy Tutorials’ project type and the Tutorial you are interested in. You will find all Tutorial files in the Project Explorer

Create Default Wizard Factory

Create a Factory and Declare an Interface

Let’s start with creation of a Wizard Factory:

...
@Inject public SimpleWizardProvider(DefaultWizardFactory wizardFactory)
...

In simple words, we use SimpleWizardProvider to create an instance of a Wizard when initializing extension. DefaultWizardFactory that was previously created by a container is injected to SimpleWizardProvider.

In the above line of code we have declared an interface to use Default wizard constructor. Next, a title was set as well as a provider in which a wizard is created:

return wizardFactory.create("Wizard");

Full code is available in

com.codenvy.ide.tutorial.wizard.inject.SimpleWizardProvider

Create Annotation

To use a required instance of a Wizard from other classes/extensions an annotation should be created:

public @interface SimpleWizard

Browse full code at

package com.codenvy.ide.tutorial.wizard.inject.SimpleWizardProvider

And this is an example how to get it:

@Inject public WizardTutorialExtension(@SimpleWizard DefaultWizard simpleWizard,..)

Browse full code –

com.codenvy.ide.tutorial.wizard.WizardTutorialExtension

Bind Together

Now, it’s time to bring all pieces together using GIN (this where the magic takes place):

bind(DefaultWizard.class).annotatedWith(SimpleWizard.class).toProvider(SimpleWizardProvider.class).in(Singleton.class);

Full code is available in

com.codenvy.ide.tutorial.wizard.inject.WizardTutorialGinModule

A few words on what’s happening here:
If GIN finds a particular class (DefaultWizard.class) with a particular annotation (SimpleWizard.class), it uses a particular provider (SimpleWizardProvider.class) with an instance which is created as Singleton – i.e. this provider will be used only once, i.e. when created, and then we will use the instance it has created.

Add Pages

Now, that we have created the wizard, let’s add a few pages to it.

Get a Wizard Instance

First off, one should get an instance of the freshly created wizard

@Inject public WizardTutorialExtension(@SimpleWizard DefaultWizard simpleWizard,
                                       Provider<Page1Presenter> page1,
                                       Provider<Page2Presenter> page2,
                                       Provider<Page3Presenter> page3,
                                       Provider<Page4Presenter> page4,
                                       ...)

and providers to all the listed pages. Why providers? Default wizards interacts with providers not pages. We have some good reasons to justify such an approach. If we get a page, not its provider, we will then create an object. GWT.create is rather resource-intensive so this can be a problem if many wizards are created (thus, many objects). Providers make it possible to create objects only when a wizard (i.e.its pages) is actually used.

Add Pages

Here we add pages to the Wizard:

...
        simpleWizard.addPage(page1);
        simpleWizard.addPage(page2);
        simpleWizard.addPage(page3);
        simpleWizard.addPage(page4);
...

Browse full code –

com.codenvy.ide.tutorial.wizard.WizardTutorialExtension

Create Pages

To create a view without implementing all the methods, we’ll use the following Abstract Wizard Page (provided in our API; examples of how to create pages can be found here: com.codenvy.ide.tutorial.wizard.pages)

public class Page1Presenter extends AbstractWizardPage

However, you can do it on your own:

public class Page1Presenter implements WizardPage

Create Logic

The wizard uses own unique Wizard context. If a page is added to a wizard, it will use this context. By adding parameters to pages, we can specify logic/behavior of the wizard. Here’s an example of how to show PAGE2 after PAGE1

wizardContext.putData(PAGE2_NEXT, true);

Browse full code in

com.codenvy.ide.tutorial.wizard.pages.page1.Page1Presenter

Here we initialize a key that will be used in a Wizard context to handle parameters:

public static final WizardContext.Key<Boolean> PAGE2_NEXT = new WizardContext.Key<Boolean>("Page 2 next"); (WizardTutorialExtension)

Full code is available in

com.codenvy.ide.tutorial.wizard.WizardTutorialExtension

Parameter in context – Choose Next Page

When adding a new page, we ‘ask’ it if or not it is in the context. Below is an example for PAGE2 – the default wizard asks a page if or not it is in context. In the following example PAGE2 is in context.

@Override
public boolean inContext() {
    Boolean data = wizardContext.getData(PAGE2_NEXT);
    return data != null && data;
}

Full code is available in

com.codenvy.ide.tutorial.wizard.pages.page2.Page2Presenter

Parameter in context – Skip Page

When we skip a page, it is not shown, but the page will still perform the task assigned to it (for instance, we may choose not to show a page with one template of a project, but the template will be still chosen). In the below example, a Wizard is asking if or not a page is in context, and if it is, it can be skipped. If it can be skipped, the wizard does not show the page.

@Override
public boolean canSkip() {
    Boolean skip = wizardContext.getData(PAGE4_SKIP);
    return skip != null && skip;
}

View full source code in

com.codenvy.ide.tutorial.wizard.pages.page4.Page4Presenter

Parts API Tutorial

Tutorials source code is available in the SDK itself. Choose ‘Codenvy Tutorials’ project type and the Tutorial you are interested in. You will find all Tutorial files in the Project Explorer

Parts are visual elements that can be placed in a developer’s workspace. Normally, parts are tab-like UI elements that perform certain tasks and business logic, for instance output console, build result view, file outline, project explorer. With Codenvy SDK, developers can add own parts. We provide Parts API, where AbstractPartPresenter actually stands behind all parts. Read through this quick tutorial to learn how to create and add new parts.

Create Own Part

It is possible to create 2 types of parts (they are essentially the same, but different visually, and use different classes).

Create part with own UI

This kind of part is created in EDITOR in this Tutorial, but it is possible to create it in any available area.

Let’s get started. As mentioned above, we’ll have to extend AbstractPartPresenter class:

public class TutorialHowToPresenter extends AbstractPartPresenter implements TutorialHowToView.ActionDelegate {
    private TutorialHowToView view;

    @Inject
    public TutorialHowToPresenter(TutorialHowToView view) {
        this.view = view;
        this.view.setDelegate(this);
    }

    /** {@inheritDoc} */
    @Override
    public String getTitle() {
        return "Parts tutorial";
    }

    /** {@inheritDoc} */
    @Override
    public ImageResource getTitleImage() {
        return null;
    }

    /** {@inheritDoc} */
    @Override
    public String getTitleToolTip() {
        return null;
    }

    /** {@inheritDoc} */
    @Override
    public void go(AcceptsOneWidget container) {
        container.setWidget(view);
    }
}

Browse code in

com.codenvy.ide.tutorial.parts.howto.TutorialHowToPresenter

As seen above, we can implement methods to add UI elements, like Pic, title, tooltip etc. In the above example, we return null for almost all elements, but the Title (return return “Parts tutorial”;), but you can use own UI elements.

Create Own Part Partially Using Provided UI

These types of parts are available with pre-defined UI elements, and can be created in Navigation, Tooling and Information areas in this tutorial. However, developers can create them in any other suitable workspace areas.

The workflow here slightly differs from the above case. We’ll need to extend com.codenvy.ide.api.parts.base.BasePresenter:


public class MyPartPresenter extends BasePresenter implements MyPartView.ActionDelegate {
    private MyPartView view;
    private String     title;

    @Inject
    public MyPartPresenter(MyPartView view, @Assisted String title) {
        this.view = view;
        this.view.setDelegate(this);
        this.view.setTitle(title);
        this.title = title;
    }

    /** {@inheritDoc} */
    @Override
    public void onButtonClicked() {
        Window.alert(title);
    }

    /** {@inheritDoc} */
    @Override
    public String getTitle() {
        return title;
    }

    /** {@inheritDoc} */
    @Override
    public ImageResource getTitleImage() {
        return null;
    }

    /** {@inheritDoc} */
    @Override
    public String getTitleToolTip() {
        return "Tooltip";
    }

    /** {@inheritDoc} */
    @Override
    public void go(AcceptsOneWidget container) {
        container.setWidget(view);
    }
}

while basePresenter extends abstractPartPresenter. This basePresent class has a pre-defined method

void minimize()

that comes into play when you click minimize button. This button won’t work if a part has been created in the EDITOR area (minimizing an editor is a fairly rare use case).

Use of MVP

We use our MVP model with delegating functions. Here’s a simple example:

 /** Required for delegating functions in view. */
    public interface ActionDelegate extends BaseActionDelegate {
        /** Performs some actions in response to a user's clicking on Button */
        void onButtonClicked();
    }

ActionDelegate is delegating functions from presenter. When we click minimize, a minimize method is called on the presenter through this delegate class. So, ActionDelegate for this kind of part should extend BaseActionDelegate.

This also concerns View that should extend com.codenvy.ide.api.parts.base.BaseView. We extend baseView and pass it over to ActionDelegate. BaseView has own UI, background, minimize icon, and possibility to add title.

public class MyPartViewImpl extends BaseView<MyPartView.ActionDelegate> implements MyPartView {
    interface MyPartViewImplUiBinder extends UiBinder<Widget, MyPartViewImpl> {
    }

    private static MyPartViewImplUiBinder ourUiBinder = GWT.create(MyPartViewImplUiBinder.class);

    @UiField
    Button button;

    @Inject
    public MyPartViewImpl(PartStackUIResources resources) {
        super(resources);
        container.add(ourUiBinder.createAndBindUi(this));
    }

    @UiHandler("button")
    public void onButtonClicked(ClickEvent event) {
        delegate.onButtonClicked();
    }
}

Reference to a full class:

com.codenvy.ide.tutorial.parts.part.MyPartView
Methods Overview
  • setTitle (in baseView) – sets title
  • void hidePart(PartPresenter part) – minimizes a part
  • void removePart(PartPresenter part) – deletes the part
  • void setActivePart(PartPresenter part) – sets active part when several of them are opened. By default the last opened part is is active. This concerns only parts created in the EDIT group. Parts created in other groups are closed by default.
  • Add Own Parts

    Traditionally, when adding something, we need to get something first. In our case, this is a workspace agent.

    @Inject
        public PartsTutorialExtension(WorkspaceAgent workspaceAgent…) {

    Full Code is available in

    com.codenvy.ide.tutorial.parts.PartsTutorialExtension

    Having received a workspace agent, let’s call openPart method on it:

    workspaceAgent.openPart(howToPresenter, EDITING);
    Parameters Used
  • What part should be added: howToPresenter in our example
  • Where this part needs to be added: (EDITING in our example)
  • Where to Add Parts?

    There are 4 options available on where to add parts:

  • EDITING: area just above the editor, like a file tab
  • NAVIGATION: area on the left to project explorer
  • TOOLING: area to the right of the editor
  • INFORMATION: area under the editor, ‘console’ area
  • New Resource Wizard API

    Tutorials source code is available in the SDK itself. Choose ‘Codenvy Tutorials’ project type and the Tutorial you are interested in. You will find all Tutorial files in the Project Explorer

    Our API model has an abstract resource class com.codenvy.ide.resources.model.Resource. Resources are files and folders of a project. There can be project specific files and folders, for instance classes/interfaces (as specific file types) and packages (as specific folder types), in case this is a Java project.

    Resources are created with a New Resource Wizard. Below is a quick tutorial on how to create resources and add own pages to a New Resource Wizard.

    Create New Resources Through a New Resource Wizard

    Get a New Resource Agent

    To create own resources, we’ll first need to get a new resource agent

    @Inject public NewResourceWizardTutorialExtension(...newResourceAgent...) {

    The complete code can be found in

    com.codenvy.ide.tutorial.wizard.newresource.NewResourceWizardTutorialExtension
    Register New Resource Type

    Done with that. Now, let’s register a new resource type – we’ll call a register method on this agent:

    newResourceAgent.register(myResourceProvider);
    Create a Resource Provider

    A resource provider is given to a register method. myResourceProvider is a class that can create a new resource of any type it supports/provides. Consequently, to create this provider, let’s extend the following class:

    com.codenvy.ide.api.ui.wizard.newresource.NewResourceProvider

    Here’s an example of a provider we have created in our template:

        public class MyResourceProvider extends NewResourceProvider {
        public static final String LOGIN_PLACE    = "@login";
        public static final String PASSWORD_PLACE = "@password";
        private SelectionAgent selectionAgent;
    
        @Inject
        public MyResourceProvider(SelectionAgent selectionAgent) {
            super("My file", "My file", null, "xml");
            this.selectionAgent = selectionAgent;
        }
    
        /** {@inheritDoc} */
        @Override
        public void create(@NotNull String name, @NotNull Folder parent, @NotNull Project project,
                           @NotNull final AsyncCallback<Resource> callback) {
            String fileName = name + '.' + getExtension();
            String content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                             "<configuration>\n" +
                             "    <login>" + LOGIN_PLACE + "</login>\n" +
                             "    <password>" + PASSWORD_PLACE + "</password>\n" +
                             "</configuration>";
    
            project.createFile(parent, fileName, content, TEXT_XML, new AsyncCallback<File>() {
                @Override
                public void onSuccess(File result) {
                    callback.onSuccess(result);
                }
    
                @Override
                public void onFailure(Throwable caught) {
                    callback.onFailure(caught);
                }
            });
        }
    
        /** {@inheritDoc} */
        @Override
        public boolean inContext() {
            // Possible to create this type of resource just in folder
            Selection<?> selection = selectionAgent.getSelection();
            if (selection != null) {
                if (selectionAgent.getSelection().getFirstElement() instanceof Resource) {
                    Resource resource = (Resource)selectionAgent.getSelection().getFirstElement();
                    if (resource.isFile()) {
                        resource = resource.getParent();
                    }
                    return resource.getResourceType().equals(TYPE);
                }
            }
            return false;
        }
    }
    

    Full code is available in

    com.codenvy.ide.tutorial.wizard.newresource.provider.MyResourceProvider

    New resource provider has a Title and Icon that will show up in New Resource Wizard page (for instance, Java, Ruby or Python file, each having a different logo). Also, this provider contains an extension (this mostly concerns files) that can be given null parameter, if this is a folder. This is done to check if a particular resource type with a particular name already exists in the project.

    Methods Overview

    There are a few methods that actually do the trick, namely:

    Create method

    As its name suggests, this method does the actual job, i.e. creates a new resource:

    create(@NotNull String name, @NotNull Folder parent, @NotNull Project project, @NotNull AsyncCallback callback);

    Its input parameters are:

    • resource name
    • parent folder (i.e. where this resource should be created), project (in which project a new resource should be created)
    • callback, which is a class that returns result of a new resource creation (successful or fail)
    inContext Method
    boolean inContext()

    Its primary role is to filter possibility to create resources according to pre-defined rules/parameters. In the above example, we set the following rule – a new resource can be created only inside a folder, not in project root, or in a package.

    A developer may set own rules/filters for creating a new resource.

    Add Own Page to a New Resource Wizard

    Get a Wizard

    To add a new page, one needs to get this wizard:

    @Inject public NewResourceWizardTutorialExtension(...@NewResource DefaultWizard newResourceWizard...) {
    Add a Page

    Now, it’s time to add page. We can do it this way:

    newResourceWizard.addPage(myResourcePage);

    Full code is available in

    com.codenvy.ide.tutorial.wizard.newresource.NewResourceWizardTutorialExtension
    Re-set inContext Method

    So, we have added a new page after the first (and the only) Wizard page. It is possible to add as many of them as required. But to connect this page with the resource provider, we need to re-set inContext method for a Wizard page. In simple words, we specify when we need this page to be displayed (for example, only when creating a Java or a CSS file)

        @Override
        public boolean inContext() {
            NewResourceProvider resourceProvider = wizardContext.getData(NEW_RESOURCE_PROVIDER);
            return resourceProvider != null && resourceProvider instanceof MyResourceProvider;
        }
    

    Browse full code in

    com.codenvy.ide.tutorial.wizard.newresource.page.MyResourcePagePresenter

    The added Wizard page will be shown only if a particular resource type is chosen. If this method remains unchanged, added page will be shown for all resource types.

    Wizard Keys

    com.codenvy.ide.api.ui.wizard.newresource.NewResourceWizardKeys has keys with parameters that you can get from Wizard context.

    Text Editor API Tutorial: Adding Text Editor

    Tutorials source code is available in the SDK itself. Choose ‘Codenvy Tutorials’ project type and the Tutorial you are interested in. You will find all Tutorial files in the Project Explorer

    Codenvy SDK provides API to create own text and non-text editors, both using implementation of an existing text editor or using own implementations. This tutorial focuses on creation of a new editor that supports a new file type (Grrovy) using existing implementation of Codenvy text editor. The whole procedure takes a while and includes a few major steps:

    Create a New Editor

    Let’s create an editor for Groovy files. By creation of an editor we mean adding syntax highlighter for a particular file type.

    Get a Provider

    First, we’ll need a provider for an editor. This provider will return an instance of an editor for Groovy files. Let’s create it by implementing com.codenvy.ide.api.editor.EditorProvider that we provide in our API:

    GroovyEditorProvider implements EditorProvider {
        private final DocumentProvider            documentProvider;
        private       Provider<CodenvyTextEditor> editorProvider;
        private final NotificationManager         notificationManager;
    
        @Inject
        public GroovyEditorProvider(DocumentProvider documentProvider, Provider<CodenvyTextEditor> editorProvider,
                                    NotificationManager notificationManager) {
            super();
            this.documentProvider = documentProvider;
            this.editorProvider = editorProvider;
            this.notificationManager = notificationManager;
        }
    
        /** {@inheritDoc} */
        @Override
        public EditorPartPresenter getEditor() {
            CodenvyTextEditor textEditor = editorProvider.get();
            textEditor.initialize(new GroovyEditorConfiguration(), documentProvider, notificationManager);
            return textEditor;
        }
    }
    

    Here, getEditor() method does the trick by returning an instance of a ‘Groovy editor’ which is a Codenvy text editor configured in a special way. Creation of a text editor is performed in two stages:

    • injection of Provider editorProvider
    • getting its instance CodenvyTextEditor textEditor = editorProvider.get();
    Initialize and Configure Editor

    Having done that, we’ve got a Text editor, which now needs to be initialized and configured. In other words, we have to add syntax highlighting for Groovy. This job is done by initialize method which gets configuration settings from GroovyEditorConfiguration (in our example, it just has configuration for syntax highlighting, but users can add other language support elements like code-assistant, error marking, code outline etc.)

     textEditor.initialize(new GroovyEditorConfiguration()...)

    Class reference: com.codenvy.ide.tutorial.editor.editor.GroovyEditorProvider

    Set up Configuration

    In our API, we provide com.codenvy.ide.texteditor.api.TextEditorConfiguration class. So, let’s implement it:

    public class GroovyEditorConfiguration extends TextEditorConfiguration {
    
        public GroovyEditorConfiguration() {
            super();
        }
    
        /** {@inheritDoc} */
        @Override
        public Parser getParser(@NotNull TextEditorPartView view) {
            CmParser parser = getParserForMime(GROOVY_MIME_TYPE);
            parser.setNameAndFactory("groovy", new BasicTokenFactory());
            return parser;
        }
    }
    

    By default, TextEditorConfiguration has no configuration settings (it returns null by default). If you want to have own editor configuration, you’ll need to override those methods you are interested in. In our example, getParser method has been overridden. The rest of the methods remained unconfigured by default:

    public Parser getParser(TextEditorPartView view)
    Choosing the Parser

    We have used CodeMirror parser (http://codemirror.net/) and the script that does the job https://github.com/marijnh/CodeMirror/tree/master/mode/groovy

    The CodeMirror parses the code, and getParser method returns CodeMirror parser for a particular language that is located in the script (see GitHub URL above).

    Class reference: com.codenvy.ide.tutorial.editor.editor.GroovyEditorConfiguration

    Making Parser Work

    To make getParserForMime method work, we should upload the script to our extension (project). In this example it is located at src/main/resources/com/codenvy/ide/tutorial/editor/groovy.js

    Having this JavaScript in the project, let’s make the script be executed on the page. So, it’s time to add grrovy.js to client bundle:

    
        @Source("groovy.js")
        TextResource groovyParserJS();
    

    Full code: com.codenvy.ide.tutorial.editor.EditorTutorialResource

    Inject JavaScript

    Now, we are ready for script injection. It is very important to make sure the script is injected when the extension is starting. That explains why it is located in EditorTutorialExtension. Code example:

    ScriptInjector.fromString(editorTutorialResource.groovyParserJS().getText()).setWindow(TOP_WINDOW).inject();

    Full code: com.codenvy.ide.tutorial.editor.EditorTutorialExtension

    Keep Configuring a Parser

    Now let’s get back to GroovyEditorConfiguration class:

    CmParser parser = getParserForMime(GROOVY_MIME_TYPE);

    Here, a parser is returned. Pay attention to a mime type. Since we have used CodeMirror, Groovy mime type is defined there as:

    text/x-groovy

    It is defined exactly the same way in the JavaScript file.

    parser.setNameAndFactory("groovy", new BasicTokenFactory());

    A user can configure the parser itself. In the above example, we’ve set a parser name and initialized TokenFactory. A TokenFactory is a factory that creates tokens based on the info received from a parser. It adds these tokens to the list of tokens which it also receives from a parser.

    In this example, we used BasicTokenFactory which we have in our API: com.codenvy.ide.texteditor.api.parser.BasicTokenFactory

    This Factory creates a token that passes over stylePrefix and tokenValue, as well as resolves token type. Therefore, stylePrefix is identical to parser name (“groovy” in our example):

    new Token(stylePrefix, TokenType.resolveTokenType(tokenType, tokenValue), tokenValue)
    Add Syntax Highlighting

    With those steps completed, a parser does its job, i.e. parses the file, but we don’t have syntax highlighting yet. To add syntax highlighting, we’ll need a relevant css file. We’ve got it at http://codemirror.net/lib/codemirror.css and created it at src/main/resources/com/codenvy/ide/tutorial/editor/groovy.css

    In style css, there’s one thing to bear in mind. In the original ccs on CodeMirror we have:

    cm-keyword

    while in our example is it is

    groovy-keyword

    Let’s add some explanations:

    cm stands for Code Mirror and this is a stylePrefix, while keyword is a TokenType (see above example). In our example stylePrefix is identical to parser name, which is groovy.

    This is how a parsed element enum looks in dom:

    <span class="groovy-keyword" style="display: inline-block;">enum</span>

    CSS class groovy-keyword is applied to the value. Keyword is just example, it can be variable, whitespace, comment, number, string etc.

    It is also necessary to re-define existing CSS classes. Here’s an example for one class:

    @external groovy-keyword

    The parser has applied non-existing CSS classes, and we should re-set them to existing ones with our real values. This applies only to our particular example, since GWT is used.

    Customize CSS

    Depending on what theme you use, it may be necessary to adapt colors to fit theme color scheme, editor background in particular. The following are typical TokenTypes for groovy syntax highlighting. You may need to play around with colors to choose universal ones, fitting used color schemes.

    .groovy-keyword {
            color: ##A100EC;
        }
    
    .groovy-variable {
            color: rgb(0, 116, 223);
        }
    
    .groovy-property {
            color: rgb(197, 70, 70);
        }
    
    .groovy-operator {
            color: #0074df;
        }
    
    .groovy-comment {
            color: #a50;
        }
    
    Inject CSS

    It’s time to add it to client bundle as css resource:

     @Source("groovy.css")
        CssResource groovyCSS();
    

    Full code: com.codenvy.ide.tutorial.editor.EditorTutorialResource

    Similar to JavaScript injection, we’ll inject our CSS file to HTML page.

    editorTutorialResource.groovyCSS().ensureInjected();

    Congrats! The editor is ready and is awaiting registration!

    Register a New Editor

    Register a New File Type

    So, we have created an editor for a new file type – Groovy. Let’s register a new file type:

    Getting ResourceProvider through injection:

     @Inject
        public EditorTutorialExtension(...
                                       ResourceProvider resourceProvider,
    ...)
    [/code]
    And create a new file type:
    1
    FileType groovyFile = new FileType(resource.groovyFile(), GROOVY_MIME_TYPE, "groovy");
            resourceProvider.registerFileType(groovyFile);
    

    Parameters Used:

    • icon
    • mime type
    • file extension (Groove may have several file extensions; we used default)
    Register Editor for a New File Type

    Starting with injection as usual. We're getting editorRegistry:

     @Inject
        public EditorTutorialExtension(...
                                       EditorRegistry editorRegistry,
                                       ...)
    

    and calling register method on it:

    editorRegistry.register(groovyFile, groovyEditorProvider);

    Here, groovyFile is a new file type we have registered and groovyEditorProvider is a previously created editor.

    Full code: com.codenvy.ide.tutorial.editor.EditorTutorialExtension

    In this tutorial we've also created a new resource (Groovy file). Creation of new resources is covered in Create a New Resource Tutorial

    Gin Tutorial

    Tutorials source code is available in the SDK itself. Choose 'Codenvy Tutorials' project type and the Tutorial you are interested in. You will find all Tutorial files in the Project Explorer

    Gin Usage Overview

    What Is GIN?

    GIN (GWT INjection) brings automatic dependency injection to Google Web Toolkit client-side code. GIN is built on top of Guice and uses (a subset of) Guice's binding language. Here's the link to GIN Wiki and FAQ.

    Gin Version and Project Configuration

    Currently, we use Gin 2.1.1. A dependency should be added to pom.xml as:

            <dependency>
                <groupId>com.google.gwt.inject</groupId>
                <artifactId>gin</artifactId>
                <version>2.1.1</version>
            </dependency>

    Also, Gin inject inherit reference should be added to a GWT module:

    <inherits name="com.google.gwt.inject.Inject"/>

    Reference to a GWT.xml: /src/main/resources/com/codenvy/ide/tutorial/gin/GinTutorial.gwt.xml

    Basic Gin Principles

    When Gin is used to create an instance of an object, it runs the following code inside:

    GWT.create(MyClass.class);

    where MyClass is a class that you want to inject.

    Here's an example from GWT Project:

      public interface MyResources extends ClientBundle {
      public static final MyResources INSTANCE =  GWT.create(MyResources.class);
    
      @Source("my.css")
      public CssResource css();
    
      @Source("config.xml")
      public TextResource initialConfiguration();
    
      @Source("manual.pdf")
      public DataResource ownersManual();
    }

    Here Gin is not used. The following line of code

    public static final MyResources INSTANCE =  GWT.create(MyResources.class);

    would appear unnecessary in case of Gin usage where we would inject MyResources directly:

      @Inject
      public MyClass(MyResources resource)

    This also concerns other classes, where GWT.create is used. Gin will do this job behind the scenes.

    Add Own Gin Module

    To create own Gin module, one should extend the following class com.google.gwt.inject.client.AbstractGinModule

    Next, let’s override protected void configure() method. This method will contain configuration and bindings of interfaces and implementations. So, far, this is a standard procedure. However, if you have created a Gin module and want it to be added to our implementation of Ginjector (com.google.gwt.inject.client.Ginjector), we should use this annotation:

    @ExtensionGinModule

    Full code is available in class -

    com.codenvy.ide.api.extension.ExtensionGinModule

    This is how it is added to Ginjector:

    @GinModules({
    ...
    com.codenvy.ide.tutorial.gin.inject.GinModule.class,
    ...
    })
    

    Once the annotation is added, all the bindings will take force.

    You can look at example of our Gin module at com.codenvy.ide.tutorial.gin.inject.GinModule

    Using an Inject Annotation

    We use inject annotation com.google.inject.Inject that can be applied to class fields, constructors and methods. In our example, we use it with a constructor:

         @Inject
        public MyClass(MyInterface myInterface) {
            this.myInterface = myInterface;
        }
    

    In the above example, we have added an inject annotation which creates an instance of MyInterface that will be passed over to a constructor. Gin will automatically inject all pre-defined parameters (we have one parameter in our example (myInterface), but you can use as many as you need).

    Full code can be browsed at com.codenvy.ide.tutorial.gin.sample.MyClass

    No additional configuration and/or code is necessary when only one class needs to be injected.

    You can find more info on injecting annotations for classes, fields, constructors and methods in Guice docs.

    How to Bind an Interface With its Implementation

    In the below example we have an interface and its implementation (just sample examples that are not related to IDE3):

    com.codenvy.ide.tutorial.gin.sample.MyInterface
    

    and

    com.codenvy.ide.tutorial.gin.sample.MyImplementation
    

    MyImplementation implements MyInterface. Let’s bind them together. We’ll do this in a Gin module (it is only necessary in more complex cases when a few classes need to be bound):

    bind(MyInterface.class).to(MyImplementation.class);

    Full code: com.codenvy.ide.tutorial.gin.inject.GinModule

    What have we just done? We have made myInterface parameter an actual instance for MyImplementation. It also makes it possible to hide an actually used implementation:

         @Inject
        public MyClass(MyInterface myInterface) {
            this.myInterface = myInterface;
        }
    

    Full code - com.codenvy.ide.tutorial.gin.sample.MyClass

    Example of Creating a Singleton Object

    What is making an object as a singleton? This means that an instance of this object will be created just once, and any attempts to create it again will return the initially created instance. So, there can be just one instance of a given object. Here’s how we do this:

    Using a Singleton Annotation

    To create an object as a Singleton using an annotation, let's add this annotation before we declare a class:

    @Singleton
    public class MySingletonClass {
    

    Full code - com.codenvy.ide.tutorial.gin.singleton.MySingletonClass

    The rest of the code does not differ from a non-singleton class. In other words, we should follow all recommendations as to injecting annotations.

    Creating a Singleton Object Through Configuration of a Gin Module

    This usecase looks like binding interface with its implementation that we have focused on earlier in this Tutorial. The difference is that now we bind classes, and as a result, a Singleton instance is returned. Here are the classes:

    com.codenvy.ide.tutorial.gin.singleton.MySingletonInterface

    and

    com.codenvy.ide.tutorial.gin.singleton.MySingletonImplementation

    These classes have content identical to code in classes described in How to Bind an Interface With its Implementation:

    bind(MySingletonInterface.class).to(MySingletonImplementation.class).in(Singleton.class);

    And this is how we get this Singleton instance:

         @Inject
        public MySingletonClass(MySingletonInterface mySingletonInterface) {
            this.mySingletonInterface = mySingletonInterface;
        }
    

    Full code - com.codenvy.ide.tutorial.gin.singleton.MySingletonClass

    Class Providers

    Providers represent factory patterns that make it possible to create instances of particular objects. Also, providers make it possible to create 'lazy instance objects' - i.e. create objects only when they are required.

    Using com.google.inject.Provider

    If we need to use a provider for myClass, this is how we do it:

     @Inject
        public GinExtension(...
                            Provider<MyClass> myClassProvider,
    ...) {
    

    So, we've created a provider and specified which class we need it for.

    So, we have now got myClassProvider. Now, we should get an instance of myClass:

    MyClass myClass1 = myClassProvider.get();

    Full code: com.codenvy.ide.tutorial.gin.GinExtension

    Using com.google.gwt.inject.client.AsyncProvider

    When using com.google.inject.Provider, JavaScript code is generated when compiling a project. AsyncProvider is compiled into a JavaScript but it is not loaded immediately. AsyncProvider represents GWT.runAsync. JavaScript will be loaded the first time it is required. This leads to faster loading of IDE. You can find more details on AsyncProvider in GWT docs.

    • First, we should get an AsyncProvider:
       @Inject
          public GinExtension(...AsyncProvider<MyClass> myClassAsyncProvider...) {
      
    • Now it’s time to create an object through a Provider:
      myClassAsyncProvider.get(new AsyncCallback<MyClass>() {
                  @Override
                  public void onSuccess(MyClass result) {
                      result.doSomething();
                  }
      
                  @Override
                  public void onFailure(Throwable caught) {
                      console.print(caught.getMessage());
                  }
              });
      

      Full code: com.codenvy.ide.tutorial.gin.GinExtension

    IDE3 team recommends using AsyncProvider to decrease amount of JavaScript to be initially loaded.

    Method Providers

    To create a method provider, we should first create a method inside Gin module:

    @Provides
        @Singleton
        protected String provideStringValue(ConsolePart console) {
            console.print("initialize string value in gin module");
            return "my string value from provider method";
        }
    

    Full code: com.codenvy.ide.tutorial.gin.inject.GinModule

    So, here we have created provideStringValue and used @Provides (we use com.google.inject.Provides) annotation before it. @Singleton is not mandatory, so we voluntarily used it in our example.

    Now we pass over necessary objects to this method. In our example, we pass over console that prints out a message. This is a simple example. You can use other objects for more complex usecases.

    The parameter type, which this method returns, corresponds to the type of a class that you need to create. This is how we have used this method:

          @Inject
        public MyClassWithProvideParam(ConsolePart console, String someText) {
            this.console = console;
            this.someText = someText;
        }
    

    Here, someText is created with the abovementioned provider. So, anytime we need to use String someText, provideStringValue method with @Provider is automatically called, and the method code is executed.

    Full code: com.codenvy.ide.tutorial.gin.sample.MyClassWithProvideParam

    Read more on method providers at https://code.google.com/p/google-guice/wiki/ProvidesMethods

    Method Providers With Named Annotation

    The drawback of provider methods is that if you have created a method for a string (like in the above example) but you need to return its different instances, you will get just one instance for this string anyway. To fix this we will use a named annotation. So, we will be able to create several method providers that will create various instances of one object type:

    @Provides
        @Named("myString")
        @Singleton
        protected String provideMyString() {
            return "my string value from named annotation";
        }
    

    In the above example we have used a named annotation for a particular string - @Named("myString")

    Full code: com.codenvy.ide.tutorial.gin.inject.GinModule

    This is how we've used this method:

    @Inject
        public MyClassWithNamedParam(ConsolePart console, @Named("myString") String someText) {
            this.console = console;
            this.someText = someText;
        }
    

    In this case, someText has a named annotation myString which is found in GinModule class, where provideMyString method is executed through a Provider.

    Full code: com.codenvy.ide.tutorial.gin.named.MyClassWithNamedParam

    Factories

    The above-mentioned examples are not quite helpful when we need to create instances of objects where a constructor contains specific objects that can differ depending on the instance.

    When we’re talking about inject of an object, we inject a particular object in the above examples. It is a new instance for a new object. In case of provided method and named annotations, we use a particular implementation.

    On the other hand, a Factory makes it possible to inject a new object anytime an instance is created. At that, Factories makes it possible to inject more than 1 object.

    Factories are also useful to adapt classes that Gin is not aware of to be used inside Gin.

    Create an Object

    First, we have a constructor:

    @Inject
        public MyFactoryClass(ConsolePart console, @Assisted String someText) {
            this.console = console;
            this.someText = someText;
        }
    

    @Assisted annotation (com.google.inject.assistedinject.Assisted) makes it possible to take an object from the Factory and inject it into the constructor. We can have more than one parameter (someText, someText1, someText2 etc)

    Full code: com.codenvy.ide.tutorial.gin.factory.MyFactoryClass

    When using 2 or more method’s parameters of the same type, @Assisted annotation will be a named one. Here’s an example from Google JavaDoc:

    public interface PaymentFactory {
       Payment create(
           @Assisted("startDate") Date startDate,
           @Assisted("dueDate") Date dueDate,
           Money amount);
     }
    
    Create a Factory

    Now we need to create a Factory that in its turn will create an object:

    public interface MyFactory {
    ...
        MyFactoryClass createMyFactoryClass(String someText);
    ...
    }
    

    Full code: com.codenvy.ide.tutorial.gin.factory.MyFactory

    A few words on what happens here. MyFactoryClass is an object that we have just created. createMyFactoryClass has the following parameter: String someText that has been previously marked with @Assisted annotation. If there are several parameters, they should be used in the exact order as specified in the constructor.

    Bind All Elements Together

    Here, Gin injects the factory to initialize it for use. We can’t use a factory until the injector has been initialized.

    install(new GinFactoryModuleBuilder().build(MyFactory.class));
    Use a Factory

    Now, let’s inject a Factory:

    @Inject
        public GinExtension(
    ...
                            MyFactory myFactory,...
    ) {
    

    Here’s an example how objects have been created:

     MyFactoryClass myFactoryClass1 = myFactory.createMyFactoryClass("my factory class 1");
            myFactoryClass1.doSomething();
            MyFactoryClass myFactoryClass2 = myFactory.createMyFactoryClass("my factory class 2");
            myFactoryClass2.doSomething();
    

    Full code: com.codenvy.ide.tutorial.gin.GinExtension

    Create Implementation of an Interface Through a Factory

    Here, we have two classes: an interface and its implementation:

    com.codenvy.ide.tutorial.gin.factory.assited.SomeInterface

    and

    com.codenvy.ide.tutorial.gin.factory.assited.SomeImplementationWithAssistedParam
    

    An implementation class has got the following code:

    @Inject
        public SomeImplementationWithAssistedParam(ConsolePart console, @Assisted String text) {
            this.console = console;
            this.text = text;
        }
    

    And this is how we create a Factory:

    public interface MyFactory {
    ...
        SomeInterface createSomeInterface(String text);
    ...
    }
    

    Full code: com.codenvy.ide.tutorial.gin.factory.MyFactory

    In such a way, we create an implementation through a Factory.

    Now, we have to initialize Factory and bind an interface and its implementation in a GinModule:

     install(new GinFactoryModuleBuilder().implement(SomeInterface.class, SomeImplementationWithAssistedParam.class)
                                                 .build(MyFactory.class));

    Full code: com.codenvy.ide.tutorial.gin.inject.GinModule

    MyFactory class can contain many methods that return various instances of objects. More complex usecases can be found in Gin Tutorial.
    We have given two examples for Factory initialization. You will need just one of course. These are just two different examples.

    Using Custom Annotations Instead of Named Annotations

    Custom annotations is an alternative for named annotations. They have got the same functionality - inject an object - but instead of using a named annotation we’ll use a custom annotation. This options helps make code more readable. When using a named annotation, you should know the exact name to receive the exact object. When using a custom annotation, you have this annotation in API (in our case).

    Create a Custom Annotation

    Let’s start with creating a custom annotation:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
    @BindingAnnotation
    public @interface SimpleClass {
    }
    

    Full code: com.codenvy.ide.tutorial.gin.annotation.SimpleClass

    The first two annotations are typical for Java and you can find detailed info on them in Java tutorials. The next annotation is most important - @BindingAnnotation (com.google.inject.BindingAnnotation) which tells Gin that this is a binding annotation. You can find more info at https://code.google.com/p/google-guice/wiki/BindingAnnotations

    Using Custom Annotation: Example 1

    We have created an interface com.codenvy.ide.tutorial.gin.annotation.SimpleInterface and its implementation com.codenvy.ide.tutorial.gin.annotation.SimpleImplementation

    Let's imagine we have several implementations for this interface. However, we know nothing about these implementation. We know they do exist, but we are not aware of any additional details. The other usecase is that developers may be reluctant to show off their implementations. So, we hide our implementation and create an annotation to get an instance of our implementation.

    We need to bind everything together:

    bind(SimpleInterface.class).annotatedWith(SimpleClass.class).to(SimpleImplementation.class).in(Singleton.class);

    Here, a SimpleInterface marked with an annotation is bound to SimpleImplementation created as Singleton. In other words, a new class is created as a Singleton, and a real implementation is not shown.

    Having bound an interface to implementation, we need to get an object:

          @Inject
        public GinExtension(...
                            @SimpleClass SimpleInterface simpleInterface) {
        simpleInterface.doSomething();
    
    

    Full code: com.codenvy.ide.tutorial.gin.GinExtension

    Here, we have got SimpleInterface which is an instance of SimpleImplementation.

    Using Custom Annotation: Example 2

    In this example we will have com.codenvy.ide.tutorial.gin.annotation.MyString class and a provider that will create an instance of MyString.

    MyString class will declare the following annotations:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
    @BindingAnnotation
    public @interface MyString {
    }
    

    And here’s a provider that creates an instance of MyString:

    public class MyStringProvider implements Provider<String> {
    
        @Inject
        public MyStringProvider() {
        }
    
        /** {@inheritDoc} */
        @Override
        public String get() {
            return "my string value from MyStringProvider class";
        }
    }
    

    Full code: com.codenvy.ide.tutorial.gin.annotation.MyStringProvider

    Now, we bind everything together in a GinModule:

    bind(String.class).annotatedWith(MyString.class).toProvider(MyStringProvider.class).in(Singleton.class);
    

    Full code: com.codenvy.ide.tutorial.gin.inject.GinModule

    A few words on what happens here. String type parameter annotated with MyString is bound to a provider that creates an instance of this string as a Singleton.

    Now we should get this parameter:

    @Inject
        public MyClassWithAnnotationParam(ConsolePart console, @MyString String text) {
            this.console = console;
            this.text = text;
        }
    

    Here we get a variable text which is returned by MyStringProvider.

    Full code: com.codenvy.ide.tutorial.gin.annotation.MyClassWithAnnotationParam

    This may look a bit complex, but this is the only solution here since Gin does not support toInstance, like in Guice. Here’s an example from Guice:

             bind(String.class)
            .annotatedWith(Names.named("JDBC URL"))
            .toInstance("jdbc:mysql://localhost/pizza");
    

    Editor API Tutorial: Adding Non-Text Editor

    Tutorials source code is available in the SDK itself. Choose 'Codenvy Tutorials' project type and the Tutorial you are interested in. You will find all Tutorial files in the Project Explorer

    In this tutorial we will use a standard GWT component RichTextArea as a WYSIWYG editor for HTML files that allows complex styling and formatting.

    Ways to Add an Editor

    There are two ways to add an editor:

    • implement com.codenvy.ide.api.editor.EditorPartPresenter
    • or

    • extend com.codenvy.ide.api.editor.AbstractEditorPresenter that has partially implemented a few methods. Both classes are provided in the API, although the second option is recommended by IDE3 team as a simple and more convenient. This tutorial will follow it as well.

    Create an Editor

    The editor actually starts working when calling init method that gets a file to be opened in an editor. The init method that has been already implemented in AbstractEditorPresenter which calls an abstract method - initializeEditor. Our goal is to implement it:

    @Override
        protected void initializeEditor() {
            //create editor
            textArea = new RichTextArea();
    
            //use or load content of the file
            if(input.getFile().getContent() == null){
                input.getFile().getProject().getContent(input.getFile(), new AsyncCallback<File>() {
                    @Override
                    public void onFailure(Throwable caught) {
                        Log.error(WysiwygEditor.class, caught);
                    }
    
                    @Override
                    public void onSuccess(File result) {
                        textArea.setHTML(input.getFile().getContent());
                    }
                });
            }
            else{
               textArea.setHTML(input.getFile().getContent());
            }
        }
    

    What happens in the above code is:

    • First we actually create an editor:
      textArea = new RichTextArea();
    • Then, it is time to check if the file that needs to be opened has content. If it has content, we'll paste it into the editor.

    Full code: com.codenvy.ide.tutorial.wysiwyg.editor.WysiwygEditor

    Displaying an Editor in IDE

    Having done that, we should now specify how an editor will be displayed in IDE. Here we rely on go method comes from Interface Presenter that is common for all IDE UI components.

    public void go(AcceptsOneWidget container) {
            textArea.setSize("100%", "100%");
            RichTextToolbar toolbar = new RichTextToolbar(textArea);
    
            DockLayoutPanel panel = new DockLayoutPanel(Style.Unit.PX);
            panel.setWidth("100%");
            panel.setHeight("100%");
            panel.addNorth(toolbar, 60);
            panel.add(textArea);
    
            // Add the components to a panel
            container.setWidget(panel);
        }
    

    Here we’ve set editor size (100% for width and height), and then created a RichTextToolbar which is a custom component that represents rich text editor buttons (changing background color, font color, text layout, adding bullet lists etc).

    With DockLayoutPanel (class provided by RichTextArea) we group this toolbar to ensure it is always located on the top (60px stripe on the top of the editor) and the rest area is reserved for editor itself.

    Getting Instance of an Editor

    Editor provider is used to create a new instance on an editor. To do that we should implement EditorProvider class. It is important that Editor Provider always returns a new instance.

     public EditorPartPresenter getEditor() {
            return new WysiwygEditor();
        }
    

    Full code: com.codenvy.ide.tutorial.wysiwyg.WysiwygEditorProvider

    Binding Editor With a Particular File Type

    Now we need to make sure IDE knows what exact editor to use when opening particular files types. In other words, we should ensure all HTML files are opened with WYSIWYG editor.

    @Inject
        public WysiwygExtension(ResourceProvider resourceProvider, WysiwygEditorProvider editorProvider, EditorRegistry editorRegistry, NewHTMLFileProvider newHTMLFileProvider, NewResourceAgent newResourceAgent) {
    
            FileType htmlFileType = new FileType(null, "text/html", "html");
            resourceProvider.registerFileType(htmlFileType);
            editorRegistry.register(htmlFileType, editorProvider);
            newResourceAgent.register(newHTMLFileProvider);
        }
    
    

    Full code: com.codenvy.ide.tutorial.wysiwyg.WysiwygExtension

    A few words on what happens in this code:

    • Register a new file type (can be skipped if you already have it)
      new FileType(null, "text/html", "html")

      Instead of null an icon should be used (it’ll be displayed in the project tree and file tab in the editor)

    • bind this file type with editor provider
        editorRegistry.register(htmlFileType, editorProvider);

    Theme API

    Themes API is required to be able to quickly change look and feel of the IDE, and have an easy way to change fonts, their color and the entire color scheme of IDE in a centralized way. As a result, with Themes API it has become possible to choose different color schemes and add new ones.

    Scope

    In fact, Themes API is all about changing colors of IDE components without changing layout and forms of UI objects. Themes API rather supports color schemes, but not the entire UI and its components in a broad sense.

    In Themes API it has become possible to change color scheme for syntax highlighting which is thought to be one of potentially most typical use cases. So, if a user needs to change syntax highlighting there are two ways to do it:

    • create own color schemes
    • use tools to configure just syntax highlighting, editor background, fonts etc. (possible, but not implemented yet)

    Theme is related to user account and is stored as part of user account settings.

    Limitations

    As said above, it is possible to configure colors for UI elements, as well as change types and size of fonts. It is impossible to change the look and layout of UI elements, like changing the look of a button or a dialogue box (its geometrical shape etc.)

    How to Use Theme API

    There are basically two scenarios possible: adding own theme and using existing Theme API in own custom theme components

    Adding Own Theme

    To add a new theme one needs to implement an interface com.codenvy.ide.api.ui.theme.Theme, and register it in a ThemeAgent – com.codenvy.ide.api.ui.theme.ThemeAgent. This is how it's done:

    void addTheme(@NotNull Theme theme);
    

    Alternatively, it is possible just to extend an existing theme (rather than implementing own). However, in this case it will still be a new Theme which will be based on an existing one. In such a way, users will just change colors they need to change. Of course, if the goal is a total theme overhaul – then the work should be done from the ground up (i.e. own implementation)

    Using Theme API in Own Custom Components

    To be able to use Themes API one needs to be familiar with CssResource, where runtime substitution is used:

    public interface CoreCss extends CssResource {…
     
    }
     
    public interface Resources extends ClientBudnle {
     
    @Source({"Core.css", "com/codenvy/ide/api/ui/style.css"})
     
    CoreCss coreCss();
     
    }
    

    In the above example, we ensure that variables declared in style.css (provided by Themes API) are visible in a custom CSS – Core.css.

    For example, if in your custom css file you need to use a default font family, font color and font size, this is how it will look like:

     
    textarea {
     
    font-family: mainFontFamily;
     
    color: mainFontColor;
     
    background-color: inputBackground;
     
    border: 1px solid tabBorder;
     
    border-radius: 2px;
     
    font-size: fontSize;
     
    }
    

    Here, mainFontColor, mainFontFamily and mainFontSize are declared in style.css provided by Themes API, and thus can be re-used in a custom css file.

    Theme API: Extending Dark Theme

    SDK users can easily change looks and feel of the product by adding own themes or extending existing themes developed by Codenvy SDK team. In this tutorial we’ll extend Dark Theme and add two custom colors to change font and background of some UI components. Also, we’ll provide screenshots with methods that are responsible for UI colors, so that users can easily find necessary UI components and provide their own colors.

    Extend Dark Theme

    To get started with creating own theme based on the existing one, let’s extend Dark Theme which is provided in API.

    public class DarkThemeExt extends DarkTheme {
    

    Next, we’ll override Theme ID and name:

        @Override
        public String getId() {
            return "new theme id";
        }
    
        @Override
        public String getDescription() {
            return "New extended dark theme";
        }
    
    

    A new name will be displayed in the list of available themes at Window > Preferences > Themes

    Now, let’s override colors for main font and some panel background:

        @Override
        public String getMainFontColor() {
            return "red";
        }
    
        @Override
        public String getPartBackground() {
            return "white";
        }
    
        @Override
        public String getTabsPanelBackground() {
            return "white";
        }
    }
    
    

    Register Own Theme Using GIN Multibinding

    Let’s register a new theme:

    GinMultibinder<Theme> themeBinder = GinMultibinder.newSetBinder(binder(), Theme.class);
            themeBinder.addBinding().to(DarkThemeExt.class);
    

    You can learn more about GIN and GIN multibinding in particular in a dedicated GIN tutorial.

    Add Theme to SDK Bundle

    Having tested your new theme (you can update source code and update extension in runtime), you can add it to sdk bundle, so that it loads along with all other extensions when starting the SDK. Check this guide to add own extensions to SDK. Having recompiled the project, you can find your new extension in Window > Preferences > Themes.

    Screenshots With Methods

    Click to see a larger image

    Click to see a larger image

    Click to see a larger image

    Click to see a larger image

    Click to see a larger image

    Click to see a larger image

    Icon Registry API

    Plug-ins usually require own resources, including icons. Icon Registry API simplifies the procedure of adding own images to Codenvy extensions. In this tutorial we'll add a new item to Help menu, and show popup with an image upon clicking on it.

    Use Images in Extensions

    Add Image
     @Inject
     public MyExtension(IconRegistry iconRegistry){
          iconRegistry.registerIcon("my.icon", "my-extension/mammoth_happy.png");
     }
    

    Key points to focus on:

    • provide image ID which is my.icon in our case
    • provide image name which is mammoth_happy.png in our case
    • reference to the image should contain parent folder (!obligatory). Moreover, make sure thus folder has a unique name
    • an icon should be located in a public resources directory. Also, it is important to have extension gwt.xml file in a public folder:

    iconregistry

    Use Image

    Now, we'll add a new item to Help menu and call a popup with a provided image upon clicking on it:

    @Inject
        public MyAction(IconRegistry iconRegistry) {
            super("Show Image");
            this.iconRegistry = iconRegistry;
        }
        /**
         * Define the action required when calling this method. In our case it'll open a dialogue box with defined Image
         */
        @Override
        public void actionPerformed(ActionEvent arg0) {
            PopupPanel popup = new PopupPanel(true);
            popup.add(iconRegistry.getIcon("my.icon"));
            popup.center();
            popup.show();
    
        }
    
    Run Extension and Test Success

    Launch extension at Run menu, click on the extension link that will show up in the Output Console, go to Help > Show Image.

    Use Default Image

    If a non-existing image ID has been used, a default image will be loaded which is located at assembly-sdk/target/tomcat-ide/webapps/ide/_app/default

    Use Icons in Project Tree

    Provide Own Icons

    If you need own icons for a custom/new project type, there are 3 types of icons you'll need:

    • big icon used in a Create a New Project Wizard
    • small icon for folder (or whatever folder type it may be, e.g. package for Java)
    • small icons for file types related to a registered project type

    Directory with an icon set should be located in /resources.../public folder in your extension.

    Use Icons

    Here's an example of how to provide types 3 icons for a JAR project type:

     iconRegistry.registerIcon("jar.projecttype.big.icon", "java-extension/jar_64.png");
     iconRegistry.registerSVGIcon("jar.folder.small.icon", "java-extension/package-icon.png");
     iconRegistry.registerSVGIcon("jar/java.file.small.icon", "java-extension/java-icon.png");
     iconRegistry.registerSVGIcon("jar/pom.xml.file.small.icon", resources.maven());
    

    A few words on how to reference icons:

    • jar is a project type
    • projecttype.big.icon says that this icon is to be used in Create a New Project Wizard
    • folder.small.icon is telling us this is a folder icon
    • java.file.small.icon is basically telling 2 things:
    • this icon is to be used for a file
    • this file has .java extension
    • jar/pom.xml.file.small.icon: here we specify an icon for a file with a particular name and a particular extension

    All project files are located in java-extension directory under /resources.../public.

    Use Default Image

    If an extension cannot find a specified resource ID, it will first look for the identical resource for a default project type, for example default.folder.small.icon, and having failed to find it, use the default image (which is a Codenvy logo).

    Server Side Tutorial

    In this Tutorial we will register a server side component, get in on client side and demonstrate it in a pop-up window. The tutorial is based on a sample Hello World extension which is available in templates for Codenvy Extension project type in Create a New Project Wizard.

    Server Side Hello World

    Register Server Side Component

    First, let’s register a RESTful server side component:

    @Path("hello")
    public class HelloWorldService {
        
        private MyDependency d;
        
        @Inject
        public HelloWorldService(MyDependency d) {
            this.d = d;
        }
        
        @GET
        @Path("{name}")
        public String sayHello(@PathParam("name") String name ) {
           return d.sayHello(name);
        }
    
    }
    
    Extend AbstractModule and Bind Classes

    Next, we’ll extend AbstractModule and override its configure method and create new bindings. DynaModule annotation is necessary for auto-deploy of components to a Guice container.

    @DynaModule
    public class MyModule extends AbstractModule {
        @Override
        protected void configure() {
            bind(HelloWorldService.class); // required, otherwise everrest framework won't recognize it
            bind(MyDependency.class); // may be omitted, class has simple constructor
        }
    }
    
    Add Logic to Server Side

    Add some logic to a server side component, making it return some text:

    @Singleton
    public class MyDependency {
        public String sayHello(String name) {
            return "Howdy, " + name;
        }
    }
    Full code - MyDependency.java
    
    Get Server Side Component on Client Side

    Having done that, it’s time to get server side component on a client side. Here, we prompt a user to enter his name, and return a server side component + name. Path to a server side component is the following - /api/ComponentName

    @Override
        public void actionPerformed(ActionEvent arg0) {
            String name = Window.prompt("What's your name?", "");
            AsyncRequest.build(RequestBuilder.GET, "/api/hello/" + name).send(new AsyncRequestCallback<String>(new StringUnmarshaller()) {
                protected void onSuccess(String answer) {
                    
                      Window.alert(answer);
                };
                protected void onFailure(Throwable arg0) {};
            });
          
        }
    

    Congrats! We have just got server side components on client side and used it in a simple popu-up window.

    Advanced Use Cases

    Multiple binding

    Sometimes it is necessary to inject multiple dependencies of the same type to some component, e.g. set of implementations of some interface.

    public interface Service {
        ...
    }
     
    public class Service1 implements Service {
        ...
    }
     
    public class Service2 implements Service {
        ...
    }
     
    @Singleton
    public class MyClass {
        @Inject
        public MyClass(Set<Service> services) {
            ...
        }
    }
    Example of Module that describes bindings:
    
    @DynaModule
    public class MyModule extends AbstractModule {
        @Override
        protected void configure() {
            ...
            Multibinder<Service> multiBinder = Multibinder.newSetBinder(binder(), Service.class);
            multiBinder.addBinding().to(Service1.class);
            multiBinder.addBinding().to(Service2.class);
             
            ...
        }
    }
    
    Lifecycle

    By default, Guice returns a new instance for each request. This behavior may be unacceptable for some components. This behavior may changed by putting @Singleton annotation on the class we want to treat as singleton.

    @Singleton
    public class MySingleton {
    ...
    }
    

    There is support for to other "lifecycle" annotations: @javax.annotation.PostConstruct and @javax.annotation.PreDestroy. They are applicable to any methods, methods may be private. See more details about both annotations in corresponded java-docs. Example of usage:

    class MyLongTask implements Runnable {
        public void run() {
            // do something
        }
    } 
    public class MyClass {
        ExecutorService pool = Executors.newSingleThreadExecutor();
     
        @PostConstruct
        public void init() {
            // start task
            pool.execute(new MyLongTask());
        }
     
        @PreDestroy
        public void dispose() {
            // attempt to stop ExecutorService
            pool.shutdownNow();
        }
    }
    
    Bootstrap

    To make deployment work at least two Java Servlet API components must be configured. Here is fragment of web.xml file:

    <listener>
        <listener-class>com.codenvy.inject.CodenvyBootstrap</listener-class>
    </listener>
    <filter>
        <filter-name>guiceFilter</filter-name>
        <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>guiceFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    Configuration

    All components may be configured with *.properties files, /WEB-INF/classes/codenvy directory inside web application is default location for configuration files. Configuration may be added in one or few *.properties files. File name doesn't matter. It is possible to setup external location for configuration. Location of configuration directory may be set with environment variable CODENVY_LOCAL_CONF_DIR. Properties loaded from external location prevails over properties loaded from a default location. Each Java component may inject any configuration property. There are support conversion of property to set of Java types (of course it is possible to get property as plain String):

    • boolean
    • Numeric (byte/short/int/long/float/double)
    • java.net.URI
    • java.net.URL
    • java.io.File
    • String[] (in configuration value must be set as comma separated string )

    Configuration parameters may be injected with constructor or directly in the fields. Parameter of field must be annotated with annotation @javax.inject.Named, value of annotation is name of corresponded property. For example, if configuration property is set to: data_file:/home/user/storage then in Java code it looks like:

    public class MyClass {
        ...
     
        @Inject
        public MyClass(@Named("data_file") File storage) {
            ...
        }
    }
    

    or

    public class MyClass {
        @Inject
        @Named("data_file")
        private File storage;
     
        ...
    }
    

    All system properties and environment variable may be injected in Java components. They get the prefixes "sys." and "env." respectively. So, for example, environment variable HOME name must be env.HOME. Here is example how inject value of system property java.io.tmpdir and value of environment variable HOME.

    public class MyClass {
        @Inject
        public MyClass(@Named("sys.java.io.tmpdir") File tmp, @Named("env.HOME") File home) {
            ...
        }
    }
    

    On other hand system properties and environment variables may be used in *.properties files. They are allowed in values on any properties in configuration in form ${name}. Actual value of system property (if it is not set then try environment variable) is used as placeholder for ${name}. Here is example of properties file:

    ...
    index.dir=${java.io.tmpdir}/my-index
    data.dir=${HOME}/my-data
    ...
    

    If java.io.tmpdir is "/tmp" and $HOME is "/home/andrew" then in you Java code you will get "/tmp/my-index" and "/home/andrew/my-data" respectively.

    Project API: Providing Icons for Project Types

    Having registered a new project type it is possible to provide custom icons for it. Moreover, such icons can be used not only by the IDE itself or its plugins but also third-party apps, for instance User Dashboard.

    Icons are provided by an extension as a Map, where icon type, file extension and file location are provided, as in the example below:

    @Override
        public Map<String, String> getIconRegistry() {
            Map<String, String> iconRegistry = new HashMap<>();
            iconRegistry.put("big.project.icon.svg", "maven/icons/maven.svg");
            return iconRegistry;
        }
    

    Here, big.project.icon is an indication that this is a big icon for a project type, while svgprojecttype directory in the extension:

    com.codenvy.ide.extension.maven.public.projecttype

    So, an absolute path will be:

    com.codenvy.ide.extension.maven.public.projecttype.maven.icons.maven.svg
    

    After GWT compilation the icon will acquire a permanent URL accessible for third-party applications that can use it without any restrictions, for example:

    
    https://codenvy.com/ide/_app/projecttype/maven/icons/maven.svg