diff --git a/dashboard/src/app/ide/ide-iframe-button-link/ide-iframe-button-link.styl b/dashboard/src/app/ide/ide-iframe-button-link/ide-iframe-button-link.styl
index 65e8feecb6..9edd0a0ed5 100644
--- a/dashboard/src/app/ide/ide-iframe-button-link/ide-iframe-button-link.styl
+++ b/dashboard/src/app/ide/ide-iframe-button-link/ide-iframe-button-link.styl
@@ -4,7 +4,7 @@
outline none
width 24px
height 16px
- top 4px
+ top 9px
background-color $navbar-ide-iframe-button-background-color
color $light-gray-color
font-size 8px
diff --git a/dashboard/src/app/navbar/navbar.styl b/dashboard/src/app/navbar/navbar.styl
index 9481c4c7d8..ce253d99b6 100644
--- a/dashboard/src/app/navbar/navbar.styl
+++ b/dashboard/src/app/navbar/navbar.styl
@@ -298,7 +298,7 @@ che-nav-bar
.navbar-iframe-button-left-border
position absolute
- top 4px
+ top 9px
right 0
width 0
height 16px
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/action/IdeActions.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/action/IdeActions.java
index 4d046912b8..38aa616d21 100644
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/action/IdeActions.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/action/IdeActions.java
@@ -44,6 +44,7 @@ public interface IdeActions {
String GROUP_OTHER_MENU = "otherMenu";
String GROUP_LEFT_MAIN_MENU = "leftMainMenu";
+ @Deprecated
String GROUP_RIGHT_MAIN_MENU = "rightMainMenu";
String GROUP_CENTER_STATUS_PANEL = "centerStatusPanelGroup";
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/BaseCommandGoal.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/BaseCommandGoal.java
new file mode 100644
index 0000000000..819914f304
--- /dev/null
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/BaseCommandGoal.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.api.command;
+
+import java.util.Objects;
+
+/**
+ * Base implementation of the {@link CommandGoal}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class BaseCommandGoal implements CommandGoal {
+
+ private final String id;
+
+ public BaseCommandGoal(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof CommandGoal)) {
+ return false;
+ }
+
+ CommandGoal other = (CommandGoal)o;
+
+ return Objects.equals(getId(), other.getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+}
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandExecutor.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandExecutor.java
new file mode 100644
index 0000000000..6355bba3af
--- /dev/null
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandExecutor.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.api.command;
+
+import org.eclipse.che.api.core.model.machine.Command;
+import org.eclipse.che.api.core.model.machine.Machine;
+import org.eclipse.che.ide.api.macro.Macro;
+
+/**
+ * Allows to execute a command.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface CommandExecutor {
+
+ /**
+ * Sends the the given {@code command} to the specified {@code machine} for execution.
+ *
Note that all {@link Macro}s will be expanded into
+ * real values before sending the {@code command} for execution.
+ *
+ * @param command
+ * command to execute
+ * @param machine
+ * machine to execute the command
+ * @see Macro
+ */
+ void executeCommand(Command command, Machine machine);
+
+ /**
+ * Sends the the given {@code command} for execution.
+ *
If any machine is currently selected it will be used as execution target.
+ * Otherwise user will be asked for choosing execution target.
+ *
Note that all {@link Macro}s will be expanded into
+ * real values before sending the {@code command} for execution.
+ *
+ * @param command
+ * command to execute
+ * @see Macro
+ */
+ void executeCommand(CommandImpl command);
+}
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandGoal.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandGoal.java
new file mode 100644
index 0000000000..1092083d47
--- /dev/null
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandGoal.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.api.command;
+
+/**
+ * Contract for command goal.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface CommandGoal {
+
+ /** Returns goal ID. */
+ String getId();
+}
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandGoalRegistry.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandGoalRegistry.java
new file mode 100644
index 0000000000..6f493f3584
--- /dev/null
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandGoalRegistry.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.api.command;
+
+import org.eclipse.che.commons.annotation.Nullable;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Registry of command goals.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface CommandGoalRegistry {
+
+ /** Returns all registered predefined {@link CommandGoal}s. */
+ List getAllPredefinedGoals();
+
+ /** Returns the default command goal which is used for grouping commands which doesn't belong to any goal. */
+ CommandGoal getDefaultGoal();
+
+ /**
+ * Returns an optional {@link CommandGoal} by the given ID
+ * or {@code Optional.absent()} if none was registered.
+ *
+ * @param id
+ * the ID of the predefined command goal to get
+ * @return an optional {@link CommandGoal} or {@code Optional.absent()} if none was registered
+ */
+ Optional getPredefinedGoalById(String id);
+
+ /**
+ * Returns a predefined {@link CommandGoal} by the given ID
+ * or the custom (non-predefined) goal if none was registered
+ * or the default goal if the given {@code id} is {@code null} or empty string.
+ *
+ * @param id
+ * the ID of the command goal
+ * @return a predefined {@link CommandGoal} with the given ID
+ * or the custom (non-predefined) goal if none was registered
+ * or the default goal if the given {@code id} is {@code null} or empty string. Never null.
+ */
+ CommandGoal getGoalForId(@Nullable String id);
+}
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandImpl.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandImpl.java
index 414f16bea8..3813d0c16d 100644
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandImpl.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandImpl.java
@@ -11,42 +11,38 @@
package org.eclipse.che.ide.api.command;
import org.eclipse.che.api.core.model.machine.Command;
+import org.eclipse.che.commons.annotation.Nullable;
-import java.util.Collections;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
-/**
- * Model of the command.
- *
- * @author Artem Zatsarynnyi
- */
+import static java.util.Collections.unmodifiableList;
+import static org.eclipse.che.api.workspace.shared.Constants.COMMAND_GOAL_ATTRIBUTE_NAME;
+
+/** Data object for {@link Command}. */
public class CommandImpl implements Command {
- private final String type;
+ private final String typeId;
+ private final ApplicableContext context;
private String name;
private String commandLine;
private Map attributes;
- /**
- * Creates new command of the specified type with the given name and command line.
- *
- * @param name
- * command name
- * @param commandLine
- * command line
- * @param type
- * type of the command
- */
- public CommandImpl(String name, String commandLine, String type) {
- this(name, commandLine, type, Collections.emptyMap());
+ /** Creates new {@link CommandImpl} based on the given data. */
+ public CommandImpl(Command command, ApplicableContext context) {
+ this(command.getName(),
+ command.getCommandLine(),
+ command.getType(),
+ new HashMap<>(command.getAttributes()),
+ context);
}
- public CommandImpl(String name, String commandLine, String type, Map attributes) {
- this.name = name;
- this.commandLine = commandLine;
- this.type = type;
- this.attributes = attributes;
+ /** Creates new {@link CommandImpl} based on the provided data. */
+ public CommandImpl(String name, String commandLine, String typeId) {
+ this(name, commandLine, typeId, new HashMap<>());
}
/** Creates copy of the given {@link Command}. */
@@ -54,7 +50,34 @@ public class CommandImpl implements Command {
this(command.getName(),
command.getCommandLine(),
command.getType(),
- command.getAttributes());
+ new HashMap<>(command.getAttributes()));
+ }
+
+ /** Creates copy of the given {@code command}. */
+ public CommandImpl(CommandImpl command) {
+ this(command.getName(),
+ command.getCommandLine(),
+ command.getType(),
+ new HashMap<>(command.getAttributes()),
+ new ApplicableContext(command.getApplicableContext()));
+ }
+
+ /** Creates new {@link CommandImpl} based on the provided data. */
+ public CommandImpl(String name, String commandLine, String typeId, Map attributes) {
+ this.name = name;
+ this.commandLine = commandLine;
+ this.typeId = typeId;
+ this.attributes = attributes;
+ this.context = new ApplicableContext();
+ }
+
+ /** Creates new {@link CommandImpl} based on the provided data. */
+ public CommandImpl(String name, String commandLine, String typeId, Map attributes, ApplicableContext context) {
+ this.name = name;
+ this.commandLine = commandLine;
+ this.typeId = typeId;
+ this.attributes = attributes;
+ this.context = context;
}
@Override
@@ -77,7 +100,7 @@ public class CommandImpl implements Command {
@Override
public String getType() {
- return type;
+ return typeId;
}
@Override
@@ -89,6 +112,27 @@ public class CommandImpl implements Command {
this.attributes = attributes;
}
+ /** Returns ID of the command's goal or {@code null} if none. */
+ @Nullable
+ public String getGoal() {
+ return getAttributes().get(COMMAND_GOAL_ATTRIBUTE_NAME);
+ }
+
+ /** Sets command's goal ID. */
+ public void setGoal(String goalId) {
+ getAttributes().put(COMMAND_GOAL_ATTRIBUTE_NAME, goalId);
+ }
+
+ /** Returns command's applicable context. */
+ public ApplicableContext getApplicableContext() {
+ return context;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see #equalsIgnoreContext(CommandImpl)
+ */
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -102,13 +146,109 @@ public class CommandImpl implements Command {
CommandImpl other = (CommandImpl)o;
return Objects.equals(getName(), other.getName())
- && Objects.equals(type, other.type)
+ && Objects.equals(typeId, other.typeId)
&& Objects.equals(commandLine, other.commandLine)
- && Objects.equals(getAttributes(), other.getAttributes());
+ && Objects.equals(getAttributes(), other.getAttributes())
+ && Objects.equals(getApplicableContext(), other.getApplicableContext());
+ }
+
+ /**
+ * Compares this {@link CommandImpl} to another {@link CommandImpl}, ignoring applicable context considerations.
+ *
+ * @param anotherCommand
+ * the {@link CommandImpl} to compare this {@link CommandImpl} against
+ * @return {@code true} if the argument represents an equivalent {@link CommandImpl}
+ * ignoring applicable context; {@code false} otherwise
+ */
+ public boolean equalsIgnoreContext(CommandImpl anotherCommand) {
+ if (this == anotherCommand) {
+ return true;
+ }
+
+ return Objects.equals(getName(), anotherCommand.getName())
+ && Objects.equals(typeId, anotherCommand.typeId)
+ && Objects.equals(commandLine, anotherCommand.commandLine)
+ && Objects.equals(getAttributes(), anotherCommand.getAttributes());
}
@Override
public int hashCode() {
- return Objects.hash(name, type, commandLine, getAttributes());
+ return Objects.hash(name, typeId, commandLine, getAttributes(), getApplicableContext());
+ }
+
+ /** Defines the context in which command is applicable. */
+ public static class ApplicableContext {
+
+ private boolean workspaceApplicable;
+ private List projects;
+
+ /** Creates new {@link ApplicableContext} which is workspace applicable. */
+ public ApplicableContext() {
+ workspaceApplicable = true;
+ projects = new ArrayList<>();
+ }
+
+ /** Creates new {@link ApplicableContext} which is applicable to the single project only. */
+ public ApplicableContext(String projectPath) {
+ projects = new ArrayList<>();
+ projects.add(projectPath);
+ }
+
+ /** Creates new {@link ApplicableContext} based on the provided data. */
+ public ApplicableContext(boolean workspaceApplicable, List projects) {
+ this.workspaceApplicable = workspaceApplicable;
+ this.projects = projects;
+ }
+
+ /** Creates copy of the given {@code context}. */
+ public ApplicableContext(ApplicableContext context) {
+ this(context.isWorkspaceApplicable(), new ArrayList<>(context.getApplicableProjects()));
+ }
+
+ /** Returns {@code true} if command is applicable to the workspace and {@code false} otherwise. */
+ public boolean isWorkspaceApplicable() {
+ return workspaceApplicable;
+ }
+
+ /** Sets whether the command should be applicable to the workspace or not. */
+ public void setWorkspaceApplicable(boolean applicable) {
+ this.workspaceApplicable = applicable;
+ }
+
+ /** Returns immutable list of the paths of the applicable projects. */
+ public List getApplicableProjects() {
+ return unmodifiableList(projects);
+ }
+
+ /** Adds applicable project's path. */
+ public void addProject(String path) {
+ projects.add(path);
+ }
+
+ /** Removes applicable project's path. */
+ public void removeProject(String path) {
+ projects.remove(path);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof ApplicableContext)) {
+ return false;
+ }
+
+ ApplicableContext other = (ApplicableContext)o;
+
+ return workspaceApplicable == other.workspaceApplicable &&
+ Objects.equals(projects, other.projects);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(workspaceApplicable, projects);
+ }
}
}
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandManager.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandManager.java
index 5c9b70c289..3b1e80d121 100644
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandManager.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/command/CommandManager.java
@@ -10,73 +10,158 @@
*******************************************************************************/
package org.eclipse.che.ide.api.command;
-import org.eclipse.che.api.core.model.machine.Machine;
import org.eclipse.che.api.promises.client.Promise;
-import org.eclipse.che.ide.api.macro.Macro;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.api.command.CommandImpl.ApplicableContext;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
/**
- * Facade for command related operations.
+ * Facade for command management.
*
- * @author Artem Zatsarynnyi
+ * @see CommandImpl
*/
public interface CommandManager {
/** Returns all commands. */
List getCommands();
- /**
- * Creates new command of the specified type.
- * Note that command's name will be generated by {@link CommandManager}
- * and command line will be provided by an appropriate {@link CommandType}.
- */
- Promise create(String type);
+ /** Returns optional command by the specified name or {@link Optional#empty()} if none. */
+ Optional getCommand(String name);
+
+ /** Returns commands which are applicable to the current IDE context. */
+ List getApplicableCommands();
+
+ /** Checks whether the given {@code command} is applicable to the current IDE context or not. */
+ boolean isCommandApplicable(CommandImpl command);
/**
- * Creates new command with the specified arguments.
- * Note that name of the created command may differ from
- * the specified {@code desirableName} in order to prevent name duplication.
+ * Creates new command based on the given data.
+ * Command's name and command line will be generated automatically.
+ * Command will be bound to the workspace.
+ *
+ * @param goalId
+ * ID of the goal to which created command should belong
+ * @param typeId
+ * ID of the type to which created command should belong
+ * @return created command
*/
- Promise create(String desirableName, String commandLine, String type, Map attributes);
+ Promise createCommand(String goalId, String typeId);
+
+ /**
+ * Creates new command based on the given data.
+ * Command's name and command line will be generated automatically.
+ *
+ * @param goalId
+ * ID of the goal to which created command should belong
+ * @param typeId
+ * ID of the type to which created command should belong
+ * @param context
+ * command's {@link ApplicableContext}
+ * @return created command
+ */
+ Promise createCommand(String goalId, String typeId, ApplicableContext context);
+
+ /**
+ * Creates new command based on the given data. Command will be bound to the workspace.
+ *
+ * @param goalId
+ * ID of the goal to which created command should belong
+ * @param typeId
+ * ID of the type to which created command should belong
+ * @param name
+ * command's name.
+ * Note that actual name may differ from the given one in order to prevent duplication.
+ * If {@code null}, name will be generated automatically.
+ * @param commandLine
+ * actual command line. If {@code null}, command line will be generated by the corresponding command type.
+ * @param attributes
+ * command's attributes
+ * @return created command
+ */
+ Promise createCommand(String goalId,
+ String typeId,
+ @Nullable String name,
+ @Nullable String commandLine,
+ Map attributes);
+
+ /**
+ * Creates new command based on the given data.
+ *
+ * @param goalId
+ * ID of the goal to which created command should belong
+ * @param typeId
+ * ID of the type to which created command should belong
+ * @param name
+ * command's name.
+ * Note that actual name may differ from the given one in order to prevent duplication.
+ * If {@code null}, name will be generated automatically.
+ * @param commandLine
+ * actual command line. If {@code null}, command line will be generated by the corresponding command type.
+ * @param attributes
+ * command's attributes
+ * @param context
+ * command's {@link ApplicableContext}
+ * @return created command
+ */
+ Promise createCommand(String goalId,
+ String typeId,
+ @Nullable String name,
+ @Nullable String commandLine,
+ Map attributes,
+ ApplicableContext context);
+
+ /**
+ * Creates copy of the given {@code command}.
+ * Note that name of the created command may differ from
+ * the given {@code command}'s name in order to prevent name duplication.
+ */
+ Promise createCommand(CommandImpl command);
/**
* Updates the command with the specified {@code name} by replacing it with the given {@code command}.
* Note that name of the updated command may differ from the name provided by the given {@code command}
* in order to prevent name duplication.
*/
- Promise update(String name, CommandImpl command);
+ Promise updateCommand(String name, CommandImpl command);
- /** Removes the command with the specified {@code name}. */
- Promise remove(String name);
+ /** Removes command with the specified {@code commandName}. */
+ Promise removeCommand(String commandName);
- /** Returns the pages for editing command of the specified {@code type}. */
- List getPages(String type);
+ void addCommandLoadedListener(CommandLoadedListener listener);
- /**
- * Sends the the given {@code command} to the specified {@code machine} for execution.
- * Note that all {@link Macro}s will be expanded into
- * real values before sending the {@code command} for execution.
- *
- * @param command
- * command to execute
- * @param machine
- * machine to execute the command
- * @see Macro
- */
- void executeCommand(CommandImpl command, Machine machine);
+ void removeCommandLoadedListener(CommandLoadedListener listener);
void addCommandChangedListener(CommandChangedListener listener);
void removeCommandChangedListener(CommandChangedListener listener);
- /** Listener that will be called when command has been changed. */
+ /** Listener to notify when all commands have been loaded. */
+ interface CommandLoadedListener {
+
+ /** Called when all commands have been loaded. */
+ void onCommandsLoaded();
+ }
+
+ /** Listener to notify when command has been changed. */
interface CommandChangedListener {
+
+ /** Called when command has been added. */
void onCommandAdded(CommandImpl command);
- void onCommandUpdated(CommandImpl command);
+ /**
+ * Called when command has been updated.
+ *
+ * @param previousCommand
+ * command before updating
+ * @param command
+ * updated command
+ */
+ void onCommandUpdated(CommandImpl previousCommand, CommandImpl command);
+ /** Called when command has been removed. */
void onCommandRemoved(CommandImpl command);
}
}
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/AbstractEditorPresenter.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/AbstractEditorPresenter.java
index ffc696677b..3dd2a9a2fe 100644
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/AbstractEditorPresenter.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/AbstractEditorPresenter.java
@@ -10,6 +10,7 @@
*******************************************************************************/
package org.eclipse.che.ide.api.editor;
+import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.IsWidget;
import org.eclipse.che.ide.api.parts.AbstractPartPresenter;
@@ -89,4 +90,9 @@ public abstract class AbstractEditorPresenter extends AbstractPartPresenter impl
public void onFileChanged() {
firePropertyChange(TITLE_PROPERTY);
}
+
+ @Override
+ public void onClosing(AsyncCallback callback) {
+ callback.onSuccess(null);
+ }
}
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorPartPresenter.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorPartPresenter.java
index ce71240e48..cc999eb7f5 100644
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorPartPresenter.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorPartPresenter.java
@@ -112,4 +112,13 @@ public interface EditorPartPresenter extends PartPresenter {
* true if unsaved changed should be saved, and false if unsaved changed should be discarded
*/
void close(boolean save);
+
+ /**
+ * Called when part is going to closing.
+ * Part can deny closing, by calling {@code callback#onFailure}.
+ *
+ * @param callback
+ * callback to allow or deny closing the part
+ */
+ void onClosing(AsyncCallback callback);
}
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/document/DocumentStorageImpl.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/document/DocumentStorageImpl.java
index 5dbcea7d6c..15fecab6cb 100644
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/document/DocumentStorageImpl.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/document/DocumentStorageImpl.java
@@ -76,7 +76,7 @@ public class DocumentStorageImpl implements DocumentStorage {
@Override
public void apply(Void arg) throws OperationException {
Log.debug(DocumentStorageImpl.class, "Document saved (" + file.getLocation() + ").");
- DocumentStorageImpl.this.eventBus.fireEvent(FileEvent.createSaveFileEvent(file));
+ DocumentStorageImpl.this.eventBus.fireEvent(FileEvent.createFileSavedEvent(file));
try {
callback.onSuccess(editorInput);
} catch (final Exception e) {
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/texteditor/EditorWidget.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/texteditor/EditorWidget.java
index 574b61332c..f3f4fda9c0 100644
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/texteditor/EditorWidget.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/texteditor/EditorWidget.java
@@ -79,6 +79,18 @@ public interface EditorWidget extends IsWidget,
*/
void setReadOnly(boolean isReadOnly);
+ /** Sets whether the annotation ruler is visible. */
+ void setAnnotationRulerVisible(boolean show);
+
+ /** Sets whether the folding ruler is visible. */
+ void setFoldingRulerVisible(boolean show);
+
+ /** Sets whether the zoom ruler is visible. */
+ void setZoomRulerVisible(boolean show);
+
+ /** Sets whether the overview ruler is visible. */
+ void setOverviewRulerVisible(boolean show);
+
/**
* Returns the readonly state of the editor.
*
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/event/FileEvent.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/event/FileEvent.java
index a9069ffb0b..ca628f31f1 100755
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/event/FileEvent.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/event/FileEvent.java
@@ -10,19 +10,21 @@
*******************************************************************************/
package org.eclipse.che.ide.api.event;
-import org.eclipse.che.commons.annotation.Nullable;
-import org.eclipse.che.ide.api.parts.EditorTab;
-import org.eclipse.che.ide.api.resources.VirtualFile;
-
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.api.editor.EditorAgent;
+import org.eclipse.che.ide.api.editor.EditorPartPresenter;
+import org.eclipse.che.ide.api.parts.EditorTab;
+import org.eclipse.che.ide.api.resources.VirtualFile;
+
import static org.eclipse.che.ide.api.event.FileEvent.FileOperation.CLOSE;
import static org.eclipse.che.ide.api.event.FileEvent.FileOperation.OPEN;
import static org.eclipse.che.ide.api.event.FileEvent.FileOperation.SAVE;
/**
- * Event that describes the fact that file is going to be opened.
+ * Event that describes the fact that file is opened/closed/saved.
*
* @author Nikolay Zamosenchuk
* @author Artem Zatsarynnyi
@@ -30,11 +32,6 @@ import static org.eclipse.che.ide.api.event.FileEvent.FileOperation.SAVE;
*/
public class FileEvent extends GwtEvent {
- /** Handles OpenFileEvent */
- public interface FileEventHandler extends EventHandler {
- void onFileOperation(FileEvent event);
- }
-
public static Type TYPE = new Type<>();
private VirtualFile file;
private FileOperation fileOperation;
@@ -69,6 +66,12 @@ public class FileEvent extends GwtEvent {
/**
* Creates a event for {@code FileOperation.OPEN}.
*/
+ public static FileEvent createFileOpenedEvent(VirtualFile file) {
+ return new FileEvent(file, OPEN);
+ }
+
+ /** @deprecated use {@link EditorAgent#openEditor(org.eclipse.che.ide.api.resources.VirtualFile)} */
+ @Deprecated
public static FileEvent createOpenFileEvent(VirtualFile file) {
return new FileEvent(file, OPEN);
}
@@ -80,6 +83,12 @@ public class FileEvent extends GwtEvent {
* @param tab
* tab of the file to close
*/
+ public static FileEvent createFileClosedEvent(EditorTab tab) {
+ return new FileEvent(tab, CLOSE);
+ }
+
+ /** @deprecated use {@link EditorAgent#closeEditor(EditorPartPresenter)} */
+ @Deprecated
public static FileEvent createCloseFileEvent(EditorTab tab) {
return new FileEvent(tab, CLOSE);
}
@@ -87,6 +96,11 @@ public class FileEvent extends GwtEvent {
/**
* Creates a event for {@code FileOperation.SAVE}.
*/
+ public static FileEvent createFileSavedEvent(VirtualFile file) {
+ return new FileEvent(file, SAVE);
+ }
+
+ @Deprecated
public static FileEvent createSaveFileEvent(VirtualFile file) {
return new FileEvent(file, SAVE);
}
@@ -120,4 +134,9 @@ public class FileEvent extends GwtEvent {
public enum FileOperation {
OPEN, SAVE, CLOSE
}
+
+ /** Handles OpenFileEvent */
+ public interface FileEventHandler extends EventHandler {
+ void onFileOperation(FileEvent event);
+ }
}
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/icon/Icon.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/icon/Icon.java
index b09bf14407..7f71bddd8d 100644
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/icon/Icon.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/icon/Icon.java
@@ -14,11 +14,10 @@ import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.Image;
+import org.eclipse.che.commons.annotation.Nullable;
import org.vectomatic.dom.svg.ui.SVGImage;
import org.vectomatic.dom.svg.ui.SVGResource;
-import org.eclipse.che.commons.annotation.Nullable;
-
/**
* Icon.
*
@@ -129,4 +128,14 @@ public class Icon {
}
return new SVGImage(svgResource);
}
+
+ /**
+ * Returns {@link SVGResource} widget.
+ *
+ * @return {@link SVGResource} widget
+ */
+ @Nullable
+ public SVGResource getSVGResource() {
+ return svgResource;
+ }
}
diff --git a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/ProcessFinishedEvent.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/events/ProcessFinishedEvent.java
similarity index 79%
rename from plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/ProcessFinishedEvent.java
rename to ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/events/ProcessFinishedEvent.java
index 05720c9ee6..f42e1f3621 100644
--- a/plugins/plugin-machine/che-plugin-machine-ext-client/src/main/java/org/eclipse/che/ide/extension/machine/client/processes/ProcessFinishedEvent.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/events/ProcessFinishedEvent.java
@@ -8,11 +8,13 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
-package org.eclipse.che.ide.extension.machine.client.processes;
+package org.eclipse.che.ide.api.machine.events;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
+import org.eclipse.che.api.core.model.machine.Machine;
+
/**
* @author Dmitry Shnurenko
*/
@@ -27,16 +29,22 @@ public class ProcessFinishedEvent extends GwtEvent
public static final Type TYPE = new Type<>();
- private final int processID;
+ private final int processID;
+ private final Machine machine;
- public ProcessFinishedEvent(int processID) {
+ public ProcessFinishedEvent(int processID, Machine machine) {
this.processID = processID;
+ this.machine = machine;
}
public int getProcessID() {
return processID;
}
+ public Machine getMachine() {
+ return machine;
+ }
+
@Override
public Type getAssociatedType() {
return TYPE;
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/events/ProcessStartedEvent.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/events/ProcessStartedEvent.java
new file mode 100644
index 0000000000..80f0cb2d01
--- /dev/null
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/machine/events/ProcessStartedEvent.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.api.machine.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+import org.eclipse.che.api.core.model.machine.Machine;
+
+public class ProcessStartedEvent extends GwtEvent {
+
+ public static final Type TYPE = new Type<>();
+
+ private final int processID;
+ private final Machine machine;
+
+ public ProcessStartedEvent(int processID, Machine machine) {
+ this.processID = processID;
+ this.machine = machine;
+ }
+
+ public int getProcessID() {
+ return processID;
+ }
+
+ public Machine getMachine() {
+ return machine;
+ }
+
+ @Override
+ public Type getAssociatedType() {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler) {
+ handler.onProcessStarted(this);
+ }
+
+ public interface Handler extends EventHandler {
+
+ void onProcessStarted(ProcessStartedEvent event);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/macro/CustomMacro.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/macro/BaseMacro.java
similarity index 84%
rename from ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/macro/CustomMacro.java
rename to ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/macro/BaseMacro.java
index 349eadf059..a338100403 100644
--- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/macro/CustomMacro.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/macro/BaseMacro.java
@@ -8,32 +8,29 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
-package org.eclipse.che.ide.macro;
+package org.eclipse.che.ide.api.macro;
-import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.js.Promises;
-import org.eclipse.che.ide.api.macro.Macro;
import static com.google.common.base.Preconditions.checkNotNull;
/**
- * Custom macro provider which allows to register user's macro with the provided value.
+ * Base implementation of {@link Macro}.
*
* @author Vlad Zhukovskyi
* @see Macro
* @since 4.7.0
*/
-@Beta
-public class CustomMacro implements Macro {
+public class BaseMacro implements Macro {
private final String key;
private final String value;
private final String description;
- public CustomMacro(String key, String value, String description) {
+ public BaseMacro(String key, String value, String description) {
this.key = checkNotNull(key, "Key should not be null");
this.value = checkNotNull(value, "Value should not be null");
this.description = checkNotNull(description, "Description should not be null");
@@ -61,7 +58,7 @@ public class CustomMacro implements Macro {
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- CustomMacro that = (CustomMacro)o;
+ BaseMacro that = (BaseMacro)o;
return Objects.equal(key, that.key) &&
Objects.equal(value, that.value) &&
Objects.equal(description, that.description);
@@ -76,7 +73,7 @@ public class CustomMacro implements Macro {
/** {@inheritDoc} */
@Override
public String toString() {
- return "CustomMacro{" +
+ return "BaseMacro{" +
"key='" + key + '\'' +
", value='" + value + '\'' +
", description='" + description + '\'' +
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/macro/Macro.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/macro/Macro.java
index 173c2e1d3b..cad42da8a5 100644
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/macro/Macro.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/macro/Macro.java
@@ -22,6 +22,7 @@ import java.util.Set;
* Also macro can be registered in 'runtime' with {@link MacroRegistry#register(Set)}.
*
* @author Artem Zatsarynnyi
+ * @see BaseMacro
* @see MacroProcessor#expandMacros(String)
* @see MacroRegistry
*/
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/macro/MacroProcessor.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/macro/MacroProcessor.java
index da74cd7e4d..7ff6f733d9 100644
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/macro/MacroProcessor.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/macro/MacroProcessor.java
@@ -10,17 +10,13 @@
*******************************************************************************/
package org.eclipse.che.ide.api.macro;
-import org.eclipse.che.api.core.model.machine.Machine;
import org.eclipse.che.api.promises.client.Promise;
-import org.eclipse.che.ide.api.command.CommandImpl;
-import org.eclipse.che.ide.api.command.CommandManager;
/**
* Expands all {@link Macro}s in the given string.
*
* @author Artem Zatsarynnyi
* @see Macro
- * @see CommandManager#executeCommand(CommandImpl, Machine)
*/
public interface MacroProcessor {
diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/SyntheticFile.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/SyntheticFile.java
index a734d02d35..e21205185c 100644
--- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/SyntheticFile.java
+++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/SyntheticFile.java
@@ -22,8 +22,7 @@ import org.eclipse.che.ide.resource.Path;
* Implementation for {@link VirtualFile} which describe resource which doesn't exist on file system and is auto generated.
* For example it may be effective version of such resource.
*
- * This file is read only and doesn't have link to the content url.
- * Calling {@link #updateContent(String)} will cause {@link UnsupportedOperationException}.
+ * This file doesn't have link to the content url.
*
* @author Vlad Zhukovskiy
* @see VirtualFile
@@ -77,7 +76,9 @@ public class SyntheticFile implements VirtualFile {
@Override
public Promise updateContent(String content) {
- throw new UnsupportedOperationException("Synthetic file is read only");
+ this.content = content;
+
+ return Promises.resolve(null);
}
@Override
diff --git a/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/machine/CustomMacroTest.java b/ide/che-core-ide-api/src/test/java/org/eclipse/che/ide/api/macro/BaseMacroTest.java
similarity index 58%
rename from ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/machine/CustomMacroTest.java
rename to ide/che-core-ide-api/src/test/java/org/eclipse/che/ide/api/macro/BaseMacroTest.java
index 94b4603000..bbc8436cfb 100644
--- a/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/machine/CustomMacroTest.java
+++ b/ide/che-core-ide-api/src/test/java/org/eclipse/che/ide/api/macro/BaseMacroTest.java
@@ -8,13 +8,10 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
-package org.eclipse.che.ide.machine;
+package org.eclipse.che.ide.api.macro;
import com.google.gwtmockito.GwtMockitoTestRunner;
-import org.eclipse.che.api.promises.client.Operation;
-import org.eclipse.che.api.promises.client.OperationException;
-import org.eclipse.che.ide.macro.CustomMacro;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -22,42 +19,39 @@ import org.junit.runner.RunWith;
import static org.junit.Assert.assertSame;
/**
- * Unit tests for the {@link CustomMacro}
+ * Unit tests for the {@link BaseMacro}
*
* @author Vlad Zhukovskyi
*/
@RunWith(GwtMockitoTestRunner.class)
-public class CustomMacroTest {
+public class BaseMacroTest {
- public static final String KEY = "key";
- public static final String VALUE = "value";
+ public static final String NAME = "name";
+ public static final String VALUE = "value";
public static final String DESCRIPTION = "description";
- private CustomMacro provider;
+ private BaseMacro macro;
@Before
public void init() throws Exception {
- provider = new CustomMacro(KEY, VALUE, DESCRIPTION);
+ macro = new BaseMacro(NAME, VALUE, DESCRIPTION);
}
@Test
public void getKey() throws Exception {
- assertSame(provider.getName(), KEY);
+ assertSame(macro.getName(), NAME);
}
@Test
public void getValue() throws Exception {
- provider.expand().then(new Operation() {
- @Override
- public void apply(String value) throws OperationException {
- assertSame(value, VALUE);
- }
+ macro.expand().then(value -> {
+ assertSame(value, VALUE);
});
}
@Test
public void getDescription() throws Exception {
- assertSame(provider.getDescription(), DESCRIPTION);
+ assertSame(macro.getDescription(), DESCRIPTION);
}
}
\ No newline at end of file
diff --git a/ide/che-core-ide-app/pom.xml b/ide/che-core-ide-app/pom.xml
index 8c16467ed1..0d064db40e 100644
--- a/ide/che-core-ide-app/pom.xml
+++ b/ide/che-core-ide-app/pom.xml
@@ -279,27 +279,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
org.eclipse.che.core
che-core-dyna-provider-generator-maven-plugin
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/Resources.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/Resources.java
index de8476e432..ebba260310 100644
--- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/Resources.java
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/Resources.java
@@ -15,6 +15,7 @@ import com.google.gwt.resources.client.CssResource.NotStrict;
import com.google.gwt.resources.client.TextResource;
import org.eclipse.che.ide.api.parts.PartStackUIResources;
+import org.eclipse.che.ide.command.CommandResources;
import org.eclipse.che.ide.menu.MenuResources;
import org.eclipse.che.ide.notification.NotificationResources;
import org.eclipse.che.ide.projecttype.wizard.ProjectWizardResources;
@@ -46,7 +47,8 @@ public interface Resources extends Tree.Resources,
CellTreeResources,
CategoriesList.Resources,
ButtonLoaderResources,
- ProjectWizardResources {
+ ProjectWizardResources,
+ CommandResources {
@Source({"Core.css", "org/eclipse/che/ide/ui/constants.css", "org/eclipse/che/ide/api/ui/style.css"})
@NotStrict
@@ -208,5 +210,8 @@ public interface Resources extends Tree.Resources,
String createWsTagsPopup();
String tagsPanel();
+
+ @ClassName("codeassistant-highlight")
+ String codeassistantHighlight();
}
}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/EditFileAction.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/EditFileAction.java
index f64cde7c1f..410b71bfbb 100644
--- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/EditFileAction.java
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/EditFileAction.java
@@ -12,13 +12,12 @@ package org.eclipse.che.ide.actions;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import com.google.web.bindery.event.shared.EventBus;
import org.eclipse.che.ide.Resources;
import org.eclipse.che.ide.api.action.AbstractPerspectiveAction;
import org.eclipse.che.ide.api.action.ActionEvent;
import org.eclipse.che.ide.api.app.AppContext;
-import org.eclipse.che.ide.api.event.FileEvent;
+import org.eclipse.che.ide.api.editor.EditorAgent;
import org.eclipse.che.ide.api.resources.File;
import org.eclipse.che.ide.api.resources.Resource;
@@ -38,17 +37,16 @@ import static org.eclipse.che.ide.workspace.perspectives.project.ProjectPerspect
@Singleton
public class EditFileAction extends AbstractPerspectiveAction {
-
- private final AppContext appContext;
- private final EventBus eventBus;
+ private final AppContext appContext;
+ private final EditorAgent editorAgent;
@Inject
public EditFileAction(AppContext appContext,
- EventBus eventBus,
- Resources resources) {
+ Resources resources,
+ EditorAgent editorAgent) {
super(singletonList(PROJECT_PERSPECTIVE_ID), "Edit file", null, null, resources.defaultFile());
this.appContext = appContext;
- this.eventBus = eventBus;
+ this.editorAgent = editorAgent;
}
/** {@inheritDoc} */
@@ -59,7 +57,7 @@ public class EditFileAction extends AbstractPerspectiveAction {
checkState(resources != null && resources.length == 1 && resources[0] instanceof File,
"Files only are allowed to be opened in editor");
- eventBus.fireEvent(FileEvent.createOpenFileEvent((File)resources[0]));
+ editorAgent.openEditor((File)resources[0]);
}
/** {@inheritDoc} */
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/OpenFileAction.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/OpenFileAction.java
index c9e63ec822..9462560297 100644
--- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/OpenFileAction.java
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/actions/OpenFileAction.java
@@ -28,10 +28,10 @@ import org.eclipse.che.ide.api.action.Action;
import org.eclipse.che.ide.api.action.ActionEvent;
import org.eclipse.che.ide.api.action.PromisableAction;
import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.editor.EditorAgent;
import org.eclipse.che.ide.api.editor.EditorPartPresenter;
import org.eclipse.che.ide.api.event.ActivePartChangedEvent;
import org.eclipse.che.ide.api.event.ActivePartChangedHandler;
-import org.eclipse.che.ide.api.event.FileEvent;
import org.eclipse.che.ide.api.notification.NotificationManager;
import org.eclipse.che.ide.api.resources.File;
import org.eclipse.che.ide.resource.Path;
@@ -43,6 +43,7 @@ import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAI
/**
* TODO maybe rename it to factory open file?
+ *
* @author Sergii Leschenko
* @author Vlad Zhukovskyi
*/
@@ -56,6 +57,7 @@ public class OpenFileAction extends Action implements PromisableAction {
private final CoreLocalizationConstant localization;
private final NotificationManager notificationManager;
private final AppContext appContext;
+ private final EditorAgent editorAgent;
private Callback actionCompletedCallback;
@@ -63,11 +65,13 @@ public class OpenFileAction extends Action implements PromisableAction {
public OpenFileAction(EventBus eventBus,
CoreLocalizationConstant localization,
NotificationManager notificationManager,
- AppContext appContext) {
+ AppContext appContext,
+ EditorAgent editorAgent) {
this.eventBus = eventBus;
this.localization = localization;
this.notificationManager = notificationManager;
this.appContext = appContext;
+ this.editorAgent = editorAgent;
}
@Override
@@ -91,7 +95,7 @@ public class OpenFileAction extends Action implements PromisableAction {
actionCompletedCallback.onSuccess(null);
}
- eventBus.fireEvent(FileEvent.createOpenFileEvent(optionalFile.get()));
+ editorAgent.openEditor(optionalFile.get());
} else {
if (actionCompletedCallback != null) {
actionCompletedCallback.onFailure(null);
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/client/BootstrapController.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/client/BootstrapController.java
index 4ab157663a..f133bea44d 100644
--- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/client/BootstrapController.java
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/client/BootstrapController.java
@@ -10,11 +10,10 @@
*******************************************************************************/
package org.eclipse.che.ide.client;
-import com.google.gwt.dom.client.Document;
-
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.Window;
@@ -97,7 +96,7 @@ public class BootstrapController {
@Inject
private void startComponents(Map> components) {
- startComponents(components.entrySet().iterator());
+ startComponents(components.values().iterator());
}
@Inject
@@ -132,37 +131,23 @@ public class BootstrapController {
});
}
- private void startComponents(final Iterator>> componentIterator) {
- if (componentIterator.hasNext()) {
- Map.Entry> entry = componentIterator.next();
- final String componentName = entry.getKey();
+ private void startComponents(final Iterator> componentProviderIterator) {
+ if (componentProviderIterator.hasNext()) {
+ Provider componentProvider = componentProviderIterator.next();
- try {
- Provider componentProvider = entry.getValue();
-
- final Component component = componentProvider.get();
- component.start(new Callback() {
- @Override
- public void onSuccess(Component result) {
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
- @Override
- public void execute() {
- startComponents(componentIterator);
- }
- });
- }
-
- @Override
- public void onFailure(Exception reason) {
- Log.error(getClass(), "Unable to start " + componentName, reason);
- initializationFailed(reason.getMessage());
- }
- });
- } catch (Exception e) {
- Log.error(getClass(), "Unable to start " + componentName, e);
- initializationFailed(e.getMessage());
- }
+ final Component component = componentProvider.get();
+ component.start(new Callback() {
+ @Override
+ public void onSuccess(Component result) {
+ startComponents(componentProviderIterator);
+ }
+ @Override
+ public void onFailure(Exception reason) {
+ Log.error(component.getClass(), reason);
+ initializationFailed(reason.getMessage());
+ }
+ });
} else {
startExtensionsAndDisplayUI();
}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandApiModule.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandApiModule.java
index 8f730cf473..ea0d2fb234 100644
--- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandApiModule.java
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandApiModule.java
@@ -13,10 +13,63 @@ package org.eclipse.che.ide.command;
import com.google.gwt.inject.client.AbstractGinModule;
import com.google.gwt.inject.client.assistedinject.GinFactoryModuleBuilder;
import com.google.gwt.inject.client.multibindings.GinMapBinder;
+import com.google.gwt.inject.client.multibindings.GinMultibinder;
+import com.google.inject.Provides;
import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import org.eclipse.che.ide.Resources;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandGoalRegistry;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.command.CommandType;
import org.eclipse.che.ide.api.command.CommandTypeRegistry;
import org.eclipse.che.ide.api.component.Component;
+import org.eclipse.che.ide.api.filetypes.FileType;
+import org.eclipse.che.ide.command.editor.CommandEditorView;
+import org.eclipse.che.ide.command.editor.CommandEditorViewImpl;
+import org.eclipse.che.ide.command.editor.page.goal.GoalPageView;
+import org.eclipse.che.ide.command.editor.page.goal.GoalPageViewImpl;
+import org.eclipse.che.ide.command.editor.page.name.NamePageView;
+import org.eclipse.che.ide.command.editor.page.name.NamePageViewImpl;
+import org.eclipse.che.ide.command.editor.page.project.ProjectsPageView;
+import org.eclipse.che.ide.command.editor.page.project.ProjectsPageViewImpl;
+import org.eclipse.che.ide.command.editor.page.text.PageWithTextEditorView;
+import org.eclipse.che.ide.command.editor.page.text.PageWithTextEditorViewImpl;
+import org.eclipse.che.ide.command.execute.ExecuteCommandActionFactory;
+import org.eclipse.che.ide.command.execute.ExecuteCommandActionManager;
+import org.eclipse.che.ide.command.execute.GoalPopUpGroupFactory;
+import org.eclipse.che.ide.command.explorer.CommandsExplorerPresenter;
+import org.eclipse.che.ide.command.explorer.CommandsExplorerView;
+import org.eclipse.che.ide.command.explorer.CommandsExplorerViewImpl;
+import org.eclipse.che.ide.command.goal.BuildGoal;
+import org.eclipse.che.ide.command.goal.CommandGoalRegistryImpl;
+import org.eclipse.che.ide.command.goal.CommonGoal;
+import org.eclipse.che.ide.command.goal.DebugGoal;
+import org.eclipse.che.ide.command.goal.DeployGoal;
+import org.eclipse.che.ide.command.goal.RunGoal;
+import org.eclipse.che.ide.command.goal.TestGoal;
+import org.eclipse.che.ide.command.manager.CommandManagerImpl;
+import org.eclipse.che.ide.command.node.NodeFactory;
+import org.eclipse.che.ide.command.palette.CommandsPaletteView;
+import org.eclipse.che.ide.command.palette.CommandsPaletteViewImpl;
+import org.eclipse.che.ide.command.producer.CommandProducerActionFactory;
+import org.eclipse.che.ide.command.producer.CommandProducerActionManager;
+import org.eclipse.che.ide.command.toolbar.CommandToolbarView;
+import org.eclipse.che.ide.command.toolbar.CommandToolbarViewImpl;
+import org.eclipse.che.ide.command.toolbar.ToolbarButtonsFactory;
+import org.eclipse.che.ide.command.toolbar.commands.ExecuteCommandView;
+import org.eclipse.che.ide.command.toolbar.commands.ExecuteCommandViewImpl;
+import org.eclipse.che.ide.command.toolbar.commands.button.PopupItemFactory;
+import org.eclipse.che.ide.command.toolbar.previews.PreviewsView;
+import org.eclipse.che.ide.command.toolbar.previews.PreviewsViewImpl;
+import org.eclipse.che.ide.command.toolbar.processes.ProcessesListView;
+import org.eclipse.che.ide.command.toolbar.processes.ProcessesListViewImpl;
+import org.eclipse.che.ide.command.type.CommandTypeRegistryImpl;
+import org.eclipse.che.ide.command.type.chooser.CommandTypeChooserView;
+import org.eclipse.che.ide.command.type.chooser.CommandTypeChooserViewImpl;
+
+import static org.eclipse.che.ide.command.node.CommandFileNode.FILE_TYPE_EXT;
/**
* GIN module for configuring Command API components.
@@ -27,11 +80,67 @@ public class CommandApiModule extends AbstractGinModule {
@Override
protected void configure() {
- bind(CommandTypeRegistry.class).to(CommandTypeRegistryImpl.class).in(Singleton.class);
+ GinMultibinder.newSetBinder(binder(), CommandType.class);
+ // predefined goals
+ GinMultibinder goalBinder = GinMultibinder.newSetBinder(binder(), CommandGoal.class);
+ goalBinder.addBinding().to(BuildGoal.class);
+ goalBinder.addBinding().to(TestGoal.class);
+ goalBinder.addBinding().to(RunGoal.class);
+ goalBinder.addBinding().to(DebugGoal.class);
+ goalBinder.addBinding().to(DeployGoal.class);
+ goalBinder.addBinding().to(CommonGoal.class);
+
+ bind(CommandTypeRegistry.class).to(CommandTypeRegistryImpl.class).in(Singleton.class);
+ bind(CommandGoalRegistry.class).to(CommandGoalRegistryImpl.class).in(Singleton.class);
+
+ bind(CommandManager.class).to(CommandManagerImpl.class).in(Singleton.class);
+
+ // start-up components
+ GinMapBinder componentBinder = GinMapBinder.newMapBinder(binder(), String.class, Component.class);
+ componentBinder.addBinding("CommandManagerImpl").to(CommandManagerImpl.class);
+ componentBinder.addBinding("CommandsExplorerPresenter").to(CommandsExplorerPresenter.class);
+ componentBinder.addBinding("CommandProducerActionManager").to(CommandProducerActionManager.class);
+ componentBinder.addBinding("ExecuteCommandActionManager").to(ExecuteCommandActionManager.class);
+
+ install(new GinFactoryModuleBuilder().build(ExecuteCommandActionFactory.class));
+ install(new GinFactoryModuleBuilder().build(GoalPopUpGroupFactory.class));
+ install(new GinFactoryModuleBuilder().build(NodeFactory.class));
install(new GinFactoryModuleBuilder().build(CommandProducerActionFactory.class));
- GinMapBinder componentsBinder = GinMapBinder.newMapBinder(binder(), String.class, Component.class);
- componentsBinder.addBinding("CommandProducerActionManager").to(CommandProducerActionManager.class);
+ bind(CommandsExplorerView.class).to(CommandsExplorerViewImpl.class).in(Singleton.class);
+ bind(CommandTypeChooserView.class).to(CommandTypeChooserViewImpl.class);
+ bind(CommandsPaletteView.class).to(CommandsPaletteViewImpl.class).in(Singleton.class);
+
+ // command editor
+ bind(CommandEditorView.class).to(CommandEditorViewImpl.class);
+ bind(NamePageView.class).to(NamePageViewImpl.class);
+ bind(GoalPageView.class).to(GoalPageViewImpl.class);
+ bind(ProjectsPageView.class).to(ProjectsPageViewImpl.class);
+ bind(PageWithTextEditorView.class).to(PageWithTextEditorViewImpl.class);
+
+ // toolbar
+ bind(CommandToolbarView.class).to(CommandToolbarViewImpl.class).in(Singleton.class);
+ bind(ExecuteCommandView.class).to(ExecuteCommandViewImpl.class).in(Singleton.class);
+ bind(ProcessesListView.class).to(ProcessesListViewImpl.class).in(Singleton.class);
+ bind(PreviewsView.class).to(PreviewsViewImpl.class).in(Singleton.class);
+
+ install(new GinFactoryModuleBuilder().build(ToolbarButtonsFactory.class));
+ install(new GinFactoryModuleBuilder().build(PopupItemFactory.class));
+ }
+
+ @Provides
+ @Singleton
+ @Named("CommandFileType")
+ protected FileType provideCommandFileType(Resources resources) {
+ return new FileType(resources.defaultImage(), FILE_TYPE_EXT);
+ }
+
+ /** Provides the goal which is used for grouping commands which doesn't belong to any goal. */
+ @Provides
+ @Named("default")
+ @Singleton
+ protected CommandGoal provideDefaultGoal(CommonGoal commonGoal) {
+ return commonGoal;
}
}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandResources.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandResources.java
new file mode 100644
index 0000000000..82a6874811
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandResources.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.DataResource;
+
+import org.vectomatic.dom.svg.ui.SVGResource;
+
+/**
+ * Client bundle for Command related resources.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface CommandResources extends ClientBundle {
+
+ /** Resource is used as CSS constant's value for setting 'background-image' property. */
+ @DataResource.MimeType("image/svg+xml")
+ @Source("magnifier.svg")
+ DataResource magnifier();
+
+ @Source("explorer/explorer-part.svg")
+ SVGResource explorerPart();
+
+ @Source("explorer/add-command-button.svg")
+ SVGResource addCommand();
+
+ @Source("explorer/duplicate-command-button.svg")
+ SVGResource duplicateCommand();
+
+ @Source("explorer/remove-command-button.svg")
+ SVGResource removeCommand();
+
+ @Source({"explorer/styles.css", "org/eclipse/che/ide/api/ui/style.css"})
+ ExplorerCSS commandsExplorerCss();
+
+ @Source({"palette/styles.css", "org/eclipse/che/ide/api/ui/style.css"})
+ PaletteCSS commandsPaletteCss();
+
+ @Source({"toolbar/processes/styles.css", "org/eclipse/che/ide/api/ui/style.css"})
+ ToolbarCSS commandToolbarCss();
+
+ @Source({"editor/styles.css", "org/eclipse/che/ide/api/ui/style.css"})
+ EditorCSS editorCss();
+
+ @Source({"type/styles.css", "org/eclipse/che/ide/api/ui/style.css"})
+ CommandTypeChooserCSS commandTypeChooserCss();
+
+ interface ExplorerCSS extends CssResource {
+ String commandGoalNode();
+
+ String commandNode();
+
+ String commandNodeText();
+
+ String commandNodeButtonsPanel();
+ }
+
+ interface PaletteCSS extends CssResource {
+ String filterField();
+ }
+
+ interface ToolbarCSS extends CssResource {
+ String toolbarButton();
+
+ String processesListLabel();
+
+ String processWidgetText();
+
+ String processWidgetMachineNameLabel();
+
+ String processWidgetCommandNameLabel();
+
+ String processWidgetPidLabel();
+
+ String processWidgetActionButton();
+
+ String previewUrlWidget();
+ }
+
+ interface EditorCSS extends CssResource {
+ String sectionLabel();
+
+ String section();
+ }
+
+ interface CommandTypeChooserCSS extends CssResource {
+ String chooserPopup();
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandUtils.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandUtils.java
new file mode 100644
index 0000000000..a9da4a7d62
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandUtils.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.command.CommandType;
+import org.eclipse.che.ide.api.command.CommandTypeRegistry;
+import org.eclipse.che.ide.api.command.CommandGoalRegistry;
+import org.eclipse.che.ide.api.icon.Icon;
+import org.eclipse.che.ide.api.icon.IconRegistry;
+import org.vectomatic.dom.svg.ui.SVGImage;
+import org.vectomatic.dom.svg.ui.SVGResource;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * A smattering of useful methods to work with commands.
+ *
+ * @author Artem Zatsarynnyi
+ */
+@Singleton
+public class CommandUtils {
+
+ private final CommandGoalRegistry goalRegistry;
+ private final CommandTypeRegistry commandTypeRegistry;
+ private final IconRegistry iconRegistry;
+
+ @Inject
+ public CommandUtils(CommandGoalRegistry commandGoalRegistry,
+ CommandTypeRegistry commandTypeRegistry,
+ IconRegistry iconRegistry) {
+ this.goalRegistry = commandGoalRegistry;
+ this.commandTypeRegistry = commandTypeRegistry;
+ this.iconRegistry = iconRegistry;
+ }
+
+ /**
+ * Groups the given {@code commands} by its goal.
+ *
+ * @return map that contains the given {@code commands} grouped by its goal
+ */
+ public Map> groupCommandsByGoal(List commands) {
+ final Map> commandsByGoal = new HashMap<>();
+
+ for (CommandImpl command : commands) {
+ final String goalId = command.getGoal();
+ final CommandGoal commandGoal = goalRegistry.getGoalForId(goalId);
+
+ commandsByGoal.computeIfAbsent(commandGoal, key -> new ArrayList<>())
+ .add(command);
+ }
+
+ return commandsByGoal;
+ }
+
+ /** Returns the icon for the given command type ID or {@code null} if none. */
+ @Nullable
+ public SVGResource getCommandTypeIcon(String typeId) {
+ final CommandType commandType = commandTypeRegistry.getCommandTypeById(typeId);
+
+ if (commandType != null) {
+ final Icon icon = iconRegistry.getIconIfExist("command.type." + commandType.getId());
+
+ if (icon != null) {
+ final SVGImage svgImage = icon.getSVGImage();
+
+ if (svgImage != null) {
+ return icon.getSVGResource();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /** Returns the icon for the given command goal ID or {@code null} if none. */
+ @Nullable
+ public SVGResource getCommandGoalIcon(String goalId) {
+ final Optional goalOptional = goalRegistry.getPredefinedGoalById(goalId);
+
+ if (goalOptional.isPresent()) {
+ final Icon icon = iconRegistry.getIconIfExist("command.goal." + goalOptional.get().getId());
+
+ if (icon != null) {
+ final SVGImage svgImage = icon.getSVGImage();
+
+ if (svgImage != null) {
+ return icon.getSVGResource();
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditor.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditor.java
new file mode 100644
index 0000000000..b02941c4fb
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditor.java
@@ -0,0 +1,305 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.AcceptsOneWidget;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.inject.Inject;
+
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.CoreLocalizationConstant;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.command.CommandManager.CommandChangedListener;
+import org.eclipse.che.ide.api.dialogs.DialogFactory;
+import org.eclipse.che.ide.api.editor.AbstractEditorPresenter;
+import org.eclipse.che.ide.api.editor.EditorAgent;
+import org.eclipse.che.ide.api.editor.EditorInput;
+import org.eclipse.che.ide.api.icon.Icon;
+import org.eclipse.che.ide.api.icon.IconRegistry;
+import org.eclipse.che.ide.api.notification.NotificationManager;
+import org.eclipse.che.ide.api.parts.WorkspaceAgent;
+import org.eclipse.che.ide.api.resources.VirtualFile;
+import org.eclipse.che.ide.command.editor.page.CommandEditorPage;
+import org.eclipse.che.ide.command.editor.page.commandline.CommandLinePage;
+import org.eclipse.che.ide.command.editor.page.goal.GoalPage;
+import org.eclipse.che.ide.command.editor.page.name.NamePage;
+import org.eclipse.che.ide.command.editor.page.previewurl.PreviewUrlPage;
+import org.eclipse.che.ide.command.editor.page.project.ProjectsPage;
+import org.eclipse.che.ide.command.node.CommandFileNode;
+import org.eclipse.che.ide.command.node.NodeFactory;
+import org.vectomatic.dom.svg.ui.SVGImage;
+import org.vectomatic.dom.svg.ui.SVGResource;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.EMERGE_MODE;
+import static org.eclipse.che.ide.api.notification.StatusNotification.Status.WARNING;
+
+/** Presenter for command editor. */
+public class CommandEditor extends AbstractEditorPresenter implements CommandEditorView.ActionDelegate,
+ CommandChangedListener {
+
+ private final CommandEditorView view;
+ private final WorkspaceAgent workspaceAgent;
+ private final IconRegistry iconRegistry;
+ private final CommandManager commandManager;
+ private final NotificationManager notificationManager;
+ private final DialogFactory dialogFactory;
+ private final EditorAgent editorAgent;
+ private final CoreLocalizationConstant coreMessages;
+ private final EditorMessages messages;
+ private final NodeFactory nodeFactory;
+
+ private final List pages;
+
+ /** Edited command. */
+ @VisibleForTesting
+ protected CommandImpl editedCommand;
+ /** Initial (before any modification) name of the edited command. */
+ private String commandNameInitial;
+
+ @Inject
+ public CommandEditor(CommandEditorView view,
+ WorkspaceAgent workspaceAgent,
+ IconRegistry iconRegistry,
+ CommandManager commandManager,
+ NamePage namePage,
+ ProjectsPage projectsPage,
+ CommandLinePage commandLinePage,
+ GoalPage goalPage,
+ PreviewUrlPage previewUrlPage,
+ NotificationManager notificationManager,
+ DialogFactory dialogFactory,
+ EditorAgent editorAgent,
+ CoreLocalizationConstant coreMessages,
+ EditorMessages messages,
+ NodeFactory nodeFactory) {
+ this.view = view;
+ this.workspaceAgent = workspaceAgent;
+ this.iconRegistry = iconRegistry;
+ this.commandManager = commandManager;
+ this.notificationManager = notificationManager;
+ this.dialogFactory = dialogFactory;
+ this.editorAgent = editorAgent;
+ this.coreMessages = coreMessages;
+ this.messages = messages;
+ this.nodeFactory = nodeFactory;
+
+ view.setDelegate(this);
+
+ commandManager.addCommandChangedListener(this);
+
+ pages = new LinkedList<>();
+ pages.add(previewUrlPage);
+ pages.add(projectsPage);
+ pages.add(goalPage);
+ pages.add(commandLinePage);
+ pages.add(namePage);
+ }
+
+ @Override
+ public void go(AcceptsOneWidget container) {
+ container.setWidget(getView());
+ }
+
+ @Override
+ protected void initializeEditor(EditorAgent.OpenEditorCallback callback) {
+ final VirtualFile file = getEditorInput().getFile();
+
+ if (file instanceof CommandFileNode) {
+ // make a copy of the given command to avoid modifying of the provided command
+ editedCommand = new CommandImpl(((CommandFileNode)file).getData());
+
+ initializePages();
+
+ pages.forEach(page -> view.addPage(page.getView(), page.getTitle()));
+ } else {
+ callback.onInitializationFailed();
+ }
+ }
+
+ /** Initialize editor's pages with the edited command. */
+ private void initializePages() {
+ commandNameInitial = editedCommand.getName();
+
+ pages.forEach(page -> {
+ page.edit(editedCommand);
+ page.setDirtyStateListener(() -> {
+ updateDirtyState(isDirtyPage());
+ view.setSaveEnabled(isDirtyPage());
+ });
+ });
+ }
+
+ /** Checks whether any page is dirty. */
+ private boolean isDirtyPage() {
+ for (CommandEditorPage page : pages) {
+ if (page.isDirty()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public SVGResource getTitleImage() {
+ final VirtualFile file = getEditorInput().getFile();
+
+ if (file instanceof CommandFileNode) {
+ final CommandImpl command = ((CommandFileNode)file).getData();
+ final Icon icon = iconRegistry.getIconIfExist("command.type." + command.getType());
+
+ if (icon != null) {
+ final SVGImage svgImage = icon.getSVGImage();
+
+ if (svgImage != null) {
+ return icon.getSVGResource();
+ }
+ }
+ }
+
+ return input.getSVGResource();
+ }
+
+ @Override
+ public String getTitle() {
+ return (isDirty() ? "* " : "") + input.getName();
+ }
+
+ @Override
+ public IsWidget getView() {
+ return view;
+ }
+
+ @Nullable
+ @Override
+ public String getTitleToolTip() {
+ return input.getName();
+ }
+
+ @Override
+ public void doSave() {
+ doSave(new AsyncCallback() {
+ @Override
+ public void onFailure(Throwable caught) {
+ }
+
+ @Override
+ public void onSuccess(EditorInput result) {
+ }
+ });
+ }
+
+ @Override
+ public void doSave(AsyncCallback callback) {
+ commandManager.updateCommand(commandNameInitial, editedCommand).then(arg -> {
+ updateDirtyState(false);
+
+ // according to the CommandManager#updateCommand contract
+ // command's name after updating may differ from the proposed name
+ // in order to prevent name duplication
+ editedCommand.setName(arg.getName());
+
+ if (!commandNameInitial.equals(editedCommand.getName())) {
+ input.setFile(nodeFactory.newCommandFileNode(editedCommand));
+ }
+
+ initializePages();
+
+ callback.onSuccess(getEditorInput());
+ }).catchError((Operation)arg -> {
+ notificationManager.notify(messages.editorMessageUnableToSave(),
+ arg.getMessage(),
+ WARNING,
+ EMERGE_MODE);
+
+ callback.onFailure(arg.getCause());
+
+ throw new OperationException(arg.getMessage());
+ });
+ }
+
+ @Override
+ public void doSaveAs() {
+ }
+
+ @Override
+ public void activate() {
+ }
+
+ @Override
+ public void close(boolean save) {
+ workspaceAgent.removePart(this);
+ }
+
+ @Override
+ public void onClosing(AsyncCallback callback) {
+ if (!isDirty()) {
+ callback.onSuccess(null);
+ } else {
+ dialogFactory.createChoiceDialog(
+ coreMessages.askWindowCloseTitle(),
+ coreMessages.messagesSaveChanges(getEditorInput().getName()),
+ coreMessages.yesButtonTitle(),
+ coreMessages.noButtonTitle(),
+ coreMessages.cancelButton(),
+ () -> doSave(new AsyncCallback() {
+ @Override
+ public void onSuccess(EditorInput result) {
+ callback.onSuccess(null);
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ callback.onFailure(null);
+ }
+ }),
+ () -> callback.onSuccess(null),
+ () -> callback.onFailure(null)).show();
+ }
+ }
+
+ @Override
+ public void onCommandCancel() {
+ close(false);
+ }
+
+ @Override
+ public void onCommandSave() {
+ doSave();
+ }
+
+ @Override
+ public void onCommandAdded(CommandImpl command) {
+ }
+
+ @Override
+ public void onCommandUpdated(CommandImpl previousCommand, CommandImpl command) {
+ }
+
+ @Override
+ public void onCommandRemoved(CommandImpl command) {
+ if (command.getName().equals(editedCommand.getName())) {
+ editorAgent.closeEditor(this);
+ Scheduler.get().scheduleDeferred(() -> commandManager.removeCommandChangedListener(this));
+ }
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorProvider.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorProvider.java
new file mode 100644
index 0000000000..d4c4f0a248
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorProvider.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.che.ide.api.editor.EditorPartPresenter;
+import org.eclipse.che.ide.api.editor.EditorProvider;
+
+/**
+ * Provides {@link CommandEditor} instances.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class CommandEditorProvider implements EditorProvider {
+
+ private final Provider editorProvider;
+ private final EditorMessages editorMessages;
+
+ @Inject
+ public CommandEditorProvider(Provider editorProvider, EditorMessages editorMessages) {
+ this.editorProvider = editorProvider;
+ this.editorMessages = editorMessages;
+ }
+
+ @Override
+ public String getId() {
+ return "che_command_editor";
+ }
+
+ @Override
+ public String getDescription() {
+ return editorMessages.editorDescription();
+ }
+
+ @Override
+ public EditorPartPresenter getEditor() {
+ return editorProvider.get();
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorView.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorView.java
new file mode 100644
index 0000000000..a3f76f8af3
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorView.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor;
+
+import com.google.gwt.user.client.ui.IsWidget;
+
+import org.eclipse.che.ide.api.mvp.View;
+
+/**
+ * The view for {@link CommandEditor}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface CommandEditorView extends View {
+
+ /**
+ * Add page to the view. New page will be added to the top.
+ *
+ * @param page
+ * page to add
+ * @param title
+ * text that should be used as page's title
+ */
+ void addPage(IsWidget page, String title);
+
+ /**
+ * Set whether saving command is enabled or not.
+ *
+ * @param enable
+ * {@code true} if command saving is enabled and {@code false} otherwise
+ */
+ void setSaveEnabled(boolean enable);
+
+ /** The action delegate for this view. */
+ interface ActionDelegate {
+
+ /** Called when reverting command changes is requested. */
+ void onCommandCancel();
+
+ /** Called when saving command is requested. */
+ void onCommandSave();
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorViewImpl.java
new file mode 100644
index 0000000000..14c51eb5db
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorViewImpl.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.command.CommandResources;
+import org.eclipse.che.ide.ui.window.Window;
+
+/**
+ * Implementation of {@link CommandEditorView}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class CommandEditorViewImpl extends Composite implements CommandEditorView {
+
+ private static final CommandEditorViewImplUiBinder UI_BINDER = GWT.create(CommandEditorViewImplUiBinder.class);
+ private static final Window.Resources WINDOW_RESOURCES = GWT.create(Window.Resources.class);
+
+ private final CommandResources resources;
+
+ @UiField
+ Button cancelButton;
+
+ @UiField
+ Button saveButton;
+
+ @UiField
+ ScrollPanel scrollPanel;
+
+ @UiField
+ FlowPanel pagesPanel;
+
+ /** The delegate to receive events from this view. */
+ private ActionDelegate delegate;
+
+ @Inject
+ public CommandEditorViewImpl(CommandResources resources) {
+ this.resources = resources;
+
+ initWidget(UI_BINDER.createAndBindUi(this));
+ setSaveEnabled(false);
+ saveButton.addStyleName(WINDOW_RESOURCES.windowCss().primaryButton());
+ }
+
+ @Override
+ public void addPage(IsWidget page, String title) {
+ page.asWidget().addStyleName(resources.editorCss().section());
+ pagesPanel.insert(page, 0);
+
+ if (!title.isEmpty()) {
+ Label label = new Label(title);
+ label.addStyleName(resources.editorCss().sectionLabel());
+ pagesPanel.insert(label, 0);
+ }
+
+ // editor must be scrolled to the top immediately after opening
+ new Timer() {
+ @Override
+ public void run() {
+ scrollPanel.scrollToTop();
+ }
+ }.schedule(1000);
+ }
+
+ @Override
+ public void setSaveEnabled(boolean enable) {
+ saveButton.setEnabled(enable);
+ }
+
+ @UiHandler("cancelButton")
+ public void handleCancelButton(ClickEvent clickEvent) {
+ delegate.onCommandCancel();
+ }
+
+ @UiHandler("saveButton")
+ public void handleSaveButton(ClickEvent clickEvent) {
+ delegate.onCommandSave();
+ }
+
+ @Override
+ public void setDelegate(ActionDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ interface CommandEditorViewImplUiBinder extends UiBinder {
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorViewImpl.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorViewImpl.ui.xml
new file mode 100644
index 0000000000..2f866e6269
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/CommandEditorViewImpl.ui.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+ @eval partBackground org.eclipse.che.ide.api.theme.Style.theme.partBackground();
+ @eval tabsPanelBackground org.eclipse.che.ide.api.theme.Style.theme.tabsPanelBackground();
+
+ .mainPanel {
+ background-color: partBackground;
+ }
+
+ .header {
+ height: 38px;
+ padding-top: 5px;
+ background-color: tabsPanelBackground;
+ }
+
+ .button {
+ float: right;
+ margin: 5px 0 0 10px;
+ font-weight: bold;
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/EditorMessages.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/EditorMessages.java
new file mode 100644
index 0000000000..4717d8ff70
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/EditorMessages.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor;
+
+import com.google.gwt.i18n.client.Messages;
+
+/**
+ * I18n messages for the Command Editor.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface EditorMessages extends Messages {
+
+ @Key("editor.description")
+ String editorDescription();
+
+ @Key("editor.message.unable_save")
+ String editorMessageUnableToSave();
+
+ @Key("button.test.text")
+ String buttonRunText();
+
+ @Key("button.save.text")
+ String buttonSaveText();
+
+ @Key("button.cancel.text")
+ String buttonCancelText();
+
+ @Key("page.name.title")
+ String pageNameTitle();
+
+ @Key("page.command_line.title")
+ String pageCommandLineTitle();
+
+ @Key("page.goal.title")
+ String pageGoalTitle();
+
+ @Key("page.projects.title")
+ String pageProjectsTitle();
+
+ @Key("page.projects.table.header.project.label")
+ String pageProjectsTableHeaderProjectLabel();
+
+ @Key("page.projects.table.header.applicable.label")
+ String pageProjectsTableHeaderApplicableLabel();
+
+ @Key("page.with_text_editor.macros")
+ String pageWithTextEditorMacros();
+
+ @Key("page.preview_url.title")
+ String pagePreviewUrlTitle();
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/AbstractCommandEditorPage.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/AbstractCommandEditorPage.java
new file mode 100644
index 0000000000..a6deb5861a
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/AbstractCommandEditorPage.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page;
+
+import org.eclipse.che.ide.api.command.CommandImpl;
+
+/**
+ * Abstract {@link CommandEditorPage} that provides basic functionality.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public abstract class AbstractCommandEditorPage implements CommandEditorPage {
+
+ private final String title;
+
+ protected CommandImpl editedCommand;
+
+ private DirtyStateListener listener;
+
+ /** Creates new page with the given title and tooltip. */
+ protected AbstractCommandEditorPage(String title) {
+ this.title = title;
+ }
+
+ @Override
+ public String getTitle() {
+ return title;
+ }
+
+ @Override
+ public void edit(CommandImpl command) {
+ editedCommand = command;
+
+ initialize();
+ notifyDirtyStateChanged();
+ }
+
+ /**
+ * This method is called every time when command is opening in the editor.
+ * Typically, implementor should do initial setup of the page with the {@link #editedCommand}.
+ */
+ protected abstract void initialize();
+
+ @Override
+ public void setDirtyStateListener(DirtyStateListener listener) {
+ this.listener = listener;
+ }
+
+ /**
+ * Should be called by page every time when any command
+ * modifications on the page have been performed.
+ */
+ protected void notifyDirtyStateChanged() {
+ if (listener != null) {
+ listener.onDirtyStateChanged();
+ }
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/CommandEditorPage.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/CommandEditorPage.java
new file mode 100644
index 0000000000..dc29656731
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/CommandEditorPage.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page;
+
+import com.google.gwt.user.client.ui.IsWidget;
+
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.command.editor.CommandEditor;
+
+/**
+ * Defines the requirements for the page for {@link CommandEditor}.
+ *
+ * @author Artem Zatsarynnyi
+ * @see CommandEditor
+ */
+public interface CommandEditorPage {
+
+ /** Returns page's title. */
+ String getTitle();
+
+ /** Returns page's view. */
+ IsWidget getView();
+
+ /**
+ * This method is called every time when command is opening in the editor.
+ * Typically, implementor should hold the given {@code command}
+ * instance for subsequent modifying it directly and do pages's initial setup.
+ */
+ void edit(CommandImpl command);
+
+ /**
+ * Whether the page has been modified or not?
+ *
+ * @return {@code true} if page is modified, and {@code false} - otherwise
+ */
+ boolean isDirty();
+
+ /** Sets {@link DirtyStateListener}. */
+ void setDirtyStateListener(DirtyStateListener listener);
+
+ /**
+ * Listener that should be called by page every time when
+ * any command modifications on the page have been performed.
+ */
+ interface DirtyStateListener {
+ void onDirtyStateChanged();
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/commandline/CommandLinePage.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/commandline/CommandLinePage.java
new file mode 100644
index 0000000000..198036340a
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/commandline/CommandLinePage.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.commandline;
+
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.editor.defaulteditor.EditorBuilder;
+import org.eclipse.che.ide.api.filetypes.FileTypeRegistry;
+import org.eclipse.che.ide.command.editor.EditorMessages;
+import org.eclipse.che.ide.command.editor.page.CommandEditorPage;
+import org.eclipse.che.ide.command.editor.page.text.AbstractPageWithTextEditor;
+import org.eclipse.che.ide.command.editor.page.text.MacroEditorConfiguration;
+import org.eclipse.che.ide.command.editor.page.text.PageWithTextEditorView;
+import org.eclipse.che.ide.macro.chooser.MacroChooser;
+
+/**
+ * Presenter for {@link CommandEditorPage} which allows to edit command line.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class CommandLinePage extends AbstractPageWithTextEditor {
+
+ @Inject
+ public CommandLinePage(PageWithTextEditorView view,
+ EditorBuilder editorBuilder,
+ FileTypeRegistry fileTypeRegistry,
+ MacroChooser macroChooser,
+ EditorMessages messages,
+ MacroEditorConfiguration editorConfiguration) {
+ super(view,
+ editorBuilder,
+ fileTypeRegistry,
+ macroChooser,
+ messages.pageCommandLineTitle(),
+ editorConfiguration);
+
+ view.asWidget().getElement().setId("command_editor-command_line");
+ }
+
+ @Override
+ protected String getCommandPropertyValue() {
+ return editedCommand.getCommandLine();
+ }
+
+ @Override
+ protected void updateCommandPropertyValue(String content) {
+ editedCommand.setCommandLine(content);
+ }
+
+ @Override
+ protected String getType() {
+ return ".sh";
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPage.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPage.java
new file mode 100644
index 0000000000..520871e639
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPage.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.goal;
+
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.command.BaseCommandGoal;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.command.CommandGoalRegistry;
+import org.eclipse.che.ide.command.editor.EditorMessages;
+import org.eclipse.che.ide.command.editor.page.AbstractCommandEditorPage;
+import org.eclipse.che.ide.command.editor.page.CommandEditorPage;
+
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+/**
+ * {@link CommandEditorPage} which allows to edit command's goal.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class GoalPage extends AbstractCommandEditorPage implements GoalPageView.ActionDelegate {
+
+ private final GoalPageView view;
+ private final CommandGoalRegistry goalRegistry;
+ private final CommandManager commandManager;
+
+ /** Initial value of the command's goal. */
+ private String goalInitial;
+
+ @Inject
+ public GoalPage(GoalPageView view,
+ CommandGoalRegistry commandGoalRegistry,
+ CommandManager commandManager,
+ EditorMessages messages) {
+ super(messages.pageGoalTitle());
+
+ this.view = view;
+ this.goalRegistry = commandGoalRegistry;
+ this.commandManager = commandManager;
+
+ view.setDelegate(this);
+ }
+
+ @Override
+ public IsWidget getView() {
+ return view;
+ }
+
+ @Override
+ protected void initialize() {
+ final String goalId = editedCommand.getGoal();
+ final CommandGoal commandGoal = goalRegistry.getGoalForId(goalId);
+
+ goalInitial = commandGoal.getId();
+
+ final Set goals = new HashSet<>();
+ goals.addAll(goalRegistry.getAllPredefinedGoals());
+ goals.addAll(getCustomGoals());
+
+ view.setAvailableGoals(goals);
+ view.setGoal(commandGoal.getId());
+ }
+
+ @Override
+ public boolean isDirty() {
+ if (editedCommand == null) {
+ return false;
+ }
+
+ final CommandGoal commandGoal = goalRegistry.getGoalForId(editedCommand.getGoal());
+
+ return !(goalInitial.equals(commandGoal.getId()));
+ }
+
+ @Override
+ public void onGoalChanged(String goalId) {
+ editedCommand.setGoal(goalId);
+
+ notifyDirtyStateChanged();
+ }
+
+ /** Returns all custom (non-predefined) command goals. */
+ private Set getCustomGoals() {
+ final Set list = new HashSet<>();
+
+ for (CommandImpl command : commandManager.getCommands()) {
+ final String goalId = command.getGoal();
+
+ final Optional goalOptional = goalRegistry.getPredefinedGoalById(goalId);
+ if (!goalOptional.isPresent() && !isNullOrEmpty(goalId)) {
+ list.add(new BaseCommandGoal(goalId));
+ }
+ }
+
+ return list;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPageView.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPageView.java
new file mode 100644
index 0000000000..e7d22d48be
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPageView.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.goal;
+
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.mvp.View;
+
+import java.util.Set;
+
+/**
+ * The view of {@link GoalPage}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface GoalPageView extends View {
+
+ /** Set the list of goals which are available to set for command. */
+ void setAvailableGoals(Set goals);
+
+ /** Sets the command's goal value. */
+ void setGoal(String goalId);
+
+ /** The action delegate for this view. */
+ interface ActionDelegate {
+
+ /**
+ * Called when command goal has been changed.
+ *
+ * @param goalId
+ * new value of the command goal
+ */
+ void onGoalChanged(String goalId);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPageViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPageViewImpl.java
new file mode 100644
index 0000000000..0235423816
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPageViewImpl.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.goal;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.ui.listbox.CustomComboBox;
+
+import java.util.Set;
+
+/**
+ * Implementation of {@link GoalPageView}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class GoalPageViewImpl extends Composite implements GoalPageView {
+
+ private static final GoalPageViewImplUiBinder UI_BINDER = GWT.create(GoalPageViewImplUiBinder.class);
+
+ @UiField
+ CustomComboBox goalComboBox;
+
+ private ActionDelegate delegate;
+
+ @Inject
+ public GoalPageViewImpl() {
+ initWidget(UI_BINDER.createAndBindUi(this));
+ }
+
+ @Override
+ public void setAvailableGoals(Set goals) {
+ goalComboBox.clear();
+ goals.forEach(g -> goalComboBox.addItem(g.getId()));
+ }
+
+ @Override
+ public void setGoal(String goalId) {
+ goalComboBox.setValue(goalId);
+ }
+
+ @Override
+ public void setDelegate(ActionDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ @UiHandler({"goalComboBox"})
+ void onGoalKeyUp(KeyUpEvent event) {
+ delegate.onGoalChanged(goalComboBox.getValue());
+ }
+
+ @UiHandler({"goalComboBox"})
+ void onGoalChanged(ChangeEvent event) {
+ delegate.onGoalChanged(goalComboBox.getValue());
+ }
+
+ interface GoalPageViewImplUiBinder extends UiBinder {
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPageViewImpl.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPageViewImpl.ui.xml
new file mode 100644
index 0000000000..b7f11ec391
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/goal/GoalPageViewImpl.ui.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ .combo-box {
+ display: block;
+ }
+
+
+
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePage.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePage.java
new file mode 100644
index 0000000000..218d795df1
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePage.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.name;
+
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.command.CommandExecutor;
+import org.eclipse.che.ide.command.editor.EditorMessages;
+import org.eclipse.che.ide.command.editor.page.AbstractCommandEditorPage;
+import org.eclipse.che.ide.command.editor.page.CommandEditorPage;
+
+/**
+ * Presenter for {@link CommandEditorPage} which allows to edit command's name.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class NamePage extends AbstractCommandEditorPage implements NamePageView.ActionDelegate {
+
+ private final NamePageView view;
+ private final CommandExecutor commandExecutor;
+
+ /** Initial value of the command's name. */
+ private String commandNameInitial;
+
+ @Inject
+ public NamePage(NamePageView view, EditorMessages messages, CommandExecutor commandExecutor) {
+ super(messages.pageNameTitle());
+
+ this.view = view;
+ this.commandExecutor = commandExecutor;
+
+ view.setDelegate(this);
+ }
+
+ @Override
+ public IsWidget getView() {
+ return view;
+ }
+
+ @Override
+ protected void initialize() {
+ commandNameInitial = editedCommand.getName();
+
+ view.setCommandName(editedCommand.getName());
+ }
+
+ @Override
+ public boolean isDirty() {
+ if (editedCommand == null) {
+ return false;
+ }
+
+ return !(commandNameInitial.equals(editedCommand.getName()));
+ }
+
+ @Override
+ public void onNameChanged(String name) {
+ editedCommand.setName(name);
+
+ notifyDirtyStateChanged();
+ }
+
+ @Override
+ public void onCommandRun() {
+ commandExecutor.executeCommand(editedCommand);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePageView.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePageView.java
new file mode 100644
index 0000000000..3c2c3c544c
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePageView.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.name;
+
+import org.eclipse.che.ide.api.mvp.View;
+
+/**
+ * The view for {@link NamePage}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface NamePageView extends View {
+
+ /** Sets the command's name value. */
+ void setCommandName(String name);
+
+ /** The action delegate for this view. */
+ interface ActionDelegate {
+
+ /**
+ * Called when command's name has been changed.
+ *
+ * @param name
+ * changed value of the command's name
+ */
+ void onNameChanged(String name);
+
+ /** Called when executing command is requested. */
+ void onCommandRun();
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePageViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePageViewImpl.java
new file mode 100644
index 0000000000..2c089714a8
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePageViewImpl.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.name;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+/**
+ * Implementation of {@link NamePageView}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class NamePageViewImpl extends Composite implements NamePageView {
+
+ private static final NamePageViewImplUiBinder UI_BINDER = GWT.create(NamePageViewImplUiBinder.class);
+
+ @UiField
+ TextBox commandName;
+
+ @UiField
+ Button runButton;
+
+ private ActionDelegate delegate;
+
+ @Inject
+ public NamePageViewImpl() {
+ initWidget(UI_BINDER.createAndBindUi(this));
+ }
+
+ @Override
+ public void setCommandName(String name) {
+ commandName.setValue(name);
+ }
+
+ @Override
+ public void setDelegate(ActionDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ @UiHandler({"commandName"})
+ void onNameChanged(KeyUpEvent event) {
+ delegate.onNameChanged(commandName.getValue());
+ }
+
+ @UiHandler("runButton")
+ public void handleRunButton(ClickEvent clickEvent) {
+ delegate.onCommandRun();
+ }
+
+ interface NamePageViewImplUiBinder extends UiBinder {
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePageViewImpl.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePageViewImpl.ui.xml
new file mode 100644
index 0000000000..afcc5d4ea4
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/name/NamePageViewImpl.ui.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+ .button {
+ float: right;
+ margin-top: 1px;
+ font-weight: bold;
+ background: #51b200;
+ }
+
+ .button:hover {
+ background: #51b200;
+ }
+
+
+
+
+
+
+
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/previewurl/PreviewUrlPage.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/previewurl/PreviewUrlPage.java
new file mode 100644
index 0000000000..93bc7d1ba9
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/previewurl/PreviewUrlPage.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.previewurl;
+
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.editor.defaulteditor.EditorBuilder;
+import org.eclipse.che.ide.api.filetypes.FileTypeRegistry;
+import org.eclipse.che.ide.command.editor.EditorMessages;
+import org.eclipse.che.ide.command.editor.page.CommandEditorPage;
+import org.eclipse.che.ide.command.editor.page.text.AbstractPageWithTextEditor;
+import org.eclipse.che.ide.command.editor.page.text.MacroEditorConfiguration;
+import org.eclipse.che.ide.command.editor.page.text.PageWithTextEditorView;
+import org.eclipse.che.ide.macro.chooser.MacroChooser;
+
+import static org.eclipse.che.api.workspace.shared.Constants.COMMAND_PREVIEW_URL_ATTRIBUTE_NAME;
+
+/**
+ * Presenter for {@link CommandEditorPage} which allows to edit command's preview URL.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class PreviewUrlPage extends AbstractPageWithTextEditor {
+
+ @Inject
+ public PreviewUrlPage(PageWithTextEditorView view,
+ EditorBuilder editorBuilder,
+ FileTypeRegistry fileTypeRegistry,
+ MacroChooser macroChooser,
+ EditorMessages messages,
+ MacroEditorConfiguration editorConfiguration) {
+ super(view,
+ editorBuilder,
+ fileTypeRegistry,
+ macroChooser,
+ messages.pagePreviewUrlTitle(),
+ editorConfiguration);
+
+ view.asWidget().getElement().setId("command_editor-preview_url");
+ }
+
+ @Override
+ protected String getCommandPropertyValue() {
+ final String previewUrl = editedCommand.getAttributes().get(COMMAND_PREVIEW_URL_ATTRIBUTE_NAME);
+
+ return previewUrl != null ? previewUrl : "";
+ }
+
+ @Override
+ protected void updateCommandPropertyValue(String content) {
+ editedCommand.getAttributes().put(COMMAND_PREVIEW_URL_ATTRIBUTE_NAME, content);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectSwitcher.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectSwitcher.java
new file mode 100644
index 0000000000..2be85aa40d
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectSwitcher.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.project;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+
+import org.eclipse.che.ide.ui.switcher.Switcher;
+
+/**
+ * Switcher widget which is associated with some project name.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class ProjectSwitcher extends Composite implements HasValue {
+
+ private static final ProjectSwitcherUiBinder UI_BINDER = GWT.create(ProjectSwitcherUiBinder.class);
+
+ @UiField
+ Label label;
+
+ @UiField
+ Switcher switcher;
+
+ ProjectSwitcher(String projectName) {
+ initWidget(UI_BINDER.createAndBindUi(this));
+
+ label.setText(projectName);
+ }
+
+ @Override
+ public Boolean getValue() {
+ return switcher.getValue();
+ }
+
+ @Override
+ public void setValue(Boolean value) {
+ switcher.setValue(value);
+ }
+
+ @Override
+ public void setValue(Boolean value, boolean fireEvents) {
+ switcher.setValue(value, fireEvents);
+ }
+
+ @Override
+ public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) {
+ return switcher.addValueChangeHandler(handler);
+ }
+
+ interface ProjectSwitcherUiBinder extends UiBinder {
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectSwitcher.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectSwitcher.ui.xml
new file mode 100644
index 0000000000..9e57c1acfd
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectSwitcher.ui.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+ .panel {
+ background-color: #3D4650;
+ padding: 5px 0 5px 0;
+ }
+
+ .label {
+ display: inline-block;
+ width: 150px;
+ margin: 0 0 0 10px;
+ }
+
+ .switcher {
+ display: inline-block;
+ margin-left: 10px;
+ vertical-align: middle;
+ }
+
+
+
+
+
+
+
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPage.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPage.java
new file mode 100644
index 0000000000..86d0082431
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPage.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.project;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.inject.Inject;
+import com.google.web.bindery.event.shared.EventBus;
+
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.command.CommandImpl.ApplicableContext;
+import org.eclipse.che.ide.api.resources.Project;
+import org.eclipse.che.ide.api.resources.Resource;
+import org.eclipse.che.ide.api.resources.ResourceChangedEvent;
+import org.eclipse.che.ide.api.resources.ResourceChangedEvent.ResourceChangedHandler;
+import org.eclipse.che.ide.api.resources.ResourceDelta;
+import org.eclipse.che.ide.command.editor.EditorMessages;
+import org.eclipse.che.ide.command.editor.page.AbstractCommandEditorPage;
+import org.eclipse.che.ide.command.editor.page.CommandEditorPage;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Presenter for {@link CommandEditorPage} which allows to edit command's applicable projects. */
+public class ProjectsPage extends AbstractCommandEditorPage implements ProjectsPageView.ActionDelegate,
+ ResourceChangedHandler {
+
+ private final ProjectsPageView view;
+ private final AppContext appContext;
+
+ /** Initial value of the applicable projects list. */
+ private List applicableProjectsInitial;
+
+ @Inject
+ public ProjectsPage(ProjectsPageView view,
+ AppContext appContext,
+ EditorMessages messages,
+ EventBus eventBus) {
+ super(messages.pageProjectsTitle());
+
+ this.view = view;
+ this.appContext = appContext;
+
+ eventBus.addHandler(ResourceChangedEvent.getType(), this);
+
+ view.setDelegate(this);
+ }
+
+ @Override
+ public IsWidget getView() {
+ return view;
+ }
+
+ @Override
+ protected void initialize() {
+ final ApplicableContext context = editedCommand.getApplicableContext();
+
+ applicableProjectsInitial = new ArrayList<>(context.getApplicableProjects());
+
+ refreshProjects();
+ }
+
+ /** Refresh 'Projects' section in the view. */
+ private void refreshProjects() {
+ final Map projectsState = new HashMap<>();
+
+ for (Project project : appContext.getProjects()) {
+ ApplicableContext context = editedCommand.getApplicableContext();
+ boolean applicable = context.getApplicableProjects().contains(project.getPath());
+
+ projectsState.put(project, applicable);
+ }
+
+ view.setProjects(projectsState);
+ }
+
+ @Override
+ public boolean isDirty() {
+ if (editedCommand == null) {
+ return false;
+ }
+
+ ApplicableContext context = editedCommand.getApplicableContext();
+
+ return !(applicableProjectsInitial.equals(context.getApplicableProjects()));
+ }
+
+ @Override
+ public void onApplicableProjectChanged(Project project, boolean applicable) {
+ final ApplicableContext context = editedCommand.getApplicableContext();
+
+ if (applicable) {
+ // if command is bound with one project at least
+ // then remove command from the workspace
+ if (context.getApplicableProjects().isEmpty()) {
+ context.setWorkspaceApplicable(false);
+ }
+
+ context.addProject(project.getPath());
+ } else {
+ context.removeProject(project.getPath());
+
+ // if command isn't bound to any project
+ // then save it to the workspace
+ if (context.getApplicableProjects().isEmpty()) {
+ context.setWorkspaceApplicable(true);
+ }
+ }
+
+ notifyDirtyStateChanged();
+ }
+
+ @Override
+ public void onResourceChanged(ResourceChangedEvent event) {
+ final ResourceDelta delta = event.getDelta();
+ final Resource resource = delta.getResource();
+
+ if (resource.isProject()) {
+ // defer refreshing the projects section since appContext#getProjects may return old data
+ Scheduler.get().scheduleDeferred(this::refreshProjects);
+ }
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPageView.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPageView.java
new file mode 100644
index 0000000000..7cb8309c4c
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPageView.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.project;
+
+import org.eclipse.che.ide.api.mvp.View;
+import org.eclipse.che.ide.api.resources.Project;
+
+import java.util.Map;
+
+/**
+ * The view for {@link ProjectsPage}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface ProjectsPageView extends View {
+
+ /** Sets the applicable projects. */
+ void setProjects(Map projects);
+
+ /** The action delegate for this view. */
+ interface ActionDelegate {
+
+ /** Called when applicable project has been changed. */
+ void onApplicableProjectChanged(Project project, boolean applicable);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPageViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPageViewImpl.java
new file mode 100644
index 0000000000..aaabf216df
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPageViewImpl.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.project;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.resources.Project;
+
+import java.util.Map;
+
+/**
+ * Implementation of {@link ProjectsPageView}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class ProjectsPageViewImpl extends Composite implements ProjectsPageView {
+
+ private static final ProjectsPageViewImplUiBinder UI_BINDER = GWT.create(ProjectsPageViewImplUiBinder.class);
+
+ @UiField
+ FlowPanel mainPanel;
+
+ @UiField
+ FlowPanel projectsPanel;
+
+ private ActionDelegate delegate;
+
+ @Inject
+ public ProjectsPageViewImpl() {
+ initWidget(UI_BINDER.createAndBindUi(this));
+
+ mainPanel.setVisible(false);
+ }
+
+ @Override
+ public void setProjects(Map projects) {
+ projectsPanel.clear();
+ mainPanel.setVisible(!projects.isEmpty());
+
+ projects.entrySet().forEach(entry -> addProjectSwitcherToPanel(entry.getKey(), entry.getValue()));
+ }
+
+ private void addProjectSwitcherToPanel(Project project, boolean applicable) {
+ final ProjectSwitcher switcher = new ProjectSwitcher(project.getName());
+ switcher.setValue(applicable);
+ switcher.addValueChangeHandler(event -> delegate.onApplicableProjectChanged(project, event.getValue()));
+
+ projectsPanel.add(switcher);
+ }
+
+ @Override
+ public void setDelegate(ActionDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ interface ProjectsPageViewImplUiBinder extends UiBinder {
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPageViewImpl.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPageViewImpl.ui.xml
new file mode 100644
index 0000000000..2a9dc452dc
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/project/ProjectsPageViewImpl.ui.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ @eval textFieldBorderColor org.eclipse.che.ide.api.theme.Style.theme.toolButtonActiveBorder();
+
+ .table {
+ border: textFieldBorderColor;
+ }
+
+ .table-header {
+ background-color: #2E353B;
+ }
+
+ .column-header {
+ display: inline-block;
+ width: 150px;
+ margin-left: 10px;
+ margin-top: 3px;
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/AbstractPageWithTextEditor.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/AbstractPageWithTextEditor.java
new file mode 100644
index 0000000000..bf7d6b1d29
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/AbstractPageWithTextEditor.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.text;
+
+import com.google.gwt.user.client.ui.IsWidget;
+
+import org.eclipse.che.ide.api.editor.OpenEditorCallbackImpl;
+import org.eclipse.che.ide.api.editor.defaulteditor.EditorBuilder;
+import org.eclipse.che.ide.api.editor.document.Document;
+import org.eclipse.che.ide.api.editor.editorconfig.TextEditorConfiguration;
+import org.eclipse.che.ide.api.editor.texteditor.TextEditor;
+import org.eclipse.che.ide.api.filetypes.FileTypeRegistry;
+import org.eclipse.che.ide.api.resources.SyntheticFile;
+import org.eclipse.che.ide.api.resources.VirtualFile;
+import org.eclipse.che.ide.command.editor.page.AbstractCommandEditorPage;
+import org.eclipse.che.ide.command.editor.page.CommandEditorPage;
+import org.eclipse.che.ide.macro.chooser.MacroChooser;
+
+import static org.eclipse.che.ide.api.editor.EditorPartPresenter.PROP_DIRTY;
+import static org.eclipse.che.ide.api.editor.EditorPartPresenter.PROP_INPUT;
+
+/**
+ * Abstract {@link CommandEditorPage} which allows to edit a command's property
+ * with a text editor that provides autocompletion for macros names.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public abstract class AbstractPageWithTextEditor extends AbstractCommandEditorPage implements PageWithTextEditorView.ActionDelegate {
+
+ private final PageWithTextEditorView view;
+ private final FileTypeRegistry fileTypeRegistry;
+ private final MacroChooser macroChooser;
+ private final TextEditorConfiguration editorConfiguration;
+
+ private TextEditor editor;
+
+ /** Initial value of the edited command's property. */
+ private String initialValue;
+
+ protected AbstractPageWithTextEditor(PageWithTextEditorView view,
+ EditorBuilder editorBuilder,
+ FileTypeRegistry fileTypeRegistry,
+ MacroChooser macroChooser,
+ String title,
+ TextEditorConfiguration editorConfiguration) {
+ super("");
+
+ this.view = view;
+ this.fileTypeRegistry = fileTypeRegistry;
+ this.macroChooser = macroChooser;
+ this.editorConfiguration = editorConfiguration;
+
+ view.setDelegate(this);
+ view.setHeight(getHeight());
+ view.setEditorTitle(title);
+
+ initializeEditor(editorBuilder);
+ }
+
+ private void initializeEditor(EditorBuilder editorBuilder) {
+ editor = editorBuilder.buildEditor();
+ editor.initialize(editorConfiguration);
+ editor.activate();
+
+ editor.addPropertyListener((source, propId) -> {
+ switch (propId) {
+ case PROP_INPUT:
+ editor.go(view.getEditorContainer());
+
+ editor.getEditorWidget().setAnnotationRulerVisible(false);
+ editor.getEditorWidget().setFoldingRulerVisible(false);
+ editor.getEditorWidget().setZoomRulerVisible(false);
+ editor.getEditorWidget().setOverviewRulerVisible(false);
+
+ break;
+ case PROP_DIRTY:
+ updateCommandPropertyValue(editor.getDocument().getContents());
+ notifyDirtyStateChanged();
+
+ break;
+ default:
+ }
+ });
+ }
+
+ @Override
+ public IsWidget getView() {
+ return view;
+ }
+
+ @Override
+ protected void initialize() {
+ initialValue = getCommandPropertyValue();
+
+ setContent(initialValue);
+ }
+
+ /** Sets editor's content. */
+ private void setContent(String content) {
+ VirtualFile file = new SyntheticFile(editedCommand.getName() + getType(), content);
+
+ editor.init(new EditorInputImpl(fileTypeRegistry.getFileTypeByFile(file), file), new OpenEditorCallbackImpl());
+ }
+
+ @Override
+ public boolean isDirty() {
+ if (editedCommand == null) {
+ return false;
+ }
+
+ return !initialValue.equals(getCommandPropertyValue());
+ }
+
+ /** Returns the current value of the edited command's property. */
+ protected abstract String getCommandPropertyValue();
+
+ /**
+ * Updates the value of the edited command's property.
+ *
+ * @param newValue
+ * new value of the edited command's property
+ */
+ protected abstract void updateCommandPropertyValue(String newValue);
+
+ /** Returns height of the page in pixels. Default height is 150 px. */
+ protected int getHeight() {
+ return 150;
+ }
+
+ /**
+ * Returns type of the edited content.
+ * Type must be specified as file's extension e.g.: .sh, .css.
+ * Default type is text/plain.
+ */
+ protected String getType() {
+ return "";
+ }
+
+ @Override
+ public void onExploreMacros() {
+ macroChooser.show(macro -> {
+ Document document = editor.getDocument();
+ document.replace(document.getCursorOffset(), 0, macro.getName());
+ });
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/EditorInputImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/EditorInputImpl.java
new file mode 100644
index 0000000000..1afa2195bc
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/EditorInputImpl.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.text;
+
+import org.eclipse.che.ide.api.editor.EditorInput;
+import org.eclipse.che.ide.api.filetypes.FileType;
+import org.eclipse.che.ide.api.resources.VirtualFile;
+import org.vectomatic.dom.svg.ui.SVGResource;
+
+class EditorInputImpl implements EditorInput {
+
+ private VirtualFile file;
+ private FileType fileType;
+
+ EditorInputImpl(FileType fileType, VirtualFile file) {
+ this.fileType = fileType;
+ this.file = file;
+ }
+
+ @Override
+ public String getToolTipText() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return file.getDisplayName();
+ }
+
+ @Override
+ public SVGResource getSVGResource() {
+ return fileType.getImage();
+ }
+
+ @Override
+ public VirtualFile getFile() {
+ return file;
+ }
+
+ @Override
+ public void setFile(VirtualFile file) {
+ this.file = file;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/MacroCodeAssistProcessor.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/MacroCodeAssistProcessor.java
new file mode 100644
index 0000000000..8b7652d3a0
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/MacroCodeAssistProcessor.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.text;
+
+import org.eclipse.che.ide.Resources;
+import org.eclipse.che.ide.api.editor.codeassist.CodeAssistCallback;
+import org.eclipse.che.ide.api.editor.codeassist.CodeAssistProcessor;
+import org.eclipse.che.ide.api.editor.codeassist.CompletionProposal;
+import org.eclipse.che.ide.api.editor.document.Document;
+import org.eclipse.che.ide.api.editor.text.TextPosition;
+import org.eclipse.che.ide.api.editor.texteditor.TextEditor;
+import org.eclipse.che.ide.api.macro.Macro;
+import org.eclipse.che.ide.api.macro.MacroRegistry;
+import org.eclipse.che.ide.filters.FuzzyMatches;
+import org.eclipse.che.ide.filters.Match;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Code assist processor for macro names. */
+public class MacroCodeAssistProcessor implements CodeAssistProcessor {
+
+ private MacroRegistry registry;
+ private FuzzyMatches fuzzyMatches;
+ private Resources resources;
+ private LastCompletion lastCompletion;
+
+ @Inject
+ public MacroCodeAssistProcessor(MacroRegistry registry, FuzzyMatches fuzzyMatches, Resources resources) {
+ this.registry = registry;
+ this.fuzzyMatches = fuzzyMatches;
+ this.resources = resources;
+ lastCompletion = new LastCompletion();
+ }
+
+ @Override
+ public void computeCompletionProposals(TextEditor editor, int offset, boolean triggered, CodeAssistCallback callback) {
+ Document document = editor.getDocument();
+ TextPosition position = document.getPositionFromIndex(offset);
+
+ String currentLine = editor.getDocument().getLineContent(position.getLine());
+ final String currentWord = getCurrentWord(currentLine, position.getCharacter());
+
+ List result = new ArrayList<>();
+ if (triggered && !lastCompletion.isGoodFor(currentWord, offset)) {
+ lastCompletion.offset = offset;
+ lastCompletion.wordStartOffset = offset - currentWord.length(); // start completion word
+ lastCompletion.word = currentWord;
+ }
+
+ List macros = registry.getMacros();
+ for (Macro macro : macros) {
+ List matches = fuzzyMatches.fuzzyMatch(currentWord, macro.getName());
+ if (matches != null) {
+ MacroCompletionProposal proposal = new MacroCompletionProposal(macro,
+ matches,
+ resources,
+ lastCompletion.wordStartOffset,
+ currentWord.length());
+ result.add(proposal);
+ }
+ }
+
+ result.sort((o1, o2) -> {
+ MacroCompletionProposal p1 = ((MacroCompletionProposal)o1);
+ MacroCompletionProposal p2 = ((MacroCompletionProposal)o2);
+
+ return p1.getMacro().getName().compareTo(p2.getMacro().getName());
+ });
+
+ callback.proposalComputed(result);
+ }
+
+ private String getCurrentWord(String text, int offset) {
+ int i = offset - 1;
+ while (i >= 0 && isWordChar(text.charAt(i))) {
+ i--;
+ }
+ return text.substring(i + 1, offset);
+ }
+
+ private boolean isWordChar(char c) {
+ return c >= 'a' && c <= 'z' ||
+ c >= 'A' && c <= 'Z' ||
+ c >= '0' && c <= '9' ||
+ c >= '\u007f' && c <= '\u00ff' ||
+ c == '$' ||
+ c == '{' ||
+ c == '.' ||
+ c == '_' ||
+ c == '-';
+ }
+
+
+ @Override
+ public String getErrorMessage() {
+ return null;
+ }
+
+ private class LastCompletion {
+ String word = "";
+ int wordStartOffset;
+ int offset;
+
+ boolean isGoodFor(String currentWord, int offset) {
+ return currentWord.startsWith(word) &&
+ offset - this.offset == currentWord.length() - word.length();
+ }
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/MacroCompletionProposal.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/MacroCompletionProposal.java
new file mode 100644
index 0000000000..95fa5901ab
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/MacroCompletionProposal.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.text;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import org.eclipse.che.ide.Resources;
+import org.eclipse.che.ide.api.editor.codeassist.Completion;
+import org.eclipse.che.ide.api.editor.codeassist.CompletionProposal;
+import org.eclipse.che.ide.api.editor.document.Document;
+import org.eclipse.che.ide.api.editor.text.LinearRange;
+import org.eclipse.che.ide.api.icon.Icon;
+import org.eclipse.che.ide.api.macro.Macro;
+import org.eclipse.che.ide.filters.Match;
+
+import java.util.List;
+
+/** Completion proposal for {@link Macro} that will insert {@link Macro#getName()} value. */
+class MacroCompletionProposal implements CompletionProposal {
+
+ private final Macro macro;
+ private final List matches;
+ private Resources resources;
+ private int offset;
+ private int length;
+
+ MacroCompletionProposal(Macro macro, List matches, Resources resources, int offset, int length) {
+ this.macro = macro;
+ this.matches = matches;
+ this.resources = resources;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ @Override
+ public void getAdditionalProposalInfo(AsyncCallback callback) {
+ String documentation = macro.getDescription();
+ if (documentation == null || documentation.trim().isEmpty()) {
+ documentation = "No documentation found.";
+ }
+
+ Label label = new Label(documentation);
+ label.setWordWrap(true);
+ label.getElement().getStyle().setFontSize(13, Style.Unit.PX);
+ label.getElement().getStyle().setMarginLeft(4, Style.Unit.PX);
+ label.setSize("100%", "100%");
+ callback.onSuccess(label);
+ }
+
+ @Override
+ public String getDisplayString() {
+ SafeHtmlBuilder builder = new SafeHtmlBuilder();
+
+ String label = macro.getName();
+ int pos = 0;
+ for (Match highlight : matches) {
+ if (highlight.getStart() == highlight.getEnd()) {
+ continue;
+ }
+
+ if (pos < highlight.getStart()) {
+ appendPlain(builder, label.substring(pos, highlight.getStart()));
+ }
+
+ appendHighlighted(builder, label.substring(highlight.getStart(), highlight.getEnd()));
+ pos = highlight.getEnd();
+ }
+
+ if (pos < label.length()) {
+ appendPlain(builder, label.substring(pos));
+ }
+
+ return builder.toSafeHtml().asString();
+ }
+
+ private void appendPlain(SafeHtmlBuilder builder, String text) {
+ builder.appendEscaped(text);
+ }
+
+ private void appendHighlighted(SafeHtmlBuilder builder, String text) {
+ builder.appendHtmlConstant("");
+ builder.appendEscaped(text);
+ builder.appendHtmlConstant("");
+ }
+
+ @Override
+ public Icon getIcon() {
+ return null;
+ }
+
+ @Override
+ public void getCompletion(final CompletionCallback callback) {
+ callback.onCompletion(new Completion() {
+ @Override
+ public void apply(Document document) {
+ document.replace(offset, length, macro.getName());
+ }
+
+ @Override
+ public LinearRange getSelection(Document document) {
+ LinearRange.PartialLinearRange start = LinearRange.createWithStart(offset + macro.getName().length());
+ return start.andLength(0);
+ }
+ });
+ }
+
+ public Macro getMacro() {
+ return macro;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/MacroEditorConfiguration.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/MacroEditorConfiguration.java
new file mode 100644
index 0000000000..6041930598
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/MacroEditorConfiguration.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.text;
+
+import org.eclipse.che.ide.api.editor.codeassist.CodeAssistProcessor;
+import org.eclipse.che.ide.api.editor.editorconfig.DefaultTextEditorConfiguration;
+import org.eclipse.che.ide.api.editor.editorconfig.TextEditorConfiguration;
+import org.eclipse.che.ide.api.editor.partition.DocumentPartitioner;
+
+import javax.inject.Inject;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {@link TextEditorConfiguration} which provides {@link CodeAssistProcessor} for macros names.
+ */
+public class MacroEditorConfiguration extends DefaultTextEditorConfiguration {
+
+ private MacroCodeAssistProcessor codeAssistProcessor;
+
+ @Inject
+ public MacroEditorConfiguration(MacroCodeAssistProcessor codeAssistProcessor) {
+ this.codeAssistProcessor = codeAssistProcessor;
+ }
+
+ @Override
+ public Map getContentAssistantProcessors() {
+ Map map = new HashMap<>();
+ map.put(DocumentPartitioner.DEFAULT_CONTENT_TYPE, codeAssistProcessor);
+
+ return map;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/PageWithTextEditorView.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/PageWithTextEditorView.java
new file mode 100644
index 0000000000..0816b37d69
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/PageWithTextEditorView.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.text;
+
+import com.google.gwt.user.client.ui.SimpleLayoutPanel;
+
+import org.eclipse.che.ide.api.mvp.View;
+
+/**
+ * View for {@link AbstractPageWithTextEditor}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface PageWithTextEditorView extends View {
+
+ /** Returns the container where the editor should be placed. */
+ SimpleLayoutPanel getEditorContainer();
+
+ /** Sets height of the view. */
+ void setHeight(int height);
+
+ /** Sets title for the editor. */
+ void setEditorTitle(String title);
+
+ /** The action delegate for this view. */
+ interface ActionDelegate {
+
+ /** Called when exploring macros is requested. */
+ void onExploreMacros();
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/PageWithTextEditorViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/PageWithTextEditorViewImpl.java
new file mode 100644
index 0000000000..2fa326d3d2
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/PageWithTextEditorViewImpl.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.editor.page.text;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.Hyperlink;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.SimpleLayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+/**
+ * Implementation of {@link PageWithTextEditorView}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class PageWithTextEditorViewImpl extends Composite implements PageWithTextEditorView {
+
+ private static final PageWithTextEditorViewImplUiBinder UI_BINDER = GWT.create(PageWithTextEditorViewImplUiBinder.class);
+
+ @UiField
+ DockLayoutPanel mainPanel;
+
+ @UiField
+ Label title;
+
+ @UiField
+ Hyperlink exploreMacrosLink;
+
+ @UiField
+ SimpleLayoutPanel editorPanel;
+
+ /** The delegate to receive events from this view. */
+ private ActionDelegate delegate;
+
+ @Inject
+ public PageWithTextEditorViewImpl() {
+ initWidget(UI_BINDER.createAndBindUi(this));
+ }
+
+ @Override
+ public void setDelegate(ActionDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public SimpleLayoutPanel getEditorContainer() {
+ return editorPanel;
+ }
+
+ @Override
+ public void setHeight(int height) {
+ mainPanel.setHeight(height + "px");
+ }
+
+ @Override
+ public void setEditorTitle(String title) {
+ this.title.setText(title);
+ }
+
+ @UiHandler("exploreMacrosLink")
+ public void handleExploreMacrosLinkClick(ClickEvent event) {
+ delegate.onExploreMacros();
+ }
+
+ interface PageWithTextEditorViewImplUiBinder extends UiBinder {
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/PageWithTextEditorViewImpl.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/PageWithTextEditorViewImpl.ui.xml
new file mode 100644
index 0000000000..406b87287d
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/editor/page/text/PageWithTextEditorViewImpl.ui.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+ @eval textFieldBorderColor org.eclipse.che.ide.api.theme.Style.theme.toolButtonActiveBorder();
+
+ .editor {
+ border: textFieldBorderColor;
+ }
+
+ .title {
+ float: left;
+ margin-left: 0;
+ }
+
+ .link a, a:visited {
+ float: right;
+ margin: 8px 10px 8px 0;
+ color: #4990E2;
+ font-size: 11px;
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/CommandsActionGroup.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/CommandsActionGroup.java
new file mode 100644
index 0000000000..26999f799c
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/CommandsActionGroup.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.execute;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.core.model.machine.Machine;
+import org.eclipse.che.ide.api.action.ActionEvent;
+import org.eclipse.che.ide.api.action.ActionManager;
+import org.eclipse.che.ide.api.action.DefaultActionGroup;
+import org.eclipse.che.ide.api.resources.Resource;
+import org.eclipse.che.ide.api.selection.Selection;
+import org.eclipse.che.ide.api.selection.SelectionAgent;
+import org.eclipse.che.ide.resources.tree.ResourceNode;
+
+/**
+ * Action group that contains all actions for executing commands.
+ *
+ * @author Artem Zatsarynnyi
+ */
+@Singleton
+public class CommandsActionGroup extends DefaultActionGroup {
+
+ private final SelectionAgent selectionAgent;
+
+ @Inject
+ public CommandsActionGroup(ActionManager actionManager,
+ SelectionAgent selectionAgent,
+ ExecMessages messages) {
+ super(messages.actionCommandsTitle(), true, actionManager);
+
+ this.selectionAgent = selectionAgent;
+ }
+
+ @Override
+ public void update(ActionEvent e) {
+ e.getPresentation().setEnabledAndVisible(false);
+
+ // action group should be visible when current selection is machine or project
+
+ final Selection> selection = selectionAgent.getSelection();
+
+ if (selection != null && !selection.isEmpty() && selection.isSingleSelection()) {
+ final Object possibleNode = selection.getHeadElement();
+
+ if (possibleNode instanceof Machine) {
+ e.getPresentation().setEnabledAndVisible(true);
+ } else if (possibleNode instanceof ResourceNode) {
+ final Resource selectedResource = ((ResourceNode)possibleNode).getData();
+
+ e.getPresentation().setEnabledAndVisible(selectedResource.isProject());
+ }
+ }
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecMessages.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecMessages.java
new file mode 100644
index 0000000000..71066376f2
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecMessages.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.execute;
+
+import com.google.gwt.i18n.client.Messages;
+
+/**
+ * I18n messages relate to the command execution.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface ExecMessages extends Messages {
+
+ @Key("action.commands.title")
+ String actionCommandsTitle();
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecuteCommandAction.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecuteCommandAction.java
new file mode 100644
index 0000000000..9eb3f81e2f
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecuteCommandAction.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.execute;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.che.ide.api.action.Action;
+import org.eclipse.che.ide.api.action.ActionEvent;
+import org.eclipse.che.ide.api.command.CommandExecutor;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.command.CommandUtils;
+import org.vectomatic.dom.svg.ui.SVGResource;
+
+/** Action for executing a {@link CommandImpl}. */
+class ExecuteCommandAction extends Action {
+
+ private final CommandImpl command;
+ private final CommandExecutor commandExecutor;
+ private final CommandManager commandManager;
+
+ @Inject
+ ExecuteCommandAction(@Assisted CommandImpl command,
+ CommandUtils commandUtils,
+ CommandExecutor commandExecutor,
+ CommandManager commandManager) {
+ super(command.getName());
+
+ this.command = command;
+ this.commandExecutor = commandExecutor;
+ this.commandManager = commandManager;
+
+ final SVGResource commandIcon = commandUtils.getCommandTypeIcon(command.getType());
+ if (commandIcon != null) {
+ getTemplatePresentation().setSVGResource(commandIcon);
+ }
+ }
+
+ @Override
+ public void update(ActionEvent e) {
+ e.getPresentation().setEnabledAndVisible(commandManager.isCommandApplicable(command));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ commandExecutor.executeCommand(command);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecuteCommandActionFactory.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecuteCommandActionFactory.java
new file mode 100644
index 0000000000..b61340a2cf
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecuteCommandActionFactory.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.execute;
+
+import org.eclipse.che.ide.api.command.CommandImpl;
+
+/**
+ * Factory for creating {@link ExecuteCommandAction} instances.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface ExecuteCommandActionFactory {
+
+ /** Creates new instance of {@link ExecuteCommandAction} for executing the specified {@code command}. */
+ ExecuteCommandAction create(CommandImpl command);
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecuteCommandActionManager.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecuteCommandActionManager.java
new file mode 100644
index 0000000000..9e6a64e481
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/ExecuteCommandActionManager.java
@@ -0,0 +1,185 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.execute;
+
+import com.google.gwt.core.client.Callback;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.ide.api.action.Action;
+import org.eclipse.che.ide.api.action.ActionManager;
+import org.eclipse.che.ide.api.action.DefaultActionGroup;
+import org.eclipse.che.ide.api.command.CommandGoalRegistry;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.command.CommandManager.CommandChangedListener;
+import org.eclipse.che.ide.api.command.CommandManager.CommandLoadedListener;
+import org.eclipse.che.ide.api.component.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.eclipse.che.ide.api.action.IdeActions.GROUP_CONSOLES_TREE_CONTEXT_MENU;
+import static org.eclipse.che.ide.api.action.IdeActions.GROUP_EDITOR_TAB_CONTEXT_MENU;
+import static org.eclipse.che.ide.api.action.IdeActions.GROUP_MAIN_CONTEXT_MENU;
+
+/**
+ * Manager listens for creating/removing commands and adds/removes
+ * related {@link ExecuteCommandAction}s in the context menus.
+ */
+@Singleton
+public class ExecuteCommandActionManager implements Component, CommandLoadedListener, CommandChangedListener {
+
+ private static final String COMMANDS_ACTION_GROUP_ID_PREFIX = "commandsActionGroup";
+ private static final String COMMAND_ACTION_ID_PREFIX = "command_";
+ private static final String GOAL_ACTION_GROUP_ID_PREFIX = "goal_";
+
+ private final CommandManager commandManager;
+ private final ActionManager actionManager;
+ private final CommandsActionGroup commandsActionGroup;
+ private final GoalPopUpGroupFactory goalPopUpGroupFactory;
+ private final ExecuteCommandActionFactory commandActionFactory;
+ private final CommandGoalRegistry goalRegistry;
+
+ /** Map of command's name to an appropriate {@link ExecuteCommandAction}. */
+ private final Map commandActions;
+ /** Map of command goal's ID to an appropriate action group. */
+ private final Map goalPopUpGroups;
+
+ @Inject
+ public ExecuteCommandActionManager(CommandManager commandManager,
+ ActionManager actionManager,
+ CommandsActionGroup commandsActionGroup,
+ GoalPopUpGroupFactory goalPopUpGroupFactory,
+ ExecuteCommandActionFactory commandActionFactory,
+ CommandGoalRegistry goalRegistry) {
+ this.commandManager = commandManager;
+ this.actionManager = actionManager;
+ this.commandsActionGroup = commandsActionGroup;
+ this.goalPopUpGroupFactory = goalPopUpGroupFactory;
+ this.commandActionFactory = commandActionFactory;
+ this.goalRegistry = goalRegistry;
+
+ commandActions = new HashMap<>();
+ goalPopUpGroups = new HashMap<>();
+ }
+
+ @Override
+ public void start(Callback callback) {
+ callback.onSuccess(this);
+
+ commandManager.addCommandLoadedListener(this);
+ commandManager.addCommandChangedListener(this);
+
+ actionManager.registerAction(COMMANDS_ACTION_GROUP_ID_PREFIX, commandsActionGroup);
+
+ // inject 'Commands' menu into context menus
+ ((DefaultActionGroup)actionManager.getAction(GROUP_MAIN_CONTEXT_MENU)).add(commandsActionGroup);
+ ((DefaultActionGroup)actionManager.getAction(GROUP_EDITOR_TAB_CONTEXT_MENU)).add(commandsActionGroup);
+ ((DefaultActionGroup)actionManager.getAction(GROUP_CONSOLES_TREE_CONTEXT_MENU)).add(commandsActionGroup);
+ }
+
+ @Override
+ public void onCommandsLoaded() {
+ commandManager.getCommands().forEach(this::addAction);
+ }
+
+ @Override
+ public void onCommandAdded(CommandImpl command) {
+ addAction(command);
+ }
+
+ /**
+ * Creates action for executing the given command and
+ * adds created action to the appropriate action group.
+ */
+ private void addAction(CommandImpl command) {
+ final ExecuteCommandAction action = commandActionFactory.create(command);
+
+ actionManager.registerAction(COMMAND_ACTION_ID_PREFIX + command.getName(), action);
+ commandActions.put(command.getName(), action);
+
+ getActionGroupForCommand(command).add(action);
+ }
+
+ /**
+ * Returns the action group which is appropriate for placing the action for executing the given command.
+ * If appropriate action group doesn't exist it will be created and added to the right place.
+ */
+ private DefaultActionGroup getActionGroupForCommand(CommandImpl command) {
+ String goalId = command.getGoal();
+
+ if (isNullOrEmpty(goalId)) {
+ goalId = goalRegistry.getDefaultGoal().getId();
+ }
+
+ DefaultActionGroup commandGoalPopUpGroup = goalPopUpGroups.get(goalId);
+
+ if (commandGoalPopUpGroup == null) {
+ commandGoalPopUpGroup = goalPopUpGroupFactory.create(goalId);
+
+ actionManager.registerAction(GOAL_ACTION_GROUP_ID_PREFIX + goalId, commandGoalPopUpGroup);
+ goalPopUpGroups.put(goalId, commandGoalPopUpGroup);
+
+ commandsActionGroup.add(commandGoalPopUpGroup);
+ }
+
+ return commandGoalPopUpGroup;
+ }
+
+ @Override
+ public void onCommandUpdated(CommandImpl previousCommand, CommandImpl command) {
+ removeAction(previousCommand);
+ addAction(command);
+ }
+
+ @Override
+ public void onCommandRemoved(CommandImpl command) {
+ removeAction(command);
+ }
+
+ /**
+ * Removes action for executing the given command and
+ * removes the appropriate action group in case it's empty.
+ */
+ private void removeAction(CommandImpl command) {
+ final Action commandAction = commandActions.remove(command.getName());
+
+ if (commandAction != null) {
+ final String commandActionId = actionManager.getId(commandAction);
+ if (commandActionId != null) {
+ actionManager.unregisterAction(commandActionId);
+ }
+
+ // remove action from it's action group
+ String goalId = command.getGoal();
+ if (isNullOrEmpty(goalId)) {
+ goalId = goalRegistry.getDefaultGoal().getId();
+ }
+
+ // remove action group if it's empty
+ final DefaultActionGroup goalPopUpGroup = goalPopUpGroups.remove(goalId);
+
+ if (goalPopUpGroup != null) {
+ goalPopUpGroup.remove(commandAction);
+
+ if (goalPopUpGroup.getChildrenCount() == 0) {
+ final String goalActionId = actionManager.getId(goalPopUpGroup);
+ if (goalActionId != null) {
+ actionManager.unregisterAction(goalActionId);
+ }
+ commandsActionGroup.remove(goalPopUpGroup);
+ }
+ }
+ }
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/GoalPopUpGroup.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/GoalPopUpGroup.java
new file mode 100644
index 0000000000..28cd7664e1
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/GoalPopUpGroup.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.execute;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.che.ide.api.action.ActionEvent;
+import org.eclipse.che.ide.api.action.ActionManager;
+import org.eclipse.che.ide.api.action.DefaultActionGroup;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandGoalRegistry;
+import org.eclipse.che.ide.api.icon.Icon;
+import org.eclipse.che.ide.api.icon.IconRegistry;
+import org.vectomatic.dom.svg.ui.SVGImage;
+import org.vectomatic.dom.svg.ui.SVGResource;
+
+/**
+ * Action group that represents command goal.
+ *
+ * @author Artem Zatsarynnyi
+ */
+class GoalPopUpGroup extends DefaultActionGroup {
+
+ private final CommandGoal commandGoal;
+ private final IconRegistry iconRegistry;
+
+ @Inject
+ GoalPopUpGroup(@Assisted String goalId,
+ ActionManager actionManager,
+ CommandGoalRegistry goalRegistry,
+ IconRegistry iconRegistry) {
+ super(actionManager);
+
+ this.iconRegistry = iconRegistry;
+ commandGoal = goalRegistry.getGoalForId(goalId);
+
+ setPopup(true);
+
+ // set icon
+ final SVGResource commandTypeIcon = getCommandGoalIcon();
+ if (commandTypeIcon != null) {
+ getTemplatePresentation().setSVGResource(commandTypeIcon);
+ }
+ }
+
+ @Override
+ public void update(ActionEvent e) {
+ e.getPresentation().setText(commandGoal.getId() + " (" + getChildrenCount() + ")");
+ }
+
+ private SVGResource getCommandGoalIcon() {
+ final String goalId = commandGoal.getId();
+
+ final Icon icon = iconRegistry.getIconIfExist("command.goal." + goalId);
+
+ if (icon != null) {
+ final SVGImage svgImage = icon.getSVGImage();
+
+ if (svgImage != null) {
+ return icon.getSVGResource();
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/GoalPopUpGroupFactory.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/GoalPopUpGroupFactory.java
new file mode 100644
index 0000000000..aa51db6baf
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/execute/GoalPopUpGroupFactory.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.execute;
+
+/**
+ * Factory for creating {@link GoalPopUpGroup} instances.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface GoalPopUpGroupFactory {
+
+ /** Creates new {@link GoalPopUpGroup} for the command goal with the given {@code id}. */
+ GoalPopUpGroup create(String id);
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerPresenter.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerPresenter.java
new file mode 100644
index 0000000000..fbf6df576a
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerPresenter.java
@@ -0,0 +1,305 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.explorer;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gwt.core.client.Callback;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.AcceptsOneWidget;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.DelayedTask;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandGoalRegistry;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.command.CommandImpl.ApplicableContext;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.command.CommandManager.CommandChangedListener;
+import org.eclipse.che.ide.api.command.CommandManager.CommandLoadedListener;
+import org.eclipse.che.ide.api.command.CommandType;
+import org.eclipse.che.ide.api.component.Component;
+import org.eclipse.che.ide.api.constraints.Constraints;
+import org.eclipse.che.ide.api.dialogs.DialogFactory;
+import org.eclipse.che.ide.api.editor.EditorAgent;
+import org.eclipse.che.ide.api.notification.NotificationManager;
+import org.eclipse.che.ide.api.parts.WorkspaceAgent;
+import org.eclipse.che.ide.api.parts.base.BasePresenter;
+import org.eclipse.che.ide.command.CommandResources;
+import org.eclipse.che.ide.command.CommandUtils;
+import org.eclipse.che.ide.command.node.NodeFactory;
+import org.eclipse.che.ide.command.type.chooser.CommandTypeChooser;
+import org.vectomatic.dom.svg.ui.SVGResource;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.EMERGE_MODE;
+import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
+import static org.eclipse.che.ide.api.parts.PartStackType.NAVIGATION;
+
+/** Presenter for Commands Explorer. */
+@Singleton
+public class CommandsExplorerPresenter extends BasePresenter implements CommandsExplorerView.ActionDelegate,
+ Component,
+ CommandChangedListener,
+ CommandLoadedListener {
+
+ private final CommandsExplorerView view;
+ private final CommandResources resources;
+ private final WorkspaceAgent workspaceAgent;
+ private final CommandManager commandManager;
+ private final NotificationManager notificationManager;
+ private final CommandTypeChooser commandTypeChooser;
+ private final ExplorerMessages messages;
+ private final RefreshViewTask refreshViewTask;
+ private final DialogFactory dialogFactory;
+ private final NodeFactory nodeFactory;
+ private final EditorAgent editorAgent;
+ private final AppContext appContext;
+
+ @Inject
+ public CommandsExplorerPresenter(CommandsExplorerView view,
+ CommandResources commandResources,
+ WorkspaceAgent workspaceAgent,
+ CommandManager commandManager,
+ NotificationManager notificationManager,
+ CommandTypeChooser commandTypeChooser,
+ ExplorerMessages messages,
+ RefreshViewTask refreshViewTask,
+ DialogFactory dialogFactory,
+ NodeFactory nodeFactory,
+ EditorAgent editorAgent,
+ AppContext appContext) {
+ this.view = view;
+ this.resources = commandResources;
+ this.workspaceAgent = workspaceAgent;
+ this.commandManager = commandManager;
+ this.notificationManager = notificationManager;
+ this.commandTypeChooser = commandTypeChooser;
+ this.messages = messages;
+ this.refreshViewTask = refreshViewTask;
+ this.dialogFactory = dialogFactory;
+ this.nodeFactory = nodeFactory;
+ this.editorAgent = editorAgent;
+ this.appContext = appContext;
+
+ view.setDelegate(this);
+ }
+
+ @Override
+ public void start(Callback callback) {
+ workspaceAgent.openPart(this, NAVIGATION, Constraints.LAST);
+
+ commandManager.addCommandLoadedListener(this);
+ commandManager.addCommandChangedListener(this);
+
+ callback.onSuccess(this);
+ }
+
+ @Override
+ public void go(AcceptsOneWidget container) {
+ refreshView();
+
+ container.setWidget(getView());
+ }
+
+ @Override
+ public String getTitle() {
+ return messages.partTitle();
+ }
+
+ @Override
+ public IsWidget getView() {
+ return view;
+ }
+
+ @Nullable
+ @Override
+ public String getTitleToolTip() {
+ return messages.partTooltip();
+ }
+
+ @Nullable
+ @Override
+ public SVGResource getTitleImage() {
+ return resources.explorerPart();
+ }
+
+ @Override
+ public void onCommandAdd(int left, int top) {
+ commandTypeChooser.show(left, top).then(createCommand(getDefaultContext()));
+ }
+
+ /** Returns the default {@link ApplicableContext} for the new command. */
+ private ApplicableContext getDefaultContext() {
+ final ApplicableContext context = new ApplicableContext();
+
+ if (appContext.getProjects().length > 0) {
+ context.setWorkspaceApplicable(false);
+
+ Arrays.stream(appContext.getProjects())
+ .forEach(p -> context.addProject(p.getPath()));
+ }
+
+ return context;
+ }
+
+ /** Returns an operation which creates a command with the given context. */
+ private Operation createCommand(ApplicableContext context) {
+ return selectedCommandType -> {
+ final CommandGoal selectedGoal = view.getSelectedGoal();
+
+ if (selectedGoal == null) {
+ return;
+ }
+
+ commandManager.createCommand(selectedGoal.getId(), selectedCommandType.getId(), context)
+ .then(command -> {
+ refreshViewAndSelectCommand(command);
+ editorAgent.openEditor(nodeFactory.newCommandFileNode(command));
+ })
+ .catchError(showErrorNotification(messages.unableCreate()));
+ };
+ }
+
+ @Override
+ public void onCommandDuplicate(CommandImpl command) {
+ commandManager.createCommand(command)
+ .then(this::refreshViewAndSelectCommand)
+ .catchError(showErrorNotification(messages.unableDuplicate()));
+ }
+
+ @Override
+ public void onCommandRemove(CommandImpl command) {
+ dialogFactory.createConfirmDialog(messages.removeCommandConfirmationTitle(),
+ messages.removeCommandConfirmationMessage(command.getName()),
+ () -> commandManager.removeCommand(command.getName())
+ .catchError(showErrorNotification(messages.unableRemove())),
+ null).show();
+ }
+
+ /** Returns an operation which shows an error notification with the given title. */
+ private Operation showErrorNotification(String title) {
+ return err -> {
+ notificationManager.notify(title, err.getMessage(), FAIL, EMERGE_MODE);
+ throw new OperationException(err.getMessage());
+ };
+ }
+
+ @Override
+ public void onCommandsLoaded() {
+ refreshView();
+ }
+
+ @Override
+ public void onCommandAdded(CommandImpl command) {
+ refreshView();
+ }
+
+ @Override
+ public void onCommandUpdated(CommandImpl previousCommand, CommandImpl command) {
+ refreshView();
+ }
+
+ @Override
+ public void onCommandRemoved(CommandImpl command) {
+ refreshView();
+ }
+
+ /** Refresh view and preserve current selection. */
+ private void refreshView() {
+ refreshViewAndSelectCommand(null);
+ }
+
+ private void refreshViewAndSelectCommand(CommandImpl command) {
+ refreshViewTask.delayAndSelectCommand(command);
+ }
+
+ /**
+ * {@link DelayedTask} for refreshing the view and optionally selecting the specified command.
+ * Tree widget in the view works asynchronously using events
+ * and it needs some time to be fully rendered.
+ * So successive refreshing view must be called with some delay.
+ */
+ // since GIN can't instantiate inner classes
+ // made it nested in order to allow injection
+ @VisibleForTesting
+ static class RefreshViewTask extends DelayedTask {
+
+ // 300 milliseconds should be enough to fully refreshing the tree
+ private static final int DELAY_MILLIS = 300;
+
+ private final CommandsExplorerView view;
+ private final CommandGoalRegistry goalRegistry;
+ private final CommandManager commandManager;
+ private final CommandUtils commandUtils;
+
+ private CommandImpl commandToSelect;
+
+ @Inject
+ public RefreshViewTask(CommandsExplorerView view,
+ CommandGoalRegistry goalRegistry,
+ CommandManager commandManager,
+ CommandUtils commandUtils) {
+ this.view = view;
+ this.goalRegistry = goalRegistry;
+ this.commandManager = commandManager;
+ this.commandUtils = commandUtils;
+ }
+
+ @Override
+ public void onExecute() {
+ refreshView();
+
+ if (commandToSelect != null) {
+ // wait some time while tree in the view will be fully refreshed
+ new Timer() {
+ @Override
+ public void run() {
+ view.selectCommand(commandToSelect);
+ }
+ }.schedule(DELAY_MILLIS);
+ }
+ }
+
+ void delayAndSelectCommand(@Nullable CommandImpl command) {
+ if (command != null) {
+ commandToSelect = command;
+ }
+
+ delay(DELAY_MILLIS);
+ }
+
+ private void refreshView() {
+ final Map> commandsByGoals = new HashMap<>();
+
+ // all predefined commandToSelect goals must be shown in the view
+ // so populate map by all registered commandToSelect goals
+ for (CommandGoal goal : goalRegistry.getAllPredefinedGoals()) {
+ commandsByGoals.put(goal, new ArrayList<>());
+ }
+
+ commandsByGoals.putAll(commandUtils.groupCommandsByGoal(commandManager.getCommands()));
+
+ view.setCommands(commandsByGoals);
+ }
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerView.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerView.java
new file mode 100644
index 0000000000..a4bd7bb0f4
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerView.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.explorer;
+
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.mvp.View;
+import org.eclipse.che.ide.api.parts.base.BaseActionDelegate;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The view for {@link CommandsExplorerPresenter}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface CommandsExplorerView extends View {
+
+ /**
+ * Sets the commands to display in the view.
+ *
+ * @param commands
+ * commands grouped by its type
+ */
+ void setCommands(Map> commands);
+
+ /** Returns the currently selected command goal or {@code null} if none. */
+ @Nullable
+ CommandGoal getSelectedGoal();
+
+ /** Returns the currently selected command or {@code null} if none. */
+ @Nullable
+ CommandImpl getSelectedCommand();
+
+ /** Select the given {@code command}. */
+ void selectCommand(CommandImpl command);
+
+ /** The action delegate for this view. */
+ interface ActionDelegate extends BaseActionDelegate {
+
+ /** Called when adding new command is requested. */
+ void onCommandAdd(int left, int top);
+
+ /**
+ * Called when duplicating command is requested.
+ *
+ * @param command
+ * command duplication of which is requested
+ */
+ void onCommandDuplicate(CommandImpl command);
+
+ /**
+ * Called when removing command is requested.
+ *
+ * @param command
+ * command removing of which is requested
+ */
+ void onCommandRemove(CommandImpl command);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerViewImpl.java
new file mode 100644
index 0000000000..7656f6fcd8
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerViewImpl.java
@@ -0,0 +1,170 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.explorer;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.Resources;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.data.tree.Node;
+import org.eclipse.che.ide.api.parts.base.BaseView;
+import org.eclipse.che.ide.command.CommandResources;
+import org.eclipse.che.ide.command.node.CommandFileNode;
+import org.eclipse.che.ide.command.node.CommandGoalNode;
+import org.eclipse.che.ide.command.node.NodeFactory;
+import org.eclipse.che.ide.ui.smartTree.NodeLoader;
+import org.eclipse.che.ide.ui.smartTree.NodeStorage;
+import org.eclipse.che.ide.ui.smartTree.Tree;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static java.util.Collections.singletonList;
+import static org.eclipse.che.ide.ui.smartTree.SelectionModel.Mode.SINGLE;
+
+/**
+ * Implementation of {@link CommandsExplorerView}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+@Singleton
+public class CommandsExplorerViewImpl extends BaseView implements CommandsExplorerView {
+
+ private static final CommandsExplorerViewImplUiBinder UI_BINDER = GWT.create(CommandsExplorerViewImplUiBinder.class);
+
+ private final CommandsTreeRenderer treeRenderer;
+ private final NodeFactory nodeFactory;
+ /** Mapping of the commands to the rendered tree nodes. */
+ private final Map commandNodes;
+
+ @UiField(provided = true)
+ Tree tree;
+
+ @Inject
+ public CommandsExplorerViewImpl(Resources coreResources,
+ ExplorerMessages messages,
+ CommandResources resources,
+ NodeFactory nodeFactory) {
+ super(coreResources);
+
+ this.nodeFactory = nodeFactory;
+ commandNodes = new HashMap<>();
+
+ setTitle(messages.viewTitle());
+
+ tree = new Tree(new NodeStorage(), new NodeLoader());
+ tree.ensureDebugId("commands-explorer");
+
+ treeRenderer = new CommandsTreeRenderer(tree.getTreeStyles(), resources, delegate);
+
+ tree.setPresentationRenderer(treeRenderer);
+ tree.getSelectionModel().setSelectionMode(SINGLE);
+
+ tree.getSelectionModel().addSelectionHandler(event -> {
+ for (Node node : tree.getNodeStorage().getAll()) {
+ final Element nodeContainerElement = tree.getNodeDescriptor(node).getNodeContainerElement();
+
+ if (nodeContainerElement != null) {
+ nodeContainerElement.removeAttribute("selected");
+ }
+ }
+
+ tree.getNodeDescriptor(event.getSelectedItem())
+ .getNodeContainerElement()
+ .setAttribute("selected", "selected");
+ });
+
+ setContentWidget(UI_BINDER.createAndBindUi(this));
+ }
+
+ @Override
+ protected void focusView() {
+ tree.setFocus(true);
+ }
+
+ @Override
+ public void setCommands(Map> commands) {
+ treeRenderer.setDelegate(delegate);
+
+ renderCommands(commands);
+ }
+
+ private void renderCommands(Map> commands) {
+ commandNodes.clear();
+ tree.getNodeStorage().clear();
+
+ for (Entry> entry : commands.entrySet()) {
+ List commandNodes = new ArrayList<>(entry.getValue().size());
+ for (CommandImpl command : entry.getValue()) {
+ final CommandFileNode commandFileNode = nodeFactory.newCommandFileNode(command);
+ commandNodes.add(commandFileNode);
+
+ this.commandNodes.put(command, commandFileNode);
+ }
+
+ final CommandGoalNode commandGoalNode = nodeFactory.newCommandGoalNode(entry.getKey(), commandNodes);
+ tree.getNodeStorage().add(commandGoalNode);
+ }
+
+ tree.expandAll();
+ }
+
+ @Nullable
+ @Override
+ public CommandGoal getSelectedGoal() {
+ final List selectedNodes = tree.getSelectionModel().getSelectedNodes();
+
+ if (!selectedNodes.isEmpty()) {
+ final Node selectedNode = selectedNodes.get(0);
+
+ if (selectedNode instanceof CommandGoalNode) {
+ return ((CommandGoalNode)selectedNode).getData();
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public CommandImpl getSelectedCommand() {
+ final List selectedNodes = tree.getSelectionModel().getSelectedNodes();
+
+ if (!selectedNodes.isEmpty()) {
+ final Node selectedNode = selectedNodes.get(0);
+
+ if (selectedNode instanceof CommandFileNode) {
+ return ((CommandFileNode)selectedNode).getData();
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void selectCommand(CommandImpl command) {
+ tree.getSelectionModel().setSelection(singletonList(commandNodes.get(command)));
+ }
+
+ interface CommandsExplorerViewImplUiBinder extends UiBinder {
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerViewImpl.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerViewImpl.ui.xml
new file mode 100644
index 0000000000..c565a1d31b
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsExplorerViewImpl.ui.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsTreeRenderer.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsTreeRenderer.java
new file mode 100644
index 0000000000..59963b42e2
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/CommandsTreeRenderer.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.explorer;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.Event;
+
+import org.eclipse.che.ide.api.data.tree.Node;
+import org.eclipse.che.ide.command.CommandResources;
+import org.eclipse.che.ide.command.explorer.CommandsExplorerView.ActionDelegate;
+import org.eclipse.che.ide.command.node.CommandFileNode;
+import org.eclipse.che.ide.command.node.CommandGoalNode;
+import org.eclipse.che.ide.ui.smartTree.Tree;
+import org.eclipse.che.ide.ui.smartTree.TreeStyles;
+import org.eclipse.che.ide.ui.smartTree.presentation.DefaultPresentationRenderer;
+import org.vectomatic.dom.svg.ui.SVGResource;
+
+import static com.google.gwt.user.client.Event.ONCLICK;
+
+/**
+ * Renderer for the commands tree.
+ *
+ * @author Artem Zatsarynnyi
+ */
+class CommandsTreeRenderer extends DefaultPresentationRenderer {
+
+ private final CommandResources resources;
+
+ private ActionDelegate delegate;
+
+ CommandsTreeRenderer(TreeStyles treeStyles, CommandResources resources, ActionDelegate delegate) {
+ super(treeStyles);
+
+ this.resources = resources;
+ this.delegate = delegate;
+ }
+
+ /** Sets the delegate that will handle events from the rendered DOM elements. */
+ void setDelegate(ActionDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Element getPresentableTextContainer(Element content) {
+ final Element presentableTextContainer = super.getPresentableTextContainer(content);
+ presentableTextContainer.addClassName(resources.commandsExplorerCss().commandNodeText());
+
+ return presentableTextContainer;
+ }
+
+ @Override
+ public Element render(Node node, String domID, Tree.Joint joint, int depth) {
+ final Element element = super.render(node, domID, joint, depth);
+ final Element nodeContainerElement = element.getFirstChildElement();
+
+ if (node instanceof CommandFileNode) {
+ CommandFileNode commandNode = (CommandFileNode)node;
+
+ nodeContainerElement.setId("command_" + commandNode.getDisplayName());
+
+ renderCommandNode(commandNode, nodeContainerElement);
+ } else if (node instanceof CommandGoalNode) {
+ CommandGoalNode goalNode = (CommandGoalNode)node;
+
+ nodeContainerElement.setId("goal_" + goalNode.getName());
+
+ renderCommandGoalNode(nodeContainerElement);
+ }
+
+ return element;
+ }
+
+ private void renderCommandNode(CommandFileNode node, Element nodeContainerElement) {
+ nodeContainerElement.addClassName(resources.commandsExplorerCss().commandNode());
+
+ final Element removeCommandButton = createButton(resources.removeCommand());
+ Event.setEventListener(removeCommandButton, event -> {
+ if (ONCLICK == event.getTypeInt()) {
+ event.stopPropagation();
+ delegate.onCommandRemove(node.getData());
+ }
+ });
+
+ final Element duplicateCommandButton = createButton(resources.duplicateCommand());
+ Event.setEventListener(duplicateCommandButton, event -> {
+ if (ONCLICK == event.getTypeInt()) {
+ event.stopPropagation();
+ delegate.onCommandDuplicate(node.getData());
+ }
+ });
+
+ final Element buttonsPanel = Document.get().createSpanElement();
+ buttonsPanel.setClassName(resources.commandsExplorerCss().commandNodeButtonsPanel());
+ buttonsPanel.appendChild(removeCommandButton);
+ buttonsPanel.appendChild(duplicateCommandButton);
+
+ nodeContainerElement.appendChild(buttonsPanel);
+
+ removeCommandButton.setId("commands_tree-button-remove");
+ duplicateCommandButton.setId("commands_tree-button-duplicate");
+ }
+
+ private void renderCommandGoalNode(Element nodeContainerElement) {
+ nodeContainerElement.addClassName(resources.commandsExplorerCss().commandGoalNode());
+
+ final Element addCommandButton = createButton(resources.addCommand());
+
+ Event.setEventListener(addCommandButton, event -> {
+ if (ONCLICK == event.getTypeInt()) {
+ event.stopPropagation();
+ delegate.onCommandAdd(addCommandButton.getAbsoluteLeft(), addCommandButton.getAbsoluteTop());
+ }
+ });
+
+ nodeContainerElement.appendChild(addCommandButton);
+
+ addCommandButton.setId("commands_tree-button-add");
+ }
+
+ private Element createButton(SVGResource icon) {
+ final Element button = Document.get().createSpanElement();
+ button.appendChild(icon.getSvg().getElement());
+
+ Event.sinkEvents(button, ONCLICK);
+
+ return button;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/ExplorerMessages.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/ExplorerMessages.java
new file mode 100644
index 0000000000..8126e15d9f
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/explorer/ExplorerMessages.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.explorer;
+
+import com.google.gwt.i18n.client.Messages;
+
+/**
+ * I18n messages for the Command Explorer.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface ExplorerMessages extends Messages {
+
+ @Key("explorer.part.title")
+ String partTitle();
+
+ @Key("explorer.part.tooltip")
+ String partTooltip();
+
+ @Key("explorer.view.title")
+ String viewTitle();
+
+ @Key("explorer.message.unable_create")
+ String unableCreate();
+
+ @Key("explorer.message.unable_duplicate")
+ String unableDuplicate();
+
+ @Key("explorer.message.unable_remove")
+ String unableRemove();
+
+ @Key("explorer.remove_confirmation.title")
+ String removeCommandConfirmationTitle();
+
+ @Key("explorer.remove_confirmation.message")
+ String removeCommandConfirmationMessage(String commandName);
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/BuildGoal.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/BuildGoal.java
new file mode 100644
index 0000000000..85d88abd5c
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/BuildGoal.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.goal;
+
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.command.BaseCommandGoal;
+
+/**
+ * Represents predefined 'Build' goal.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class BuildGoal extends BaseCommandGoal {
+
+ @Inject
+ public BuildGoal() {
+ super("Build");
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/CommandGoalRegistryImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/CommandGoalRegistryImpl.java
new file mode 100644
index 0000000000..500f8926f6
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/CommandGoalRegistryImpl.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.goal;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.api.command.BaseCommandGoal;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandGoalRegistry;
+import org.eclipse.che.ide.util.loging.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.Collections.unmodifiableList;
+import static java.util.Optional.ofNullable;
+
+/**
+ * Implementation of {@link CommandGoalRegistry}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+@Singleton
+public class CommandGoalRegistryImpl implements CommandGoalRegistry {
+
+ private final CommandGoal defaultGoal;
+ private final GoalMessages messages;
+ private final Map commandGoals;
+
+ @Inject
+ public CommandGoalRegistryImpl(@Named("default") CommandGoal defaultCommandGoal, GoalMessages messages) {
+ defaultGoal = defaultCommandGoal;
+ this.messages = messages;
+
+ commandGoals = new HashMap<>();
+ }
+
+ @Inject(optional = true)
+ private void register(Set goals) {
+ for (CommandGoal type : goals) {
+ final String id = type.getId();
+
+ if (commandGoals.containsKey(id)) {
+ Log.warn(getClass(), messages.messageGoalAlreadyRegistered(id));
+ } else {
+ commandGoals.put(id, type);
+ }
+ }
+ }
+
+ @Override
+ public List getAllPredefinedGoals() {
+ return unmodifiableList(new ArrayList<>(commandGoals.values()));
+ }
+
+ @Override
+ public CommandGoal getDefaultGoal() {
+ return defaultGoal;
+ }
+
+ @Override
+ public Optional getPredefinedGoalById(String id) {
+ return ofNullable(commandGoals.get(id));
+ }
+
+ @Override
+ public CommandGoal getGoalForId(@Nullable String id) {
+ if (isNullOrEmpty(id)) {
+ return getDefaultGoal();
+ }
+
+ return getPredefinedGoalById(id).orElse(new BaseCommandGoal(id));
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/CommonGoal.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/CommonGoal.java
new file mode 100644
index 0000000000..1dcc880193
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/CommonGoal.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.goal;
+
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.command.BaseCommandGoal;
+
+/**
+ * Represents predefined 'Common' goal.
+ * By default it's used for grouping commands which doesn't belong to any goal.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class CommonGoal extends BaseCommandGoal {
+
+ @Inject
+ public CommonGoal() {
+ super("Common");
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/DebugGoal.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/DebugGoal.java
new file mode 100644
index 0000000000..c988451013
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/DebugGoal.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.goal;
+
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.command.BaseCommandGoal;
+
+/**
+ * Represents predefined 'Debug' goal.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class DebugGoal extends BaseCommandGoal {
+
+ @Inject
+ public DebugGoal() {
+ super("Debug");
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/DeployGoal.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/DeployGoal.java
new file mode 100644
index 0000000000..a4694cc536
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/DeployGoal.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.goal;
+
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.command.BaseCommandGoal;
+
+/**
+ * Represents predefined 'Deploy' goal.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class DeployGoal extends BaseCommandGoal {
+
+ @Inject
+ public DeployGoal() {
+ super("Deploy");
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/GoalMessages.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/GoalMessages.java
new file mode 100644
index 0000000000..93b12fd978
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/GoalMessages.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.goal;
+
+import com.google.gwt.i18n.client.Messages;
+
+/**
+ * I18n messages relate to the command goals.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface GoalMessages extends Messages {
+
+ @Key("message.goal_already_registered")
+ String messageGoalAlreadyRegistered(String goalId);
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/RunGoal.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/RunGoal.java
new file mode 100644
index 0000000000..7b5dd34da6
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/RunGoal.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.goal;
+
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.command.BaseCommandGoal;
+
+/**
+ * Represents predefined 'Run' goal.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class RunGoal extends BaseCommandGoal {
+
+ @Inject
+ public RunGoal() {
+ super("Run");
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/TestGoal.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/TestGoal.java
new file mode 100644
index 0000000000..87c6354c45
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/goal/TestGoal.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.goal;
+
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.command.BaseCommandGoal;
+
+/**
+ * Represents predefined 'Test' goal.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public class TestGoal extends BaseCommandGoal {
+
+ @Inject
+ public TestGoal() {
+ super("Test");
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/manager/CommandManagerImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/manager/CommandManagerImpl.java
new file mode 100644
index 0000000000..012605dbec
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/manager/CommandManagerImpl.java
@@ -0,0 +1,456 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.manager;
+
+import elemental.util.ArrayOf;
+import elemental.util.Collections;
+
+import com.google.gwt.core.client.Callback;
+import com.google.gwt.core.client.Scheduler;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.web.bindery.event.shared.EventBus;
+
+import org.eclipse.che.api.core.model.machine.Machine;
+import org.eclipse.che.api.promises.client.Function;
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.api.promises.client.PromiseProvider;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.command.CommandImpl.ApplicableContext;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.command.CommandType;
+import org.eclipse.che.ide.api.command.CommandTypeRegistry;
+import org.eclipse.che.ide.api.component.Component;
+import org.eclipse.che.ide.api.resources.Project;
+import org.eclipse.che.ide.api.resources.Resource;
+import org.eclipse.che.ide.api.selection.Selection;
+import org.eclipse.che.ide.api.selection.SelectionAgent;
+import org.eclipse.che.ide.api.workspace.WorkspaceReadyEvent;
+import org.eclipse.che.ide.api.workspace.WorkspaceReadyEvent.WorkspaceReadyHandler;
+import org.eclipse.che.ide.util.loging.Log;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.stream.Collectors.toList;
+import static org.eclipse.che.api.workspace.shared.Constants.COMMAND_GOAL_ATTRIBUTE_NAME;
+import static org.eclipse.che.api.workspace.shared.Constants.COMMAND_PREVIEW_URL_ATTRIBUTE_NAME;
+
+/** Implementation of {@link CommandManager}. */
+@Singleton
+public class CommandManagerImpl implements CommandManager, Component, WorkspaceReadyHandler {
+
+ private final AppContext appContext;
+ private final PromiseProvider promiseProvider;
+ private final CommandTypeRegistry commandTypeRegistry;
+ private final ProjectCommandManagerDelegate projectCommandManager;
+ private final WorkspaceCommandManagerDelegate workspaceCommandManager;
+ private final EventBus eventBus;
+ private final SelectionAgent selectionAgent;
+
+ /** Map of the commands' names to the commands. */
+ private final Map commands;
+ private final Set commandLoadedListeners;
+ private final Set commandChangedListeners;
+
+ @Inject
+ public CommandManagerImpl(AppContext appContext,
+ PromiseProvider promiseProvider,
+ CommandTypeRegistry commandTypeRegistry,
+ ProjectCommandManagerDelegate projectCommandManagerDelegate,
+ WorkspaceCommandManagerDelegate workspaceCommandManagerDelegate,
+ EventBus eventBus,
+ SelectionAgent selectionAgent) {
+ this.appContext = appContext;
+ this.promiseProvider = promiseProvider;
+ this.commandTypeRegistry = commandTypeRegistry;
+ this.projectCommandManager = projectCommandManagerDelegate;
+ this.workspaceCommandManager = workspaceCommandManagerDelegate;
+ this.eventBus = eventBus;
+ this.selectionAgent = selectionAgent;
+
+ commands = new HashMap<>();
+ commandLoadedListeners = new HashSet<>();
+ commandChangedListeners = new HashSet<>();
+ }
+
+ @Override
+ public void start(Callback callback) {
+ eventBus.addHandler(WorkspaceReadyEvent.getType(), this);
+
+ callback.onSuccess(this);
+ }
+
+ @Override
+ public void onWorkspaceReady(WorkspaceReadyEvent event) {
+ fetchCommands();
+ }
+
+ private void fetchCommands() {
+ // get all commands related to the workspace
+ workspaceCommandManager.getCommands(appContext.getWorkspaceId()).then(workspaceCommands -> {
+ workspaceCommands.forEach(workspaceCommand -> commands.put(workspaceCommand.getName(),
+ new CommandImpl(workspaceCommand, new ApplicableContext())));
+
+ // get all commands related to the projects
+ Arrays.stream(appContext.getProjects())
+ .forEach(project -> projectCommandManager.getCommands(project).forEach(projectCommand -> {
+ final CommandImpl existedCommand = commands.get(projectCommand.getName());
+
+ if (existedCommand == null) {
+ commands.put(projectCommand.getName(),
+ new CommandImpl(projectCommand, new ApplicableContext(project.getPath())));
+ } else {
+ if (projectCommand.equalsIgnoreContext(existedCommand)) {
+ existedCommand.getApplicableContext().addProject(project.getPath());
+ } else {
+ // normally, should never happen
+ Log.error(CommandManagerImpl.this.getClass(), "Different commands with the same names found");
+ }
+ }
+ }));
+
+ notifyCommandsLoaded();
+ });
+ }
+
+ @Override
+ public List getCommands() {
+ return commands.values()
+ .stream()
+ .map(CommandImpl::new)
+ .collect(toList());
+ }
+
+ @Override
+ public java.util.Optional getCommand(String name) {
+ return commands.values()
+ .stream()
+ .filter(command -> name.equals(command.getName()))
+ .findFirst();
+ }
+
+ @Override
+ public List getApplicableCommands() {
+ return commands.values()
+ .stream()
+ .filter(this::isCommandApplicable)
+ .map(CommandImpl::new)
+ .collect(toList());
+ }
+
+ @Override
+ public boolean isCommandApplicable(CommandImpl command) {
+ return isMachineSelected() || isCommandApplicableToCurrentProject(command);
+
+ }
+
+ /** Checks whether the machine is currently selected. */
+ private boolean isMachineSelected() {
+ final Selection> selection = selectionAgent.getSelection();
+
+ if (selection != null && !selection.isEmpty() && selection.isSingleSelection()) {
+ return selection.getHeadElement() instanceof Machine;
+ }
+
+ return false;
+ }
+
+ /** Checks whether the given command is applicable to the current project. */
+ private boolean isCommandApplicableToCurrentProject(CommandImpl command) {
+ final List applicableProjects = command.getApplicableContext().getApplicableProjects();
+
+ if (applicableProjects.isEmpty()) {
+ return true;
+ }
+
+ final Resource currentResource = appContext.getResource();
+ if (currentResource != null) {
+ final Project currentProject = currentResource.getProject();
+ if (currentProject != null) {
+ return applicableProjects.contains(currentProject.getPath());
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public Promise createCommand(String goalId, String typeId) {
+ return createCommand(goalId,
+ typeId,
+ null,
+ null,
+ new HashMap<>(),
+ new ApplicableContext());
+ }
+
+ @Override
+ public Promise createCommand(String goalId, String typeId, ApplicableContext context) {
+ return createCommand(goalId, typeId, null, null, new HashMap<>(), context);
+ }
+
+ @Override
+ public Promise createCommand(String goalId,
+ String typeId,
+ String name,
+ String commandLine,
+ Map attributes) {
+ return createCommand(goalId, typeId, name, commandLine, attributes, new ApplicableContext());
+ }
+
+ @Override
+ public Promise createCommand(String goalId,
+ String typeId,
+ @Nullable String name,
+ @Nullable String commandLine,
+ Map attributes,
+ ApplicableContext context) {
+ final CommandType commandType = commandTypeRegistry.getCommandTypeById(typeId);
+
+ if (commandType == null) {
+ return promiseProvider.reject(new Exception("Unknown command type: '" + typeId + "'"));
+ }
+
+ final Map attr = new HashMap<>(attributes);
+ attr.put(COMMAND_PREVIEW_URL_ATTRIBUTE_NAME, commandType.getPreviewUrlTemplate());
+ attr.put(COMMAND_GOAL_ATTRIBUTE_NAME, goalId);
+
+ return createCommand(new CommandImpl(getUniqueCommandName(typeId, name),
+ commandLine != null ? commandLine : commandType.getCommandLineTemplate(),
+ typeId,
+ attr,
+ context));
+ }
+
+ @Override
+ public Promise createCommand(CommandImpl command) {
+ return doCreateCommand(command).then((Function)newCommand -> {
+ // postpone the notification because
+ // listeners should be notified after returning from #createCommand method
+ Scheduler.get().scheduleDeferred(() -> notifyCommandAdded(newCommand));
+
+ return newCommand;
+ });
+ }
+
+ /** Does the actual work for command creation. Doesn't notify listeners. */
+ private Promise doCreateCommand(CommandImpl command) {
+ final ApplicableContext context = command.getApplicableContext();
+ if (!context.isWorkspaceApplicable() && context.getApplicableProjects().isEmpty()) {
+ return promiseProvider.reject(new Exception("Command has to be applicable to the workspace or at least one project"));
+ }
+
+ final CommandType commandType = commandTypeRegistry.getCommandTypeById(command.getType());
+ if (commandType == null) {
+ return promiseProvider.reject(new Exception("Unknown command type: '" + command.getType() + "'"));
+ }
+
+ final CommandImpl newCommand = new CommandImpl(command);
+ newCommand.setName(getUniqueCommandName(command.getType(), command.getName()));
+
+ final ArrayOf> commandPromises = Collections.arrayOf();
+
+ if (context.isWorkspaceApplicable()) {
+ Promise p = workspaceCommandManager.createCommand(newCommand)
+ .then((Function)arg -> {
+ newCommand.getApplicableContext().setWorkspaceApplicable(true);
+ return newCommand;
+ });
+
+ commandPromises.push(p);
+ }
+
+ for (final String projectPath : context.getApplicableProjects()) {
+ final Project project = getProjectByPath(projectPath);
+
+ if (project == null) {
+ continue;
+ }
+
+ Promise p = projectCommandManager.createCommand(project, newCommand)
+ .then((Function)arg -> {
+ newCommand.getApplicableContext().addProject(projectPath);
+ return newCommand;
+ });
+
+ commandPromises.push(p);
+ }
+
+ return promiseProvider.all2(commandPromises)
+ .then((Function, CommandImpl>)ignore -> {
+ commands.put(newCommand.getName(), newCommand);
+ return newCommand;
+ });
+ }
+
+ @Override
+ public Promise updateCommand(String name, CommandImpl commandToUpdate) {
+ final CommandImpl existedCommand = commands.get(name);
+
+ if (existedCommand == null) {
+ return promiseProvider.reject(new Exception("Command '" + name + "' does not exist."));
+ }
+
+ return doRemoveCommand(name).thenPromise(aVoid -> doCreateCommand(commandToUpdate)
+ .then((Function)updatedCommand -> {
+ // listeners should be notified after returning from #updateCommand method
+ // so let's postpone notification
+ Scheduler.get().scheduleDeferred(() -> notifyCommandUpdated(existedCommand, updatedCommand));
+
+ return updatedCommand;
+ }));
+ }
+
+ @Override
+ public Promise removeCommand(String name) {
+ final CommandImpl command = commands.get(name);
+
+ if (command == null) {
+ return promiseProvider.reject(new Exception("Command '" + name + "' does not exist."));
+ }
+
+ return doRemoveCommand(name).then(aVoid -> {
+ // listeners should be notified after returning from #removeCommand method
+ // so let's postpone notification
+ Scheduler.get().scheduleDeferred(() -> notifyCommandRemoved(command));
+ });
+ }
+
+ /** Removes the command without notifying listeners. */
+ private Promise doRemoveCommand(String name) {
+ final CommandImpl command = commands.get(name);
+
+ if (command == null) {
+ return promiseProvider.reject(new Exception("Command '" + name + "' does not exist."));
+ }
+
+ final ApplicableContext context = command.getApplicableContext();
+
+ final ArrayOf> commandPromises = Collections.arrayOf();
+
+ if (context.isWorkspaceApplicable()) {
+ final Promise p = workspaceCommandManager.removeCommand(name).then((Function)aVoid -> {
+ command.getApplicableContext().setWorkspaceApplicable(false);
+ return null;
+ });
+
+ commandPromises.push(p);
+ }
+
+ for (final String projectPath : context.getApplicableProjects()) {
+ final Project project = getProjectByPath(projectPath);
+
+ if (project == null) {
+ continue;
+ }
+
+ final Promise p = projectCommandManager.removeCommand(project, name).then((Function)aVoid -> {
+ command.getApplicableContext().removeProject(projectPath);
+ return null;
+ });
+
+ commandPromises.push(p);
+ }
+
+ return promiseProvider.all2(commandPromises)
+ .then((Function, Void>)arg -> {
+ commands.remove(command.getName());
+ return null;
+ });
+ }
+
+ @Nullable
+ private Project getProjectByPath(String path) {
+ for (Project project : appContext.getProjects()) {
+ if (path.equals(project.getPath())) {
+ return project;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void addCommandLoadedListener(CommandLoadedListener listener) {
+ commandLoadedListeners.add(listener);
+ }
+
+ @Override
+ public void removeCommandLoadedListener(CommandLoadedListener listener) {
+ commandLoadedListeners.remove(listener);
+ }
+
+ @Override
+ public void addCommandChangedListener(CommandChangedListener listener) {
+ commandChangedListeners.add(listener);
+ }
+
+ @Override
+ public void removeCommandChangedListener(CommandChangedListener listener) {
+ commandChangedListeners.remove(listener);
+ }
+
+ private void notifyCommandsLoaded() {
+ commandLoadedListeners.forEach(CommandLoadedListener::onCommandsLoaded);
+ }
+
+ private void notifyCommandAdded(CommandImpl command) {
+ commandChangedListeners.forEach(listener -> listener.onCommandAdded(command));
+ }
+
+ private void notifyCommandRemoved(CommandImpl command) {
+ commandChangedListeners.forEach(listener -> listener.onCommandRemoved(command));
+ }
+
+ private void notifyCommandUpdated(CommandImpl prevCommand, CommandImpl command) {
+ commandChangedListeners.forEach(listener -> listener.onCommandUpdated(prevCommand, command));
+ }
+
+ /**
+ * Returns {@code customName} if it's unique within the given {@code commandTypeId}
+ * or newly generated name if it isn't unique within the given {@code commandTypeId}.
+ */
+ private String getUniqueCommandName(String commandTypeId, @Nullable String customName) {
+ final CommandType commandType = commandTypeRegistry.getCommandTypeById(commandTypeId);
+ final Set commandNames = commands.keySet();
+
+ final String newCommandName;
+
+ if (isNullOrEmpty(customName)) {
+ newCommandName = "new" + commandType.getDisplayName();
+ } else {
+ if (!commandNames.contains(customName)) {
+ return customName;
+ }
+ newCommandName = customName + " copy";
+ }
+
+ if (!commandNames.contains(newCommandName)) {
+ return newCommandName;
+ }
+
+ for (int count = 1; count < 1000; count++) {
+ if (!commandNames.contains(newCommandName + "-" + count)) {
+ return newCommandName + "-" + count;
+ }
+ }
+
+ return newCommandName;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/manager/ProjectCommandManagerDelegate.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/manager/ProjectCommandManagerDelegate.java
new file mode 100644
index 0000000000..fcd4c23d7b
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/manager/ProjectCommandManagerDelegate.java
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.manager;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.machine.shared.dto.CommandDto;
+import org.eclipse.che.api.promises.client.Function;
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.api.promises.client.PromiseProvider;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.command.CommandType;
+import org.eclipse.che.ide.api.project.MutableProjectConfig;
+import org.eclipse.che.ide.api.resources.Project;
+import org.eclipse.che.ide.dto.DtoFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.eclipse.che.api.project.shared.Constants.COMMANDS_ATTRIBUTE_NAME;
+
+/** Responsible for managing the commands which are stored with projects. */
+@Singleton
+class ProjectCommandManagerDelegate {
+
+ private final DtoFactory dtoFactory;
+ private final PromiseProvider promiseProvider;
+
+ @Inject
+ ProjectCommandManagerDelegate(DtoFactory dtoFactory, PromiseProvider promiseProvider) {
+ this.dtoFactory = dtoFactory;
+ this.promiseProvider = promiseProvider;
+ }
+
+ /** Returns commands for the specified {@code project}. */
+ List getCommands(Project project) {
+ List attrValues = project.getAttributes(COMMANDS_ATTRIBUTE_NAME);
+ if (attrValues == null) {
+ return new ArrayList<>();
+ }
+
+ Map commands = new HashMap<>(attrValues.size());
+ for (String commandJson : attrValues) {
+ CommandDto command = dtoFactory.createDtoFromJson(commandJson, CommandDto.class);
+ commands.put(command.getName(), new CommandImpl(command));
+ }
+
+ return new ArrayList<>(commands.values());
+ }
+
+ /**
+ * Creates new command of the specified type.
+ * Note that command's name will be generated by {@link CommandManager}
+ * and command line will be provided by an appropriate {@link CommandType}.
+ */
+ Promise createCommand(Project project, final CommandImpl newCommand) {
+ final List commands = getCommands(project);
+
+ for (CommandImpl projectCommand : commands) {
+ if (projectCommand.getName().equals(newCommand.getName())) {
+ return promiseProvider.reject(new Exception("Command '" + newCommand.getName() +
+ "' is already associated to the project '" + project.getName() + "'"));
+ }
+ }
+
+ commands.add(newCommand);
+
+ return updateProject(project, commands).then((Function)arg -> newCommand);
+ }
+
+ /** Updates the specified {@code project} with the given {@code commands}. */
+ private Promise updateProject(Project project, List commands) {
+ MutableProjectConfig config = new MutableProjectConfig(project);
+ Map> attributes = config.getAttributes();
+
+ List attrValue = new ArrayList<>(attributes.size());
+ for (CommandImpl command : commands) {
+ CommandDto commandDto = dtoFactory.createDto(CommandDto.class)
+ .withName(command.getName())
+ .withType(command.getType())
+ .withCommandLine(command.getCommandLine())
+ .withAttributes(command.getAttributes());
+ attrValue.add(dtoFactory.toJson(commandDto));
+ }
+
+ attributes.put(COMMANDS_ATTRIBUTE_NAME, attrValue);
+
+ return project.update()
+ .withBody(config)
+ .send()
+ .then((Function)arg -> null);
+ }
+
+ /**
+ * Updates the command with the specified {@code name} by replacing it with the given {@code command}.
+ * Note that name of the updated command may differ from the name provided by the given {@code command}
+ * in order to prevent name duplication.
+ */
+ Promise updateCommand(Project project, final CommandImpl command) {
+ final List projectCommands = getCommands(project);
+
+ if (projectCommands.isEmpty()) {
+ return promiseProvider.reject(new Exception("Command '" + command.getName() + "' is not associated with the project '" +
+ project.getName() + "'"));
+ }
+
+ final List commandsToUpdate = new ArrayList<>();
+ for (CommandImpl projectCommand : projectCommands) {
+ // skip existed command with the same name
+ if (!command.getName().equals(projectCommand.getName())) {
+ commandsToUpdate.add(projectCommand);
+ }
+ }
+
+ commandsToUpdate.add(command);
+
+ return updateProject(project, new ArrayList<>(commandsToUpdate))
+ .then((Function)arg -> command);
+ }
+
+ /** Removes the command with the specified {@code commandName}. */
+ Promise removeCommand(Project project, String commandName) {
+ final List commands = getCommands(project);
+
+ if (commands.isEmpty()) {
+ return promiseProvider.reject(new Exception("Command '" + commandName + "' is not associated with the project '" +
+ project.getName() + "'"));
+ }
+
+ final List commandsToUpdate = new ArrayList<>();
+ for (CommandImpl projectCommand : commands) {
+ if (!commandName.equals(projectCommand.getName())) {
+ commandsToUpdate.add(projectCommand);
+ }
+ }
+
+ return updateProject(project, new ArrayList<>(commandsToUpdate));
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/manager/WorkspaceCommandManagerDelegate.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/manager/WorkspaceCommandManagerDelegate.java
new file mode 100644
index 0000000000..7dfe27d1e6
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/manager/WorkspaceCommandManagerDelegate.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.manager;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.machine.shared.dto.CommandDto;
+import org.eclipse.che.api.promises.client.Function;
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.command.CommandType;
+import org.eclipse.che.ide.api.workspace.WorkspaceServiceClient;
+import org.eclipse.che.ide.dto.DtoFactory;
+
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+/** Responsible for managing the commands which are stored with workspace. */
+@Singleton
+class WorkspaceCommandManagerDelegate {
+
+ private final DtoFactory dtoFactory;
+ private final WorkspaceServiceClient workspaceServiceClient;
+ private final AppContext appContext;
+
+ @Inject
+ WorkspaceCommandManagerDelegate(DtoFactory dtoFactory,
+ WorkspaceServiceClient workspaceServiceClient,
+ AppContext appContext) {
+ this.dtoFactory = dtoFactory;
+ this.workspaceServiceClient = workspaceServiceClient;
+ this.appContext = appContext;
+ }
+
+ /** Returns commands which are stored in the workspace with the specified {@code workspaceId}. */
+ Promise> getCommands(String workspaceId) {
+ return workspaceServiceClient.getCommands(workspaceId)
+ .then((Function,
+ List>)commands -> commands.stream()
+ .map(CommandImpl::new)
+ .collect(toList()));
+ }
+
+ /**
+ * Creates new command of the specified type.
+ * Note that command's name will be generated by {@link CommandManager}
+ * and command line will be provided by an appropriate {@link CommandType}.
+ */
+ Promise createCommand(final CommandImpl command) {
+ final CommandDto commandDto = dtoFactory.createDto(CommandDto.class)
+ .withName(command.getName())
+ .withCommandLine(command.getCommandLine())
+ .withType(command.getType())
+ .withAttributes(command.getAttributes());
+
+ return workspaceServiceClient.addCommand(appContext.getWorkspaceId(), commandDto)
+ .then((Function)arg -> command);
+ }
+
+ /**
+ * Updates the command with the specified {@code name} by replacing it with the given {@code command}.
+ * Note that name of the updated command may differ from the name provided by the given {@code command}
+ * in order to prevent name duplication.
+ */
+ Promise updateCommand(final CommandImpl command) {
+ final CommandDto commandDto = dtoFactory.createDto(CommandDto.class)
+ .withName(command.getName())
+ .withCommandLine(command.getCommandLine())
+ .withType(command.getType())
+ .withAttributes(command.getAttributes());
+
+ return workspaceServiceClient.updateCommand(appContext.getWorkspaceId(), command.getName(), commandDto)
+ .then((Function)arg -> command);
+ }
+
+ /** Removes the command with the specified {@code commandName}. */
+ Promise removeCommand(String commandName) {
+ return workspaceServiceClient.deleteCommand(appContext.getWorkspaceId(), commandName)
+ .then((Function)arg -> null);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/AbstractCommandNode.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/AbstractCommandNode.java
new file mode 100644
index 0000000000..432992bf2d
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/AbstractCommandNode.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.node;
+
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.data.tree.Node;
+import org.eclipse.che.ide.api.data.tree.settings.NodeSettings;
+import org.eclipse.che.ide.command.CommandUtils;
+import org.eclipse.che.ide.project.node.SyntheticNode;
+import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation;
+import org.vectomatic.dom.svg.ui.SVGResource;
+
+import java.util.List;
+
+/** Abstract tree node that represents {@link CommandImpl}. */
+class AbstractCommandNode extends SyntheticNode {
+
+ private final CommandUtils commandUtils;
+
+ AbstractCommandNode(CommandImpl data, NodeSettings nodeSettings, CommandUtils commandUtils) {
+ super(data, nodeSettings);
+
+ this.commandUtils = commandUtils;
+ }
+
+ @Override
+ public void updatePresentation(NodePresentation presentation) {
+ presentation.setPresentableText(getName());
+
+ final SVGResource commandTypeIcon = commandUtils.getCommandTypeIcon(getData().getType());
+
+ if (commandTypeIcon != null) {
+ presentation.setPresentableIcon(commandTypeIcon);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return getData().getName();
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return true;
+ }
+
+ @Override
+ protected Promise> getChildrenImpl() {
+ return null;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/CommandFileNode.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/CommandFileNode.java
new file mode 100644
index 0000000000..f096ab2faa
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/CommandFileNode.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.node;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.data.tree.HasAction;
+import org.eclipse.che.ide.api.editor.EditorAgent;
+import org.eclipse.che.ide.api.resources.VirtualFile;
+import org.eclipse.che.ide.command.CommandUtils;
+import org.eclipse.che.ide.resource.Path;
+import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation;
+
+/** Extension of {@link AbstractCommandNode} that also acts as a {@link VirtualFile} for using it in editor. */
+public class CommandFileNode extends AbstractCommandNode implements HasAction, VirtualFile {
+
+ /** Extension for the file type that represents a command. */
+ public static final String FILE_TYPE_EXT = "che_command_internal";
+
+ private final EditorAgent editorAgent;
+
+ @Inject
+ public CommandFileNode(@Assisted CommandImpl data,
+ CommandUtils commandUtils,
+ EditorAgent editorAgent) {
+ super(data, null, commandUtils);
+
+ this.editorAgent = editorAgent;
+ }
+
+ @Override
+ public void updatePresentation(NodePresentation presentation) {
+ super.updatePresentation(presentation);
+
+ presentation.setPresentableText(getDisplayName());
+ presentation.setPresentableTextCss("overflow: hidden; text-overflow: ellipsis;");
+ }
+
+ @Override
+ public void actionPerformed() {
+ editorAgent.openEditor(this);
+ }
+
+ @Override
+ public Path getLocation() {
+ return Path.valueOf("commands/" + getData().getType() + "/" + getData().getName());
+ }
+
+ @Override
+ public String getName() {
+ return getData().getName() + "." + FILE_TYPE_EXT;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return getData().getName();
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ @Override
+ public String getContentUrl() {
+ return null;
+ }
+
+ @Override
+ public Promise getContent() {
+ return null;
+ }
+
+ @Override
+ public Promise updateContent(String content) {
+ return null;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/CommandGoalNode.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/CommandGoalNode.java
new file mode 100644
index 0000000000..aebde39774
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/CommandGoalNode.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.node;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.api.promises.client.PromiseProvider;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.data.tree.Node;
+import org.eclipse.che.ide.command.CommandUtils;
+import org.eclipse.che.ide.project.node.SyntheticNode;
+import org.eclipse.che.ide.ui.smartTree.presentation.NodePresentation;
+import org.vectomatic.dom.svg.ui.SVGResource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tree node that represents {@link CommandGoal}. */
+public class CommandGoalNode extends SyntheticNode {
+
+ private final List extends AbstractCommandNode> commands;
+ private final PromiseProvider promiseProvider;
+ private final CommandUtils commandUtils;
+
+ @Inject
+ public CommandGoalNode(@Assisted CommandGoal data,
+ @Assisted List extends AbstractCommandNode> commands,
+ PromiseProvider promiseProvider,
+ CommandUtils commandUtils) {
+ super(data, null);
+
+ this.commands = commands;
+ this.promiseProvider = promiseProvider;
+ this.commandUtils = commandUtils;
+ }
+
+ @Override
+ public void updatePresentation(NodePresentation presentation) {
+ presentation.setPresentableText(getName().toUpperCase() + " (" + commands.size() + ")");
+ presentation.setPresentableTextCss("font-weight: bold;");
+
+ final SVGResource goalIcon = commandUtils.getCommandGoalIcon(getData().getId());
+ if (goalIcon != null) {
+ presentation.setPresentableIcon(goalIcon);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return getData().getId();
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return false;
+ }
+
+ @Override
+ protected Promise> getChildrenImpl() {
+ List children = new ArrayList<>();
+ children.addAll(commands);
+
+ return promiseProvider.resolve(children);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/ExecutableCommandNode.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/ExecutableCommandNode.java
new file mode 100644
index 0000000000..cde81f59eb
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/ExecutableCommandNode.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.node;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.data.tree.HasAction;
+import org.eclipse.che.ide.command.CommandUtils;
+
+/**
+ * Extension of {@link AbstractCommandNode} that can execute
+ * a command when performing an action is requested.
+ */
+public class ExecutableCommandNode extends AbstractCommandNode implements HasAction {
+
+ private final ActionDelegate actionDelegate;
+
+ @Inject
+ public ExecutableCommandNode(@Assisted CommandImpl data,
+ @Assisted ActionDelegate actionDelegate,
+ CommandUtils commandUtils) {
+ super(data, null, commandUtils);
+
+ this.actionDelegate = actionDelegate;
+ }
+
+ @Override
+ public void actionPerformed() {
+ actionDelegate.actionPerformed();
+ }
+
+ /** Interface for delegating performing action on node. */
+ public interface ActionDelegate {
+ void actionPerformed();
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/NodeFactory.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/NodeFactory.java
new file mode 100644
index 0000000000..8bda3339d1
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/node/NodeFactory.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.node;
+
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandImpl;
+
+import java.util.List;
+
+/** Factory for different command tree nodes. */
+public interface NodeFactory {
+
+ CommandGoalNode newCommandGoalNode(CommandGoal data, List extends AbstractCommandNode> commands);
+
+ ExecutableCommandNode newExecutableCommandNode(CommandImpl command, ExecutableCommandNode.ActionDelegate actionDelegate);
+
+ CommandFileNode newCommandFileNode(CommandImpl data);
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPalettePresenter.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPalettePresenter.java
new file mode 100644
index 0000000000..241b7a6c4e
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPalettePresenter.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.palette;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.core.model.machine.Machine;
+import org.eclipse.che.api.core.model.workspace.WorkspaceRuntime;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.command.CommandExecutor;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.dialogs.DialogFactory;
+import org.eclipse.che.ide.command.CommandUtils;
+import org.eclipse.che.ide.machine.chooser.MachineChooser;
+
+import java.util.List;
+import java.util.ListIterator;
+
+import static java.util.Collections.emptyList;
+import static org.eclipse.che.ide.util.StringUtils.containsIgnoreCase;
+
+/**
+ * Presenter for Commands Palette.
+ *
+ * @author Artem Zatsarynnyi
+ */
+@Singleton
+public class CommandsPalettePresenter implements CommandsPaletteView.ActionDelegate {
+
+ private final CommandsPaletteView view;
+ private final CommandManager commandManager;
+ private final CommandExecutor commandExecutor;
+ private final DialogFactory dialogFactory;
+ private final AppContext appContext;
+ private final MachineChooser machineChooser;
+ private final CommandUtils commandUtils;
+ private final PaletteMessages messages;
+
+ @Inject
+ public CommandsPalettePresenter(CommandsPaletteView view,
+ CommandManager commandManager,
+ CommandExecutor commandExecutor,
+ DialogFactory dialogFactory,
+ AppContext appContext,
+ MachineChooser machineChooser,
+ CommandUtils commandUtils,
+ PaletteMessages messages) {
+ this.view = view;
+ this.commandManager = commandManager;
+ this.commandExecutor = commandExecutor;
+ this.dialogFactory = dialogFactory;
+ this.appContext = appContext;
+ this.machineChooser = machineChooser;
+ this.commandUtils = commandUtils;
+ this.messages = messages;
+
+ view.setDelegate(this);
+ }
+
+ public void showDialog() {
+ view.show();
+ view.setCommands(commandUtils.groupCommandsByGoal(commandManager.getCommands()));
+ }
+
+ @Override
+ public void onFilterChanged(String filterValue) {
+ final List filteredCommands = commandManager.getCommands();
+
+ if (!filterValue.isEmpty()) {
+ final ListIterator it = filteredCommands.listIterator();
+
+ while (it.hasNext()) {
+ final CommandImpl command = it.next();
+
+ if (!containsIgnoreCase(command.getName(), filterValue)) {
+ it.remove();
+ }
+ }
+ }
+
+ view.setCommands(commandUtils.groupCommandsByGoal(filteredCommands));
+ }
+
+ @Override
+ public void onCommandExecute(CommandImpl command) {
+ view.close();
+
+ if (getMachines().isEmpty()) {
+ // should not happen, but let's play safe
+ dialogFactory.createMessageDialog("", messages.messageNoMachine(), null).show();
+ } else {
+ machineChooser.show().then(machine -> {
+ commandExecutor.executeCommand(command, machine);
+ });
+ }
+ }
+
+ private List extends Machine> getMachines() {
+ final WorkspaceRuntime runtime = appContext.getWorkspace().getRuntime();
+
+ if (runtime != null) {
+ return runtime.getMachines();
+ }
+
+ return emptyList();
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPaletteView.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPaletteView.java
new file mode 100644
index 0000000000..532a55a24e
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPaletteView.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.palette;
+
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.mvp.View;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The view for {@link CommandsPalettePresenter}.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface CommandsPaletteView extends View {
+
+ /** Show the view. */
+ void show();
+
+ /** Close the view. */
+ void close();
+
+ /**
+ * Sets the commands to display in the view.
+ *
+ * @param commands
+ * commands grouped by type
+ */
+ void setCommands(Map> commands);
+
+ /** The action delegate for this view. */
+ interface ActionDelegate {
+
+ /** Called when filtering commands is requested. */
+ void onFilterChanged(String filterValue);
+
+ /** Called when command execution is requested. */
+ void onCommandExecute(CommandImpl command);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPaletteViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPaletteViewImpl.java
new file mode 100644
index 0000000000..fb95274b17
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPaletteViewImpl.java
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.palette;
+
+import elemental.html.DivElement;
+import elemental.html.SpanElement;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.ide.FontAwesome;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.data.tree.Node;
+import org.eclipse.che.ide.command.node.CommandGoalNode;
+import org.eclipse.che.ide.command.node.ExecutableCommandNode;
+import org.eclipse.che.ide.command.node.NodeFactory;
+import org.eclipse.che.ide.ui.smartTree.NodeLoader;
+import org.eclipse.che.ide.ui.smartTree.NodeStorage;
+import org.eclipse.che.ide.ui.smartTree.Tree;
+import org.eclipse.che.ide.ui.window.Window;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static com.google.gwt.event.dom.client.KeyCodes.KEY_DOWN;
+import static com.google.gwt.event.dom.client.KeyCodes.KEY_ENTER;
+import static com.google.gwt.event.dom.client.KeyCodes.KEY_UP;
+import static org.eclipse.che.ide.ui.smartTree.SelectionModel.Mode.SINGLE;
+import static org.eclipse.che.ide.util.dom.Elements.createDivElement;
+import static org.eclipse.che.ide.util.dom.Elements.createSpanElement;
+import static org.eclipse.che.ide.util.dom.Elements.createTextNode;
+
+/** Implementation of {@link CommandsPaletteView}. */
+@Singleton
+public class CommandsPaletteViewImpl extends Window implements CommandsPaletteView {
+
+ private static final CommandsPaletteViewImplUiBinder UI_BINDER = GWT.create(CommandsPaletteViewImplUiBinder.class);
+
+ private final NodeFactory nodeFactory;
+
+ @UiField
+ TextBox filterField;
+
+ @UiField(provided = true)
+ Tree tree;
+
+ @UiField
+ Label hintLabel;
+
+ private ActionDelegate delegate;
+
+ @Inject
+ public CommandsPaletteViewImpl(NodeFactory nodeFactory, PaletteMessages messages) {
+ this.nodeFactory = nodeFactory;
+
+ tree = new Tree(new NodeStorage(), new NodeLoader());
+ tree.getSelectionModel().setSelectionMode(SINGLE);
+
+ setWidget(UI_BINDER.createAndBindUi(this));
+ setTitle(messages.viewTitle());
+
+ filterField.getElement().setAttribute("placeholder", messages.filterPlaceholder());
+ initHintLabel();
+ getFooter().removeFromParent();
+ }
+
+ private void initHintLabel() {
+ final SpanElement upKeyLabel = createKeyLabel();
+ upKeyLabel.setInnerHTML(FontAwesome.ARROW_UP);
+
+ final SpanElement downKeyLabel = createKeyLabel();
+ downKeyLabel.setInnerHTML(FontAwesome.ARROW_DOWN);
+
+ final SpanElement enterKeyLabel = createKeyLabel();
+ enterKeyLabel.getStyle().setPadding("0px 1px 1px 4px");
+ enterKeyLabel.setInnerText(" Enter ");
+
+ final DivElement hintElement = createDivElement();
+ hintElement.appendChild(upKeyLabel);
+ hintElement.appendChild(downKeyLabel);
+ hintElement.appendChild(createTextNode(" to select and "));
+ hintElement.appendChild(enterKeyLabel);
+ hintElement.appendChild(createTextNode(" to execute"));
+
+ hintLabel.getElement().appendChild((Element)hintElement);
+ }
+
+ /** Creates an html element for displaying keyboard key. */
+ private SpanElement createKeyLabel() {
+ SpanElement element = createSpanElement();
+
+ element.getStyle().setFontWeight("bold");
+ element.getStyle().setPadding("0 4px 1px 4px");
+ element.getStyle().setMargin("0 3px");
+ element.getStyle().setBorderWidth("1px");
+ element.getStyle().setBorderStyle("solid");
+ element.getStyle().setProperty("border-radius", "3px");
+
+ return element;
+ }
+
+ @Override
+ public void show() {
+ super.show();
+
+ filterField.setValue("");
+ filterField.setFocus(true);
+ }
+
+ @Override
+ public void close() {
+ hide();
+ }
+
+ @Override
+ public void setCommands(Map> commands) {
+ renderCommands(commands);
+ }
+
+ /** Render commands grouped by goals. */
+ private void renderCommands(Map> commands) {
+ tree.getNodeStorage().clear();
+
+ for (Entry> entry : commands.entrySet()) {
+ List commandNodes = new ArrayList<>(entry.getValue().size());
+
+ for (final CommandImpl command : entry.getValue()) {
+ commandNodes.add(nodeFactory.newExecutableCommandNode(command, () -> delegate.onCommandExecute(command)));
+ }
+
+ final CommandGoalNode commandGoalNode = nodeFactory.newCommandGoalNode(entry.getKey(), commandNodes);
+ tree.getNodeStorage().add(commandGoalNode);
+ }
+
+ tree.expandAll();
+ }
+
+ @Override
+ public void setDelegate(ActionDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ @UiHandler({"filterField"})
+ void onFilterChanged(KeyUpEvent event) {
+ switch (event.getNativeKeyCode()) {
+ case KEY_UP:
+ tree.getSelectionModel().selectPrevious();
+ break;
+ case KEY_DOWN:
+ tree.getSelectionModel().selectNext();
+ break;
+ case KEY_ENTER:
+ final List selectedNodes = tree.getSelectionModel().getSelectedNodes();
+
+ if (!selectedNodes.isEmpty()) {
+ final Node node = selectedNodes.get(0);
+
+ if (node instanceof ExecutableCommandNode) {
+ delegate.onCommandExecute(((ExecutableCommandNode)node).getData());
+ }
+ }
+ break;
+ default:
+ delegate.onFilterChanged(filterField.getValue());
+ }
+ }
+
+ interface CommandsPaletteViewImplUiBinder extends UiBinder {
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPaletteViewImpl.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPaletteViewImpl.ui.xml
new file mode 100644
index 0000000000..6b6470aae3
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/CommandsPaletteViewImpl.ui.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+ @eval partBackground org.eclipse.che.ide.api.theme.Style.theme.partBackground();
+ @eval borderColor org.eclipse.che.ide.api.theme.Style.theme.getTextFieldBackgroundColor();
+
+ .tree {
+ border: 1px solid;
+ border-color: borderColor;
+ background-color: partBackground;
+ margin-top: 7px;
+ min-width: 99%;
+ }
+
+ .hint-label {
+ margin: 7px auto;
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/PaletteMessages.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/PaletteMessages.java
new file mode 100644
index 0000000000..af6007d3e6
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/PaletteMessages.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.palette;
+
+import com.google.gwt.i18n.client.Messages;
+
+/**
+ * I18n messages for the Commands Palette.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface PaletteMessages extends Messages {
+
+ @Key("action.show_palette.title")
+ String actionShowPaletteTitle();
+
+ @Key("action.show_palette.description")
+ String actionShowPaletteDescription();
+
+ @Key("view.title")
+ String viewTitle();
+
+ @Key("view.filter.placeholder")
+ String filterPlaceholder();
+
+ @Key("view.hint.text")
+ String viewHintText();
+
+ @Key("message.no_machine")
+ String messageNoMachine();
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/ShowCommandsPaletteAction.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/ShowCommandsPaletteAction.java
new file mode 100644
index 0000000000..8625bfc7cf
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/palette/ShowCommandsPaletteAction.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.palette;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.ide.api.action.Action;
+import org.eclipse.che.ide.api.action.ActionEvent;
+
+/**
+ * Action for opening Commands Palette.
+ *
+ * @author Artem Zatsarynnyi
+ */
+@Singleton
+public class ShowCommandsPaletteAction extends Action {
+
+ private final CommandsPalettePresenter presenter;
+
+ @Inject
+ public ShowCommandsPaletteAction(PaletteMessages messages, CommandsPalettePresenter presenter) {
+ super(messages.actionShowPaletteTitle(),
+ messages.actionShowPaletteDescription(),
+ null,
+ null);
+
+ this.presenter = presenter;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ presenter.showDialog();
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandProducerAction.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/CommandProducerAction.java
similarity index 84%
rename from ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandProducerAction.java
rename to ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/CommandProducerAction.java
index 291d8b70fa..312dbc9a7b 100644
--- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandProducerAction.java
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/CommandProducerAction.java
@@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
-package org.eclipse.che.ide.command;
+package org.eclipse.che.ide.command.producer;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -17,7 +17,7 @@ import org.eclipse.che.api.core.model.machine.Machine;
import org.eclipse.che.ide.api.action.Action;
import org.eclipse.che.ide.api.action.ActionEvent;
import org.eclipse.che.ide.api.command.CommandImpl;
-import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.command.CommandExecutor;
import org.eclipse.che.ide.api.command.CommandProducer;
/**
@@ -30,18 +30,18 @@ public class CommandProducerAction extends Action {
private final CommandProducer commandProducer;
private final Machine machine;
- private final CommandManager commandManager;
+ private final CommandExecutor commandExecutor;
@Inject
public CommandProducerAction(@Assisted String name,
@Assisted CommandProducer commandProducer,
@Assisted Machine machine,
- CommandManager commandManager) {
+ CommandExecutor commandExecutor) {
super(name);
this.commandProducer = commandProducer;
this.machine = machine;
- this.commandManager = commandManager;
+ this.commandExecutor = commandExecutor;
}
@Override
@@ -52,6 +52,6 @@ public class CommandProducerAction extends Action {
@Override
public void actionPerformed(ActionEvent e) {
CommandImpl command = commandProducer.createCommand(machine);
- commandManager.executeCommand(command, machine);
+ commandExecutor.executeCommand(command, machine);
}
}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandProducerActionFactory.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/CommandProducerActionFactory.java
similarity index 95%
rename from ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandProducerActionFactory.java
rename to ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/CommandProducerActionFactory.java
index 58f04e3b65..29c13bb38f 100644
--- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandProducerActionFactory.java
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/CommandProducerActionFactory.java
@@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
-package org.eclipse.che.ide.command;
+package org.eclipse.che.ide.command.producer;
import org.eclipse.che.api.core.model.machine.Machine;
import org.eclipse.che.ide.api.command.CommandProducer;
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandProducerActionManager.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/CommandProducerActionManager.java
similarity index 92%
rename from ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandProducerActionManager.java
rename to ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/CommandProducerActionManager.java
index cf962f7c7d..219f9c9e15 100644
--- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/CommandProducerActionManager.java
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/CommandProducerActionManager.java
@@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
-package org.eclipse.che.ide.command;
+package org.eclipse.che.ide.command.producer;
import com.google.gwt.core.client.Callback;
import com.google.inject.Inject;
@@ -43,7 +43,7 @@ import static org.eclipse.che.ide.api.action.IdeActions.GROUP_MAIN_TOOLBAR;
import static org.eclipse.che.ide.api.constraints.Anchor.AFTER;
/**
- * Manages actions for the contextual commands.
+ * Manages actions for the commands.
* Manager gets all registered {@link CommandProducer}s and creates related actions in context menus.
*
Manager listens all machines's state (running/destroyed) in order to
* create/remove actions for the related {@link CommandProducer}s in case
@@ -53,12 +53,15 @@ import static org.eclipse.che.ide.api.constraints.Anchor.AFTER;
* @see CommandProducer
*/
@Singleton
-public class CommandProducerActionManager implements MachineStateEvent.Handler, WsAgentStateHandler, Component {
+public class CommandProducerActionManager implements MachineStateEvent.Handler,
+ WsAgentStateHandler,
+ Component {
private final ActionManager actionManager;
private final CommandProducerActionFactory commandProducerActionFactory;
private final AppContext appContext;
private final Resources resources;
+ private final ProducerMessages messages;
private final List machines;
private final Set commandProducers;
@@ -73,11 +76,13 @@ public class CommandProducerActionManager implements MachineStateEvent.Handler,
ActionManager actionManager,
CommandProducerActionFactory commandProducerActionFactory,
AppContext appContext,
- Resources resources) {
+ Resources resources,
+ ProducerMessages messages) {
this.actionManager = actionManager;
this.commandProducerActionFactory = commandProducerActionFactory;
this.appContext = appContext;
this.resources = resources;
+ this.messages = messages;
machines = new ArrayList<>();
commandProducers = new HashSet<>();
@@ -93,10 +98,10 @@ public class CommandProducerActionManager implements MachineStateEvent.Handler,
private void start(Set commandProducers) {
this.commandProducers.addAll(commandProducers);
- commandActionsPopUpGroup = new DefaultActionGroup("Commands", true, actionManager);
+ commandActionsPopUpGroup = new DefaultActionGroup(messages.actionCommandsTitle(), true, actionManager);
actionManager.registerAction("commandActionsPopUpGroup", commandActionsPopUpGroup);
commandActionsPopUpGroup.getTemplatePresentation().setSVGResource(resources.compile());
- commandActionsPopUpGroup.getTemplatePresentation().setDescription("Execute command");
+ commandActionsPopUpGroup.getTemplatePresentation().setDescription(messages.actionCommandsDescription());
DefaultActionGroup mainContextMenu = (DefaultActionGroup)actionManager.getAction(GROUP_MAIN_CONTEXT_MENU);
mainContextMenu.add(commandActionsPopUpGroup);
@@ -183,11 +188,7 @@ public class CommandProducerActionManager implements MachineStateEvent.Handler,
CommandProducerAction machineAction = commandProducerActionFactory.create(machine.getConfig().getName(),
commandProducer,
machine);
- List actionList = actionsByMachines.get(machine);
- if (actionList == null) {
- actionList = new ArrayList<>();
- actionsByMachines.put(machine, actionList);
- }
+ final List actionList = actionsByMachines.computeIfAbsent(machine, key -> new ArrayList<>());
actionList.add(machineAction);
actionManager.registerAction(machine.getConfig().getName(), machineAction);
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/ProducerMessages.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/ProducerMessages.java
new file mode 100644
index 0000000000..503ba298ad
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/producer/ProducerMessages.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.producer;
+
+import com.google.gwt.i18n.client.Messages;
+
+/**
+ * I18n messages for the command producers related UI.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface ProducerMessages extends Messages {
+
+ @Key("action.commands.title")
+ String actionCommandsTitle();
+
+ @Key("action.commands.description")
+ String actionCommandsDescription();
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandCreationGuide.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandCreationGuide.java
new file mode 100644
index 0000000000..bc25808a3e
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandCreationGuide.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.editor.EditorAgent;
+import org.eclipse.che.ide.api.parts.WorkspaceAgent;
+import org.eclipse.che.ide.command.explorer.CommandsExplorerPresenter;
+import org.eclipse.che.ide.command.goal.RunGoal;
+import org.eclipse.che.ide.command.node.NodeFactory;
+
+/**
+ * Guides the user into the flow of creating a command and helps
+ * him to understand how he can configure his workspace's commands.
+ */
+@Singleton
+public class CommandCreationGuide {
+
+ private final Provider workspaceAgentProvider;
+ private final Provider commandsExplorerPresenterProvider;
+ private final CommandManager commandManager;
+ private final Provider editorAgentProvider;
+ private final NodeFactory nodeFactory;
+ private final RunGoal runGoal;
+
+ @Inject
+ public CommandCreationGuide(Provider workspaceAgentProvider,
+ Provider commandsExplorerPresenterProvider,
+ CommandManager commandManager,
+ Provider editorAgentProvider,
+ NodeFactory nodeFactory,
+ RunGoal runGoal) {
+ this.workspaceAgentProvider = workspaceAgentProvider;
+ this.commandsExplorerPresenterProvider = commandsExplorerPresenterProvider;
+ this.commandManager = commandManager;
+ this.editorAgentProvider = editorAgentProvider;
+ this.nodeFactory = nodeFactory;
+ this.runGoal = runGoal;
+ }
+
+
+ /** Shows the guide of creating a command. */
+ public void guide() {
+ guide(runGoal);
+ }
+
+ /** Shows the guide of creating a command of the specified {@code goal}. */
+ public void guide(CommandGoal goal) {
+ workspaceAgentProvider.get().setActivePart(commandsExplorerPresenterProvider.get());
+
+ commandManager.createCommand(goal.getId(), "custom").then(command -> {
+ editorAgentProvider.get().openEditor(nodeFactory.newCommandFileNode(command));
+ });
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarPresenter.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarPresenter.java
new file mode 100644
index 0000000000..857c974d44
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarPresenter.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar;
+
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.ui.AcceptsOneWidget;
+
+import org.eclipse.che.ide.FontAwesome;
+import org.eclipse.che.ide.api.mvp.Presenter;
+import org.eclipse.che.ide.command.toolbar.commands.ExecuteCommandPresenter;
+import org.eclipse.che.ide.command.toolbar.previews.PreviewsPresenter;
+import org.eclipse.che.ide.command.toolbar.processes.ProcessesListPresenter;
+import org.eclipse.che.ide.ui.menubutton.MenuPopupButton;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** Presenter for command toolbar. */
+@Singleton
+public class CommandToolbarPresenter implements Presenter, CommandToolbarView.ActionDelegate {
+
+ private final ProcessesListPresenter processesListPresenter;
+ private final PreviewsPresenter previewsPresenter;
+ private final ExecuteCommandPresenter executeCommandPresenter;
+ private final ToolbarButtonsFactory toolbarButtonsFactory;
+ private final CommandToolbarView view;
+ private MenuPopupButton openCommandsPaletteButton;
+
+ @Inject
+ public CommandToolbarPresenter(CommandToolbarView view,
+ ProcessesListPresenter processesListPresenter,
+ PreviewsPresenter previewsPresenter,
+ ExecuteCommandPresenter executeCommandPresenter,
+ ToolbarButtonsFactory toolbarButtonsFactory) {
+ this.view = view;
+ this.processesListPresenter = processesListPresenter;
+ this.previewsPresenter = previewsPresenter;
+ this.executeCommandPresenter = executeCommandPresenter;
+ this.toolbarButtonsFactory = toolbarButtonsFactory;
+
+ initButtons();
+
+ view.setDelegate(this);
+ }
+
+ private void initButtons() {
+ final SafeHtmlBuilder safeHtmlBuilder = new SafeHtmlBuilder();
+ safeHtmlBuilder.appendHtmlConstant(FontAwesome.LIST);
+
+ openCommandsPaletteButton = toolbarButtonsFactory.createOpenPaletteButton(safeHtmlBuilder.toSafeHtml());
+ }
+
+ @Override
+ public void go(AcceptsOneWidget container) {
+ container.setWidget(view);
+
+ executeCommandPresenter.go(view.getCommandsPanelContainer());
+ processesListPresenter.go(view.getProcessesListContainer());
+ previewsPresenter.go(view.getPreviewUrlsListContainer());
+
+ view.addButton(openCommandsPaletteButton);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarView.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarView.java
new file mode 100644
index 0000000000..78e31c7608
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarView.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar;
+
+import com.google.gwt.user.client.ui.AcceptsOneWidget;
+
+import org.eclipse.che.ide.api.mvp.View;
+import org.eclipse.che.ide.ui.menubutton.MenuPopupButton;
+
+/** View for command toolbar. */
+public interface CommandToolbarView extends View {
+
+ AcceptsOneWidget getCommandsPanelContainer();
+
+ AcceptsOneWidget getProcessesListContainer();
+
+ AcceptsOneWidget getPreviewUrlsListContainer();
+
+ void addButton(MenuPopupButton button);
+
+ interface ActionDelegate {
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarViewImpl.java
new file mode 100644
index 0000000000..cd8f7c2ffc
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarViewImpl.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.AcceptsOneWidget;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+
+import org.eclipse.che.ide.ui.menubutton.MenuPopupButton;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** Implementation of {@link CommandToolbarView}. */
+@Singleton
+public class CommandToolbarViewImpl implements CommandToolbarView {
+
+ private static final CommandToolbarViewImplUiBinder UI_BINDER = GWT.create(CommandToolbarViewImplUiBinder.class);
+
+ @UiField
+ FlowPanel rootPanel;
+ @UiField
+ SimplePanel commandsPanel;
+ @UiField
+ SimplePanel processesListPanel;
+ @UiField
+ SimplePanel buttonsPanel;
+ @UiField
+ SimplePanel previewUrlListPanel;
+
+ private ActionDelegate delegate;
+
+ @Inject
+ public CommandToolbarViewImpl() {
+ UI_BINDER.createAndBindUi(this);
+ }
+
+ @Override
+ public void setDelegate(ActionDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Widget asWidget() {
+ return rootPanel;
+ }
+
+ @Override
+ public AcceptsOneWidget getCommandsPanelContainer() {
+ return commandsPanel;
+ }
+
+ @Override
+ public AcceptsOneWidget getProcessesListContainer() {
+ return processesListPanel;
+ }
+
+ @Override
+ public AcceptsOneWidget getPreviewUrlsListContainer() {
+ return previewUrlListPanel;
+ }
+
+ @Override
+ public void addButton(MenuPopupButton button) {
+ buttonsPanel.add(button);
+ }
+
+ interface CommandToolbarViewImplUiBinder extends UiBinder {
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarViewImpl.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarViewImpl.ui.xml
new file mode 100644
index 0000000000..10cb06ebac
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/CommandToolbarViewImpl.ui.xml
@@ -0,0 +1,48 @@
+
+
+
+
+ .rootPanel {
+ margin-top: 5px;
+ }
+
+ .commandsPanel {
+ float: left;
+ }
+
+ .processesListPanel {
+ width: literal("calc(100% - 285px)");
+ float: left;
+ margin-left: 15px;
+ }
+
+ .buttonsPanel {
+ float: right;
+ }
+
+ .previewUrlListPanel {
+ float: right;
+ margin-left: 15px;
+ margin-right: 15px;
+ }
+
+
+
+
+
+
+
+
+
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/OpenCommandsPaletteButton.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/OpenCommandsPaletteButton.java
new file mode 100644
index 0000000000..915a772c41
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/OpenCommandsPaletteButton.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar;
+
+import elemental.dom.Element;
+import elemental.html.DivElement;
+import elemental.html.SpanElement;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.user.client.Timer;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.che.ide.api.action.Action;
+import org.eclipse.che.ide.api.action.ActionManager;
+import org.eclipse.che.ide.api.keybinding.KeyBindingAgent;
+import org.eclipse.che.ide.command.palette.CommandsPalettePresenter;
+import org.eclipse.che.ide.command.palette.PaletteMessages;
+import org.eclipse.che.ide.command.palette.ShowCommandsPaletteAction;
+import org.eclipse.che.ide.ui.Tooltip;
+import org.eclipse.che.ide.ui.menubutton.MenuPopupButton;
+import org.eclipse.che.ide.ui.menubutton.MenuPopupItemDataProvider;
+import org.eclipse.che.ide.ui.menubutton.PopupItem;
+import org.eclipse.che.ide.util.Pair;
+import org.eclipse.che.ide.util.dom.Elements;
+import org.eclipse.che.ide.util.input.CharCodeWithModifiers;
+import org.eclipse.che.ide.util.input.KeyMapUtil;
+
+import java.util.List;
+
+import static org.eclipse.che.ide.ui.menu.PositionController.HorizontalAlign.MIDDLE;
+import static org.eclipse.che.ide.ui.menu.PositionController.VerticalAlign.BOTTOM;
+
+/** Button for opening Commands Palette. */
+class OpenCommandsPaletteButton extends MenuPopupButton {
+
+ private final Provider actionManagerProvider;
+ private final Provider keyBindingAgentProvider;
+
+ @Inject
+ OpenCommandsPaletteButton(Provider commandsPalettePresenterProvider,
+ PaletteMessages messages,
+ Provider actionManagerProvider,
+ Provider keyBindingAgentProvider,
+ Provider showCommandsPaletteActionProvider,
+ @Assisted SafeHtml content) {
+ super(content, new MenuPopupItemDataProvider() {
+ @Override
+ public PopupItem getDefaultItem() {
+ return null;
+ }
+
+ @Override
+ public List getItems() {
+ return null;
+ }
+
+ @Override
+ public boolean isGroup(PopupItem item) {
+ return false;
+ }
+
+ @Override
+ public Pair, String> getChildren(PopupItem parent) {
+ return null;
+ }
+
+ @Override
+ public void setItemDataChangedHandler(ItemDataChangeHandler handler) {
+ }
+ });
+
+ this.actionManagerProvider = actionManagerProvider;
+ this.keyBindingAgentProvider = keyBindingAgentProvider;
+
+ addClickHandler(event -> commandsPalettePresenterProvider.get().showDialog());
+
+ // We need to get action's shortcut but action may not be registered yet.
+ // So postpone tooltip creation and wait 1 sec. while action will be registered.
+ new Timer() {
+ @Override
+ public void run() {
+ final DivElement divElement = Elements.createDivElement();
+ divElement.setInnerText(messages.actionShowPaletteTitle());
+ divElement.appendChild(getHotKey(showCommandsPaletteActionProvider.get()));
+
+ Tooltip.create((Element)OpenCommandsPaletteButton.this.getElement(), BOTTOM, MIDDLE, divElement);
+ }
+ }.schedule(1000);
+
+ ensureDebugId("button-open_command_palette");
+ }
+
+ private SpanElement getHotKey(Action action) {
+ final SpanElement spanElement = Elements.createSpanElement();
+ spanElement.getStyle().setMarginLeft("5px");
+ spanElement.getStyle().setColor("#aaaaaa");
+
+ final String actionId = actionManagerProvider.get().getId(action);
+ final CharCodeWithModifiers keyBinding = keyBindingAgentProvider.get().getKeyBinding(actionId);
+ final String hotKey = KeyMapUtil.getShortcutText(keyBinding);
+
+ if (hotKey != null) {
+ spanElement.setInnerText("[" + hotKey + "]");
+ }
+
+ return spanElement;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/ToolbarButtonsFactory.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/ToolbarButtonsFactory.java
new file mode 100644
index 0000000000..7545f39873
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/ToolbarButtonsFactory.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+/** Factory for the buttons placed on Commands Toolbar. */
+public interface ToolbarButtonsFactory {
+
+ OpenCommandsPaletteButton createOpenPaletteButton(SafeHtml content);
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/ToolbarMessages.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/ToolbarMessages.java
new file mode 100644
index 0000000000..94ec0903d2
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/ToolbarMessages.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar;
+
+import com.google.gwt.i18n.client.Messages;
+
+/**
+ * I18n messages for the Command Toolbar.
+ *
+ * @author Artem Zatsarynnyi
+ */
+public interface ToolbarMessages extends Messages {
+
+ @Key("guide.label")
+ String guideItemLabel();
+
+ @Key("goal_button.tooltip.no_command")
+ String goalButtonTooltipNoCommand(String goalId);
+
+ @Key("goal_button.tooltip.choose_command")
+ String goalButtonTooltipChooseCommand(String goalId);
+
+ @Key("goal_button.tooltip.execute_prompt")
+ String goalButtonTooltipExecutePrompt(String commandName);
+
+ @Key("previews.tooltip")
+ String previewsTooltip();
+
+ @Key("previews.error.not_available")
+ String previewsNotAvailableError();
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/ExecuteCommandPresenter.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/ExecuteCommandPresenter.java
new file mode 100644
index 0000000000..ed90347ec3
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/ExecuteCommandPresenter.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.commands;
+
+import com.google.gwt.user.client.ui.AcceptsOneWidget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.core.model.machine.Machine;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.api.command.CommandExecutor;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.command.CommandManager;
+import org.eclipse.che.ide.api.mvp.Presenter;
+import org.eclipse.che.ide.command.CommandUtils;
+import org.eclipse.che.ide.command.toolbar.CommandCreationGuide;
+
+/** Presenter drives UI of the toolbar for executing commands. */
+@Singleton
+public class ExecuteCommandPresenter implements Presenter, ExecuteCommandView.ActionDelegate {
+
+ private final ExecuteCommandView view;
+ private final CommandManager commandManager;
+ private final CommandUtils commandUtils;
+ private final Provider commandExecutorProvider;
+ private final CommandCreationGuide commandCreationGuide;
+
+ @Inject
+ public ExecuteCommandPresenter(ExecuteCommandView view,
+ CommandManager commandManager,
+ CommandUtils commandUtils,
+ Provider commandExecutorProvider,
+ CommandCreationGuide commandCreationGuide) {
+ this.view = view;
+ this.commandManager = commandManager;
+ this.commandUtils = commandUtils;
+ this.commandExecutorProvider = commandExecutorProvider;
+ this.commandCreationGuide = commandCreationGuide;
+
+ view.setDelegate(this);
+
+ commandManager.addCommandLoadedListener(this::updateView);
+ commandManager.addCommandChangedListener(new CommandManager.CommandChangedListener() {
+ @Override
+ public void onCommandAdded(CommandImpl command) {
+ updateView();
+ }
+
+ @Override
+ public void onCommandUpdated(CommandImpl previousCommand, CommandImpl command) {
+ updateView();
+ }
+
+ @Override
+ public void onCommandRemoved(CommandImpl command) {
+ updateView();
+ }
+ });
+ }
+
+ private void updateView() {
+ view.setCommands(commandUtils.groupCommandsByGoal(commandManager.getCommands()));
+ }
+
+ @Override
+ public void go(AcceptsOneWidget container) {
+ container.setWidget(view);
+ }
+
+ @Override
+ public void onCommandExecute(CommandImpl command, @Nullable Machine machine) {
+ final CommandExecutor commandExecutor = commandExecutorProvider.get();
+
+ if (machine == null) {
+ commandExecutor.executeCommand(command);
+ } else {
+ commandExecutor.executeCommand(command, machine);
+ }
+ }
+
+ @Override
+ public void onGuide(CommandGoal goal) {
+ commandCreationGuide.guide(goal);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/ExecuteCommandView.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/ExecuteCommandView.java
new file mode 100644
index 0000000000..affda3049b
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/ExecuteCommandView.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.commands;
+
+import org.eclipse.che.api.core.model.machine.Machine;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.mvp.View;
+
+import java.util.List;
+import java.util.Map;
+
+/** View for {@link ExecuteCommandPresenter}. */
+public interface ExecuteCommandView extends View {
+
+ /** Set commands grouped by goals for displaying in the view. */
+ void setCommands(Map> commands);
+
+ interface ActionDelegate {
+
+ /** Called when command execution is requested. */
+ void onCommandExecute(CommandImpl command, @Nullable Machine machine);
+
+ /** Called when guide of commands creation is requested. */
+ void onGuide(CommandGoal goal);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/ExecuteCommandViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/ExecuteCommandViewImpl.java
new file mode 100644
index 0000000000..69de2ea104
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/ExecuteCommandViewImpl.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.commands;
+
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.command.goal.DebugGoal;
+import org.eclipse.che.ide.command.goal.RunGoal;
+import org.eclipse.che.ide.command.toolbar.commands.button.GoalButton;
+import org.eclipse.che.ide.command.toolbar.commands.button.GoalButtonDataProvider;
+import org.eclipse.che.ide.command.toolbar.commands.button.GoalButtonFactory;
+import org.eclipse.che.ide.ui.menubutton.MenuPopupButton;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Collections.emptyList;
+
+/**
+ * Implementation of {@link ExecuteCommandView} uses {@link MenuPopupButton}s
+ * for displaying commands grouped by goal.
+ * Allows to execute command by choosing one from the button's dropdown menu.
+ */
+@Singleton
+public class ExecuteCommandViewImpl implements ExecuteCommandView {
+
+ private final Map> commands;
+ /** Stores created buttons by goals in order to reuse it. */
+ private final Map buttonsCache;
+
+ private final FlowPanel buttonsPanel;
+ private final RunGoal runGoal;
+ private final DebugGoal debugGoal;
+ private final GoalButtonFactory goalButtonFactory;
+
+ private ActionDelegate delegate;
+
+ @Inject
+ public ExecuteCommandViewImpl(RunGoal runGoal, DebugGoal debugGoal, GoalButtonFactory goalButtonFactory) {
+ this.runGoal = runGoal;
+ this.debugGoal = debugGoal;
+ this.goalButtonFactory = goalButtonFactory;
+
+ commands = new HashMap<>();
+ buttonsCache = new HashMap<>();
+ buttonsPanel = new FlowPanel();
+ }
+
+ @Override
+ public void setDelegate(ActionDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Widget asWidget() {
+ return buttonsPanel;
+ }
+
+ @Override
+ public void setCommands(Map> commands) {
+ this.commands.clear();
+ this.commands.putAll(commands);
+
+ buttonsCache.clear();
+ buttonsPanel.clear();
+
+ createOrUpdateButtons();
+ }
+
+ /** Add buttons with commands to panel. */
+ private void createOrUpdateButtons() {
+ // for now, display commands of Run and Debug goals only
+ List goals = new ArrayList<>();
+ goals.add(runGoal);
+ goals.add(debugGoal);
+
+ goals.forEach(this::createOrUpdateButton);
+ }
+
+ /** Add button with commands of the given goal to panel. */
+ private void createOrUpdateButton(CommandGoal goal) {
+ final GoalButton button = buttonsCache.getOrDefault(goal, goalButtonFactory.newButton(goal, delegate));
+ buttonsCache.put(goal, button);
+
+ final List commandsOfGoal = commands.getOrDefault(goal, emptyList());
+ final GoalButtonDataProvider dataProvider = button.getPopupItemDataProvider();
+
+ dataProvider.setCommands(commandsOfGoal);
+
+ button.updateTooltip();
+
+ buttonsPanel.add(button);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/CommandPopupItem.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/CommandPopupItem.java
new file mode 100644
index 0000000000..ff80d1cd2c
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/CommandPopupItem.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.commands.button;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.ui.menubutton.PopupItem;
+
+/** A {@link PopupItem} represents {@link CommandImpl}. */
+public class CommandPopupItem implements PopupItem {
+
+ private final CommandImpl command;
+
+ @Inject
+ public CommandPopupItem(@Assisted CommandImpl command) {
+ this.command = command;
+ }
+
+ @Override
+ public String getName() {
+ return command.getName();
+ }
+
+ @Override
+ public boolean isDisabled() {
+ return command.getApplicableContext().isWorkspaceApplicable();
+ }
+
+ public CommandImpl getCommand() {
+ return command;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GoalButton.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GoalButton.java
new file mode 100644
index 0000000000..290aaba7ba
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GoalButton.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.commands.button;
+
+import elemental.dom.Element;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.command.toolbar.ToolbarMessages;
+import org.eclipse.che.ide.ui.Tooltip;
+import org.eclipse.che.ide.ui.menubutton.MenuPopupButton;
+import org.eclipse.che.ide.ui.menubutton.MenuPopupItemDataProvider;
+import org.eclipse.che.ide.ui.menubutton.PopupItem;
+
+import static org.eclipse.che.ide.ui.menu.PositionController.HorizontalAlign.MIDDLE;
+import static org.eclipse.che.ide.ui.menu.PositionController.VerticalAlign.BOTTOM;
+
+/** {@link MenuPopupButton} for displaying commands belong to the same {@link CommandGoal}. */
+public class GoalButton extends MenuPopupButton {
+
+ private final CommandGoal goal;
+ private final ToolbarMessages messages;
+
+ private Tooltip tooltip;
+ private String tooltipText;
+
+ GoalButton(CommandGoal goal,
+ SafeHtml icon,
+ MenuPopupItemDataProvider dataProvider,
+ ToolbarMessages messages) {
+ super(icon, dataProvider);
+
+ this.goal = goal;
+ this.messages = messages;
+ }
+
+ public GoalButtonDataProvider getPopupItemDataProvider() {
+ return (GoalButtonDataProvider)dataProvider;
+ }
+
+ public CommandGoal getGoal() {
+ return goal;
+ }
+
+ /** Updates button's tooltip depending on it's state (what child elements it contains). */
+ public void updateTooltip() {
+ final PopupItem defaultItem = dataProvider.getDefaultItem();
+
+ if (defaultItem != null) {
+ setTooltip(messages.goalButtonTooltipExecutePrompt(defaultItem.getName()));
+ } else if (getPopupItemDataProvider().hasGuideOnly()) {
+ setTooltip(messages.goalButtonTooltipNoCommand(goal.getId()));
+ } else {
+ setTooltip(messages.goalButtonTooltipChooseCommand(goal.getId()));
+ }
+ }
+
+ private void setTooltip(String newTooltipText) {
+ if (newTooltipText.equals(tooltipText)) {
+ return;
+ }
+
+ tooltipText = newTooltipText;
+
+ if (tooltip != null) {
+ tooltip.destroy();
+ }
+
+ tooltip = Tooltip.create((Element)getElement(), BOTTOM, MIDDLE, newTooltipText);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GoalButtonDataProvider.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GoalButtonDataProvider.java
new file mode 100644
index 0000000000..41ee18ad74
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GoalButtonDataProvider.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.commands.button;
+
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.api.machine.MachineEntity;
+import org.eclipse.che.ide.ui.menubutton.MenuPopupItemDataProvider;
+import org.eclipse.che.ide.ui.menubutton.PopupItem;
+import org.eclipse.che.ide.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+/** Provides items for {@link GoalButton}. */
+public class GoalButtonDataProvider implements MenuPopupItemDataProvider {
+
+ private final List commands;
+ private final AppContext appContext;
+ private final PopupItemFactory popupItemFactory;
+
+ private ItemDataChangeHandler handler;
+ private PopupItem defaultItem;
+
+ public GoalButtonDataProvider(AppContext appContext, PopupItemFactory popupItemFactory) {
+ this.appContext = appContext;
+ this.popupItemFactory = popupItemFactory;
+ this.commands = new ArrayList<>();
+ }
+
+ @Nullable
+ @Override
+ public PopupItem getDefaultItem() {
+ if (defaultItem != null) {
+ return defaultItem;
+ }
+
+ return null;
+ }
+
+ public void setDefaultItem(PopupItem item) {
+ defaultItem = item;
+ }
+
+ @Override
+ public List getItems() {
+ List items = new ArrayList<>(commands.size());
+
+ if (defaultItem != null && defaultItem instanceof MachinePopupItem) {
+ items.add(popupItemFactory.newMachinePopupItem((MachinePopupItem)defaultItem));
+ }
+
+ for (CommandImpl command : commands) {
+ items.add(popupItemFactory.newCommandPopupItem(command));
+ }
+
+ if (items.isEmpty()) {
+ items.add(popupItemFactory.newHintPopupItem());
+ }
+
+ return items;
+ }
+
+ @Override
+ public boolean isGroup(PopupItem item) {
+ if (item instanceof CommandPopupItem) {
+ return appContext.getWorkspace().getRuntime().getMachines().size() > 1;
+ }
+
+ return false;
+ }
+
+ @Override
+ public Pair, String> getChildren(PopupItem parent) {
+ List items = new ArrayList<>();
+
+ if (parent instanceof CommandPopupItem) {
+ CommandImpl command = ((CommandPopupItem)parent).getCommand();
+ List machines = appContext.getActiveRuntime().getMachines();
+
+ items.addAll(machines.stream()
+ .map(machine -> popupItemFactory.newMachinePopupItem(command, machine))
+ .collect(toList()));
+ }
+
+ return Pair.of(items, null);
+ }
+
+ @Override
+ public void setItemDataChangedHandler(ItemDataChangeHandler handler) {
+ this.handler = handler;
+ }
+
+ public void setCommands(List commands) {
+ this.commands.clear();
+ this.commands.addAll(commands);
+
+ handler.onItemDataChanged();
+ }
+
+ /** Checks whether the {@link GuidePopupItem} is the only item. */
+ public boolean hasGuideOnly() {
+ List items = getItems();
+ return items.isEmpty() || (items.size() == 1 && items.get(0) instanceof GuidePopupItem);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GoalButtonFactory.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GoalButtonFactory.java
new file mode 100644
index 0000000000..54896b9277
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GoalButtonFactory.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.commands.button;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.ide.FontAwesome;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.command.CommandGoal;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.command.CommandResources;
+import org.eclipse.che.ide.command.goal.DebugGoal;
+import org.eclipse.che.ide.command.goal.RunGoal;
+import org.eclipse.che.ide.command.toolbar.ToolbarMessages;
+import org.eclipse.che.ide.command.toolbar.commands.ExecuteCommandView.ActionDelegate;
+
+/**
+ * Factory for {@link GoalButton}s.
+ *
+ * @see GoalButton
+ */
+@Singleton
+public class GoalButtonFactory {
+
+ private final CommandResources resources;
+ private final AppContext appContext;
+ private final PopupItemFactory popupItemFactory;
+ private final ToolbarMessages messages;
+ private final RunGoal runGoal;
+ private final DebugGoal debugGoal;
+
+ @Inject
+ public GoalButtonFactory(CommandResources resources,
+ AppContext appContext,
+ PopupItemFactory popupItemFactory,
+ ToolbarMessages messages,
+ RunGoal runGoal,
+ DebugGoal debugGoal) {
+ this.resources = resources;
+ this.appContext = appContext;
+ this.popupItemFactory = popupItemFactory;
+ this.messages = messages;
+ this.runGoal = runGoal;
+ this.debugGoal = debugGoal;
+ }
+
+ /**
+ * Creates new instance of the {@link GoalButton}.
+ *
+ * @param goal
+ * {@link CommandGoal} for displaying commands
+ * @param delegate
+ * delegate for receiving events
+ * @return {@link GoalButton}
+ */
+ public GoalButton newButton(CommandGoal goal, ActionDelegate delegate) {
+ final GoalButtonDataProvider dataProvider = new GoalButtonDataProvider(appContext, popupItemFactory);
+ final GoalButton button = new GoalButton(goal, getIconForGoal(goal), dataProvider, messages);
+
+ button.setActionHandler(item -> {
+ if (item instanceof CommandPopupItem) {
+ final CommandImpl command = ((CommandPopupItem)item).getCommand();
+
+ delegate.onCommandExecute(command, null);
+ dataProvider.setDefaultItem(item);
+ button.updateTooltip();
+ } else if (item instanceof MachinePopupItem) {
+ final MachinePopupItem machinePopupItem = (MachinePopupItem)item;
+
+ delegate.onCommandExecute(machinePopupItem.getCommand(), machinePopupItem.getMachine());
+ dataProvider.setDefaultItem(item);
+ button.updateTooltip();
+ } else if (item instanceof GuidePopupItem) {
+ delegate.onGuide(goal);
+ }
+ });
+
+ button.addStyleName(resources.commandToolbarCss().toolbarButton());
+
+ button.ensureDebugId("command_toolbar-button_" + goal.getId());
+
+ return button;
+ }
+
+ /** Returns {@link FontAwesome} icon for the given goal. */
+ private SafeHtml getIconForGoal(CommandGoal goal) {
+ final SafeHtmlBuilder safeHtmlBuilder = new SafeHtmlBuilder();
+
+ if (goal.equals(runGoal)) {
+ safeHtmlBuilder.appendHtmlConstant(FontAwesome.PLAY);
+ } else if (goal.equals(debugGoal)) {
+ safeHtmlBuilder.appendHtmlConstant(FontAwesome.BUG);
+ }
+
+ return safeHtmlBuilder.toSafeHtml();
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GuidePopupItem.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GuidePopupItem.java
new file mode 100644
index 0000000000..7e25adb3d7
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/GuidePopupItem.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.commands.button;
+
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.command.toolbar.ToolbarMessages;
+import org.eclipse.che.ide.ui.menubutton.PopupItem;
+
+/** A {@link PopupItem} represents a hint which guides the user into the flow of creating a command. */
+public class GuidePopupItem implements PopupItem {
+
+ private final ToolbarMessages messages;
+
+ @Inject
+ public GuidePopupItem(ToolbarMessages messages) {
+ this.messages = messages;
+ }
+
+ @Override
+ public String getName() {
+ return messages.guideItemLabel();
+ }
+
+ @Override
+ public boolean isDisabled() {
+ return false;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/MachinePopupItem.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/MachinePopupItem.java
new file mode 100644
index 0000000000..b42b6b9c10
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/MachinePopupItem.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.commands.button;
+
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import org.eclipse.che.api.core.model.machine.Machine;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.ui.menubutton.PopupItem;
+
+/** Item contains {@link CommandImpl} and {@link Machine}. */
+public class MachinePopupItem implements PopupItem {
+
+ private final CommandImpl command;
+ private final Machine machine;
+ private final String name;
+
+ @AssistedInject
+ public MachinePopupItem(@Assisted CommandImpl command, @Assisted Machine machine) {
+ this.command = command;
+ this.machine = machine;
+ this.name = machine.getConfig().getName();
+ }
+
+ @AssistedInject
+ public MachinePopupItem(@Assisted MachinePopupItem item) {
+ this.command = item.command;
+ this.machine = item.machine;
+ this.name = command.getName() + " on " + machine.getConfig().getName();
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean isDisabled() {
+ return false;
+ }
+
+ public CommandImpl getCommand() {
+ return command;
+ }
+
+ public Machine getMachine() {
+ return machine;
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/PopupItemFactory.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/PopupItemFactory.java
new file mode 100644
index 0000000000..acb8a45044
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/commands/button/PopupItemFactory.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.commands.button;
+
+import org.eclipse.che.api.core.model.machine.Machine;
+import org.eclipse.che.ide.api.command.CommandImpl;
+import org.eclipse.che.ide.ui.menubutton.PopupItem;
+
+/** Factory for {@link PopupItem}s for {@link GoalButton}. */
+public interface PopupItemFactory {
+
+ GuidePopupItem newHintPopupItem();
+
+ CommandPopupItem newCommandPopupItem(CommandImpl command);
+
+ MachinePopupItem newMachinePopupItem(CommandImpl command, Machine machine);
+
+ MachinePopupItem newMachinePopupItem(MachinePopupItem item);
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/previews/PreviewUrl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/previews/PreviewUrl.java
new file mode 100644
index 0000000000..4826a1be97
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/previews/PreviewUrl.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.previews;
+
+import java.util.Objects;
+
+/** Holds preview URL and it's name displaying in a 'Previews' list. */
+class PreviewUrl {
+
+ private final String url;
+ private final String displayName;
+
+ PreviewUrl(String url, String displayName) {
+ this.url = url;
+ this.displayName = displayName;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PreviewUrl that = (PreviewUrl)o;
+ return Objects.equals(url, that.url) &&
+ Objects.equals(displayName, that.displayName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(url, displayName);
+ }
+}
diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/previews/PreviewUrlItemRenderer.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/previews/PreviewUrlItemRenderer.java
new file mode 100644
index 0000000000..d17eb41b7c
--- /dev/null
+++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/command/toolbar/previews/PreviewUrlItemRenderer.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.ide.command.toolbar.previews;
+
+import elemental.dom.Element;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+
+import org.eclipse.che.ide.FontAwesome;
+import org.eclipse.che.ide.command.CommandResources;
+import org.eclipse.che.ide.command.toolbar.ToolbarMessages;
+import org.eclipse.che.ide.ui.Tooltip;
+import org.eclipse.che.ide.ui.dropdown.BaseListItem;
+import org.eclipse.che.ide.ui.dropdown.DropdownListItemRenderer;
+
+import static com.google.gwt.dom.client.Style.Unit.PX;
+import static org.eclipse.che.ide.ui.menu.PositionController.HorizontalAlign.MIDDLE;
+import static org.eclipse.che.ide.ui.menu.PositionController.VerticalAlign.BOTTOM;
+
+/** Renders widgets for the 'Previews' list. Always returns the same instance of header widget. */
+class PreviewUrlItemRenderer implements DropdownListItemRenderer {
+
+ static final HeaderWidget HEADER_WIDGET = new HeaderWidget();
+
+ private final BaseListItem item;
+
+ private Widget listWidget;
+
+ PreviewUrlItemRenderer(BaseListItem