From 46d078fb820bcc6c56b61401cc602c6eb4570b9b Mon Sep 17 00:00:00 2001
From: Philip Langer <planger@users.noreply.github.com>
Date: Fri, 10 Jan 2020 13:41:13 +0100
Subject: [PATCH] Let server handle the dirty state (#32)

* Enable handlers returning multiple actions to a client action

This is in preparation for #6.

* Let server handle the dirty state

Resolves #6
---
 .../org/eclipse/glsp/api/action/Action.java   |  1 +
 .../glsp/api/action/ActionProcessor.java      | 18 +++---
 .../api/action/kind/SetDirtyStateAction.java  | 63 +++++++++++++++++++
 .../glsp/api/handler/ActionHandler.java       | 20 +++++-
 .../api/handler/ServerCommandHandler.java     |  4 +-
 .../eclipse/glsp/api/model/ModelState.java    |  4 ++
 .../actionhandler/AbstractActionHandler.java  |  7 ++-
 .../CollapseExpandActionHandler.java          | 15 +++--
 .../ComputedBoundsActionHandler.java          |  6 +-
 .../actionhandler/DIActionProcessor.java      |  6 +-
 .../ExecuteServerCommandActionHandler.java    |  5 +-
 .../actionhandler/LayoutActionHandler.java    |  8 +--
 .../actionhandler/ModelSubmissionHandler.java | 13 ++--
 .../actionhandler/OpenActionHandler.java      |  6 +-
 .../actionhandler/OperationActionHandler.java | 13 ++--
 .../RequestContextActionsHandler.java         |  7 +--
 .../actionhandler/RequestMarkersHandler.java  |  4 +-
 .../RequestModelActionHandler.java            |  9 +--
 .../RequestOperationsActionHandler.java       | 14 ++---
 .../RequestPopupModelActionHandler.java       |  9 +--
 .../RequestTypeHintsActionHandler.java        | 14 ++---
 .../actionhandler/SaveModelActionHandler.java | 12 ++--
 .../actionhandler/SelectActionHandler.java    | 14 ++---
 .../actionhandler/UndoRedoActionHandler.java  | 12 ++--
 .../ValidateLabelEditActionHandler.java       |  7 ++-
 .../glsp/server/model/ModelStateImpl.java     | 13 +++-
 26 files changed, 202 insertions(+), 102 deletions(-)
 create mode 100644 plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/kind/SetDirtyStateAction.java

diff --git a/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/Action.java b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/Action.java
index 50b3b17b..b1407672 100644
--- a/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/Action.java
+++ b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/Action.java
@@ -109,6 +109,7 @@ public static class Kind {
       public static final String EXECUTE_SERVER_COMMAND = "executeServerCommand";
       public static final String REQUEST_CONTEXT_ACTIONS = "requestContextActions";
       public static final String SET_CONTEXT_ACTIONS = "setContextActions";
+      public static final String SET_DIRTY_STATE = "setDirtyState";
       public static final String REQUEST_MARKERS = "requestMarkers";
       public static final String SET_MARKERS = "setMarkers";
       public static final String LAYOUT = "layout";
diff --git a/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/ActionProcessor.java b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/ActionProcessor.java
index b085edeb..b467067d 100644
--- a/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/ActionProcessor.java
+++ b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/ActionProcessor.java
@@ -15,9 +15,10 @@
  ********************************************************************************/
 package org.eclipse.glsp.api.action;
 
-import java.util.Optional;
+import static org.eclipse.glsp.api.action.kind.ResponseAction.respond;
 
-import org.eclipse.glsp.api.action.kind.ResponseAction;
+import java.util.Collections;
+import java.util.List;
 
 public interface ActionProcessor {
 
@@ -31,11 +32,8 @@ public interface ActionProcessor {
     * @param action   The action to process
     */
    default void process(final String clientId, final Action action) {
-      Optional<Action> responseOpt = dispatch(clientId, action);
-      if (responseOpt.isPresent()) {
-         // ensure request and response have same id if necessary
-         Action response = ResponseAction.respond(action, responseOpt.get());
-         send(clientId, response);
+      for (Action responseAction : dispatch(clientId, action)) {
+         send(clientId, respond(action, responseAction));
       }
    }
 
@@ -59,7 +57,7 @@ default void process(final ActionMessage message) {
     * @return An optional Action to be sent to the client as the result of handling
     *         the received <code>action</code>
     */
-   Optional<Action> dispatch(String clientId, Action action);
+   List<Action> dispatch(String clientId, Action action);
 
    /**
     * Send the given action to the specified clientId.
@@ -72,8 +70,8 @@ default void process(final ActionMessage message) {
    class NullImpl implements ActionProcessor {
 
       @Override
-      public Optional<Action> dispatch(final String clientId, final Action action) {
-         return Optional.empty();
+      public List<Action> dispatch(final String clientId, final Action action) {
+         return Collections.emptyList();
       }
 
       @Override
diff --git a/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/kind/SetDirtyStateAction.java b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/kind/SetDirtyStateAction.java
new file mode 100644
index 00000000..a11aeddd
--- /dev/null
+++ b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/action/kind/SetDirtyStateAction.java
@@ -0,0 +1,63 @@
+/********************************************************************************
+ * Copyright (c) 2020 EclipseSource and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+package org.eclipse.glsp.api.action.kind;
+
+import org.eclipse.glsp.api.action.Action;
+
+public class SetDirtyStateAction extends Action {
+
+   private boolean isDirty;
+
+   public SetDirtyStateAction() {
+      super(Action.Kind.SET_DIRTY_STATE);
+   }
+
+   public SetDirtyStateAction(final boolean isDirty) {
+      this();
+      this.isDirty = isDirty;
+   }
+
+   public boolean isDirty() { return isDirty; }
+
+   public void setDirty(final boolean isDirty) { this.isDirty = isDirty; }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = super.hashCode();
+      result = prime * result + (isDirty ? 1231 : 1237);
+      return result;
+   }
+
+   @Override
+   public boolean equals(final Object obj) {
+      if (this == obj) {
+         return true;
+      }
+      if (!super.equals(obj)) {
+         return false;
+      }
+      if (getClass() != obj.getClass()) {
+         return false;
+      }
+      SetDirtyStateAction other = (SetDirtyStateAction) obj;
+      if (isDirty != other.isDirty) {
+         return false;
+      }
+      return true;
+   }
+
+}
diff --git a/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/handler/ActionHandler.java b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/handler/ActionHandler.java
index d48f42d7..86e861d6 100644
--- a/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/handler/ActionHandler.java
+++ b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/handler/ActionHandler.java
@@ -15,12 +15,30 @@
  ********************************************************************************/
 package org.eclipse.glsp.api.handler;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Optional;
 
 import org.eclipse.glsp.api.action.Action;
 
 public interface ActionHandler extends Handler<Action> {
 
-   Optional<Action> execute(String clientId, Action action);
+   List<Action> execute(String clientId, Action action);
+
+   default List<Action> listOf(final Action... action) {
+      return Arrays.asList(action);
+   }
+
+   default List<Action> listOf(final Optional<Action> optionalAction) {
+      List<Action> actions = new ArrayList<>();
+      optionalAction.ifPresent(action -> actions.add(action));
+      return actions;
+   }
+
+   default List<Action> none() {
+      return Collections.emptyList();
+   }
 
 }
diff --git a/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/handler/ServerCommandHandler.java b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/handler/ServerCommandHandler.java
index 735ee1ab..14be6994 100644
--- a/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/handler/ServerCommandHandler.java
+++ b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/handler/ServerCommandHandler.java
@@ -16,8 +16,8 @@
 package org.eclipse.glsp.api.handler;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 
 import org.eclipse.glsp.api.action.Action;
 import org.eclipse.glsp.api.model.GraphicalModelState;
@@ -28,5 +28,5 @@ default void execute(final String commandId, final GraphicalModelState modelStat
       execute(commandId, Collections.emptyMap(), modelState);
    }
 
-   Optional<Action> execute(String commandId, Map<String, String> options, GraphicalModelState modelState);
+   List<Action> execute(String commandId, Map<String, String> options, GraphicalModelState modelState);
 }
diff --git a/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/model/ModelState.java b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/model/ModelState.java
index 37ea17e6..b75eeee5 100644
--- a/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/model/ModelState.java
+++ b/plugins/org.eclipse.glsp.api/src/org/eclipse/glsp/api/model/ModelState.java
@@ -37,4 +37,8 @@ public interface ModelState<T> {
 
    void redo();
 
+   boolean isDirty();
+
+   void saveIsDone();
+
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/AbstractActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/AbstractActionHandler.java
index b2b4b13a..26356e31 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/AbstractActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/AbstractActionHandler.java
@@ -15,6 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
+import java.util.List;
 import java.util.Optional;
 
 import org.eclipse.glsp.api.action.Action;
@@ -37,7 +38,7 @@ public abstract class AbstractActionHandler implements ActionHandler {
     * the client. If no response to the client is need a NoOpAction is returned
     */
    @Override
-   public Optional<Action> execute(final String clientId, final Action action) {
+   public List<Action> execute(final String clientId, final Action action) {
       this.clientId = clientId;
       Optional<GraphicalModelState> modelState = modelStateProvider.getModelState(clientId);
       if (modelState.isPresent()) {
@@ -45,8 +46,8 @@ public Optional<Action> execute(final String clientId, final Action action) {
       }
       ServerStatus status = new ServerStatus(Severity.FATAL,
          "Could not retrieve the model state for client with id '" + clientId + "'");
-      return Optional.of(new ServerStatusAction(status));
+      return listOf(new ServerStatusAction(status));
    }
 
-   protected abstract Optional<Action> execute(Action action, GraphicalModelState modelState);
+   protected abstract List<Action> execute(Action action, GraphicalModelState modelState);
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/CollapseExpandActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/CollapseExpandActionHandler.java
index ed43a6a2..a912bfec 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/CollapseExpandActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/CollapseExpandActionHandler.java
@@ -15,7 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
-import java.util.Optional;
+import java.util.List;
 import java.util.Set;
 
 import org.eclipse.glsp.api.action.Action;
@@ -36,18 +36,18 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       switch (action.getKind()) {
          case Action.Kind.COLLAPSE_EXPAND:
             return handleCollapseExpandAction((CollapseExpandAction) action, modelState);
          case Action.Kind.COLLAPSE_EXPAND_ALL:
             return handleCollapseExpandAllAction((CollapseExpandAllAction) action, modelState);
          default:
-            return Optional.empty();
+            return none();
       }
    }
 
-   private Optional<Action> handleCollapseExpandAllAction(final CollapseExpandAllAction action,
+   private List<Action> handleCollapseExpandAllAction(final CollapseExpandAllAction action,
       final GraphicalModelState modelState) {
       Set<String> expandedElements = modelState.getExpandedElements();
       expandedElements.clear();
@@ -57,10 +57,10 @@ private Optional<Action> handleCollapseExpandAllAction(final CollapseExpandAllAc
       if (expansionListener != null) {
          expansionListener.expansionChanged(action);
       }
-      return Optional.empty();
+      return none();
    }
 
-   private Optional<Action> handleCollapseExpandAction(final CollapseExpandAction action,
+   private List<Action> handleCollapseExpandAction(final CollapseExpandAction action,
       final GraphicalModelState modelState) {
       Set<String> expandedElements = modelState.getExpandedElements();
       if (action.getCollapseIds() != null) {
@@ -74,8 +74,7 @@ private Optional<Action> handleCollapseExpandAction(final CollapseExpandAction a
          expansionListener.expansionChanged(action);
       }
 
-      return Optional.empty();
-
+      return none();
    }
 
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ComputedBoundsActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ComputedBoundsActionHandler.java
index 91bcf4f5..56ab4b2d 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ComputedBoundsActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ComputedBoundsActionHandler.java
@@ -15,7 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
-import java.util.Optional;
+import java.util.List;
 
 import org.eclipse.glsp.api.action.Action;
 import org.eclipse.glsp.api.action.kind.ComputedBoundsAction;
@@ -35,7 +35,7 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (action instanceof ComputedBoundsAction) {
          ComputedBoundsAction computedBoundsAction = (ComputedBoundsAction) action;
 
@@ -47,7 +47,7 @@ public Optional<Action> execute(final Action action, final GraphicalModelState m
             }
          }
       }
-      return Optional.empty();
+      return none();
    }
 
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/DIActionProcessor.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/DIActionProcessor.java
index 27a98e7c..6ca92fd6 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/DIActionProcessor.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/DIActionProcessor.java
@@ -15,6 +15,8 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Optional;
 
 import org.eclipse.glsp.api.action.Action;
@@ -36,12 +38,12 @@ public class DIActionProcessor implements ActionProcessor {
    protected ActionHandlerProvider handlerProvider;
 
    @Override
-   public Optional<Action> dispatch(final String clientId, final Action action) {
+   public List<Action> dispatch(final String clientId, final Action action) {
       Optional<ActionHandler> handler = handlerProvider.getHandler(action);
       if (handler.isPresent()) {
          return handler.get().execute(clientId, action);
       }
-      return Optional.empty();
+      return Collections.emptyList();
    }
 
    @Override
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ExecuteServerCommandActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ExecuteServerCommandActionHandler.java
index f1afaed0..e0a7060b 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ExecuteServerCommandActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ExecuteServerCommandActionHandler.java
@@ -15,6 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
+import java.util.List;
 import java.util.Optional;
 
 import org.eclipse.glsp.api.action.Action;
@@ -35,7 +36,7 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (action instanceof ExecuteServerCommandAction) {
          ExecuteServerCommandAction commandAction = (ExecuteServerCommandAction) action;
          Optional<ServerCommandHandler> handler = commandHandlerProvider.getHandler(commandAction.getCommandId());
@@ -43,7 +44,7 @@ public Optional<Action> execute(final Action action, final GraphicalModelState m
             return handler.get().execute(commandAction.getCommandId(), commandAction.getOptions(), modelState);
          }
       }
-      return Optional.empty();
+      return none();
    }
 
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/LayoutActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/LayoutActionHandler.java
index 61639165..0bbe9a75 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/LayoutActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/LayoutActionHandler.java
@@ -15,7 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
-import java.util.Optional;
+import java.util.List;
 
 import org.eclipse.glsp.api.action.Action;
 import org.eclipse.glsp.api.action.kind.LayoutAction;
@@ -41,13 +41,13 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   protected Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   protected List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (serverConfiguration.getLayoutKind() == ServerLayoutKind.MANUAL) {
          if (layoutEngine != null) {
             layoutEngine.layout(modelState);
          }
-         return Optional.of(new RequestBoundsAction(modelState.getRoot()));
+         return listOf(new RequestBoundsAction(modelState.getRoot()));
       }
-      return Optional.empty();
+      return none();
    }
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ModelSubmissionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ModelSubmissionHandler.java
index 90459998..f4627f2f 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ModelSubmissionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ModelSubmissionHandler.java
@@ -15,7 +15,9 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
-import java.util.Optional;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 
 import org.eclipse.glsp.api.action.Action;
 import org.eclipse.glsp.api.action.kind.SetModelAction;
@@ -38,7 +40,7 @@ public class ModelSubmissionHandler {
    private final Object modelLock = new Object();
    private final int revision = 0;
 
-   public Optional<Action> doSubmitModel(final boolean update, final GraphicalModelState modelState) {
+   public List<Action> doSubmitModel(final boolean update, final GraphicalModelState modelState) {
       GModelRoot newRoot = modelState.getRoot();
       if (serverConfiguration.getLayoutKind() == ServerLayoutKind.AUTOMATIC) {
          layoutEngine.layout(modelState);
@@ -46,13 +48,12 @@ public Optional<Action> doSubmitModel(final boolean update, final GraphicalModel
       synchronized (modelLock) {
          if (newRoot.getRevision() == revision) {
             if (update) {
-               return Optional.of(new UpdateModelAction(newRoot, true));
-            } else {
-               return Optional.of(new SetModelAction(newRoot));
+               return Arrays.asList(new UpdateModelAction(newRoot, true));
             }
+            return Arrays.asList(new SetModelAction(newRoot));
          }
       }
-      return Optional.empty();
+      return Collections.emptyList();
    }
 
    public synchronized Object getModelLock() { return modelLock; }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/OpenActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/OpenActionHandler.java
index 120b64d8..90077a20 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/OpenActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/OpenActionHandler.java
@@ -15,7 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
-import java.util.Optional;
+import java.util.List;
 
 import org.eclipse.glsp.api.action.Action;
 import org.eclipse.glsp.api.action.kind.OpenAction;
@@ -34,13 +34,13 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (action instanceof OpenAction) {
          if (modelElementOpenListener != null) {
             modelElementOpenListener.elementOpened((OpenAction) action);
          }
       }
-      return Optional.empty();
+      return none();
    }
 
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/OperationActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/OperationActionHandler.java
index d6c4055c..37123642 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/OperationActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/OperationActionHandler.java
@@ -15,11 +15,12 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
-import java.util.Optional;
+import java.util.List;
 
 import org.eclipse.glsp.api.action.Action;
 import org.eclipse.glsp.api.action.kind.AbstractOperationAction;
 import org.eclipse.glsp.api.action.kind.RequestBoundsAction;
+import org.eclipse.glsp.api.action.kind.SetDirtyStateAction;
 import org.eclipse.glsp.api.handler.OperationHandler;
 import org.eclipse.glsp.api.model.GraphicalModelState;
 import org.eclipse.glsp.api.provider.OperationHandlerProvider;
@@ -37,24 +38,24 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (action instanceof AbstractOperationAction
          && operationHandlerProvider.isHandled((AbstractOperationAction) action)) {
          return doHandle((AbstractOperationAction) action, modelState);
       }
-      return Optional.empty();
+      return none();
    }
 
-   public Optional<Action> doHandle(final AbstractOperationAction action, final GraphicalModelState modelState) {
+   public List<Action> doHandle(final AbstractOperationAction action, final GraphicalModelState modelState) {
       if (operationHandlerProvider.isHandled(action)) {
          OperationHandler handler = operationHandlerProvider.getHandler(action).get();
          String label = handler.getLabel(action);
          GModelRecordingCommand command = new GModelRecordingCommand(modelState.getRoot(), label,
             () -> handler.execute(action, modelState));
          modelState.execute(command);
-         return Optional.of(new RequestBoundsAction(modelState.getRoot()));
+         return listOf(new RequestBoundsAction(modelState.getRoot()), new SetDirtyStateAction(modelState.isDirty()));
       }
-      return Optional.empty();
+      return none();
    }
 
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestContextActionsHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestContextActionsHandler.java
index f88cb717..f732a77f 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestContextActionsHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestContextActionsHandler.java
@@ -18,7 +18,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 
 import org.eclipse.glsp.api.action.Action;
 import org.eclipse.glsp.api.action.kind.RequestContextActions;
@@ -46,7 +45,7 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (action instanceof RequestContextActions) {
          RequestContextActions requestContextAction = (RequestContextActions) action;
          List<String> selectedElementIds = requestContextAction.getSelectedElementIds();
@@ -60,9 +59,9 @@ public Optional<Action> execute(final Action action, final GraphicalModelState m
                requestContextAction.getLastMousePosition(), args));
 
          }
-         return Optional.of(new SetContextActions(items, requestContextAction.getArgs()));
+         return listOf(new SetContextActions(items, requestContextAction.getArgs()));
       }
-      return Optional.empty();
+      return none();
    }
 
    protected boolean equalsUiControl(final Map<String, String> args, final String uiControlKey) {
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestMarkersHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestMarkersHandler.java
index 3e0f742c..7fdc578b 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestMarkersHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestMarkersHandler.java
@@ -37,7 +37,7 @@ public class RequestMarkersHandler extends AbstractActionHandler {
    protected ModelValidator validator;
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       RequestMarkersAction execAction = (RequestMarkersAction) action;
       List<String> elementsIDs = execAction.getElementsIDs();
       if (elementsIDs == null || elementsIDs.size() == 0) { // if no element ID is provided, compute the markers for
@@ -55,7 +55,7 @@ public Optional<Action> execute(final Action action, final GraphicalModelState m
       }
 
       SetMarkersAction setMarkersAction = new SetMarkersAction(markers);
-      return Optional.of(setMarkersAction);
+      return listOf(setMarkersAction);
    }
 
    @Override
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestModelActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestModelActionHandler.java
index 7d9423de..96eb7fbc 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestModelActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestModelActionHandler.java
@@ -15,6 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
+import java.util.List;
 import java.util.Optional;
 
 import org.eclipse.glsp.api.action.Action;
@@ -34,7 +35,7 @@ public class RequestModelActionHandler extends AbstractActionHandler {
    protected ModelFactory modelFactory;
 
    @Override
-   public Optional<Action> execute(final String clientId, final Action action) {
+   public List<Action> execute(final String clientId, final Action action) {
       this.clientId = clientId;
       Optional<GraphicalModelState> modelState = modelStateProvider.getModelState(clientId);
       if (modelState.isPresent()) {
@@ -44,7 +45,7 @@ public Optional<Action> execute(final String clientId, final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (action instanceof RequestModelAction) {
          RequestModelAction requestAction = (RequestModelAction) action;
          GModelRoot model = modelFactory.loadModel(requestAction, modelState);
@@ -56,9 +57,9 @@ public Optional<Action> execute(final Action action, final GraphicalModelState m
 
          Action responseAction = needsClientLayout ? new RequestBoundsAction(modelState.getRoot())
             : new SetModelAction(modelState.getRoot());
-         return Optional.of(responseAction);
+         return listOf(responseAction);
       }
-      return Optional.empty();
+      return none();
    }
 
    @Override
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestOperationsActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestOperationsActionHandler.java
index 4475d680..fa17ca4b 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestOperationsActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestOperationsActionHandler.java
@@ -15,6 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
+import java.util.List;
 import java.util.Optional;
 
 import org.apache.log4j.Logger;
@@ -39,31 +40,30 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (action instanceof RequestOperationsAction) {
          RequestOperationsAction requestAction = (RequestOperationsAction) action;
          Optional<String> diagramType = getDiagramType(requestAction, modelState);
          if (!diagramType.isPresent()) {
             LOG.info("RequestOperationsAction failed: No diagram type is present");
-            return Optional.empty();
+            return none();
          }
          Optional<DiagramConfiguration> configuration = diagramConfigurationProvider.get(diagramType.get());
          if (!configuration.isPresent()) {
             LOG.info("RequestOperationsAction failed: No diagram confiuration found for : " + diagramType.get());
-            return Optional.empty();
+            return none();
          }
-         return Optional.of(new SetOperationsAction(configuration.get().getOperations()));
+         return listOf(new SetOperationsAction(configuration.get().getOperations()));
       }
 
-      return Optional.empty();
+      return none();
    }
 
    private Optional<String> getDiagramType(final RequestOperationsAction action, final GraphicalModelState modelState) {
       if (action.getDiagramType() != null && !action.getDiagramType().isEmpty()) {
          return Optional.of(action.getDiagramType());
-      } else {
-         return ClientOptions.getValue(modelState.getClientOptions(), ClientOptions.DIAGRAM_TYPE);
       }
+      return ClientOptions.getValue(modelState.getClientOptions(), ClientOptions.DIAGRAM_TYPE);
    }
 
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestPopupModelActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestPopupModelActionHandler.java
index 2c1e742f..fbfacb07 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestPopupModelActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestPopupModelActionHandler.java
@@ -15,6 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
+import java.util.List;
 import java.util.Optional;
 
 import org.eclipse.glsp.api.action.Action;
@@ -36,16 +37,16 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (action instanceof RequestPopupModelAction && popupModelFactory != null) {
          RequestPopupModelAction requestAction = (RequestPopupModelAction) action;
          Optional<GModelElement> element = modelState.getIndex().get(requestAction.getElementId());
          if (popupModelFactory != null && element.isPresent()) {
-            return popupModelFactory.createPopupModel(element.get(), requestAction, modelState)
-               .map(popupModel -> new SetPopupModelAction(popupModel, requestAction.getBounds()));
+            return listOf(popupModelFactory.createPopupModel(element.get(), requestAction, modelState)
+               .map(popupModel -> new SetPopupModelAction(popupModel, requestAction.getBounds())));
          }
       }
-      return Optional.empty();
+      return none();
    }
 
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestTypeHintsActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestTypeHintsActionHandler.java
index 48525697..8fc74f9c 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestTypeHintsActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/RequestTypeHintsActionHandler.java
@@ -15,6 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
+import java.util.List;
 import java.util.Optional;
 
 import org.apache.log4j.Logger;
@@ -39,31 +40,30 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (action instanceof RequestTypeHintsAction) {
          Optional<String> diagramType = getDiagramType((RequestTypeHintsAction) action, modelState);
          if (!diagramType.isPresent()) {
             log.info("RequestTypeHintsAction failed: No diagram type is present");
-            return Optional.empty();
+            return none();
          }
          Optional<DiagramConfiguration> configuration = diagramConfigurationProvider.get(diagramType.get());
          if (!configuration.isPresent()) {
             log.info("RequestTypeHintsAction failed: No diagram confiuration found for : " + diagramType.get());
-            return Optional.empty();
+            return none();
          }
 
-         return Optional.of(new SetTypeHintsAction(configuration.get().getNodeTypeHints(),
+         return listOf(new SetTypeHintsAction(configuration.get().getNodeTypeHints(),
             configuration.get().getEdgeTypeHints()));
 
       }
-      return Optional.empty();
+      return none();
    }
 
    private Optional<String> getDiagramType(final RequestTypeHintsAction action, final GraphicalModelState modelState) {
       if (action.getDiagramType() == null && !action.getDiagramType().isEmpty()) {
          return Optional.of(action.getDiagramType());
-      } else {
-         return ClientOptions.getValue(modelState.getClientOptions(), ClientOptions.DIAGRAM_TYPE);
       }
+      return ClientOptions.getValue(modelState.getClientOptions(), ClientOptions.DIAGRAM_TYPE);
    }
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/SaveModelActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/SaveModelActionHandler.java
index 0320be29..eb6d1fdd 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/SaveModelActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/SaveModelActionHandler.java
@@ -21,11 +21,13 @@
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.nio.charset.StandardCharsets;
+import java.util.List;
 import java.util.Optional;
 
 import org.apache.log4j.Logger;
 import org.eclipse.glsp.api.action.Action;
 import org.eclipse.glsp.api.action.kind.SaveModelAction;
+import org.eclipse.glsp.api.action.kind.SetDirtyStateAction;
 import org.eclipse.glsp.api.factory.GraphGsonConfiguratorFactory;
 import org.eclipse.glsp.api.model.GraphicalModelState;
 import org.eclipse.glsp.api.utils.ClientOptions;
@@ -46,14 +48,11 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (action instanceof SaveModelAction) {
-         SaveModelAction saveAction = (SaveModelAction) action;
-         if (saveAction != null) {
-            saveModelState(modelState);
-         }
+         saveModelState(modelState);
       }
-      return Optional.empty();
+      return listOf(new SetDirtyStateAction(modelState.isDirty()));
    }
 
    private void saveModelState(final GraphicalModelState modelState) {
@@ -61,6 +60,7 @@ private void saveModelState(final GraphicalModelState modelState) {
          try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
             Gson gson = gsonConfigurationFactory.configureGson().setPrettyPrinting().create();
             gson.toJson(modelState.getRoot(), GGraph.class, writer);
+            modelState.saveIsDone();
          } catch (IOException e) {
             LOG.error(e);
          }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/SelectActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/SelectActionHandler.java
index 4361a091..87c4e7e8 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/SelectActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/SelectActionHandler.java
@@ -15,7 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
-import java.util.Optional;
+import java.util.List;
 import java.util.Set;
 
 import org.eclipse.glsp.api.action.Action;
@@ -36,19 +36,19 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       switch (action.getKind()) {
          case Action.Kind.SELECT:
             return handleSelectAction((SelectAction) action, modelState);
          case Action.Kind.SELECT_ALL:
             return handleSelectAllAction((SelectAllAction) action, modelState);
          default:
-            return Optional.empty();
+            return none();
       }
 
    }
 
-   private Optional<Action> handleSelectAllAction(final SelectAllAction action, final GraphicalModelState modelState) {
+   private List<Action> handleSelectAllAction(final SelectAllAction action, final GraphicalModelState modelState) {
       Set<String> selectedElements = modelState.getSelectedElements();
       if (action.isSelect()) {
          modelState.getIndex().allIds().forEach(id -> selectedElements.add(id));
@@ -58,10 +58,10 @@ private Optional<Action> handleSelectAllAction(final SelectAllAction action, fin
       if (modelSelectionListener != null) {
          modelSelectionListener.selectionChanged(action);
       }
-      return Optional.empty();
+      return none();
    }
 
-   private Optional<Action> handleSelectAction(final SelectAction action, final GraphicalModelState modelState) {
+   private List<Action> handleSelectAction(final SelectAction action, final GraphicalModelState modelState) {
       Set<String> selectedElements = modelState.getSelectedElements();
       if (action.getDeselectedElementsIDs() != null) {
          selectedElements.removeAll(action.getDeselectedElementsIDs());
@@ -72,7 +72,7 @@ private Optional<Action> handleSelectAction(final SelectAction action, final Gra
       if (modelSelectionListener != null) {
          modelSelectionListener.selectionChanged(action);
       }
-      return Optional.empty();
+      return none();
    }
 
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/UndoRedoActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/UndoRedoActionHandler.java
index 81863010..9574a3b2 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/UndoRedoActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/UndoRedoActionHandler.java
@@ -15,12 +15,13 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
-import java.util.Optional;
+import java.util.List;
 
 import org.apache.log4j.Logger;
 import org.eclipse.glsp.api.action.Action;
 import org.eclipse.glsp.api.action.kind.RedoAction;
 import org.eclipse.glsp.api.action.kind.RequestBoundsAction;
+import org.eclipse.glsp.api.action.kind.SetDirtyStateAction;
 import org.eclipse.glsp.api.action.kind.UndoAction;
 import org.eclipse.glsp.api.model.GraphicalModelState;
 
@@ -33,16 +34,15 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   public Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   public List<Action> execute(final Action action, final GraphicalModelState modelState) {
       if (action instanceof UndoAction && modelState.canUndo()) {
          modelState.undo();
-         return Optional.of(new RequestBoundsAction(modelState.getRoot()));
+         return listOf(new RequestBoundsAction(modelState.getRoot()), new SetDirtyStateAction(modelState.isDirty()));
       } else if (action instanceof RedoAction && modelState.canRedo()) {
          modelState.redo();
-         return Optional.of(new RequestBoundsAction(modelState.getRoot()));
+         return listOf(new RequestBoundsAction(modelState.getRoot()), new SetDirtyStateAction(modelState.isDirty()));
       }
-
       LOG.warn("Cannot undo or redo");
-      return Optional.empty();
+      return none();
    }
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ValidateLabelEditActionHandler.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ValidateLabelEditActionHandler.java
index 984431f2..455ecb0a 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ValidateLabelEditActionHandler.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/actionhandler/ValidateLabelEditActionHandler.java
@@ -15,6 +15,7 @@
  ********************************************************************************/
 package org.eclipse.glsp.server.actionhandler;
 
+import java.util.List;
 import java.util.Optional;
 
 import org.eclipse.glsp.api.action.Action;
@@ -38,14 +39,14 @@ public boolean handles(final Action action) {
    }
 
    @Override
-   protected Optional<Action> execute(final Action action, final GraphicalModelState modelState) {
+   protected List<Action> execute(final Action action, final GraphicalModelState modelState) {
       ValidateLabelEditAction validateAction = (ValidateLabelEditAction) action;
       Optional<GModelElement> element = modelState.getIndex().get(validateAction.getLabelId());
       if (element.isPresent()) {
-         return Optional.of(new SetEditLabelValidationResultAction(
+         return listOf(new SetEditLabelValidationResultAction(
             editLabelValidator.validate(modelState, validateAction.getValue(), element.get())));
       }
-      return Optional.of(new SetEditLabelValidationResultAction(EditLabelValidationResult.OK_RESULT));
+      return listOf(new SetEditLabelValidationResultAction(EditLabelValidationResult.OK_RESULT));
    }
 
 }
diff --git a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/model/ModelStateImpl.java b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/model/ModelStateImpl.java
index 9984fdf2..3308e7b7 100644
--- a/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/model/ModelStateImpl.java
+++ b/plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/model/ModelStateImpl.java
@@ -19,6 +19,7 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.eclipse.emf.common.command.BasicCommandStack;
 import org.eclipse.emf.common.command.Command;
 import org.eclipse.emf.common.command.CommandStack;
 import org.eclipse.glsp.api.model.GraphicalModelState;
@@ -31,7 +32,7 @@ public class ModelStateImpl implements GraphicalModelState {
    private Map<String, String> options;
    private String clientId;
    private GModelRoot currentModel;
-   private CommandStack commandStack;
+   private BasicCommandStack commandStack;
    private Set<String> expandedElements;
    private Set<String> selectedElements;
 
@@ -67,7 +68,7 @@ protected void initializeCommandStack() {
 
    public CommandStack getCommandStack() { return commandStack; }
 
-   protected void setCommandStack(final CommandStack commandStack) {
+   protected void setCommandStack(final BasicCommandStack commandStack) {
       if (this.commandStack != null) {
          this.commandStack.flush();
       }
@@ -136,4 +137,12 @@ public void redo() {
       commandStack.redo();
    }
 
+   @Override
+   public boolean isDirty() { return commandStack.isSaveNeeded(); }
+
+   @Override
+   public void saveIsDone() {
+      commandStack.saveIsDone();
+   }
+
 }