console history controller with persistence
authorGregory Shrago <Gregory.Shrago@jetbrains.com>
Tue, 29 Mar 2011 11:58:02 +0000 (15:58 +0400)
committerGregory Shrago <Gregory.Shrago@jetbrains.com>
Tue, 29 Mar 2011 12:07:31 +0000 (16:07 +0400)
platform/lang-impl/lang-impl.iml
platform/lang-impl/src/com/intellij/execution/process/ConsoleHistoryController.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/execution/runners/AbstractConsoleRunnerWithHistory.java
platform/platform-impl/src/com/intellij/execution/process/ConsoleHistoryModel.java

index 7bc18547561fc02a413abe68d0121d7a170ce043..8919fdd62281f5fdbea4233bb96ee6689d2c610c 100644 (file)
@@ -21,6 +21,7 @@
     <orderEntry type="library" name="Guava" level="project" />
     <orderEntry type="library" scope="TEST" name="Mocks" level="project" />
     <orderEntry type="module" module-name="xdebugger-api" />
+    <orderEntry type="library" name="xpp3-1.1.4-min" level="project" />
   </component>
 </module>
 
diff --git a/platform/lang-impl/src/com/intellij/execution/process/ConsoleHistoryController.java b/platform/lang-impl/src/com/intellij/execution/process/ConsoleHistoryController.java
new file mode 100644 (file)
index 0000000..2414a77
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.execution.process;
+
+import com.intellij.codeInsight.lookup.LookupManager;
+import com.intellij.execution.console.LanguageConsoleImpl;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.EmptyAction;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.CaretModel;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ex.ProjectEx;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.text.StringHash;
+import com.intellij.openapi.util.text.StringUtil;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.xml.XppReader;
+import org.jetbrains.annotations.NotNull;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.awt.event.KeyEvent;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.ListIterator;
+
+/**
+ * @author gregsh
+ */
+public class ConsoleHistoryController {
+
+  private static final Logger LOG = Logger.getInstance("com.intellij.execution.process.ConsoleHistoryController");
+
+  private final String myType;
+  private final String myId;
+  private final LanguageConsoleImpl myConsole;
+  private final ConsoleHistoryModel myModel;
+  private final AnAction myHistoryNext = new MyAction(true);
+  private final AnAction myHistoryPrev = new MyAction(false);
+  private boolean myMultiline;
+  private long myLastSaveStamp;
+
+
+  public ConsoleHistoryController(final String type,
+                                  final String persistenceId,
+                                  final LanguageConsoleImpl console,
+                                  final ConsoleHistoryModel model) {
+    myType = type;
+    myId = "".equals(persistenceId)? console.getProject().getLocation() : persistenceId;
+    myConsole = console;
+    myModel = model;
+  }
+
+  public boolean isMultiline() {
+    return myMultiline;
+  }
+
+  public ConsoleHistoryController setMultiline(boolean  multiline) {
+    myMultiline = multiline;
+    return this;
+  }
+
+  public ConsoleHistoryModel getModel() {
+    return myModel;
+  }
+
+  public void install() {
+    if (myId != null) {
+      ApplicationManager.getApplication().getMessageBus().connect(myConsole).subscribe(
+        ProjectEx.ProjectSaved.TOPIC, new ProjectEx.ProjectSaved() {
+          @Override
+          public void saved(@NotNull final Project project) {
+            saveHistory();
+          }
+        });
+      Disposer.register(myConsole, new Disposable() {
+        @Override
+        public void dispose() {
+          saveHistory();
+        }
+      });
+      loadHistory();
+    }
+    configureActions();
+    myLastSaveStamp = myModel.getModificationCount();
+  }
+
+  private void configureActions() {
+    // todo add clipboard-like history viewer/selector action
+    EmptyAction.setupAction(myHistoryNext, "Console.History.Next", null);
+    EmptyAction.setupAction(myHistoryPrev, "Console.History.Previous", null);
+    if (!myMultiline) {
+      myHistoryNext.registerCustomShortcutSet(KeyEvent.VK_UP, 0, null);
+      myHistoryPrev.registerCustomShortcutSet(KeyEvent.VK_DOWN, 0, null);
+    }
+    myHistoryNext.registerCustomShortcutSet(myHistoryNext.getShortcutSet(), myConsole.getConsoleEditor().getComponent());
+    myHistoryPrev.registerCustomShortcutSet(myHistoryPrev.getShortcutSet(), myConsole.getConsoleEditor().getComponent());
+  }
+
+  private File getFile() {
+    final StringBuilder sb = new StringBuilder().append(PathManager.getSystemPath()).append(File.separator).append("userHistory").
+      append(File.separator).append(myType).append(Long.toHexString(StringHash.calc(myId))).append(".hist.xml");
+    return new File(sb.toString());
+  }
+
+
+  private void loadHistory() {
+    final File file = getFile();
+    if (!file.exists()) return;
+    HierarchicalStreamReader xmlReader = null;
+    try {
+      xmlReader = new XppReader(new FileReader(file));
+      loadHistory(xmlReader);
+    }
+    catch (Exception ex) {
+      LOG.error(ex);
+    }
+    finally {
+      if (xmlReader != null) {
+        xmlReader.close();
+      }
+    }
+
+  }
+
+  private void saveHistory() {
+    if (myLastSaveStamp == myModel.getModificationCount()) return;
+
+    final File file = getFile();
+    final File dir = file.getParentFile();
+    if (!dir.exists() && !dir.mkdirs() || !dir.isDirectory()) {
+      LOG.error("failed to create folder: "+dir.getAbsolutePath());
+      return;
+    }
+    final File tmpFile = new File(dir, file.getName()+".tmp");
+    FileOutputStream os = null;
+    try {
+      final XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
+      try {
+        serializer.setProperty("http://xmlpull.org/v1/doc/properties.html#serializer-indentation", "  ");
+      }
+      catch (Exception e) {
+        // not recognized
+      }
+      serializer.setOutput(new PrintWriter(os = new FileOutputStream(tmpFile)));
+      saveHistory(serializer);
+      file.delete();
+      tmpFile.renameTo(file);
+      myLastSaveStamp = myModel.getModificationCount();
+    }
+    catch (Exception ex) {
+      LOG.error(ex);
+    }
+    finally {
+      try {
+        os.close();
+      }
+      catch (Exception e) {
+        // nothing
+      }
+      cleanupOldFiles(dir);
+    }
+  }
+
+  private static void cleanupOldFiles(final File dir) {
+    final long keep2weeks = 2 * 1000L * 60 * 60 * 24 * 7;
+    final long curTime = System.currentTimeMillis();
+    for (File file : dir.listFiles()) {
+      if (file.isFile() && file.getName().endsWith(".hist.xml") && curTime - file.lastModified() > keep2weeks) {
+        file.delete();
+      }
+    }
+  }
+
+
+  public AnAction getHistoryNext() {
+    return myHistoryNext;
+  }
+
+  public AnAction getHistoryPrev() {
+    return myHistoryPrev;
+  }
+
+
+  protected void actionTriggered(final String command) {
+    final Editor editor = myConsole.getConsoleEditor();
+    final Document document = editor.getDocument();
+    new WriteCommandAction(myConsole.getProject(), myConsole.getFile()) {
+      protected void run(final Result result) throws Throwable {
+        document.setText(StringUtil.notNullize(command));
+        editor.getCaretModel().moveToOffset(document.getTextLength());
+      }
+    }.execute();
+  }
+
+
+  private class MyAction extends AnAction {
+    private boolean myNext;
+
+    public MyAction(final boolean next) {
+      myNext = next;
+      getTemplatePresentation().setVisible(false);
+    }
+
+    @Override
+    public void actionPerformed(final AnActionEvent e) {
+      actionTriggered(myNext ? myModel.getHistoryNext() : myModel.getHistoryPrev());
+    }
+
+    @Override
+    public void update(final AnActionEvent e) {
+      super.update(e);
+      e.getPresentation().setEnabled(myModel.hasHistory(myNext) &&
+                                     (myMultiline || canMoveInEditor(myNext)));
+    }
+  }
+
+  private boolean canMoveInEditor(final boolean next) {
+    final EditorEx consoleEditor = myConsole.getConsoleEditor();
+    final Document document = consoleEditor.getDocument();
+    final CaretModel caretModel = consoleEditor.getCaretModel();
+
+    if (LookupManager.getActiveLookup(consoleEditor) != null) return false;
+
+    if (next) {
+      return document.getLineNumber(caretModel.getOffset()) == 0;
+    }
+    else {
+      return document.getLineNumber(caretModel.getOffset()) == document.getLineCount() - 1 &&
+             StringUtil.isEmptyOrSpaces(document.getText().substring(caretModel.getOffset()));
+    }
+  }
+
+
+  private void loadHistory(final HierarchicalStreamReader in) {
+    if (!in.getNodeName().equals("console-history")) return;
+    final String id = in.getAttribute("id");
+    if (!myId.equals(id)) return;
+    final ArrayList<String> entries = new ArrayList<String>();
+    while (in.hasMoreChildren()) {
+      in.moveDown();
+      if ("history-entry".equals(in.getNodeName())) {
+        entries.add(in.getValue());
+      }
+      in.moveUp();
+    }
+    for (ListIterator<String> iterator = entries.listIterator(entries.size()); iterator.hasPrevious(); ) {
+      final String entry = iterator.previous();
+      myModel.addToHistory(entry);
+
+    }
+  }
+
+  private void saveHistory(final XmlSerializer out) throws IOException {
+    out.startDocument(null, null);
+    out.startTag(null, "console-history");
+    out.attribute(null, "id", myId);
+    for (String s : myModel.getHistory()) {
+      out.startTag(null, "history-entry");
+      out.text(s);
+      out.endTag(null, "history-entry");
+    }
+    out.endTag(null, "console-history");
+    out.endDocument();
+  }
+}
index bac08d44fa0fcbb53cf7bed619911496900a69fb..fc703f549627159caf2234022c622efb5ea8bd4a 100644 (file)
@@ -29,31 +29,21 @@ import com.intellij.ide.CommonActionsManager;
 import com.intellij.openapi.actionSystem.*;
 import com.intellij.openapi.application.Application;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.Result;
-import com.intellij.openapi.command.WriteCommandAction;
-import com.intellij.openapi.editor.CaretModel;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.ex.EditorEx;
 import com.intellij.openapi.project.DumbAwareAction;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Computable;
 import com.intellij.openapi.util.IconLoader;
-import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.wm.IdeFocusManager;
 import com.intellij.openapi.wm.ToolWindow;
 import com.intellij.openapi.wm.ToolWindowManager;
 import com.intellij.ui.SideBorder;
-import com.intellij.util.IJSwingUtilities;
 import com.intellij.util.NotNullFunction;
-import com.intellij.util.PairProcessor;
 import com.intellij.util.ui.UIUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
 import java.awt.*;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -71,8 +61,6 @@ public abstract class AbstractConsoleRunnerWithHistory {
 
   private LanguageConsoleViewImpl myConsoleView;
 
-  private AnAction myRunAction;
-
   private ConsoleExecuteActionHandler myConsoleExecuteActionHandler;
 
   public AbstractConsoleRunnerWithHistory(@NotNull final Project project,
@@ -141,6 +129,8 @@ public abstract class AbstractConsoleRunnerWithHistory {
     panel.add(actionToolbar.getComponent(), BorderLayout.WEST);
     panel.add(myConsoleView.getComponent(), BorderLayout.CENTER);
 
+    actionToolbar.setTargetComponent(panel);
+
     final RunContentDescriptor contentDescriptor =
       new RunContentDescriptor(myConsoleView, myProcessHandler, panel, constructConsoleTitle(myConsoleTitle));
 
@@ -202,13 +192,7 @@ public abstract class AbstractConsoleRunnerWithHistory {
   }
 
   protected void finishConsole() {
-    myRunAction.getTemplatePresentation().setEnabled(false);
     myConsoleView.getConsole().setEditable(false);
-    ApplicationManager.getApplication().invokeLater(new Runnable() {
-      public void run() {
-        myConsoleView.getConsole().getConsoleEditor().getComponent().updateUI();
-      }
-    });
   }
 
   protected abstract LanguageConsoleViewImpl createConsoleView();
@@ -240,10 +224,8 @@ public abstract class AbstractConsoleRunnerWithHistory {
     final AnAction closeAction = createCloseAction(defaultExecutor, contentDescriptor);
     actionList.add(closeAction);
 
-// run and history actions
-
-    ConsoleExecutionActions executionActions = createConsoleExecutionActions();
-    actionList.addAll(executionActions.getActionsAsList());
+// run action
+    actionList.add(createConsoleExecAction(getLanguageConsole(), myProcessHandler, myConsoleExecuteActionHandler));
 
 // Help
     actionList.add(CommonActionsManager.getInstance().createHelpAction("interactive_console"));
@@ -255,13 +237,6 @@ public abstract class AbstractConsoleRunnerWithHistory {
     return actions;
   }
 
-  protected ConsoleExecutionActions createConsoleExecutionActions() {
-    ConsoleExecutionActions executionActions =
-      createConsoleActions(getLanguageConsole(), myProcessHandler, myConsoleExecuteActionHandler);
-    myRunAction = executionActions.getRunAction();
-    return executionActions;
-  }
-
   protected AnAction createCloseAction(final Executor defaultExecutor, final RunContentDescriptor myDescriptor) {
     return new CloseAction(defaultExecutor, myDescriptor, myProject);
   }
@@ -274,104 +249,15 @@ public abstract class AbstractConsoleRunnerWithHistory {
     return myConsoleView.getConsole();
   }
 
-  public static ConsoleExecutionActions createConsoleActions(final LanguageConsoleImpl languageConsole,
-                                                             final ProcessHandler processHandler,
-                                                             final ConsoleExecuteActionHandler consoleExecuteActionHandler) {
-    final AnAction runAction = new ConsoleExecuteAction(languageConsole,
-                                                        processHandler, consoleExecuteActionHandler);
-
-    final PairProcessor<AnActionEvent, String> historyProcessor = new PairProcessor<AnActionEvent, String>() {
-      public boolean process(final AnActionEvent e, final String s) {
-        new WriteCommandAction(languageConsole.getProject(), languageConsole.getFile()) {
-          protected void run(final Result result) throws Throwable {
-            String text = s == null ? "" : s;
-            languageConsole.getEditorDocument().setText(text);
-            languageConsole.getConsoleEditor().getCaretModel().moveToOffset(text.length());
-          }
-        }.execute();
-        return true;
-      }
-    };
-
-    final EditorEx consoleEditor = languageConsole.getConsoleEditor();
-    final AnAction upAction = ConsoleHistoryModel.createConsoleHistoryUpAction(createCanMoveUpComputable(consoleEditor),
-                                                                               consoleExecuteActionHandler.getConsoleHistoryModel(),
-                                                                               historyProcessor);
-    final AnAction downAction = ConsoleHistoryModel.createConsoleHistoryDownAction(createCanMoveDownComputable(consoleEditor),
-                                                                                   consoleExecuteActionHandler.getConsoleHistoryModel(),
-                                                                                   historyProcessor);
-
-    return new ConsoleExecutionActions(runAction, downAction, upAction);
-  }
-
-  public static Computable<Boolean> createCanMoveDownComputable(final Editor consoleEditor) {
-    return new Computable<Boolean>() {
-      @Override
-      public Boolean compute() {
-        final Document document = consoleEditor.getDocument();
-        final CaretModel caretModel = consoleEditor.getCaretModel();
-        // Check if we have focus
-        if (!IJSwingUtilities.hasFocus(consoleEditor.getComponent())) {
-          return true;
-        }
-        // Check if we have active lookup or if we can move in editor
-        return LookupManager.getActiveLookup(consoleEditor) != null ||
-               document.getLineNumber(caretModel.getOffset()) < document.getLineCount() - 1 &&
-               !StringUtil.isEmptyOrSpaces(document.getText().substring(caretModel.getOffset()));
-      }
-    };
-  }
-
-  public static Computable<Boolean> createCanMoveUpComputable(final Editor consoleEditor) {
-    return new Computable<Boolean>() {
-      @Override
-      public Boolean compute() {
-        final Document document = consoleEditor.getDocument();
-        final CaretModel caretModel = consoleEditor.getCaretModel();
-        // Check if we have focus
-        if (!IJSwingUtilities.hasFocus(consoleEditor.getComponent())) {
-          return true;
-        }
-        // Check if we have active lookup or if we can move in editor
-        return LookupManager.getActiveLookup(consoleEditor) != null || document.getLineNumber(caretModel.getOffset()) > 0;
-      }
-    };
+  public static AnAction createConsoleExecAction(final LanguageConsoleImpl languageConsole,
+                                                       final ProcessHandler processHandler,
+                                                       final ConsoleExecuteActionHandler consoleExecuteActionHandler) {
+    return new ConsoleExecuteAction(languageConsole, processHandler, consoleExecuteActionHandler);
   }
 
   @NotNull
   protected abstract ConsoleExecuteActionHandler createConsoleExecuteActionHandler();
 
-  public static class ConsoleExecutionActions {
-    private final AnAction myRunAction;
-    private final AnAction myNextAction;
-    private final AnAction myPrevAction;
-    private final List<AnAction> myAdditionalActions = Lists.newArrayList();
-
-    public ConsoleExecutionActions(AnAction runAction, AnAction nextAction, AnAction prevAction) {
-      myRunAction = runAction;
-      myNextAction = nextAction;
-      myPrevAction = prevAction;
-    }
-
-    public AnAction[] getActions() {
-      return getActionsAsList().toArray(new AnAction[getActionsAsList().size()]);
-    }
-
-    public List<AnAction> getActionsAsList() {
-      ArrayList<AnAction> list = Lists.newArrayList(myRunAction, myNextAction, myPrevAction);
-      list.addAll(myAdditionalActions);
-      return list;
-    }
-
-    public boolean addAdditionalAction(AnAction action) {
-      return myAdditionalActions.add(action);
-    }
-
-    public AnAction getRunAction() {
-      return myRunAction;
-    }
-  }
-
 
   public static class ConsoleExecuteAction extends DumbAwareAction {
     public static final String ACTIONS_EXECUTE_ICON = "/actions/execute.png";
@@ -400,7 +286,7 @@ public abstract class AbstractConsoleRunnerWithHistory {
     public void update(final AnActionEvent e) {
       final EditorEx editor = myLanguageConsole.getConsoleEditor();
       final Lookup lookup = LookupManager.getActiveLookup(editor);
-      e.getPresentation().setEnabled(!myProcessHandler.isProcessTerminated() &&
+      e.getPresentation().setEnabled(!editor.isRendererMode() && !myProcessHandler.isProcessTerminated() &&
                                      (lookup == null || !(lookup.isCompletion() && lookup.isFocused())));
     }
   }
index 659ee3a2c7f7666442ba68aa1657aa1fe2b3ad74..3e7e397a8ff855d672d94886c74e560515014970 100644 (file)
@@ -1,13 +1,8 @@
 package com.intellij.execution.process;
 
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.EmptyAction;
-import com.intellij.openapi.util.Computable;
-import com.intellij.util.PairProcessor;
+import com.intellij.openapi.util.ModificationTracker;
 import org.jetbrains.annotations.Nullable;
 
-import java.awt.event.KeyEvent;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
@@ -15,17 +10,19 @@ import java.util.List;
 /**
  * @author Gregory.Shrago
  */
-public class ConsoleHistoryModel {
+public class ConsoleHistoryModel implements ModificationTracker {
 
   public static final int DEFAULT_MAX_SIZE = 20;
 
   private int myHistoryCursor;
   private int myMaxHistorySize = DEFAULT_MAX_SIZE;
   private final LinkedList<String> myHistory = new LinkedList<String>();
+  private volatile long myModificationTracker;
 
 
   public void addToHistory(final String statement) {
     synchronized (myHistory) {
+      myModificationTracker ++;
       myHistoryCursor = -1;
       myHistory.remove(statement);
       if (myHistory.size() >= myMaxHistorySize) {
@@ -85,70 +82,8 @@ public class ConsoleHistoryModel {
     }
   }
 
-  public static AnAction createConsoleHistoryUpAction(final Computable<Boolean> canMoveUpInEditor,
-                                                      final ConsoleHistoryModel model,
-                                                      final PairProcessor<AnActionEvent, String> processor) {
-    final AnAction upAction = new AnAction() {
-      @Override
-      public void actionPerformed(final AnActionEvent e) {
-        processor.process(e, model.getHistoryNext());
-      }
-
-      @Override
-      public void update(final AnActionEvent e) {
-        // Check if we have anything in history
-        final boolean hasHistory = model.hasHistory(true);
-        if (!hasHistory){
-          e.getPresentation().setEnabled(false);
-          return;
-        }
-        e.getPresentation().setEnabled(!canMoveUpInEditor.compute());
-      }
-    };
-    upAction.registerCustomShortcutSet(KeyEvent.VK_UP, 0, null);
-    upAction.getTemplatePresentation().setVisible(false);
-    return upAction;
-  }
-
-  public static AnAction createConsoleHistoryDownAction(final Computable<Boolean> canMoveDownInEditor,
-                                                        final ConsoleHistoryModel model,
-                                                        final PairProcessor<AnActionEvent, String> processor) {
-    final AnAction downAction = new AnAction() {
-      @Override
-      public void actionPerformed(final AnActionEvent e) {
-        processor.process(e, model.getHistoryPrev());
-      }
-
-      @Override
-      public void update(final AnActionEvent e) {
-        // Check if we have anything in history
-        final boolean hasHistory = model.hasHistory(false);
-        if (!hasHistory){
-          e.getPresentation().setEnabled(false);
-          return;
-        }
-        e.getPresentation().setEnabled(!canMoveDownInEditor.compute());
-      }
-    };
-
-    downAction.registerCustomShortcutSet(KeyEvent.VK_DOWN, 0, null);
-    downAction.getTemplatePresentation().setVisible(false);
-    return downAction;
-  }
-
-  public static AnAction createHistoryAction(final ConsoleHistoryModel model, final boolean next, final PairProcessor<AnActionEvent,String> processor) {
-    final AnAction action = new AnAction(null, null, null) {
-      @Override
-      public void actionPerformed(final AnActionEvent e) {
-        processor.process(e, next ? model.getHistoryNext() : model.getHistoryPrev());
-      }
-
-      @Override
-      public void update(final AnActionEvent e) {
-        e.getPresentation().setEnabled(model.hasHistory(next));
-      }
-    };
-    EmptyAction.setupAction(action, next? "Console.History.Next" : "Console.History.Previous", null);
-    return action;
+  @Override
+  public long getModificationCount() {
+    return myModificationTracker;
   }
 }